{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"navigation-menu","title":"Navigation Menu","description":"Open site sections from a row of links with dropdown panels. Search terms: navbar, mega menu.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button"],"files":[{"path":"packages/ui/src/components/navigation-menu/index.ts","content":"export * from \"./navigation-menu\";\n","type":"registry:ui","target":"~/src/siteplane/components/navigation-menu/index.ts"},{"path":"packages/ui/src/components/navigation-menu/navigation-menu.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { NavigationMenu as NavigationMenuPrimitive } from \"@base-ui/react/navigation-menu\";\nimport { ChevronDown } from \"lucide-react\";\nimport { SiteplaneUIPortalScope } from \"../../system/provider/siteplane-ui-provider\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport { Button } from \"../button\";\nimport \"./navigation-menu.css\";\n\n// Links inside NavigationMenuContent take the panel row style without a\n// hand-set `popup` flag: the content tells its links where they sit.\nconst NavigationMenuContentContext: React.Context<boolean> =\n  React.createContext(false);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Base UI's own default for the item value type.\nexport function NavigationMenu<Value = any>(\n  props: WithStringClassName<NavigationMenuPrimitive.Root.Props<Value>>,\n): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.Root\n      data-slot=\"navigation-menu\"\n      {...props}\n      className={cn(slotClass(\"navigation-menu\", props), props.className)}\n    />\n  );\n}\n\nexport function NavigationMenuItem(\n  props: WithStringClassName<NavigationMenuPrimitive.Item.Props>,\n): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.Item\n      data-slot=\"navigation-menu-item\"\n      {...props}\n      className={cn(slotClass(\"navigation-menu-item\", props), props.className)}\n    />\n  );\n}\n\nexport function NavigationMenuList({\n  className,\n  ...props\n}: WithStringClassName<NavigationMenuPrimitive.List.Props>): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.List\n      data-slot=\"navigation-menu-list\"\n      className={cn(\n        slotClass(\"navigation-menu-list\", props),\n        \"flex items-center gap-1\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function NavigationMenuTrigger({\n  children,\n  render,\n  ...props\n}: WithStringClassName<NavigationMenuPrimitive.Trigger.Props>): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      // The trigger is a Button. A render of the consumer's replaces the\n      // element inside that Button, which keeps its styling and states; a\n      // render function still receives the trigger's state.\n      render={(triggerProps, triggerState) => (\n        <Button\n          {...triggerProps}\n          variant=\"ghost\"\n          profile=\"navigation-link\"\n          render={\n            typeof render === \"function\"\n              ? (elementProps) => render(elementProps, triggerState)\n              : render\n          }\n        />\n      )}\n      {...props}\n      className={cn(\n        slotClass(\"navigation-menu-trigger\", props),\n        props.className,\n      )}\n    >\n      {children}\n      <NavigationMenuPrimitive.Icon\n        className=\"sp-navigation-menu-icon\"\n        data-slot=\"navigation-menu-icon\"\n      >\n        <ChevronDown />\n      </NavigationMenuPrimitive.Icon>\n    </NavigationMenuPrimitive.Trigger>\n  );\n}\n\nexport function NavigationMenuLink({\n  popup: popupProp,\n  description,\n  children,\n  render,\n  ...props\n}: WithStringClassName<NavigationMenuPrimitive.Link.Props> & {\n  /**\n   * Row style for a link in a panel: full width and aligned to the start.\n   * Defaults to true inside NavigationMenuContent and false elsewhere.\n   */\n  popup?: boolean;\n  description?: React.ReactNode;\n}): React.ReactElement {\n  const inContent = React.useContext(NavigationMenuContentContext);\n  const popup = popupProp ?? inContent;\n  return (\n    <NavigationMenuPrimitive.Link\n      closeOnClick\n      data-slot=\"navigation-menu-link\"\n      // The link is a Button-styled anchor. A render of the consumer's, such\n      // as a router link, takes the place of the anchor inside that Button,\n      // which keeps its styling and states; a render function still receives\n      // the link's state.\n      render={(linkProps, linkState) => (\n        <Button\n          // The link's props and ref are an anchor's, and the Button renders\n          // that anchor; its props are typed for its default button.\n          {...(linkProps as React.ComponentPropsWithRef<\"button\">)}\n          variant=\"ghost\"\n          profile={popup ? \"default\" : \"navigation-link\"}\n          align={popup ? \"start\" : \"center\"}\n          width={popup ? \"full\" : \"auto\"}\n          multiline={Boolean(description)}\n          render={\n            typeof render === \"function\"\n              ? (elementProps) => render(elementProps, linkState)\n              : (render ?? <a />)\n          }\n        />\n      )}\n      {...props}\n      className={cn(slotClass(\"navigation-menu-link\", props), props.className)}\n    >\n      <span className=\"min-w-0\">\n        <span\n          className=\"sp-navigation-menu-link-title flex items-center gap-2\"\n          data-slot=\"navigation-menu-link-title\"\n        >\n          {children}\n        </span>\n        {description && (\n          // Beside a leading icon the description starts under the title text\n          // (I7), a design-rework.css rule.\n          <span\n            className=\"sp-navigation-menu-link-description block text-sm font-normal text-muted-foreground\"\n            data-slot=\"navigation-menu-link-description\"\n          >\n            {description}\n          </span>\n        )}\n      </span>\n    </NavigationMenuPrimitive.Link>\n  );\n}\n\nexport function NavigationMenuContent({\n  children,\n  className,\n  ...props\n}: WithStringClassName<NavigationMenuPrimitive.Content.Props>): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\n        slotClass(\"navigation-menu-content\", props),\n        \"sp-navigation-content\",\n        className,\n      )}\n      {...props}\n    >\n      <NavigationMenuContentContext.Provider value>\n        {children}\n      </NavigationMenuContentContext.Provider>\n    </NavigationMenuPrimitive.Content>\n  );\n}\n\nexport function NavigationMenuPopup({\n  anchor,\n  ...props\n}: WithStringClassName<NavigationMenuPrimitive.Popup.Props> & {\n  anchor?: NavigationMenuPrimitive.Positioner.Props[\"anchor\"];\n}): React.ReactElement {\n  return (\n    <NavigationMenuPrimitive.Portal>\n      <SiteplaneUIPortalScope>\n        <NavigationMenuPrimitive.Positioner\n          anchor={anchor}\n          sideOffset={8}\n          align=\"start\"\n          collisionPadding={16}\n          className=\"sp-navigation-menu-positioner z-50\"\n          data-slot=\"navigation-menu-positioner\"\n        >\n          <NavigationMenuPrimitive.Popup\n            data-slot=\"navigation-menu-popup\"\n            {...props}\n            className={cn(\n              slotClass(\"navigation-menu-popup\", props),\n              props.className,\n            )}\n          >\n            <NavigationMenuPrimitive.Viewport className=\"relative size-full overflow-hidden\" />\n          </NavigationMenuPrimitive.Popup>\n        </NavigationMenuPrimitive.Positioner>\n      </SiteplaneUIPortalScope>\n    </NavigationMenuPrimitive.Portal>\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/navigation-menu/navigation-menu.tsx"},{"path":"packages/ui/src/components/navigation-menu/navigation-menu.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* Calm navigation motion. The popup fades in while it settles a few pixels\n   * into place and leaves with a plain fade. Switching between groups cross-fades\n   * the outgoing and incoming content, which travel a short distance in the\n   * direction of the switch, while the popup resizes with the same timing. No\n   * scale, no blur. */\n  .sp-navigation-menu-popup {\n    position: relative;\n    width: var(--popup-width);\n    height: var(--popup-height);\n    max-width: var(--available-width);\n    overflow: hidden;\n    transition-property: opacity, translate, width, height;\n    transition-duration: var(--motion-duration-navigation);\n    transition-timing-function: var(--motion-ease-navigation);\n  }\n  .sp-navigation-menu-popup[data-starting-style] {\n    opacity: 0;\n    translate: 0 calc(-1 * var(--motion-overlay-y));\n  }\n  .sp-navigation-menu-popup[data-starting-style][data-side=\"top\"] {\n    translate: 0 var(--motion-overlay-y);\n  }\n  .sp-navigation-menu-popup[data-ending-style] {\n    opacity: 0;\n  }\n  /* The content element carries the transitions Base UI waits for before it\n   * unmounts the outgoing content. */\n  .sp-navigation-content {\n    max-height: var(--available-height);\n    overflow-y: auto;\n    transition-property: opacity, translate;\n    transition-duration: var(--motion-duration-navigation);\n    transition-timing-function: var(--motion-ease-navigation);\n  }\n  .sp-navigation-content[data-activation-direction]:is(\n      [data-starting-style],\n      [data-ending-style]\n    ) {\n    opacity: 0;\n  }\n  .sp-navigation-content[data-starting-style][data-activation-direction=\"right\"],\n  .sp-navigation-content[data-ending-style][data-activation-direction=\"left\"] {\n    translate: var(--motion-navigation-distance) 0;\n  }\n  .sp-navigation-content[data-starting-style][data-activation-direction=\"left\"],\n  .sp-navigation-content[data-ending-style][data-activation-direction=\"right\"] {\n    translate: calc(-1 * var(--motion-navigation-distance)) 0;\n  }\n  /* Closing the menu: the popup owns the fade. A near-1 opacity step registers a\n   * transition, which keeps the content mounted until the popup has left. */\n  .sp-navigation-content[data-ending-style]:not([data-activation-direction]) {\n    opacity: 0.999;\n  }\n  .sp-navigation-menu-icon {\n    display: flex;\n    transition: transform var(--motion-duration-expand)\n      var(--motion-ease-navigation);\n  }\n  .sp-navigation-menu-icon[data-popup-open] {\n    transform: translateY(1px);\n  }\n  :where(html[data-motion-reduce=\"true\"], [data-sp-motion=\"reduced\"])\n    .sp-navigation-menu-popup,\n  :where(html[data-motion-reduce=\"true\"], [data-sp-motion=\"reduced\"])\n    .sp-navigation-content,\n  :where(html[data-motion-reduce=\"true\"], [data-sp-motion=\"reduced\"])\n    .sp-navigation-menu-icon {\n    transition-duration: 1ms;\n    translate: none !important;\n    transform: none !important;\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .sp-navigation-menu-popup,\n    .sp-navigation-content,\n    .sp-navigation-menu-icon {\n      transition-duration: 1ms;\n      translate: none !important;\n      transform: none !important;\n    }\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/navigation-menu/navigation-menu.css"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/navigation-menu.md","categories":["Navigation"],"meta":{"avoidWhen":["Avoid when the items are commands rather than destinations; use Menu or Dropdown Menu.","Avoid when the links need no panels; a plain list of links is enough."],"description":"Open site sections from a row of links with dropdown panels.","knownLimitations":["A link inside NavigationMenuContent takes the full-width panel row style; set popup to choose the style yourself.","Render exactly one NavigationMenuPopup inside NavigationMenu.","Triggers and links take the control size of the provider; the menu has no size prop of its own."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["navigation-menu","navigation menu","navbar","mega menu","dropdown","links","panels"],"useWhen":["Use when a site needs a row of links where some items open panels of grouped links.","Use when panels should share one popup that resizes and cross-fades between items."]}}