{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu",
  "title": "Dropdown Menu",
  "description": "Dropdown Menu from the Get IT Done design system.",
  "dependencies": [
    "@radix-ui/react-dropdown-menu@^2.2.1",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/recipes.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/dropdown-menu.tsx",
      "content": "'use client';\n\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { Check, ChevronRight, Circle } from 'lucide-react';\nimport * as React from 'react';\n\nimport { menuItem, menuItemDestructive, menuItemZero, overlayMotion, overlaySurface, overlaySurfaceZero } from '@/lib/recipes';\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\n\n/**\n * Plain re-export — Radix's `Menu.Group` already spreads arbitrary div props (incl.\n * `aria-labelledby`) onto its `role=\"group\"` node (verified directly in\n * `@radix-ui/react-menu/dist/index.js`: `Primitive.div, { role: \"group\", ...groupProps }`), so no\n * wrapper is needed to *accept* the prop. What was missing (DROPDOWNMENU-A11Y-03) was a reliable id\n * to point it at: pair this with a `DropdownMenuLabel` by giving the label an explicit `id` and\n * passing that same string as this group's `aria-labelledby` — see the \"My Account\" pairing in\n * dropdown-menu.stories.tsx. `DropdownMenuLabel` also self-assigns a `React.useId()` fallback so it\n * is never id-less even when a consumer forgets to pair it.\n */\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\n/**\n * Left unstyled by design — matches the repo-wide \"headless trigger, styled child\" convention\n * already used by `PopoverTrigger`/`DialogTrigger` (see popover.tsx/dialog.tsx). A consumer always\n * composes this with `asChild` around a real focusable element (typically `<Button>`, see\n * dropdown-menu.stories.tsx), and that child already carries the system's token-based\n * `focus-visible:ring-2 ring-ring-default` treatment — adding a second, redundant ring here would\n * conflict rather than help. The Figma \"Dropdown\" component's own Button/Icon/Avatar trigger\n * looks are therefore not new props on this primitive; they're achieved by composition (see\n * dropdown-menu.figma.tsx).\n */\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\n/**\n * Same passthrough-`aria-labelledby` note as `DropdownMenuGroup` above (Radix's `MenuRadioGroup`\n * renders through `MenuGroup` internally, so it inherits the same prop spread) — pair with a\n * `DropdownMenuLabel`'s `id`, as done for \"Panel position\" in dropdown-menu.stories.tsx.\n */\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.SubTrigger\n      ref={ref}\n      className={cn(\n        zero ? menuItemZero : menuItem,\n        'data-[state=open]:bg-muted',\n        inset && 'pl-8',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRight className=\"ml-auto h-4 w-4\" />\n    </DropdownMenuPrimitive.SubTrigger>\n  );\n});\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\n/**\n * `motion-reduce:animate-none` (plus killing the transition and the closed-state animate-out\n * variant) gives `prefers-reduced-motion: reduce` users an instant show/hide instead of the\n * slide/zoom/fade — Radix still drives open/close via the `data-state` attribute, so visibility\n * itself is unaffected, only the motion is. Mirrors tooltip.tsx's `TooltipContent` /\n * select.tsx's `SelectContent` exactly.\n */\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.SubContent\n      ref={ref}\n      className={cn(\n        'z-dropdown min-w-[8rem] overflow-hidden p-1',\n        overlaySurface,\n        zero && overlaySurfaceZero,\n        overlayMotion,\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.Portal>\n      <DropdownMenuPrimitive.Content\n        ref={ref}\n        sideOffset={sideOffset}\n        className={cn(\n          'z-dropdown min-w-[8rem] overflow-hidden p-1',\n          overlaySurface,\n        zero && overlaySurfaceZero,\n          overlayMotion,\n          'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n          className,\n        )}\n        {...props}\n      />\n    </DropdownMenuPrimitive.Portal>\n  );\n});\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean;\n    /** `destructive` renders the row in the destructive role (delete, remove, sign out). */\n    variant?: 'default' | 'destructive';\n  }\n>(({ className, inset, variant = 'default', ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.Item\n      ref={ref}\n      className={cn(\n        zero ? menuItemZero : menuItem,\n        'cursor-pointer',\n        variant === 'destructive' && menuItemDestructive,\n        inset && 'pl-8',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      ref={ref}\n      className={cn(\n        zero ? menuItemZero : menuItem,\n        'pl-8',\n        className,\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"absolute left-2 flex h-[14px] w-[14px] items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <Check className=\"h-4 w-4\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.CheckboxItem>\n  );\n});\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      ref={ref}\n      className={cn(\n        zero ? menuItemZero : menuItem,\n        'pl-8',\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"absolute left-2 flex h-[14px] w-[14px] items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <Circle className=\"h-2 w-2 fill-current\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.RadioItem>\n  );\n});\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\n/**\n * Hierarchy is via size/color only (muted color), never weight — matching the org's \"weight 400\n * only\" typography rule (ui-laws) and aligning with SelectLabel's identical mechanism\n * (select.tsx:146-165, `typography-input-text text-sm text-text-subtle`, no bold). Previously\n * this used `font-semibold text-menu-foreground-default` (full-contrast + bold), which read\n * visibly heavier than the equivalent Select group label for the same semantic role\n * (MAT-1) with no documented Figma rationale.\n *\n * Checked against Figma: the only bold/Semi-Bold 14px text in this file's Dropdown-adjacent specs\n * is the \"_Dropdown list header\" component (Component Library file, page \"↳ Select\" 18:0, frame\n * 3281:382878, `typography/label-highlight`) — but that component renders a person's *name* in an\n * avatar-identity header (\"Full name\" / \"Account menu\"), a distinct composed pattern from the\n * plain group/section label role this primitive fills (\"My Account\", \"Appearance\", \"Panel\n * position\" in dropdown-menu.stories.tsx). It is not evidence for bolding this primitive and is\n * not modeled here; an avatar/identity header remains a composition of `Avatar` + text, same as\n * this file's other \"achieved by composition\" notes (see DropdownMenuTrigger above).\n *\n * Follow-up routed (not fixed here, out of this file's scope): Command's `[cmdk-group-heading]`\n * (command.tsx:82, `text-xs font-medium text-muted-foreground`) is a THIRD divergent mechanism for\n * the same role and should be reconciled to this same muted-color-only rationale.\n *\n * DROPDOWNMENU-A11Y-03 (WCAG 1.3.1): Radix's `Menu.Label` (and the plain `Menu.Group` /\n * `Menu.RadioGroup` re-exports above) render a `role=\"group\"`/label pair with no automatic\n * `id`/`aria-labelledby` link between them — a screen-reader user otherwise hears the label as\n * unstructured text followed by an anonymous \"group\". This wrapper self-assigns a `React.useId()`\n * fallback so the label always carries a stable `id` (overridable via an explicit `id` prop);\n * pairing it with a sibling `DropdownMenuGroup`/`DropdownMenuRadioGroup`'s `aria-labelledby` is the\n * consumer's responsibility (see dropdown-menu.stories.tsx's \"My Account\"/\"Appearance\"/\n * \"Panel position\" pairings) since Label and Group are unrelated siblings, not parent/child.\n */\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, id, ...props }, ref) => {\n  const generatedId = React.useId();\n  return (\n    <DropdownMenuPrimitive.Label\n      ref={ref}\n      id={id ?? generatedId}\n      className={cn('px-2 py-[6px] text-xs text-muted-foreground', inset && 'pl-8', className)}\n      {...props}\n    />\n  );\n});\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn('-mx-1 my-1 h-px bg-border-subtle', className)}\n    {...props}\n  />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\n/**\n * Uses the dedicated `menu-foreground-muted` color token (Figma shortcut text: 12px regular in\n * slate-500/slate-400) rather than dimming the inherited text color via `opacity-disabled` — the\n * previous `opacity-disabled` approach visually double-dimmed a shortcut inside an already-\n * disabled `DropdownMenuItem` (60% x 60% opacity), since `data-[disabled]:opacity-disabled` is\n * also applied one level up on the item itself.\n */\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return <span className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)} {...props} />;\n};\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut';\n\nexport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuPortal,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n};\n\n// --- KNOWN TOKEN GAPS (component-library-maintainer cannot close these from dropdown-menu.tsx —\n// they are color VALUES owned by the design tokens; this file only consumes\n// the token by name and must not hardcode a raw hex/palette value around them) ---\n//\n// 1. Item/SubTrigger highlight boundary contrast (WCAG 1.4.11 non-text contrast —\n//    DROPDOWNMENU-A11Y-01): `colors/menu/background/hover` (slate-100 light / slate-800 dark) is\n//    only one step away from `colors/dropdown/background/default` (white light / slate-900 dark)\n//    — ~1.1:1 luminance contrast in both themes, under the 3:1 minimum for a UI-component\n//    boundary. Mitigated today with an inset `ring-menu-foreground-default` (see Item/SubTrigger/\n//    CheckboxItem/RadioItem above); once the token repo gives `menu-background-hover` (or a new\n//    dedicated boundary token) more contrast against `dropdown-background-default`, the ring\n//    becomes decorative reinforcement rather than the sole boundary cue.\n//\n// 2. No dedicated \"active/pressed\" menu token (DROPDOWNMENU-MAT-01): unlike\n//    `general-{info,success,error}-active`, there is no `menu-background-active` distinct from\n//    `-hover` — the `active:` (mouse-down) press state above reuses `menu-background-hover`.\n//    Add a dedicated token if a visually distinct press state is ever required.\n//\n// 3. \"Invalid\" state — not applicable: the Figma \"_Command dropdown menu item\" component set has\n//    no Invalid/Error variant and the token search returns no `menu-*` error/invalid color. A menu\n//    item selecting an action has no form-validation semantics, so `invalid` is intentionally not\n//    modeled here (unlike Chip/Select, which back real form fields) — flagged rather than\n//    fabricated.\n//\n// 4. Light/dark parity is completely missing for this component's tokens (DROPDOWNMENU-A11Y-02):\n//    verified against the built the design tokens output in this repo\n//    (public/theme.css) — `--colors-dropdown-background-default`, `--colors-menu-background-hover`,\n//    `--colors-menu-foreground-default`, `--colors-menu-foreground-muted`,\n//    `--colors-form-border-default`, and `--colors-ring-default` are each defined exactly once in\n//    `:root` with NO override in the `.dark` block (line 347+) or the\n//    `prefers-color-scheme: dark` media block (line 456+) — light and dark render byte-identical\n//    values for every one of these 6 keys. This is a token-VALUE gap this file cannot close (this\n//    component only consumes the token names above; the values live in\n//    the design tokens (`styles/theme.css`), `figma-tokens/default_theme_dark.json` or the relevant\n//    per-theme dark override). BLOCKED on design-tokens-maintainer adding distinct dark values for\n//    all 6 keys and rebuilding (`build:css-vars`, `build:themes`), each meeting >=4.5:1 (text) /\n//    >=3:1 (ring/border) against the new dark panel background — kette-design-enterprise cannot\n//    verify or fix this from dropdown-menu.tsx.\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}