{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-menu",
  "title": "Navigation Menu",
  "description": "Site navigation whose triggers open content panels, built on Radix.",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "lucide-react@^1.27.0",
    "radix-ui@^1.6.7"
  ],
  "registryDependencies": [
    "@pestoui/style",
    "@pestoui/utils"
  ],
  "files": [
    {
      "path": "pestoui/ui/navigation-menu.tsx",
      "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { NavigationMenu as NavigationMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nfunction NavigationMenu({\n  className,\n  children,\n  viewport = true,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n  viewport?: boolean\n}) {\n  return (\n    <NavigationMenuPrimitive.Root\n      data-slot=\"navigation-menu\"\n      data-viewport={viewport}\n      className={cn(\n        \"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {viewport && <NavigationMenuViewport />}\n    </NavigationMenuPrimitive.Root>\n  )\n}\n\nfunction NavigationMenuList({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {\n  return (\n    <NavigationMenuPrimitive.List\n      data-slot=\"navigation-menu-list\"\n      className={cn(\n        \"group flex flex-1 list-none items-center justify-center gap-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {\n  return (\n    <NavigationMenuPrimitive.Item\n      data-slot=\"navigation-menu-item\"\n      className={cn(\"relative\", className)}\n      {...props}\n    />\n  )\n}\n\n/* Colour only. The trigger owns an anchored panel, so it deliberately skips the\n   press scale every other control gets — a trigger that shrinks drags the\n   surface hanging off it. */\nconst navigationMenuTriggerStyle = cva(\n  \"group/navigation-menu-trigger inline-flex h-10 w-max items-center justify-center rounded-full px-4 py-2 text-body font-medium transition-all duration-fast ease-out-expo outline-none hover:bg-accent focus:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:hover:bg-accent data-open:bg-accent data-open:hover:bg-accent data-open:focus:bg-accent\"\n)\n\nfunction NavigationMenuTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n      {...props}\n    >\n      {children}{\" \"}\n      <ChevronDownIcon className=\"relative top-px ml-1 size-3 transition-transform duration-spring-press ease-spring-press group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180\" aria-hidden=\"true\" />\n    </NavigationMenuPrimitive.Trigger>\n  )\n}\n\n/* With a viewport, content never opens or closes on its own — it hands off\n   sideways to the neighbouring panel. That is a swap, not an arrival, so it\n   rides the swap spring in and a short tween out, travelling half as far. */\nconst navigationMenuContentSwap = [\n  \"data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=from-]:duration-(--duration-spring-swap) data-[motion^=from-]:ease-spring-swap\",\n  \"data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52\",\n  \"data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out data-[motion^=to-]:duration-(--duration-exit) data-[motion^=to-]:ease-out-expo\",\n  \"data-[motion=to-end]:slide-out-to-right-26 data-[motion=to-start]:slide-out-to-left-26\",\n].join(\" \")\n\n/* Without a viewport each content block is its own popover, so it arrives like\n   one: panel spring, short rise, a blur that resolves as it lands. The exit is\n   the same shape at half the travel on a short tween. These carry the extra\n   parent selector, so they outrank the swap rules above wherever both match. */\nconst navigationMenuContentPanel = [\n  \"group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:slide-in-from-top-[4px] group-data-[viewport=false]/navigation-menu:data-open:blur-in-[6px]\",\n  \"group-data-[viewport=false]/navigation-menu:data-open:duration-(--duration-spring-panel) group-data-[viewport=false]/navigation-menu:data-open:ease-spring-panel\",\n  \"group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-98 group-data-[viewport=false]/navigation-menu:data-closed:slide-out-to-top-[2px]\",\n  \"group-data-[viewport=false]/navigation-menu:data-closed:duration-(--duration-exit) group-data-[viewport=false]/navigation-menu:data-closed:ease-out-expo\",\n].join(\" \")\n\nfunction NavigationMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\n        \"top-0 left-0 w-full p-1 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-[16px] group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-float group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/[0.06] **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto\",\n        navigationMenuContentSwap,\n        navigationMenuContentPanel,\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/* The viewport is the one surface every panel lands in, so it owns the arrival:\n   panel spring, short rise, a blur that resolves as it settles — and it grows\n   from the bar it hangs off rather than from its own middle. Resizing as one\n   trigger hands over to the next is a swap, not an arrival, so the size change\n   runs on the swap spring instead.\n\n   Keeping those two apart takes care on both axes. `anim-*` splits the duration.\n   The easing needs the same treatment and has no `anim-` equivalent: an `ease-*`\n   utility writes `transition-timing-function` as well as `--tw-ease`, and this\n   element is `data-open` for its whole visible life — so `data-open:ease-…`\n   would quietly retime the size transition too, running the swap spring's curve\n   at the panel spring's shape. Writing `--tw-ease` directly reaches the keyframe\n   and nothing else; the unconditional `ease-spring-swap` keeps the transition. */\nconst navigationMenuViewportMotion = [\n  \"origin-top transition-[width,height] duration-spring-swap ease-spring-swap\",\n  \"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-[0.96] data-open:slide-in-from-top-[4px] data-open:blur-in-[6px] data-open:anim-spring-panel data-open:[--tw-ease:var(--ease-spring-panel)]\",\n  \"data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-[0.98] data-closed:slide-out-to-top-[2px] data-closed:anim-exit data-closed:[--tw-ease:var(--ease-out-expo)]\",\n].join(\" \")\n\nfunction NavigationMenuViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {\n  return (\n    <div\n      className={cn(\n        \"absolute top-full left-0 isolate z-50 flex justify-center\"\n      )}\n    >\n      <NavigationMenuPrimitive.Viewport\n        data-slot=\"navigation-menu-viewport\"\n        className={cn(\n          \"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-[16px] bg-popover text-popover-foreground shadow-float ring-1 ring-foreground/[0.06] md:w-(--radix-navigation-menu-viewport-width)\",\n          navigationMenuViewportMotion,\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction NavigationMenuLink({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {\n  return (\n    <NavigationMenuPrimitive.Link\n      data-slot=\"navigation-menu-link\"\n      className={cn(\n        \"flex items-center gap-2 rounded-[10px] p-2.5 text-body transition-all duration-instant ease-out-expo outline-none hover:bg-accent focus:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-md data-active:bg-accent data-active:hover:bg-accent data-active:focus:bg-accent [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/* Travelling from one trigger to the next is the only thing this arrow does, so\n   the glide is the whole component: overdamped, because an indicator that\n   overshoots its target reads as imprecise.\n\n   The position arrives as an inline `transform`, and the enter/exit keyframes\n   write that same property — left alone they yank the arrow to the track origin\n   for the length of the fade and slide it back. Seeding the keyframe translate\n   from the primitive's own offset var cancels that, so appearing and leaving\n   stay a pure fade in place and every pixel of travel belongs to the glide.\n\n   The fade's easing is written as `--tw-ease` for the same reason as on the\n   viewport: an `ease-*` utility would also overwrite `transition-timing-function`\n   and flatten the glide, since the element is `data-state=visible` the entire\n   time it is travelling. */\nconst navigationMenuIndicatorMotion = [\n  \"[--tw-enter-translate-x:var(--radix-navigation-menu-indicator-translate-x,0)] [--tw-enter-translate-y:var(--radix-navigation-menu-indicator-translate-y,0)]\",\n  \"[--tw-exit-translate-x:var(--radix-navigation-menu-indicator-translate-x,0)] [--tw-exit-translate-y:var(--radix-navigation-menu-indicator-translate-y,0)]\",\n  \"transition-[transform,width,height] duration-spring-glide ease-spring-glide\",\n  \"data-[state=visible]:animate-in data-[state=visible]:fade-in data-[state=visible]:anim-base data-[state=visible]:[--tw-ease:var(--ease-out-expo)]\",\n  \"data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=hidden]:anim-exit data-[state=hidden]:[--tw-ease:var(--ease-out-expo)]\",\n].join(\" \")\n\nfunction NavigationMenuIndicator({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {\n  return (\n    <NavigationMenuPrimitive.Indicator\n      data-slot=\"navigation-menu-indicator\"\n      className={cn(\n        \"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden\",\n        navigationMenuIndicatorMotion,\n        className\n      )}\n      {...props}\n    >\n      <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md\" />\n    </NavigationMenuPrimitive.Indicator>\n  )\n}\n\nexport {\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n  navigationMenuTriggerStyle,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}