{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "menubar",
  "title": "Menubar",
  "description": "A menubar component.",
  "dependencies": [
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "@cubby-ui/switch",
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/menubar/menubar.tsx",
      "content": "import { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { Menubar as BaseMenubar } from \"@base-ui/react/menubar\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  solidSurface,\n  type SurfaceLevel,\n} from \"@/registry/default/lib/elevated\";\n\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport ArrowRight01Icon from \"@hugeicons/core-free-icons/ArrowRight01Icon\";\nimport Tick02Icon from \"@hugeicons/core-free-icons/Tick02Icon\";\nimport {\n  SwitchVisual,\n  type SwitchVisualProps,\n} from \"@/registry/default/switch/switch\";\n\n// Shared shell for checkbox and radio items. Padding matches the plain menu\n// item so labels line up across every item type; the indicator lives in a\n// reserved right-hand column so toggling never shifts the label.\nconst toggleItemClasses =\n  \"group/switch data-highlighted:bg-surface-hover data-highlighted:text-accent-foreground grid cursor-default items-center rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\";\n\n// The tick draws itself in on check. `pathLength` restates the path as 1 unit\n// long, so the dash values are fractions of the stroke and survive a HugeIcons\n// reshape. Deriving the icon array is the only way to reach the path.\nconst tickIcon = Tick02Icon.map(([tag, attrs]) => [\n  tag,\n  { ...attrs, pathLength: 1 },\n]) as typeof Tick02Icon;\n\nconst checkmarkClasses =\n  \"[&_path]:ease-out-expo [&_path]:transition-[stroke-dashoffset] [&_path]:duration-150 [&_path]:[stroke-dasharray:1] in-data-checked:[&_path]:[stroke-dashoffset:0] in-data-unchecked:[&_path]:[stroke-dashoffset:1] motion-reduce:[&_path]:transition-none\";\n\nfunction Menubar({\n  className,\n  level = 2,\n  shadowLevel = 2,\n  ...props\n}: React.ComponentProps<typeof BaseMenubar> & {\n  /** Surface elevation level (1-8). Defaults to 2 — a subtle inline toolbar that sits just above the page. */\n  level?: SurfaceLevel;\n  /** Shadow weight (1-8). Defaults to 2. */\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <BaseMenubar\n      data-slot=\"menubar\"\n      data-level={level}\n      className={cn(\n        \"relative flex h-9 items-center gap-1 rounded-lg p-1\",\n        solidSurface(level, shadowLevel),\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarMenu({ ...props }: React.ComponentProps<typeof BaseMenu.Root>) {\n  return <BaseMenu.Root data-slot=\"menubar-menu\" {...props} />;\n}\n\nfunction MenubarGroup({\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Group>) {\n  return <BaseMenu.Group data-slot=\"menubar-group\" {...props} />;\n}\n\nfunction MenubarPortal({\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Portal>) {\n  return <BaseMenu.Portal data-slot=\"menubar-portal\" {...props} />;\n}\n\nfunction MenubarRadioGroup({\n  ...props\n}: React.ComponentProps<typeof BaseMenu.RadioGroup>) {\n  return <BaseMenu.RadioGroup data-slot=\"menubar-radio-group\" {...props} />;\n}\n\nfunction MenubarTrigger({\n  className,\n  delay = 0,\n  closeDelay = 0,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Trigger>) {\n  return (\n    <BaseMenu.Trigger\n      data-slot=\"menubar-trigger\"\n      delay={delay}\n      closeDelay={closeDelay}\n      className={cn(\n        \"data-popup-open:text-accent-foreground hover:text-accent-foreground hover:bg-surface-hover data-popup-open:bg-surface-hover flex items-center rounded-sm px-2.5 py-1 text-sm font-medium select-none\",\n        // Menubar triggers are a real tab stop and arrowing between them moves\n        // DOM focus, so they need a visible indicator. `outline-offset-1`\n        // rather than the usual 2 because the triggers sit in a tight row and\n        // a wider ring would collide with its neighbours.\n        \"focus-visible:outline-ring/50 outline-0 outline-offset-0 outline-transparent outline-solid focus-visible:outline-2 focus-visible:outline-offset-1\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarContent({\n  className,\n  children,\n  align = \"start\",\n  alignOffset = -4,\n  sideOffset = 8,\n  level = 3,\n  shadowLevel = 3,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Popup> & {\n  align?: BaseMenu.Positioner.Props[\"align\"];\n  alignOffset?: BaseMenu.Positioner.Props[\"alignOffset\"];\n  sideOffset?: BaseMenu.Positioner.Props[\"sideOffset\"];\n  /** Surface elevation level for the popup bg (1-8). Bump when nesting inside a Dialog or other elevated container. Defaults to 3. */\n  level?: SurfaceLevel;\n  /** Shadow weight (1-8). Pinned to 3 by default so the menu reads the same regardless of nesting depth. */\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <MenubarPortal>\n      {/* No guard here, unlike DropdownMenu: there one positioner is shared by\n          detached triggers and physically travels between them. Each MenubarMenu\n          mounts its own, with no previous position to animate from. */}\n      <BaseMenu.Positioner\n        className=\"z-50 h-(--positioner-height) max-h-(--available-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none\"\n        align={align}\n        alignOffset={alignOffset}\n        sideOffset={sideOffset}\n      >\n        <BaseMenu.Popup\n          data-slot=\"menubar-content\"\n          data-level={level}\n          className={cn(\n            \"text-popover-foreground relative max-h-(--available-height) max-w-(--available-width) min-w-[12rem] overflow-hidden rounded-xl outline-none\",\n            solidSurface(level, shadowLevel),\n            \"h-(--popup-height,auto) w-(--popup-width,auto)\",\n            \"origin-(--transform-origin) transition-[width,height,scale,opacity] duration-[150ms,150ms,100ms,100ms] ease-[cubic-bezier(0.22,1,0.36,1),cubic-bezier(0.22,1,0.36,1),var(--ease-out-expo),var(--ease-out-expo)]\",\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n            \"data-ending-style:scale-95 data-ending-style:opacity-0\",\n            // Own compositor layer while mounted. width/height in the transition\n            // list stops Chrome compositing the scale, so rows re-raster every\n            // frame and the differential stretch reads as a ripple. ContextMenu\n            // escapes it by transitioning scale and opacity alone.\n            \"will-change-transform\",\n            \"motion-reduce:transition-none motion-reduce:will-change-auto\",\n            // No data-instant guard, deliberately. 'group' is the menubar case,\n            // File to Edit, and suppressing it is the conventional choice. Each\n            // MenubarMenu owns its popup, so the swap is a real unmount and\n            // mount: a guard would snap two separate animations where letting\n            // them run reads as a crossfade. 'dismiss' must animate or there is\n            // no exit, and 'trigger-change' cannot fire with one trigger per\n            // root.\n            className,\n          )}\n          {...props}\n        >\n          {/*\n            Kept for sizing, not for content transitions. Base UI runs the popup\n            auto-resize from inside the Viewport, and that is what writes\n            --positioner-width / --positioner-height (plus --popup-width /\n            --popup-height, which sit at `auto` between resizes). The positioner\n            above sizes off those, so dropping the Viewport leaves them unset\n            and its explicit box collapses.\n\n            Deliberately carries no morph styling. A menubar can't morph: each\n            MenubarMenu is its own Menu.Root, so moving from File to Edit mounts\n            a new popup rather than swapping content within one, and\n            data-current / data-previous / data-activation-direction never\n            appear. (Verified: two distinct popup elements across a switch.)\n          */}\n          <BaseMenu.Viewport\n            data-slot=\"menubar-viewport\"\n            // No `max-w`: `w-full` resolves against the popup's already-capped\n            // content box, so the width bound is inherited for free.\n            className=\"relative max-h-(--available-height) w-full overflow-clip overflow-y-auto p-1\"\n          >\n            {children}\n          </BaseMenu.Viewport>\n        </BaseMenu.Popup>\n      </BaseMenu.Positioner>\n    </MenubarPortal>\n  );\n}\n\nfunction MenubarItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Item> & {\n  inset?: boolean;\n  variant?: \"default\" | \"destructive\";\n}) {\n  return (\n    <BaseMenu.Item\n      data-slot=\"menubar-item\"\n      data-inset={inset || undefined}\n      data-variant={variant}\n      className={cn(\n        \"data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive-foreground data-[variant=destructive]:*:[svg]:text-destructive! data-highlighted:data-[variant=destructive]:*:[svg]:text-destructive-foreground! [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:bg-surface-hover relative flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarLinkItem({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.LinkItem> & {\n  inset?: boolean;\n}) {\n  return (\n    <BaseMenu.LinkItem\n      data-slot=\"menubar-link-item\"\n      data-inset={inset || undefined}\n      className={cn(\n        \"data-highlighted:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:bg-surface-hover relative flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-sm no-underline outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  indicator = \"check\",\n  switchColor = \"primary\",\n  switchShape = \"circle\",\n  switchSize = \"xs\",\n  switchMotion = \"default\",\n  ...props\n}: React.ComponentProps<typeof BaseMenu.CheckboxItem> & {\n  inset?: boolean;\n  /** Which indicator sits in the right-hand column. `\"switch\"` is visual only — the item keeps the `menuitemcheckbox` role. */\n  indicator?: \"check\" | \"switch\";\n  /** Checked track colour, when `indicator=\"switch\"`. */\n  switchColor?: SwitchVisualProps[\"color\"];\n  /** Thumb silhouette, when `indicator=\"switch\"`. */\n  switchShape?: SwitchVisualProps[\"shape\"];\n  /** Thumb size, when `indicator=\"switch\"`. Defaults to `xs`, which matches the row's text. */\n  switchSize?: SwitchVisualProps[\"size\"];\n  /** How the thumb travels, when `indicator=\"switch\"`. */\n  switchMotion?: SwitchVisualProps[\"motion\"];\n}) {\n  return (\n    <BaseMenu.CheckboxItem\n      data-slot=\"menubar-checkbox-item\"\n      data-inset={inset || undefined}\n      data-indicator={indicator}\n      className={cn(\n        toggleItemClasses,\n        indicator === \"switch\"\n          ? \"grid-cols-[1fr_auto] gap-3\"\n          : \"grid-cols-[1fr_1rem] gap-2\",\n        className,\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"col-start-1 flex min-w-0 items-center gap-2\">\n        {children}\n      </span>\n      {indicator === \"switch\" ? (\n        // Visual only: the row carries the role and the click target, so a real\n        // Switch here would nest a focusable control inside a menuitemcheckbox.\n        // Base UI's indicator supplies the aria-hidden.\n        <SwitchVisual\n          color={switchColor}\n          shape={switchShape}\n          size={switchSize}\n          motion={switchMotion}\n          className=\"col-start-2\"\n          render={<BaseMenu.CheckboxItemIndicator keepMounted />}\n        />\n      ) : (\n        <BaseMenu.CheckboxItemIndicator\n          keepMounted\n          className=\"col-start-2 flex items-center justify-center\"\n        >\n          <HugeiconsIcon\n            icon={tickIcon}\n            strokeWidth={2.5}\n            className={cn(\"size-4\", checkmarkClasses)}\n          />\n        </BaseMenu.CheckboxItemIndicator>\n      )}\n    </BaseMenu.CheckboxItem>\n  );\n}\n\nfunction MenubarRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.RadioItem> & {\n  inset?: boolean;\n}) {\n  return (\n    <BaseMenu.RadioItem\n      data-slot=\"menubar-radio-item\"\n      data-inset={inset || undefined}\n      className={cn(toggleItemClasses, \"grid-cols-[1fr_1rem] gap-2\", className)}\n      {...props}\n    >\n      <span className=\"col-start-1 flex min-w-0 items-center gap-2\">\n        {children}\n      </span>\n      <BaseMenu.RadioItemIndicator\n        keepMounted\n        className=\"col-start-2 flex items-center justify-center\"\n      >\n        <HugeiconsIcon\n          icon={tickIcon}\n          strokeWidth={2.5}\n          className={cn(\"size-4\", checkmarkClasses)}\n        />\n      </BaseMenu.RadioItemIndicator>\n    </BaseMenu.RadioItem>\n  );\n}\n\nfunction MenubarLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  inset?: boolean;\n}) {\n  return (\n    <div\n      data-slot=\"menubar-label\"\n      data-inset={inset || undefined}\n      className={cn(\n        \"px-2.5 py-1.5 text-xs font-medium data-inset:pl-8\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarGroupLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.GroupLabel> & {\n  inset?: boolean;\n}) {\n  return (\n    <BaseMenu.GroupLabel\n      data-slot=\"menubar-group-label\"\n      data-inset={inset || undefined}\n      className={cn(\n        \"px-2.5 py-1.5 text-xs font-medium data-inset:pl-8\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Separator>) {\n  return (\n    <BaseMenu.Separator\n      data-slot=\"menubar-separator\"\n      // Inset to the item label, not the popup edge: mx-2.5 clears the\n      // viewport's p-1 plus the item's px-2.5, so the rule starts where the\n      // text does instead of running into the popup's rounded corners.\n      className={cn(\"bg-border mx-2.5 my-1 h-px\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"menubar-shortcut\"\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarSub({\n  ...props\n}: React.ComponentProps<typeof BaseMenu.SubmenuRoot>) {\n  return <BaseMenu.SubmenuRoot data-slot=\"menubar-sub\" {...props} />;\n}\n\nfunction MenubarSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.SubmenuTrigger> & {\n  inset?: boolean;\n}) {\n  return (\n    <BaseMenu.SubmenuTrigger\n      data-slot=\"menubar-sub-trigger\"\n      data-inset={inset || undefined}\n      className={cn(\n        \"data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-highlighted:bg-surface-hover data-popup-open:bg-surface-hover flex cursor-default items-center rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-inset:pl-8\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <HugeiconsIcon\n        icon={ArrowRight01Icon}\n        className=\"ml-auto h-4 w-4\"\n        strokeWidth={2}\n      />\n    </BaseMenu.SubmenuTrigger>\n  );\n}\n\nfunction MenubarSubContent({\n  className,\n  children,\n  sideOffset = 8,\n  level = 5,\n  shadowLevel = 3,\n  ...props\n}: React.ComponentProps<typeof BaseMenu.Popup> & {\n  sideOffset?: BaseMenu.Positioner.Props[\"sideOffset\"];\n  /** Surface elevation level for the submenu bg (1-8). Defaults to 5 — one tier above the parent menu's default of 3. */\n  level?: SurfaceLevel;\n  /** Shadow weight (1-8). Pinned to 3 by default so the submenu reads the same dropdown weight as its parent. */\n  shadowLevel?: SurfaceLevel;\n}) {\n  return (\n    <MenubarPortal>\n      <BaseMenu.Positioner\n        className=\"z-50 max-h-(--available-height) max-w-(--available-width)\"\n        sideOffset={sideOffset}\n      >\n        <BaseMenu.Popup\n          data-slot=\"menubar-sub-content\"\n          data-level={level}\n          className={cn(\n            \"text-popover-foreground relative max-h-(--available-height) max-w-(--available-width) min-w-[12rem] overflow-hidden rounded-xl outline-none\",\n            solidSurface(level, shadowLevel),\n            // Submenus open as their own popup (no Viewport content-swap) — scale + fade\n            \"ease-out-expo origin-(--transform-origin) transition-[transform,scale,opacity] duration-100\",\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n            \"data-ending-style:scale-95 data-ending-style:opacity-0\",\n            \"motion-reduce:transition-none\",\n            className,\n          )}\n          {...props}\n        >\n          {/* The popup above is capped at --available-height and clips, so the\n            scroll container has to live here or a tall submenu silently\n            truncates. The main popups get this from Menu.Viewport; a\n            submenu has none, so it is spelled out. */}\n          <div className=\"max-h-(--available-height) overflow-x-hidden overflow-y-auto p-1\">\n            {children}\n          </div>\n        </BaseMenu.Popup>\n      </BaseMenu.Positioner>\n    </MenubarPortal>\n  );\n}\n\nexport {\n  Menubar,\n  MenubarPortal,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarGroup,\n  MenubarSeparator,\n  MenubarGroupLabel,\n  MenubarLabel,\n  MenubarItem,\n  MenubarLinkItem,\n  MenubarShortcut,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarSub,\n  MenubarSubTrigger,\n  MenubarSubContent,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/menubar.tsx"
    }
  ],
  "type": "registry:ui"
}