{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "nav-top",
  "type": "registry:block",
  "title": "Top navigation",
  "description": "A site header and navigation bar built from one list of links. Large screens get dropdown menus. Small screens get a drawer that keeps keyboard focus inside it.",
  "categories": [
    "section"
  ],
  "dependencies": [
    "@base-ui-components/react@1.0.0-rc.0",
    "lucide-react@^1.47.0"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/button.json",
    "https://facadeui.dev/r/container.json",
    "https://facadeui.dev/r/types.json",
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/sections/nav-top.tsx",
      "type": "registry:component",
      "target": "components/sections/nav-top.tsx",
      "content": "\"use client\"\n\n/**\n * NavTop — the site header: wordmark, primary navigation, and actions.\n *\n * Two navigations, one source of data. Above `lg` the links render through Base\n * UI's NavigationMenu, which gives items with children a hoverable, keyboard-\n * operable dropdown. Below `lg` the same array renders inside a Base UI Dialog\n * drawer.\n *\n * a11y: the hard parts, and who handles them.\n *\n *  - Drawer: Base UI's Dialog provides the focus trap, Escape to close, scroll\n *    lock and focus restoration to the trigger. Writing those by hand is how\n *    mobile menus end up leaking focus to the page behind them.\n *  - Dropdowns: NavigationMenu supplies `aria-expanded` and arrow-key movement.\n *    A dropdown trigger is a `<button>`, never a link — it opens something, it\n *    does not navigate.\n *  - The current page is marked with `aria-current=\"page\"` from `currentPath`,\n *    so it is announced rather than only highlighted.\n *  - Exactly one `<nav>` landmark is exposed at a time: the drawer's nav is\n *    inside a dialog that does not exist until opened, and the desktop nav is\n *    `hidden` below `lg`, which removes it from the accessibility tree.\n *  - The whole bar is 64px tall with 44px targets, so it stays usable on a phone.\n *\n * The drawer's links close it with a controlled `open` state rather than by\n * wrapping them in `Dialog.Close`. `Dialog.Close` is a button component, and\n * rendering it as an anchor makes Base UI warn and then either emit an invalid\n * `type=\"button\"` on the anchor or, with `nativeButton={false}`, announce a\n * navigating control as a button. A real link with an `onClick` that closes is\n * both correct and simpler.\n *\n * This component is `\"use client\"` because the drawer holds open state. It is\n * the only section in the registry that does.\n *\n * Dependencies: @base-ui-components/react, lucide-react, react,\n * @/lib/types, @/lib/utils, @/components/ui/button,\n * @/components/ui/container.\n */\n\nimport { Dialog } from \"@base-ui-components/react/dialog\"\nimport { NavigationMenu } from \"@base-ui-components/react/navigation-menu\"\nimport { ChevronDownIcon, MenuIcon, XIcon } from \"lucide-react\"\nimport { useState, type ElementType, type ReactNode } from \"react\"\n\nimport type { CtaItem, LinkComponent } from \"@/lib/types\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { Container } from \"@/components/ui/container\"\n\nexport interface NavChild {\n  label: string\n  href: string\n  description?: string\n  external?: boolean\n}\n\nexport interface NavItem {\n  label: string\n  /** A plain link. Omit when the item only opens a dropdown. */\n  href?: string\n  /** Turns the item into a dropdown. */\n  children?: NavChild[]\n  external?: boolean\n}\n\nexport interface NavTopProps {\n  items: NavItem[]\n  /** Wordmark or logo. Wrapped in a link to `homeHref`. */\n  brand: ReactNode\n  homeHref?: string\n  /** Buttons on the right. They are repeated at the bottom of the drawer. */\n  actions?: CtaItem[]\n  link?: LinkComponent\n  /** Marks the matching item with `aria-current=\"page\"`. */\n  currentPath?: string\n  /** Keeps the header at the top of the viewport. */\n  sticky?: boolean\n  /** Label for the drawer trigger and dialog. */\n  menuLabel?: string\n  className?: string\n  id?: string\n}\n\nconst isActive = (href: string | undefined, currentPath: string | undefined): boolean =>\n  Boolean(\n    href &&\n    currentPath &&\n    (href === currentPath || (href !== \"/\" && currentPath.startsWith(href))),\n  )\n\nconst triggerClass =\n  \"text-muted-foreground hover:text-foreground hover:bg-accent/60 focus-visible:ring-ring duration-facade-fast ease-facade-out inline-flex h-11 cursor-pointer items-center gap-1.5 rounded-md px-3 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none data-[popup-open]:text-foreground\"\n\nexport function NavTop({\n  items,\n  brand,\n  homeHref = \"/\",\n  actions = [],\n  link,\n  currentPath,\n  sticky = true,\n  menuLabel = \"Menu\",\n  className,\n  id,\n}: NavTopProps) {\n  const Link = (link ?? \"a\") as ElementType\n  const [menuOpen, setMenuOpen] = useState(false)\n  const closeMenu = () => setMenuOpen(false)\n\n  const externalProps = (external?: boolean) =>\n    external ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {}\n\n  return (\n    <header\n      id={id}\n      className={cn(\n        \"border-border bg-background/85 z-40 border-b backdrop-blur-md\",\n        sticky && \"sticky top-0\",\n        className,\n      )}\n    >\n      <Container className=\"flex h-16 items-center gap-3\">\n        <Link\n          href={homeHref}\n          className=\"focus-visible:ring-ring mr-2 inline-flex items-center rounded-md text-base font-semibold tracking-tight focus-visible:outline-none focus-visible:ring-2\"\n        >\n          {brand}\n        </Link>\n\n        {/* Desktop. `hidden` keeps it out of the accessibility tree below lg. */}\n        <NavigationMenu.Root\n          aria-label=\"Main\"\n          className=\"hidden min-w-0 lg:flex lg:items-center\"\n        >\n          {/*\n            Base UI 1.0.0-rc.0 puts `aria-orientation=\"horizontal\"` on the list.\n            ARIA does not allow that attribute on `role=\"list\"`, and axe flags it\n            as a critical violation. Passing it explicitly as undefined drops the\n            attribute; the arrow-key behaviour it described is handled by Base\n            UI's composite regardless. Revisit when upstream fixes it.\n          */}\n          <NavigationMenu.List\n            aria-orientation={undefined}\n            className=\"flex items-center gap-1\"\n          >\n            {items.map((item) =>\n              item.children?.length ? (\n                <NavigationMenu.Item key={item.label}>\n                  <NavigationMenu.Trigger className={triggerClass}>\n                    {item.label}\n                    <NavigationMenu.Icon>\n                      <ChevronDownIcon\n                        aria-hidden\n                        focusable=\"false\"\n                        className=\"duration-facade-fast ease-facade-out size-4 transition-transform data-[popup-open]:rotate-180\"\n                      />\n                    </NavigationMenu.Icon>\n                  </NavigationMenu.Trigger>\n\n                  <NavigationMenu.Content className=\"w-[min(28rem,90vw)] p-2\">\n                    <ul className=\"flex flex-col gap-1\">\n                      {item.children.map((child) => (\n                        <li key={child.href}>\n                          <NavigationMenu.Link\n                            render={\n                              <Link\n                                href={child.href}\n                                {...externalProps(child.external)}\n                              />\n                            }\n                            active={isActive(child.href, currentPath)}\n                            aria-current={\n                              isActive(child.href, currentPath) ? \"page\" : undefined\n                            }\n                            className=\"hover:bg-accent focus-visible:ring-ring flex flex-col gap-0.5 rounded-md p-3 transition-colors focus-visible:outline-none focus-visible:ring-2\"\n                          >\n                            <span className=\"text-foreground text-sm font-medium\">\n                              {child.label}\n                              {child.external ? (\n                                <span className=\"sr-only\"> (opens in a new tab)</span>\n                              ) : null}\n                            </span>\n                            {child.description ? (\n                              <span className=\"text-muted-foreground text-pretty text-sm\">\n                                {child.description}\n                              </span>\n                            ) : null}\n                          </NavigationMenu.Link>\n                        </li>\n                      ))}\n                    </ul>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              ) : (\n                <NavigationMenu.Item key={item.label}>\n                  <NavigationMenu.Link\n                    render={\n                      <Link href={item.href ?? \"#\"} {...externalProps(item.external)} />\n                    }\n                    active={isActive(item.href, currentPath)}\n                    aria-current={isActive(item.href, currentPath) ? \"page\" : undefined}\n                    className={cn(\n                      triggerClass,\n                      isActive(item.href, currentPath) && \"text-foreground\",\n                    )}\n                  >\n                    {item.label}\n                    {item.external ? (\n                      <span className=\"sr-only\"> (opens in a new tab)</span>\n                    ) : null}\n                  </NavigationMenu.Link>\n                </NavigationMenu.Item>\n              ),\n            )}\n          </NavigationMenu.List>\n\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner\n              sideOffset={10}\n              collisionPadding={16}\n              className=\"z-50\"\n            >\n              <NavigationMenu.Popup className=\"bg-popover text-popover-foreground duration-facade-base ease-facade-out origin-[var(--transform-origin)] rounded-xl border shadow-lg transition-[opacity,transform] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0\">\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>\n\n        <div className=\"ml-auto flex items-center gap-2\">\n          {actions.length > 0 ? (\n            <div className=\"hidden items-center gap-2 sm:flex\">\n              {actions.map((action, index) => (\n                <Link\n                  key={action.href}\n                  href={action.href}\n                  {...externalProps(action.external)}\n                  className={buttonVariants({\n                    variant:\n                      action.variant ??\n                      (index === actions.length - 1 ? \"primary\" : \"ghost\"),\n                    size: \"sm\",\n                  })}\n                >\n                  {action.label}\n                </Link>\n              ))}\n            </div>\n          ) : null}\n\n          {/* Mobile drawer. */}\n          <Dialog.Root open={menuOpen} onOpenChange={setMenuOpen}>\n            {/*\n              Styled with `buttonVariants` rather than `render={<Button />}`.\n              Base UI inspects the element handed to `render` to decide whether\n              it is a native button, and cannot see through a wrapper component\n              — so the Button form logs a warning on every mount even though the\n              DOM ends up correct. Base UI already renders a real <button> here.\n            */}\n            <Dialog.Trigger\n              aria-label={menuLabel}\n              className={cn(\n                buttonVariants({ variant: \"ghost\", size: \"icon\" }),\n                \"lg:hidden\",\n              )}\n            >\n              <MenuIcon aria-hidden focusable=\"false\" />\n            </Dialog.Trigger>\n\n            <Dialog.Portal>\n              <Dialog.Backdrop className=\"duration-facade-base fixed inset-0 z-50 bg-black/50 transition-opacity data-[ending-style]:opacity-0 data-[starting-style]:opacity-0\" />\n              <Dialog.Popup className=\"bg-background duration-facade-base ease-facade-out fixed inset-y-0 right-0 z-50 flex w-[min(22rem,88vw)] flex-col gap-6 overflow-y-auto border-l p-6 transition-transform data-[ending-style]:translate-x-full data-[starting-style]:translate-x-full\">\n                <div className=\"flex items-center justify-between gap-4\">\n                  <Dialog.Title className=\"text-base font-semibold\">\n                    {menuLabel}\n                  </Dialog.Title>\n                  <Dialog.Close\n                    aria-label=\"Close menu\"\n                    className={buttonVariants({ variant: \"ghost\", size: \"icon\" })}\n                  >\n                    <XIcon aria-hidden focusable=\"false\" />\n                  </Dialog.Close>\n                </div>\n                <Dialog.Description className=\"sr-only\">\n                  Site navigation and account actions.\n                </Dialog.Description>\n\n                <nav aria-label=\"Main\" className=\"flex flex-col gap-6\">\n                  {items.map((item) => (\n                    <div key={item.label} className=\"flex flex-col gap-1\">\n                      {item.href ? (\n                        <Link\n                          href={item.href}\n                          {...externalProps(item.external)}\n                          aria-current={\n                            isActive(item.href, currentPath) ? \"page\" : undefined\n                          }\n                          onClick={closeMenu}\n                          className={cn(\n                            \"focus-visible:ring-ring flex min-h-11 items-center rounded-md text-base font-medium focus-visible:outline-none focus-visible:ring-2\",\n                            isActive(item.href, currentPath)\n                              ? \"text-foreground\"\n                              : \"text-muted-foreground hover:text-foreground\",\n                          )}\n                        >\n                          {item.label}\n                        </Link>\n                      ) : (\n                        <p className=\"text-foreground text-sm font-semibold\">\n                          {item.label}\n                        </p>\n                      )}\n\n                      {item.children?.length ? (\n                        <ul className=\"border-border flex flex-col border-l pl-4\">\n                          {item.children.map((child) => (\n                            <li key={child.href}>\n                              <Link\n                                href={child.href}\n                                {...externalProps(child.external)}\n                                aria-current={\n                                  isActive(child.href, currentPath) ? \"page\" : undefined\n                                }\n                                onClick={closeMenu}\n                                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring flex min-h-11 items-center rounded-md text-sm transition-colors focus-visible:outline-none focus-visible:ring-2\"\n                              >\n                                {child.label}\n                              </Link>\n                            </li>\n                          ))}\n                        </ul>\n                      ) : null}\n                    </div>\n                  ))}\n                </nav>\n\n                {actions.length > 0 ? (\n                  <div className=\"mt-auto flex flex-col gap-2 pt-4\">\n                    {actions.map((action, index) => (\n                      <Link\n                        key={action.href}\n                        href={action.href}\n                        {...externalProps(action.external)}\n                        onClick={closeMenu}\n                        className={buttonVariants({\n                          variant:\n                            action.variant ??\n                            (index === actions.length - 1 ? \"primary\" : \"outline\"),\n                          size: \"md\",\n                          fullWidth: true,\n                        })}\n                      >\n                        {action.label}\n                      </Link>\n                    ))}\n                  </div>\n                ) : null}\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </div>\n      </Container>\n    </header>\n  )\n}\n"
    }
  ],
  "docs": "Import styles/facade-tokens.css in your global stylesheet, after @import \"tailwindcss\". Set headingLevel to fit the page outline (sections default to h2). Pass next/link and next/image through the link and image props. Docs: https://facadeui.dev/components/nav-top"
}
