{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "title": "Select",
  "description": "Select component built on Base UI, styled with sc1m tokens.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://sc1m.vercel.app/r/tokens.json",
    "https://sc1m.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "src/components/ui/select/select.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { cn } from \"@/lib/cn\";\n\nexport const Select = BaseSelect.Root;\nexport const SelectValue = BaseSelect.Value;\nexport const SelectGroup = BaseSelect.Group;\nexport const SelectGroupLabel = BaseSelect.GroupLabel;\n\nexport function SelectTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseSelect.Trigger>) {\n  return (\n    <BaseSelect.Trigger\n      className={cn(\n        \"flex h-9 w-full items-center justify-between gap-2 rounded-md border border-line bg-surface-raised px-3 text-sm text-fg\",\n        \"transition-colors duration-fast ease-out-quad\",\n        \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\",\n        \"data-disabled:pointer-events-none data-disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <SelectIcon />\n    </BaseSelect.Trigger>\n  );\n}\n\nfunction SelectIcon({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseSelect.Icon>) {\n  return (\n    <BaseSelect.Icon\n      className={cn(\"flex text-fg-muted\", className)}\n      {...props}\n    >\n      {/* phosphor: caret-up-down regular */}\n      <svg\n        aria-hidden\n        viewBox=\"0 0 256 256\"\n        fill=\"currentColor\"\n        className=\"size-3.5\"\n      >\n        <path d=\"M181.66,170.34a8,8,0,0,1,0,11.32l-48,48a8,8,0,0,1-11.32,0l-48-48a8,8,0,0,1,11.32-11.32L128,212.69l42.34-42.35A8,8,0,0,1,181.66,170.34Zm-96-84.68L128,43.31l42.34,42.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,85.66Z\" />\n      </svg>\n    </BaseSelect.Icon>\n  );\n}\n\n/**\n * The open trigger's scale, or `null` when there is no open trigger to\n * measure — which is not the same as \"unscaled\", and is why this does not\n * just return 1. Base UI mounts the popup before it opens, and keeps it\n * mounted through the closing animation; in both of those states the answer\n * is \"don't know\", and the caller holds whatever it last measured rather\n * than snapping the popup back to full size mid-animation.\n */\nfunction measureAnchorScale(): number | null {\n  const trigger = document.querySelector<HTMLElement>(\n    '[role=\"combobox\"][aria-expanded=\"true\"]',\n  );\n  if (!trigger?.offsetWidth) return null;\n  const scale = trigger.getBoundingClientRect().width / trigger.offsetWidth;\n  // Sub-1% is rounding, not a transform. Returning exactly 1 is what keeps\n  // every ordinary select from emitting a transform at all.\n  if (!Number.isFinite(scale) || Math.abs(scale - 1) <= 0.01) return 1;\n  return scale;\n}\n\n/**\n * The scale the popup should render at: whatever its trigger renders at.\n *\n * A popup portals to <body>, so a `transform: scale()` anywhere above the\n * trigger applies to the trigger and not to the popup — the two end up drawn\n * at different sizes on the same surface. The reel does exactly this: every\n * miniature is authored at its natural size and scaled to fit its card, so\n * without this the currency select's popup lands at 1x on a card drawn at\n * 0.75x, as wide as the whole card.\n *\n * `getBoundingClientRect()` is measured after transforms and `offsetWidth`\n * before them, so their ratio is the cumulative scale — no matter how many\n * ancestors contributed to it, and without anyone having to pass it down.\n *\n * The trigger is found rather than threaded through context: a select popup\n * takes focus, so Base UI closes any other before opening this one and\n * exactly one trigger in the document is expanded. Threading it would mean\n * wrapping `Select`, which is `BaseSelect.Root` re-exported and generic over\n * its value type — the props table in the docs is generated from that symbol.\n *\n * Hung off a ref callback rather than an effect, because this component does\n * not re-render when its own popup opens — Base UI flips that state inside\n * the parts, and the Portal mounts the popup element without anything here\n * rendering again. An effect therefore only ever sees the closed state (and\n * `useSyncExternalStore` never gets told to look twice). The ref *is* the\n * open signal: React runs it when the popup element enters the DOM, in the\n * same commit that expands the trigger, and before paint.\n */\nfunction useAnchorScale() {\n  const [scale, setScale] = React.useState(1);\n\n  const measureOnMount = React.useCallback((el: HTMLElement | null) => {\n    // null is the popup leaving. Hold the last value so it keeps its size\n    // through the closing animation instead of snapping back to full.\n    if (!el) return;\n    const next = measureAnchorScale();\n    if (next !== null) setScale((prev) => (prev === next ? prev : next));\n  }, []);\n\n  return [scale, measureOnMount] as const;\n}\n\nexport function SelectContent({\n  className,\n  children,\n  side = \"bottom\",\n  alignItemWithTrigger,\n  style,\n  ref,\n  ...props\n}: React.ComponentProps<typeof BaseSelect.Popup> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  /**\n   * Base UI's default (`true`) overlaps the popup onto the trigger so the\n   * selected item's text lands on the trigger's value — native-select\n   * behaviour, and what `side`/`sideOffset` are ignored for.\n   *\n   * Pass `false` when the select sits inside a CSS-transformed ancestor. That\n   * alignment measures the trigger and the item to compute an overlap offset,\n   * and under a `scale()` the two measurements disagree, so the popup lands\n   * away from its trigger. Turning it off falls back to ordinary side/offset\n   * anchoring, which transforms correctly.\n   */\n  alignItemWithTrigger?: boolean;\n}) {\n  const [scale, measureAnchor] = useAnchorScale();\n\n  return (\n    <BaseSelect.Portal>\n      <BaseSelect.Positioner\n        side={side}\n        sideOffset={8}\n        alignItemWithTrigger={alignItemWithTrigger}\n      >\n        <BaseSelect.Popup\n          className={cn(\n            \"max-h-72 min-w-[var(--anchor-width)] overflow-y-auto rounded-md border border-line bg-surface-raised p-1 text-fg shadow-overlay\",\n            // The popup takes focus for keyboard navigation, so without this\n            // the browser paints its own blue ring around the whole panel.\n            // The visible affordance belongs on the option (data-highlighted),\n            // not the container — same as Menu, Menubar and ContextMenu.\n            \"outline-none\",\n            \"popup-motion\",\n            className,\n          )}\n          style={\n            // `popup-motion` animates the `scale` property and sets\n            // transform-origin to Base UI's --transform-origin, which points\n            // at the anchor. So this composes with the open/close animation\n            // instead of overwriting it, and shrinks toward the trigger.\n            scale === 1 ? style : { transform: `scale(${scale})`, ...style }\n          }\n          ref={(el: HTMLDivElement | null) => {\n            measureAnchor(el);\n            if (typeof ref === \"function\") ref(el);\n            else if (ref) ref.current = el;\n          }}\n          {...props}\n        >\n          {children}\n        </BaseSelect.Popup>\n      </BaseSelect.Positioner>\n    </BaseSelect.Portal>\n  );\n}\n\nexport function SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseSelect.Item>) {\n  return (\n    <BaseSelect.Item\n      className={cn(\n        \"flex cursor-default items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm text-fg select-none\",\n        \"data-highlighted:bg-surface\",\n        \"data-disabled:pointer-events-none data-disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      <BaseSelect.ItemText>{children}</BaseSelect.ItemText>\n      <SelectItemIndicator />\n    </BaseSelect.Item>\n  );\n}\n\nfunction SelectItemIndicator({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseSelect.ItemIndicator>) {\n  return (\n    <BaseSelect.ItemIndicator\n      className={cn(\"flex text-accent\", className)}\n      {...props}\n    >\n      {/* phosphor: check regular */}\n      <svg\n        aria-hidden\n        viewBox=\"0 0 256 256\"\n        fill=\"currentColor\"\n        className=\"size-3.5\"\n      >\n        <path d=\"M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z\" />\n      </svg>\n    </BaseSelect.ItemIndicator>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}