{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "menubar",
  "title": "Menubar",
  "author": "@fma-ui/ui",
  "description": "An app-style menu bar (File, Edit, View…). `Menubar` is a react-aria `Toolbar`, so arrow keys move between menus; each `MenubarMenu` pairs a `MenubarTrigger` with a `MenubarContent` built on `DropdownMenu`, including submenus (`MenubarSub`). Checkbox- and radio-like items come from react-aria selection: give a `MenubarGroup` `selectionMode` (\"multiple\" or \"single\") and `selectedKeys`/`onSelectionChange`, and its items show a check when selected. Menus open on press or Enter/Space/ArrowDown; while one is open, hovering another trigger or pressing ArrowLeft/ArrowRight opens the neighbouring menu.",
  "dependencies": [
    "cn@^0.4.0",
    "react-aria-components@^1.21.1"
  ],
  "registryDependencies": [
    "@fma-ui/dropdown-menu"
  ],
  "files": [
    {
      "path": "src/core/menubar/menubar.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport * as React from \"react\";\nimport {\n  Button,\n  type ButtonProps,\n  composeRenderProps,\n  MenuTrigger,\n  type MenuTriggerProps,\n  Toolbar,\n  type ToolbarProps,\n  useLocale,\n} from \"react-aria-components\";\nimport {\n  DropdownMenu,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n} from \"@/core/dropdown-menu/dropdown-menu\";\n\ntype MenubarState = {\n  openId: string | null;\n  setOpenId: React.Dispatch<React.SetStateAction<string | null>>;\n};\n\nconst MenubarContext = React.createContext<MenubarState | null>(null);\nconst MenubarMenuContext = React.createContext<string | undefined>(undefined);\n\nconst TRIGGER = \"[data-slot=menubar-trigger][data-menu-id]\";\n\n/**\n * A react-aria `Toolbar`: arrow keys move between the menus' triggers. While a\n * menu is open, hovering another trigger or pressing ArrowLeft/ArrowRight in\n * the menu opens the neighbouring menu instead, like a desktop menu bar.\n */\nfunction Menubar({ className, ...props }: ToolbarProps) {\n  const [openId, setOpenId] = React.useState<string | null>(null);\n  // How the open menu was reached by switching: focus its first item\n  // (keyboard) or the menu itself (pointer). Null when opened normally.\n  const switchedBy = React.useRef<\"keyboard\" | \"pointer\" | null>(null);\n  const ref = React.useRef<HTMLDivElement>(null);\n  const { direction } = useLocale();\n\n  React.useEffect(() => {\n    if (openId === null) {\n      switchedBy.current = null;\n      return;\n    }\n    const triggers = () =>\n      Array.from(ref.current?.querySelectorAll<HTMLElement>(TRIGGER) ?? []).filter(\n        (el) => !el.hasAttribute(\"data-disabled\"),\n      );\n    const switchTo = (id: string | undefined, by: \"keyboard\" | \"pointer\") => {\n      if (!id || id === openId) return;\n      switchedBy.current = by;\n      setOpenId(id);\n    };\n\n    // A modal popover makes everything outside it inert, which also drops the\n    // previously focused menu's focus on the body. Put focus in the open menu.\n    const focusOpenMenu = () => {\n      if (!switchedBy.current) return;\n      const active = document.activeElement;\n      if (active && active !== document.body) return;\n      const trigger = triggers().find((el) => el.dataset.menuId === openId);\n      const menu = document.getElementById(trigger?.getAttribute(\"aria-controls\") ?? \"\");\n      const first = menu?.querySelector<HTMLElement>(\"[role^=menuitem]:not([aria-disabled=true])\");\n      (switchedBy.current === \"keyboard\" ? (first ?? menu) : menu)?.focus();\n    };\n    const onFocusOut = () => requestAnimationFrame(focusOpenMenu);\n    const frame = requestAnimationFrame(focusOpenMenu);\n\n    // Inert content is skipped by hit-testing, so hovering another trigger\n    // never reaches it: compare the pointer with the triggers' boxes instead.\n    const onPointerMove = (event: PointerEvent) => {\n      if (event.pointerType !== \"mouse\") return;\n      const hit = triggers().find((el) => {\n        const box = el.getBoundingClientRect();\n        return (\n          event.clientX >= box.left &&\n          event.clientX <= box.right &&\n          event.clientY >= box.top &&\n          event.clientY <= box.bottom\n        );\n      });\n      switchTo(hit?.dataset.menuId, \"pointer\");\n    };\n\n    const onKeyDown = (event: KeyboardEvent) => {\n      if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return;\n      if (!(event.target instanceof Element)) return;\n      // Only from the menu itself: submenus use these keys to open and close.\n      const surface = event.target.closest(\n        \"[data-slot=menubar-content], [data-slot=dropdown-menu-sub-content]\",\n      );\n      if (surface?.getAttribute(\"data-slot\") !== \"menubar-content\") return;\n      const forward = (event.key === \"ArrowRight\") === (direction === \"ltr\");\n      if (forward && event.target.closest(\"[aria-haspopup]\")) return;\n      const list = triggers();\n      const index = list.findIndex((el) => el.dataset.menuId === openId);\n      if (index === -1 || list.length < 2) return;\n      event.preventDefault();\n      event.stopPropagation();\n      switchTo(\n        list[(index + (forward ? 1 : -1) + list.length) % list.length]?.dataset.menuId,\n        \"keyboard\",\n      );\n    };\n\n    document.addEventListener(\"pointermove\", onPointerMove);\n    document.addEventListener(\"keydown\", onKeyDown, true);\n    document.addEventListener(\"focusout\", onFocusOut);\n    return () => {\n      cancelAnimationFrame(frame);\n      document.removeEventListener(\"pointermove\", onPointerMove);\n      document.removeEventListener(\"keydown\", onKeyDown, true);\n      document.removeEventListener(\"focusout\", onFocusOut);\n    };\n  }, [openId, direction]);\n\n  const state = React.useMemo(() => ({ openId, setOpenId }), [openId]);\n\n  return (\n    <MenubarContext.Provider value={state}>\n      <Toolbar\n        ref={ref}\n        data-slot=\"menubar\"\n        className={composeRenderProps(className, (className) =>\n          cn(\"flex h-8 items-center gap-0.5 rounded-lg border p-[3px]\", className),\n        )}\n        {...props}\n      />\n    </MenubarContext.Provider>\n  );\n}\n\n/**\n * One menu: a `MenubarTrigger` followed by its `MenubarContent`. Inside a\n * `Menubar` its open state is shared with the other menus, so only one is\n * open at a time; `isOpen` still controls it when given.\n */\nfunction MenubarMenu({ isOpen, defaultOpen, onOpenChange, ...props }: MenuTriggerProps) {\n  const id = React.useId();\n  const bar = React.useContext(MenubarContext);\n  const setOpenId = bar?.setOpenId;\n\n  React.useEffect(() => {\n    if (defaultOpen) setOpenId?.(id);\n  }, [defaultOpen, id, setOpenId]);\n\n  if (!bar) {\n    return (\n      <MenuTrigger\n        isOpen={isOpen}\n        defaultOpen={defaultOpen}\n        onOpenChange={onOpenChange}\n        {...props}\n      />\n    );\n  }\n\n  return (\n    <MenubarMenuContext.Provider value={id}>\n      <MenuTrigger\n        isOpen={isOpen ?? bar.openId === id}\n        onOpenChange={(open) => {\n          bar.setOpenId((current) => (open ? id : current === id ? null : current));\n          onOpenChange?.(open);\n        }}\n        {...props}\n      />\n    </MenubarMenuContext.Provider>\n  );\n}\n\nfunction MenubarTrigger({ className, ...props }: ButtonProps) {\n  const menuId = React.useContext(MenubarMenuContext);\n  return (\n    <Button\n      data-slot=\"menubar-trigger\"\n      data-menu-id={menuId}\n      className={composeRenderProps(className, (className) =>\n        cn(\n          \"flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none data-focus-visible:ring-3 data-focus-visible:ring-ring/50 data-hovered:bg-muted data-pressed:bg-muted aria-expanded:bg-muted\",\n          className,\n        ),\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarContent({\n  className,\n  placement = \"bottom start\",\n  offset = 8,\n  crossOffset = -4,\n  ...props\n}: React.ComponentProps<typeof DropdownMenu>) {\n  return (\n    <DropdownMenu\n      data-slot=\"menubar-content\"\n      placement={placement}\n      offset={offset}\n      crossOffset={crossOffset}\n      className={cn(\"w-auto min-w-36\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Groups items under an optional `MenubarLabel`. Set `selectionMode` (\"single\"\n * for radio-like, \"multiple\" for checkbox-like items) with\n * `selectedKeys`/`onSelectionChange` to make its items selectable.\n */\nconst MenubarGroup = DropdownMenuGroup;\nconst MenubarItem = DropdownMenuItem;\nconst MenubarLabel = DropdownMenuLabel;\nconst MenubarSeparator = DropdownMenuSeparator;\nconst MenubarShortcut = DropdownMenuShortcut;\nconst MenubarSub = DropdownMenuSub;\nconst MenubarSubTrigger = DropdownMenuSubTrigger;\nconst MenubarSubContent = DropdownMenuSubContent;\n\nexport {\n  Menubar,\n  MenubarContent,\n  MenubarGroup,\n  MenubarItem,\n  MenubarLabel,\n  MenubarMenu,\n  MenubarSeparator,\n  MenubarShortcut,\n  MenubarSub,\n  MenubarSubContent,\n  MenubarSubTrigger,\n  MenubarTrigger,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ui/menubar.tsx"
    }
  ],
  "meta": {
    "status": "stable",
    "source": "original",
    "origin": "API of shadcn/ui's Menubar (via EMITTE), rebuilt on react-aria Toolbar and the registry's DropdownMenu",
    "tags": [
      "navigation",
      "menu",
      "menubar",
      "toolbar",
      "react-aria"
    ]
  },
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}