{
  "name": "menu",
  "type": "registry:ui",
  "title": "Menu",
  "description": "ONE floating-menu engine for dropdown, right-click, and overflow actions: items, icons, printed keys, checkbox items, danger items, max one submenu level.",
  "dependencies": [
    "@candlelight-ds/tokens",
    "@radix-ui/react-dropdown-menu",
    "@radix-ui/react-context-menu",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "kbd",
    "utils"
  ],
  "files": [
    {
      "path": "components/ui/Menu.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\n/**\n * Menu — ONE floating-menu engine for dropdowns, right-click menus, and\n * overflow \"…\" actions. Radix mechanics (focus, typeahead, arrow keys,\n * collision), candlelight skin, printed keys, hold-to-strike-ready\n * danger items. A second menu engine anywhere is a bug (spec §8).\n *\n * Use when: contextual actions on an object.\n * Don't use when: navigation (links) or picking data values (Select).\n * Submenus: max one level; deeper structures get an Overlay.\n *\n *   <Menu trigger={<Button variant=\"quiet\">…</Button>}>\n *     <MenuItem icon={<Pencil/>} kbd=\"R\" onSelect={rename}>Rename</MenuItem>\n *     <MenuSeparator />\n *     <MenuItem danger onSelect={strike}>Strike</MenuItem>\n *   </Menu>\n *\n *   <ContextMenu content={<><MenuItem …/></>}>\n *     <div>right-clickable region</div>\n *   </ContextMenu>\n */\n\nimport * as Dropdown from '@radix-ui/react-dropdown-menu'\nimport * as RadixContext from '@radix-ui/react-context-menu'\nimport { Check, ChevronRight } from 'lucide-react'\nimport { createContext, useContext } from 'react'\nimport { cn } from '@/lib/utils'\nimport { Kbd } from './Kbd'\n\n/* Shared skin ----------------------------------------------------------- */\n\nconst PANEL_CLASS =\n  'z-float min-w-[200px] rounded-sm border border-hairline bg-surface-2 p-1 shadow-float animate-stage-rise'\n\nconst ITEM_CLASS =\n  'flex w-full cursor-pointer select-none items-center gap-2.5 rounded-sm px-2.5 py-2 text-body outline-none transition-colors data-[highlighted]:bg-fill-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-40'\n\n/* Mode context lets the same Item components work in both engines -------- */\n\ntype MenuMode = 'dropdown' | 'context'\nconst MenuModeContext = createContext<MenuMode>('dropdown')\n\ninterface MenuItemProps {\n  children: React.ReactNode\n  icon?: React.ReactNode\n  kbd?: string\n  danger?: boolean\n  disabled?: boolean\n  /** Keep the menu open after selecting (rapid-fire actions like tagging). */\n  closeOnSelect?: boolean\n  onSelect?: () => void\n}\n\nexport function MenuItem({ children, icon, kbd, danger, disabled, closeOnSelect = true, onSelect }: MenuItemProps) {\n  const mode = useContext(MenuModeContext)\n  const Item = mode === 'context' ? RadixContext.Item : Dropdown.Item\n\n  return (\n    <Item\n      disabled={disabled}\n      onSelect={(event) => {\n        if (!closeOnSelect) event.preventDefault()\n        onSelect?.()\n      }}\n      className={cn(\n        ITEM_CLASS,\n        danger\n          ? 'text-danger data-[highlighted]:bg-danger-fill'\n          : 'text-ink-mute data-[highlighted]:text-ink'\n      )}\n    >\n      {icon ? <span className=\"flex h-4 w-4 items-center justify-center [&>svg]:h-3.5 [&>svg]:w-3.5\">{icon}</span> : null}\n      <span className=\"flex-1\">{children}</span>\n      {kbd ? <Kbd>{kbd}</Kbd> : null}\n    </Item>\n  )\n}\n\ninterface MenuCheckboxItemProps {\n  children: React.ReactNode\n  checked: boolean\n  onCheckedChange: (checked: boolean) => void\n  disabled?: boolean\n  /** Right-aligned slot (counts, kbd). */\n  trailing?: React.ReactNode\n}\n\n/** Checkbox item for filter menus. The menu stays open while toggling. */\nexport function MenuCheckboxItem({ children, checked, onCheckedChange, disabled, trailing }: MenuCheckboxItemProps) {\n  const mode = useContext(MenuModeContext)\n  const Item = mode === 'context' ? RadixContext.CheckboxItem : Dropdown.CheckboxItem\n  const Indicator = mode === 'context' ? RadixContext.ItemIndicator : Dropdown.ItemIndicator\n\n  return (\n    <Item\n      checked={checked}\n      onCheckedChange={onCheckedChange}\n      disabled={disabled}\n      onSelect={(event) => event.preventDefault()}\n      className={cn(ITEM_CLASS, 'text-ink-mute data-[highlighted]:text-ink')}\n    >\n      <span className=\"flex h-4 w-4 items-center justify-center rounded-cap border border-hairline-strong\">\n        <Indicator>\n          <Check className=\"h-3 w-3 text-brass\" />\n        </Indicator>\n      </span>\n      <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n      {trailing ? <span className=\"shrink-0 text-eyebrow tabular-nums text-ink-faint\">{trailing}</span> : null}\n    </Item>\n  )\n}\n\nexport function MenuSeparator() {\n  const mode = useContext(MenuModeContext)\n  const Separator = mode === 'context' ? RadixContext.Separator : Dropdown.Separator\n  // A rule sits inside a baseline-height band (§2.6a): a bare 1px element\n  // in the flow would knock everything after it off the rhythm.\n  return (\n    <Separator className=\"my-1 flex h-1 items-center\">\n      <span aria-hidden className=\"h-px w-full bg-hairline\" />\n    </Separator>\n  )\n}\n\nexport function MenuLabel({ children }: { children: React.ReactNode }) {\n  const mode = useContext(MenuModeContext)\n  const Label = mode === 'context' ? RadixContext.Label : Dropdown.Label\n  return (\n    <Label className=\"px-2.5 pb-1 pt-2 text-micro uppercase tracking-label text-ink-faint\">\n      {children}\n    </Label>\n  )\n}\n\ninterface MenuSubProps {\n  label: React.ReactNode\n  icon?: React.ReactNode\n  children: React.ReactNode\n}\n\n/** One level only. Deeper than this → open an Overlay. */\nexport function MenuSub({ label, icon, children }: MenuSubProps) {\n  const mode = useContext(MenuModeContext)\n  const Sub = mode === 'context' ? RadixContext.Sub : Dropdown.Sub\n  const SubTrigger = mode === 'context' ? RadixContext.SubTrigger : Dropdown.SubTrigger\n  const Portal = mode === 'context' ? RadixContext.Portal : Dropdown.Portal\n  const SubContent = mode === 'context' ? RadixContext.SubContent : Dropdown.SubContent\n\n  return (\n    <Sub>\n      <SubTrigger className={cn(ITEM_CLASS, 'text-ink-mute data-[highlighted]:text-ink data-[state=open]:bg-fill-hover')}>\n        {icon ? <span className=\"flex h-4 w-4 items-center justify-center [&>svg]:h-3.5 [&>svg]:w-3.5\">{icon}</span> : null}\n        <span className=\"flex-1\">{label}</span>\n        <ChevronRight className=\"h-3.5 w-3.5 text-ink-faint\" />\n      </SubTrigger>\n      <Portal>\n        <SubContent className={PANEL_CLASS} sideOffset={4} collisionPadding={8}>\n          {children}\n        </SubContent>\n      </Portal>\n    </Sub>\n  )\n}\n\n/* Dropdown (trigger-based) ----------------------------------------------- */\n\ninterface MenuProps {\n  trigger: React.ReactNode\n  children: React.ReactNode\n  align?: 'start' | 'center' | 'end'\n  side?: 'top' | 'bottom' | 'left' | 'right'\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\nexport function Menu({ trigger, children, align = 'end', side = 'bottom', open, onOpenChange }: MenuProps) {\n  return (\n    <MenuModeContext.Provider value=\"dropdown\">\n      <Dropdown.Root open={open} onOpenChange={onOpenChange}>\n        <Dropdown.Trigger asChild>{trigger}</Dropdown.Trigger>\n        <Dropdown.Portal>\n          <Dropdown.Content\n            align={align}\n            side={side}\n            sideOffset={6}\n            collisionPadding={8}\n            className={PANEL_CLASS}\n          >\n            {children}\n          </Dropdown.Content>\n        </Dropdown.Portal>\n      </Dropdown.Root>\n    </MenuModeContext.Provider>\n  )\n}\n\n/* Context menu (right-click region) -------------------------------------- */\n\ninterface ContextMenuProps {\n  content: React.ReactNode\n  children: React.ReactNode\n  disabled?: boolean\n}\n\nexport function ContextMenu({ content, children, disabled }: ContextMenuProps) {\n  if (disabled) return <>{children}</>\n\n  return (\n    <MenuModeContext.Provider value=\"context\">\n      <RadixContext.Root>\n        <RadixContext.Trigger asChild>{children}</RadixContext.Trigger>\n        <RadixContext.Portal>\n          <RadixContext.Content className={PANEL_CLASS} collisionPadding={8}>\n            {content}\n          </RadixContext.Content>\n        </RadixContext.Portal>\n      </RadixContext.Root>\n    </MenuModeContext.Provider>\n  )\n}\n",
      "target": "components/ui/Menu.tsx"
    }
  ],
  "meta": {
    "candlelight": {
      "category": "primitive",
      "gRules": [
        "G2",
        "G9"
      ],
      "useWhen": "Contextual actions on an object — dropdown, right-click, or overflow.",
      "dontUseWhen": "Navigation (links) or picking data values (Select). Deeper than one submenu level (Overlay).",
      "states": [
        "closed",
        "open",
        "item highlighted",
        "item disabled",
        "danger item",
        "checkbox item",
        "submenu open"
      ],
      "keyboard": [
        {
          "keys": "↑↓",
          "does": "walk items"
        },
        {
          "keys": "Enter",
          "does": "select"
        },
        {
          "keys": "Esc",
          "does": "close"
        },
        {
          "keys": "type",
          "does": "typeahead"
        }
      ],
      "acceptanceTests": [
        "A second menu engine anywhere is a bug — dropdown and context modes share this one skin.",
        "closeOnSelect:false keeps the menu open for rapid-fire actions.",
        "Danger items wear the cold tone and pair with hold-to-strike for destruction."
      ]
    }
  }
}