{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu",
  "type": "registry:ui",
  "title": "Dropdown Menu",
  "description": "Fluent 2-styled dropdown menu with shadcn APIs. Built on @base-ui/react's Menu for portalled positioning, focus management, keyboard navigation, type-ahead and submenus; the flyout matches the kit's Select surface (Fluent shadow-16 elevation, 32px rows) and check/radio indicators use CheckmarkRegular plus a filled-circle marker on brand-neutral accent states.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "@fluentui/react-icons"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/dropdown-menu.tsx",
      "type": "registry:ui",
      "target": "components/ui/dropdown-menu.tsx",
      "content": "\"use client\";\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\";\nimport { CheckmarkRegular, ChevronRightRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * DropdownMenu — Fluent 2-styled, shadcn-API dropdown menu.\n *\n * Second overlay component in the kit, so it deliberately reuses every pattern\n * `select.tsx` established (portal + positioner + popup surface, `shadow-16`\n * flyout elevation, 32px `data-highlighted` item rows, scale+fade motion via\n * `data-starting-style`/`data-ending-style`). Its popup surface and item rows\n * are visually identical to Select's — same tokens, same radii.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus management, portalling, open/close state, roving tabindex,\n * type-ahead, collision-aware positioning, submenu orchestration — genuinely\n * needs a primitive, so the parts wrap `@base-ui/react/menu` (namespace import,\n * matching the actual export shape in node_modules, exactly like `select.tsx`).\n * shadcn part names are mapped onto Base UI's model:\n *\n * | Exported (shadcn name)     | Base UI primitive                                      |\n * | -------------------------- | ------------------------------------------------------ |\n * | `DropdownMenu`             | `Menu.Root`                                            |\n * | `DropdownMenuTrigger`      | `Menu.Trigger`                                         |\n * | `DropdownMenuContent`      | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup`      |\n * | `DropdownMenuGroup`        | `Menu.Group`                                           |\n * | `DropdownMenuLabel`        | `Menu.GroupLabel`  ← see divergence 1                  |\n * | `DropdownMenuItem`         | `Menu.Item`                                            |\n * | `DropdownMenuCheckboxItem` | `Menu.CheckboxItem` (+ `Menu.CheckboxItemIndicator`)  |\n * | `DropdownMenuRadioGroup`   | `Menu.RadioGroup`                                     |\n * | `DropdownMenuRadioItem`    | `Menu.RadioItem` (+ `Menu.RadioItemIndicator`)        |\n * | `DropdownMenuSeparator`    | `Menu.Separator`                                      |\n * | `DropdownMenuShortcut`     | plain `<span>` (no Base UI part) ← see divergence 4    |\n * | `DropdownMenuSub`          | `Menu.SubmenuRoot`  ← see divergence 2                 |\n * | `DropdownMenuSubTrigger`   | `Menu.SubmenuTrigger` (+ `ChevronRightRegular`)       |\n * | `DropdownMenuSubContent`   | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup`      |\n *\n * ## Divergences from the shadcn/Radix DropdownMenu API (all deliberate)\n * 1. **`DropdownMenuLabel` maps to Base UI `Menu.GroupLabel`.** Like `select.tsx`,\n *    the label part is the heading *inside* a `DropdownMenuGroup` (Base UI\n *    associates it with the group for screen readers). It is styled to match\n *    `SelectLabel` (muted `text-xs` caption) rather than shadcn's darker\n *    `text-sm`, keeping every group heading in the kit visually consistent. It\n *    still honours shadcn's `inset` prop (`data-[inset]:pl-8`).\n * 2. **`DropdownMenuSub` is Base UI `Menu.SubmenuRoot`, not a Radix `Sub`.** Base\n *    UI models a submenu as its own nested root; `DropdownMenuSubContent`\n *    therefore re-composes `Portal` → `Positioner` → `Popup` (Radix's\n *    `SubContent` is a single part). Submenus open to the side and Base UI\n *    positions them collision-aware; the trigger stays highlighted while its\n *    submenu is open via `data-[popup-open]`.\n * 3. **Composition uses Base UI's `render` prop, not `asChild`.** shadcn's\n *    `<DropdownMenuTrigger asChild>` has no analogue here — Base UI parts take a\n *    `render` prop for polymorphism. To use the kit `Button` as the trigger,\n *    pass `render={<Button>…</Button>}`. The wrapper forwards `render` straight\n *    through, so the honest Base UI model is preserved (same reasoning as\n *    `select.tsx` keeping `SelectValue`'s render-function child).\n * 4. **`DropdownMenuShortcut` is a plain `<span>`** (shadcn parity — it is not a\n *    Base UI part, just a right-aligned muted caption for a keyboard hint).\n * 5. **`DropdownMenuItem` keeps shadcn's `inset` + `variant` props.** `variant`\n *    is surfaced as `data-variant` (mirrors `Button`'s `data-variant` hook); the\n *    `\"destructive\"` value tints the row and its icons with the `destructive`\n *    token (never a hardcoded red), and its highlight uses `destructive/10`\n *    (`/20` in dark) so the destructive intent survives the hover state.\n *\n * ## `\"use client\"` — required (same root cause as `select.tsx`)\n * Every Base UI Menu part module carries its own `'use client'`, so on that\n * basis this wrapper could stay server-renderable. But `CheckmarkRegular` /\n * `ChevronRightRegular` from `@fluentui/react-icons` force the directive: the\n * package's shared icon-sizing module (`createFluentIcon.styles.js`) calls\n * `@griffel/react`'s `__styles()` at module scope *without* its own\n * `'use client'`, even though `__styles` is client-only. Rendering any of these\n * icons from a Server Component pulls that module into the server's RSC graph,\n * and `next build` (Turbopack) fails collecting page data with \"Attempted to\n * call __styles() from the server but __styles is on the client\" — reproduced\n * against `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and it\n * poisons every route sharing Turbopack's chunk for those icons. `\"use client\"`\n * here keeps the icon imports inside a client boundary. `select.tsx` and\n * `checkbox.tsx` carry the same fix (their doc comments explain it inline).\n *\n * ## data-slot note\n * `DropdownMenu` (Root) and `DropdownMenuSub` (SubmenuRoot) render no DOM element\n * of their own, so they carry no `data-slot`. Every part that renders an element\n * does — `dropdown-menu-trigger`, `dropdown-menu-content`, `dropdown-menu-item`,\n * etc. — which is what tests/consumers hook onto.\n */\n\nfunction DropdownMenu<Payload>(props: MenuPrimitive.Root.Props<Payload>) {\n  return <MenuPrimitive.Root {...props} />;\n}\n\nfunction DropdownMenuTrigger({\n  className,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Trigger>) {\n  return (\n    <MenuPrimitive.Trigger\n      data-slot=\"dropdown-menu-trigger\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuGroup({\n  className,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Group>) {\n  return (\n    <MenuPrimitive.Group\n      data-slot=\"dropdown-menu-group\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Content — the floating flyout: `Portal` → `Positioner` → `Popup`. Surface is\n * `bg-popover` + `border` + `shadow-16` (Fluent flyout elevation, conventions\n * §3.6), `rounded-md`, `min-w-[8rem]` (menus size to content, unlike Select\n * which matches the anchor width). A subtle scale+fade open/close animation\n * rides Base UI's `data-starting-style`/`data-ending-style` hooks with token\n * durations/easings — the exact lines from `SelectContent`.\n */\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 4,\n  align = \"start\",\n  side = \"bottom\",\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Popup> &\n  Pick<\n    ComponentProps<typeof MenuPrimitive.Positioner>,\n    \"side\" | \"align\" | \"sideOffset\"\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        data-slot=\"dropdown-menu-positioner\"\n        sideOffset={sideOffset}\n        align={align}\n        side={side}\n        className=\"z-50 outline-none\"\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-content\"\n          className={cn(\n            \"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none\",\n            \"max-h-[var(--available-height)]\",\n            // motion — subtle scale + fade on open (enter) / close (exit)\n            \"transition-[opacity,scale] duration-fast ease-decelerate-mid\",\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n            \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n            className\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\n/**\n * Label — a heading for a `DropdownMenuGroup` (Base UI `Menu.GroupLabel`, see\n * divergence 1). Small muted caption, matched to `SelectLabel`. `inset` shifts\n * it to line up with items that carry a leading indicator.\n */\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.GroupLabel> & { inset?: boolean }) {\n  return (\n    <MenuPrimitive.GroupLabel\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset || undefined}\n      className={cn(\n        // Fluent Menu section header — Caption 1 Stronger (12px bold) in\n        // NeutralForeground2, per the Figma \"Menu section\" symbol; deliberately\n        // heavier than SelectLabel's muted caption (Figma validation pass 2).\n        \"px-2 py-1.5 text-xs font-bold text-foreground-2 select-none\",\n        \"data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Item — one command row. 32px tall (`h-8`) with the Fluent 4px list radius,\n * matching `SelectItem`. Highlight (keyboard focus or hover) rides Base UI's\n * `data-highlighted` → `bg-accent`/`text-accent-foreground`. `inset` adds the\n * leading gutter used by checkbox/radio rows; `variant=\"destructive\"` tints the\n * text/icons with the `destructive` token and gives the highlight a destructive\n * wash. Icons follow `SelectItem`'s size-only rules (no muting).\n */\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Item> & {\n  inset?: boolean;\n  variant?: \"default\" | \"destructive\";\n}) {\n  return (\n    <MenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset || undefined}\n      data-variant={variant}\n      className={cn(\n        // layout — Fluent 32px row, 4px list radius\n        \"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none\",\n        // rest text is NeutralForeground2, darkening to accent-foreground on\n        // highlight (Figma validation: Fluent list rows rest at #424242)\n        \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n        // disabled item — muted + non-interactive\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        // inset — leading gutter to align with indicator rows\n        \"data-[inset]:pl-8\",\n        // destructive variant — token-tinted text, icons, and highlight wash\n        \"data-[variant=destructive]:text-destructive\",\n        \"data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20\",\n        \"data-[variant=destructive]:[&_svg:not([class*='text-'])]:text-destructive\",\n        // icons — size only (matches SelectItem)\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * CheckboxItem — a toggleable row. The check (`CheckmarkRegular`) sits in a\n * left gutter (`pl-8`), inside `Menu.CheckboxItemIndicator`, which Base UI only\n * mounts while checked. Base UI keeps the menu open on toggle (its\n * `closeOnClick` defaults to `false` for checkbox items).\n */\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      checked={checked}\n      className={cn(\n        \"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none\",\n        \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckmarkRegular className=\"size-4\" />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({\n  className,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.RadioGroup>) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * RadioItem — one option in a `DropdownMenuRadioGroup`. The selected marker is a\n * small filled circle (an inline `<svg>` using `fill-current`, so it inherits\n * the row's text color and needs no extra icon import — same rationale as the\n * kit `RadioGroup` dot). `Menu.RadioItemIndicator` only mounts while selected.\n */\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.RadioItem>) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none\",\n        \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n        <MenuPrimitive.RadioItemIndicator>\n          <svg viewBox=\"0 0 8 8\" className=\"size-2 fill-current\" aria-hidden>\n            <circle cx=\"4\" cy=\"4\" r=\"4\" />\n          </svg>\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Separator>) {\n  return (\n    <MenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Shortcut — a right-aligned keyboard hint caption. Plain `<span>` (divergence\n * 4), shadcn parity: `ml-auto text-xs tracking-widest text-muted-foreground`.\n */\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub(props: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot {...props} />;\n}\n\n/**\n * SubTrigger — an item that opens a submenu, with a trailing `ChevronRightRegular`.\n * Same row treatment as `DropdownMenuItem`; stays highlighted while its submenu\n * is open via Base UI's `data-[popup-open]`. Honours `inset`.\n */\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: ComponentProps<typeof MenuPrimitive.SubmenuTrigger> & { inset?: boolean }) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset || undefined}\n      className={cn(\n        \"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none\",\n        \"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n        \"data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground\",\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        \"data-[inset]:pl-8\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightRegular className=\"ml-auto size-4 text-muted-foreground\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\n/**\n * SubContent — a submenu's floating surface. Same `Portal` → `Positioner` →\n * `Popup` composition and surface as `DropdownMenuContent`, but defaults to\n * opening on the inline-end side (Base UI still flips it collision-aware).\n */\nfunction DropdownMenuSubContent({\n  className,\n  sideOffset = 4,\n  align = \"start\",\n  ...props\n}: ComponentProps<typeof MenuPrimitive.Popup> &\n  Pick<\n    ComponentProps<typeof MenuPrimitive.Positioner>,\n    \"side\" | \"align\" | \"sideOffset\"\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        data-slot=\"dropdown-menu-sub-positioner\"\n        sideOffset={sideOffset}\n        align={align}\n        className=\"z-50 outline-none\"\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-sub-content\"\n          className={cn(\n            \"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none\",\n            \"max-h-[var(--available-height)]\",\n            \"transition-[opacity,scale] duration-fast ease-decelerate-mid\",\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n            \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n            className\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
    }
  ],
  "docs": "Composition uses Base UI's `render` prop, not shadcn's `asChild`. To use the kit `Button` as the trigger, pass it via `render`:\n\n```tsx\n<DropdownMenuTrigger render={<Button variant=\"outline\">Open</Button>} />\n```\n\n`DropdownMenuLabel` is a group heading (Base UI `Menu.GroupLabel`) — place it inside a `DropdownMenuGroup`. `DropdownMenuSub` wraps a `DropdownMenuSubTrigger` and a `DropdownMenuSubContent` (which re-composes its own portal/positioner/popup). See `dropdown-menu.tsx`'s doc comment for the full Base UI mapping table and the numbered divergences from the shadcn/Radix API."
}
