{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "type": "registry:ui",
  "title": "Select",
  "description": "Fluent 2-styled select (dropdown listbox) with shadcn APIs. Built on @base-ui/react's Select for portalled positioning, focus management, keyboard navigation and type-ahead; the trigger matches the kit's Input field, the flyout uses Fluent shadow-16 elevation, and selected/highlighted items use CheckmarkRegular + 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/select.tsx",
      "type": "registry:ui",
      "target": "components/ui/select.tsx",
      "content": "\"use client\";\n\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport {\n  CheckmarkRegular,\n  ChevronDownRegular,\n  ChevronUpRegular,\n} from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Select — Fluent 2-styled, shadcn-API select (dropdown listbox).\n *\n * This is the kit's first popup/overlay component, so the patterns set here\n * (portal + positioner + popup surface, `shadow-16` flyout elevation, item\n * highlight via Base UI `data-highlighted`) are the reference for Dialog,\n * Tooltip, and Dropdown later.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus management, portalling, open/close state, roving tabindex,\n * type-ahead, collision-aware positioning — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/select` (namespace import, matching the actual\n * export shape in node_modules, exactly like `avatar.tsx`). shadcn part names\n * are mapped onto Base UI's model:\n *\n * | Exported (shadcn name)  | Base UI primitive                                    |\n * | ----------------------- | ---------------------------------------------------- |\n * | `Select`                | `Select.Root`                                        |\n * | `SelectGroup`           | `Select.Group`                                       |\n * | `SelectValue`           | `Select.Value`                                       |\n * | `SelectTrigger`         | `Select.Trigger` (+ `Select.Icon` chevron appended)  |\n * | `SelectContent`         | `Select.Portal` + `Select.Positioner` + `Select.Popup` + `Select.List` |\n * | `SelectLabel`           | `Select.GroupLabel`  ← see divergence 1              |\n * | `SelectItem`            | `Select.Item` (+ `Select.ItemIndicator` + `Select.ItemText`) |\n * | `SelectSeparator`       | `Select.Separator`                                   |\n * | `SelectScrollUpButton`  | `Select.ScrollUpArrow`                               |\n * | `SelectScrollDownButton`| `Select.ScrollDownArrow`                             |\n *\n * ## Divergences from the shadcn/Radix Select API (all deliberate)\n * 1. **`SelectLabel` maps to Base UI `Select.GroupLabel`, not `Select.Label`.**\n *    Base UI has *two* label parts: `Select.Label` labels the whole control\n *    (like a field `<label>`), while `Select.GroupLabel` is the heading inside a\n *    `Select.Group`. shadcn's `SelectLabel` is the group heading, so it maps to\n *    `GroupLabel`. Base UI's control-level `Select.Label` is intentionally not\n *    re-exported — pair the trigger with the kit's own `Label` component instead.\n *    `Label` is a separate registry item (`registry/items/label.json`) and is\n *    not a `registryDependency` of `select`, so install it explicitly:\n *    `npx shadcn@latest add <registry-url>/r/label.json`.\n *\n *    **Labeling recipe.** Give the `Label` an `id` and the `SelectTrigger` its\n *    own `id`, then set `aria-labelledby=\"<label-id> <trigger-id>\"` on the\n *    trigger (referencing *both*). Pointing only at the external label drops\n *    the chosen value from the accessible name; adding the trigger's own id\n *    folds the `SelectValue` text (the current selection) back in, so a screen\n *    reader announces \"<label>, <selected value>\". `htmlFor`/`for` can't be\n *    used here — the trigger renders a `<button>`, not a labelable form field —\n *    which is why the association is `aria-labelledby`, not a wrapping/`for`\n *    `<label>`. The Select preview page uses this pattern on every example.\n * 2. **`SelectValue` shows the raw value unless `<Select>` gets an `items` prop.**\n *    Unlike Radix (where `SelectValue` mirrors the selected `SelectItem`'s text),\n *    Base UI's `Select.Value` renders the raw `value` by default. To display a\n *    friendly label, either pass `items` to `<Select>` (Root) — e.g.\n *    `items={[{ value: \"apple\", label: \"Apple\" }]}` — or pass a render function\n *    as `SelectValue`'s child: `<SelectValue>{(v) => labels[v]}</SelectValue>`.\n *    This is Base UI's model; the wrapper stays thin and does not paper over it.\n * 3. **`SelectTrigger` sizes follow the kit's Button scale, not shadcn's.**\n *    shadcn ships `sm`/`default` at h-8/h-9; this kit maps Fluent's field sizes\n *    onto the Button height scale instead — `sm` 24px (`h-6`), `default` 32px\n *    (`h-8`, matches `Input`), `lg` 40px (`h-10`) — surfaced as `data-size`.\n * 4. **`SelectContent` positions below the trigger (`alignItemWithTrigger=false`).**\n *    Base UI defaults to overlapping the trigger so the selected item's text lines\n *    up with the value (native-select behavior). A shadcn-style dropdown that opens\n *    beneath the field is the expected Fluent flyout, so this is forced off; the\n *    scroll arrows/`data-[side=none]` overlap edge cases go away with it.\n *\n * ## `\"use client\"` — required (integration-pass correction)\n * Every Base UI Select part module does carry its own `'use client'`\n * directive, so on that basis alone this wrapper could stay server-renderable\n * (same reasoning as `avatar.tsx`). But `CheckmarkRegular`/`ChevronDownRegular`/\n * `ChevronUpRegular` from `@fluentui/react-icons` break that: the package's\n * shared icon-sizing module (`createFluentIcon.styles.js`) calls `@griffel/\n * react`'s `__styles()` at module scope *without* its own `'use client'`\n * directive, even though `__styles` itself is client-only. Rendering any of\n * these icons from a Server Component pulls that module into the server's\n * RSC graph, and `next build` (Turbopack) fails collecting page data with\n * \"Attempted to call __styles() from the server but __styles is on the\n * client\" — reproduced against `@fluentui/react-icons@2.0.333` /\n * `@griffel/react@1.7.5`, and confirmed to affect every route that shares\n * Turbopack's chunk for these icons, not just this one. `\"use client\"` here\n * keeps the icon imports inside a client boundary so they're never evaluated\n * on the server. `checkbox.tsx` carries the same fix for the same reason.\n *\n * ## data-slot note\n * `Select` (Root) renders no DOM element of its own and its Base UI props type\n * is strict (no `data-*` passthrough), so it carries no `data-slot`. Every part\n * that renders an element does — `select-trigger`, `select-content`,\n * `select-item`, etc. — which is what tests/consumers hook onto.\n */\n\nfunction Select<Value, Multiple extends boolean | undefined = false>(\n  props: SelectPrimitive.Root.Props<Value, Multiple>\n) {\n  return <SelectPrimitive.Root {...props} />;\n}\n\nfunction SelectGroup({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Group>) {\n  return (\n    <SelectPrimitive.Group\n      data-slot=\"select-group\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Value>) {\n  return (\n    <SelectPrimitive.Value\n      data-slot=\"select-value\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Trigger — Fluent field look, styled to match `Input` (h-8, rounded-md,\n * border-input, px-3, text-sm, flat at rest). Focus uses the sanctioned\n * per-field deviation from conventions §4: a 2px brand underline painted with\n * an inset box-shadow (no reflow) plus `border-primary`, and the `aria-invalid`\n * treatment declared *after* focus so an invalid+focused field shows the\n * destructive accent. The chevron is appended via `Select.Icon`; the trigger's\n * `[&_svg]` rules size it (`size-4`) and mute it (`text-muted-foreground`).\n */\nfunction SelectTrigger({\n  className,\n  children,\n  size = \"default\",\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Trigger> & {\n  /** Fluent field sizes on the Button height scale — see divergence 3. */\n  size?: \"sm\" | \"default\" | \"lg\";\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        // layout — Fluent field on the Button height scale (divergence 3);\n        // default is the 32px medium that matches Input. Resting bottom\n        // edge uses the darker NeutralStrokeAccessible (#616161) accent while\n        // the other sides stay border-input; focus (border-primary + inset\n        // underline) and aria-invalid (border-destructive) both override it.\n        \"flex w-full items-center justify-between gap-2 rounded-md border border-input border-b-stroke-accessible bg-background py-1 whitespace-nowrap\",\n        size === \"sm\" && \"h-6 px-2 text-xs\",\n        size === \"default\" && \"h-8 px-3 text-sm\",\n        size === \"lg\" && \"h-10 px-4 text-base\",\n        // placeholder text muted (Base UI sets data-placeholder when no value)\n        \"data-[placeholder]:text-muted-foreground\",\n        // keep a long value on one line, left-aligned\n        \"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:text-left\",\n        // icon defaults — chevron sized + muted unless overridden\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        // motion — color + box-shadow so the focus accent animates (§4)\n        \"outline-none transition-[color,box-shadow] duration-fast ease-ease\",\n        // disabled — opacity read; Base UI emits both `disabled` + `data-disabled`\n        \"disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        // focus — Fluent bottom brand accent via inset box-shadow, no reflow (§4)\n        \"focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]\",\n        // invalid — shadcn aria-invalid treatment; after focus so it wins the border\n        \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n        \"aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon>\n        <ChevronDownRegular />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  );\n}\n\n/**\n * Content — the floating flyout: `Portal` → `Positioner` → `Popup`, with the\n * items in a scrollable `List` between hover-scroll arrows. Surface is\n * `bg-popover` + `border` + `shadow-16` (Fluent flyout elevation, conventions\n * §3.6), `rounded-md`, `min-w` matched to the trigger via Base UI's\n * `--anchor-width`. A subtle scale+fade open/close animation rides Base UI's\n * `data-starting-style`/`data-ending-style` hooks with token durations/easings.\n */\nfunction SelectContent({\n  className,\n  children,\n  sideOffset = 4,\n  align = \"start\",\n  side = \"bottom\",\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Popup> &\n  Pick<\n    ComponentProps<typeof SelectPrimitive.Positioner>,\n    \"side\" | \"align\" | \"sideOffset\"\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        data-slot=\"select-positioner\"\n        sideOffset={sideOffset}\n        align={align}\n        side={side}\n        alignItemWithTrigger={false}\n        className=\"z-50 outline-none\"\n      >\n        <SelectPrimitive.Popup\n          data-slot=\"select-content\"\n          className={cn(\n            \"relative min-w-[var(--anchor-width)] origin-[var(--transform-origin)] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-16 outline-none\",\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          <SelectScrollUpButton />\n          <SelectPrimitive.List\n            data-slot=\"select-list\"\n            className=\"max-h-[var(--available-height)] overflow-y-auto overscroll-contain p-1\"\n          >\n            {children}\n          </SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\n/**\n * Label — a heading for a `SelectGroup` (Base UI `Select.GroupLabel`), *not* a\n * control-level field label (divergence 1). Small muted caption, Fluent style.\n */\nfunction SelectLabel({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.GroupLabel>) {\n  return (\n    <SelectPrimitive.GroupLabel\n      data-slot=\"select-label\"\n      className={cn(\n        \"px-2 py-1.5 text-xs font-medium text-muted-foreground select-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Item — one option. Text via `ItemText`; the selected-state check\n * (`CheckmarkRegular`) is absolutely positioned on the right (shadcn placement)\n * inside `ItemIndicator`, which Base UI only mounts for the selected item.\n * `rounded-md` (4px) matches Fluent's `Corner-radius/List/Default`. Highlight\n * (keyboard focus or hover) uses `data-highlighted` → `bg-accent`\n * (`#f0f0f0`) `text-accent-foreground`; Fluent's list-hover token is `#f5f5f5`\n * (one grey step lighter), but the kit keeps `--accent` for token consistency\n * with every other hover surface — the difference is imperceptible on a 32px\n * row. Disabled items are muted + inert.\n */\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        // layout — Fluent 32px row, 4px list radius, px-2 with room on the\n        // right for the check\n        \"relative flex h-8 w-full cursor-default items-center rounded-md pr-8 pl-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        // icons that a consumer puts inside the item text\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute right-2 flex size-4 items-center justify-center\">\n        <SelectPrimitive.ItemIndicator>\n          <CheckmarkRegular className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\n        \"pointer-events-none -mx-1 my-1 h-px bg-border\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ScrollUpButton / ScrollDownButton — Base UI hover-scroll arrows that Base UI\n * only renders when the list overflows in that direction. Rendered as siblings\n * of the `List` inside `SelectContent`, so consumers don't place them manually\n * (shadcn parity — the export exists for API compatibility / manual use).\n */\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1 text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpRegular className=\"size-4\" />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1 text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownRegular className=\"size-4\" />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n};\n"
    }
  ],
  "docs": "Base UI's `Select.Label` (a control-level field label) is intentionally not exposed by this component — pair `SelectTrigger` with the kit's own `Label` item instead, which is *not* pulled in automatically (`select`'s only registryDependency is `utils`). Install it separately:\n\n```bash\nnpx shadcn@latest add <registry-url>/r/label.json\n```\n\nThen give the `Label` an `id` and the `SelectTrigger` its own `id`, and set `aria-labelledby=\"<label-id> <trigger-id>\"` on the trigger (referencing both) — pointing only at the external label drops the selected value from the accessible name. See `select.tsx`'s doc comment for the full recipe."
}
