{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "type": "registry:ui",
  "title": "Select",
  "description": "A compact choice field with a keyboard friendly menu.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "@radix-ui/react-select",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/utils.json",
    "https://irisui.dev/r/motion-tokens.json"
  ],
  "files": [
    {
      "path": "src/registry/components/select/select.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useId, useState } from \"react\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport type { Variants } from \"motion/react\";\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\nimport { motionTokens } from \"../lib/motion-tokens\";\n\nexport interface SelectProps extends Omit<ComponentPropsWithoutRef<typeof SelectPrimitive.Root>, \"children\"> {\n  label: string;\n  description?: string;\n  placeholder?: string;\n  id?: string;\n  className?: string;\n  options: { value: string; label: string; disabled?: boolean }[];\n}\n\n/** The shown value rolls in the direction of the list: a later option rises from below, an earlier one drops from above. */\nconst valueRoll: Variants = {\n  enter: (direction: number) => ({\n    opacity: 0,\n    y: `${direction * 0.35}em`,\n    filter: `blur(${motionTokens.blur.soft}px)`,\n  }),\n  center: {\n    opacity: 1,\n    y: 0,\n    filter: \"blur(0px)\",\n    transition: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] },\n  },\n  exit: (direction: number) => ({\n    opacity: 0,\n    y: `${direction * -0.3}em`,\n    filter: `blur(${motionTokens.blur.subtle}px)`,\n    transition: { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] },\n  }),\n};\n/** Reduced motion keeps a short crossfade; the resting state matches valueRoll so server and client markup agree. */\nconst valueFade: Variants = {\n  enter: { opacity: 0 },\n  center: { opacity: 1, y: 0, filter: \"blur(0px)\", transition: { duration: motionTokens.duration.instant } },\n  exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } },\n};\n\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select(\n  {\n    label,\n    description,\n    placeholder = \"Select an option\",\n    options,\n    id,\n    className,\n    disabled,\n    onValueChange,\n    ...rootProps\n  },\n  ref,\n) {\n  const generatedId = useId();\n  const controlId = id ?? generatedId;\n  const hintId = description ? `${controlId}-description` : undefined;\n  const reduceMotion = useReducedMotion();\n  const [uncontrolledValue, setUncontrolledValue] = useState(rootProps.defaultValue ?? \"\");\n  const currentValue = rootProps.value ?? uncontrolledValue;\n  const index = options.findIndex((option) => option.value === currentValue);\n  const shown = currentValue ? (options[index]?.label ?? \"\") : placeholder;\n  const [previousIndex, setPreviousIndex] = useState(index);\n  const [direction, setDirection] = useState(1);\n  if (previousIndex !== index) {\n    setPreviousIndex(index);\n    setDirection(index > previousIndex ? 1 : -1);\n  }\n\n  return (\n    <div className=\"grid min-w-0 gap-2\">\n      <label className=\"text-sm/[inherit] font-medium\" htmlFor={controlId}>\n        {label}\n      </label>\n      <SelectPrimitive.Root\n        {...rootProps}\n        disabled={disabled}\n        onValueChange={(next) => {\n          setUncontrolledValue(next);\n          onValueChange?.(next);\n        }}\n      >\n        <SelectPrimitive.Trigger\n          ref={ref}\n          id={controlId}\n          aria-describedby={hintId}\n          // The trigger anchors the floating menu. Radix opens on pointerdown and measures this box, so it never scales: press feedback is color only.\n          className={cn(\n            \"group/select relative box-border flex min-h-control-md w-full cursor-pointer items-center justify-between gap-3 rounded-control border border-line bg-surface px-3 text-left text-sm/[inherit] text-ink transition-[border-color,background-color,box-shadow] duration-fast ease-standard motion-reduce:transition-none\",\n            \"hover:not-data-disabled:border-line-strong hover:not-data-disabled:bg-hover focus-visible:border-line-strong active:not-data-disabled:border-line-strong active:not-data-disabled:bg-hover data-[state=open]:border-line-strong data-[state=open]:bg-hover data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n            className,\n          )}\n        >\n          {/* Radix keeps the real value for assistive tech; the visible copy below animates between values. */}\n          <span className=\"sr-only\">\n            <SelectPrimitive.Value placeholder={placeholder} />\n          </span>\n          <span\n            className=\"grid min-w-0 flex-1 grid-cols-[minmax(0,1fr)] [&>[data-placeholder]]:text-ink-3 [&>span]:col-start-1 [&>span]:row-start-1 [&>span]:min-w-0 [&>span]:truncate\"\n            aria-hidden=\"true\"\n          >\n            <AnimatePresence initial={false} custom={direction}>\n              <motion.span\n                key={currentValue ? `value-${currentValue}` : \"placeholder\"}\n                data-placeholder={currentValue ? undefined : \"\"}\n                custom={direction}\n                variants={reduceMotion ? valueFade : valueRoll}\n                initial=\"enter\"\n                animate=\"center\"\n                exit=\"exit\"\n              >\n                {shown}\n              </motion.span>\n            </AnimatePresence>\n          </span>\n          <SelectPrimitive.Icon className=\"inline-flex flex-none text-ink-3 [transition:transform_var(--duration-spring)_var(--ease-spring),color_var(--duration-fast)_var(--ease-standard)] group-data-[state=open]/select:[transform:rotate(180deg)] motion-reduce:transition-none\">\n            <ChevronDown size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n          </SelectPrimitive.Icon>\n        </SelectPrimitive.Trigger>\n        <SelectPrimitive.Portal>\n          {/* Radix holds the animation until the menu is positioned, so it always grows from the trigger edge. */}\n          <SelectPrimitive.Content\n            className=\"z-1000 box-border max-h-[min(320px,var(--radix-select-content-available-height))] w-(--radix-select-trigger-width) max-w-[min(24rem,calc(100vw_-_20px))] min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) animate-select-in overflow-hidden rounded-[calc(var(--radius-control)_+_2px)] border border-line bg-surface p-1.5 text-ink shadow-floating [--menu-x:0px] [--menu-y:-6px] data-[side=left]:[--menu-x:6px] data-[side=left]:[--menu-y:0px] data-[side=right]:[--menu-x:-6px] data-[side=right]:[--menu-y:0px] data-[side=top]:[--menu-y:6px] data-[state=closed]:animate-select-out motion-reduce:animate-select-fade-in motion-reduce:data-[state=closed]:animate-select-fade-out\"\n            position=\"popper\"\n            sideOffset={4}\n            collisionPadding={12}\n          >\n            <SelectPrimitive.ScrollUpButton className=\"grid h-7 place-items-center text-ink-3\">\n              <ChevronUp size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n            </SelectPrimitive.ScrollUpButton>\n            <SelectPrimitive.Viewport className=\"py-0.5\">\n              {options.map((option) => (\n                <SelectPrimitive.Item\n                  key={option.value}\n                  value={option.value}\n                  disabled={option.disabled}\n                  className=\"relative flex min-h-9 cursor-default items-center rounded-[calc(var(--radius-control)_-_4px)] pr-[34px] pl-[11px] text-sm/[inherit] text-ink outline-none select-none transition-[background-color,color] duration-instant ease-standard data-disabled:opacity-45 data-highlighted:bg-hover motion-reduce:transition-none\"\n                >\n                  <SelectPrimitive.ItemText>{option.label}</SelectPrimitive.ItemText>\n                  <SelectPrimitive.ItemIndicator className=\"absolute right-2.5 inline-flex animate-select-indicator-in items-center text-ink motion-reduce:animate-none\">\n                    <Check size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n                  </SelectPrimitive.ItemIndicator>\n                </SelectPrimitive.Item>\n              ))}\n            </SelectPrimitive.Viewport>\n            <SelectPrimitive.ScrollDownButton className=\"grid h-7 place-items-center text-ink-3\">\n              <ChevronDown size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n            </SelectPrimitive.ScrollDownButton>\n          </SelectPrimitive.Content>\n        </SelectPrimitive.Portal>\n      </SelectPrimitive.Root>\n      {description && (\n        <span id={hintId} className=\"text-xs/[inherit] text-ink-3\">\n          {description}\n        </span>\n      )}\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "@components/iris/select/select.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/select",
  "meta": {
    "kind": "component",
    "group": "Inputs",
    "export": "Select",
    "tags": [
      "field",
      "form"
    ],
    "docs": "https://irisui.dev/components/select"
  }
}
