{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react", "motion", "lucide-react"],
  "devDependencies": [],
  "files": [
    {
      "path": "select.tsx",
      "content": "\"use client\";\n\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\";\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport {\n  type CSSProperties,\n  type Dispatch,\n  type KeyboardEvent,\n  type MutableRefObject,\n  type ReactNode,\n  type SetStateAction,\n  useCallback,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst controlCornerClassName =\n  \"rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]\";\n\nconst controlCornerInheritClassName =\n  \"rounded-[inherit] supports-[corner-shape:squircle]:[corner-shape:inherit]\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst MAX_MENU_HEIGHT = 320;\n\nconst selectListScrollbarClassName =\n  \"z-10 my-1.5 mr-0.5 w-1 shrink-0 touch-none select-none opacity-0 transition-opacity duration-150 before:absolute before:left-1/2 before:h-full before:w-5 before:-translate-x-1/2 before:content-[''] data-hovering:pointer-events-auto data-hovering:opacity-100 data-scrolling:pointer-events-auto data-scrolling:opacity-100 data-scrolling:duration-0\";\n\nconst selectListThumbClassName =\n  \"relative rounded-full bg-muted-foreground/50 bg-[color:color-mix(in_oklch,var(--ic-muted-foreground),transparent_35%)]\";\n\nconst MENU_OFFSET = 8;\nconst TYPEAHEAD_RESET_MS = 500;\nconst VIEWPORT_MARGIN = 12;\nconst SOFT_EASE = [0.22, 1, 0.36, 1] as const;\nconst EXIT_EASE = [0.55, 0.06, 0.68, 0.19] as const;\nconst ACTIVE_SPRING = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 34,\n  mass: 0.58,\n} as const;\nconst CHECK_SPRING = {\n  type: \"spring\",\n  stiffness: 520,\n  damping: 28,\n  mass: 0.55,\n} as const;\nconst PRESS_SPRING = {\n  type: \"spring\",\n  stiffness: 560,\n  damping: 32,\n  mass: 0.48,\n} as const;\n\nexport interface SelectOption {\n  value: string;\n  label: string;\n  icon?: ReactNode;\n  group?: string;\n}\n\nexport interface SelectProps {\n  options: SelectOption[];\n  value?: string;\n  onChange?: (value: string) => void;\n  placeholder?: string;\n  className?: string;\n}\n\ntype SelectSection = {\n  items: Array<{\n    index: number;\n    option: SelectOption;\n  }>;\n  key: string;\n  label?: string;\n};\n\ntype MenuPosition = {\n  left: number;\n  maxHeight: number;\n  top: number;\n  transformOrigin: string;\n  width: number;\n};\n\ntype SelectOptionRowProps = {\n  activeHighlightId: string;\n  index: number;\n  isActive: boolean;\n  isSelected: boolean;\n  itemVariants: ReturnType<typeof getItemVariants>;\n  listboxId: string;\n  onSelect: (index: number) => void;\n  option: SelectOption;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n  setOptionRef: (index: number, node: HTMLDivElement | null) => void;\n  setShowActiveHighlight: Dispatch<SetStateAction<boolean>>;\n  showActiveHighlight: boolean;\n};\n\ntype SelectMenuSectionProps = {\n  activeHighlightId: string;\n  activeIndex: number;\n  itemVariants: ReturnType<typeof getItemVariants>;\n  listboxId: string;\n  optionRefs: MutableRefObject<Array<HTMLDivElement | null>>;\n  section: SelectSection;\n  sectionIndex: number;\n  selectOption: (index: number, restoreFocus?: boolean) => void;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n  setShowActiveHighlight: Dispatch<SetStateAction<boolean>>;\n  showActiveHighlight: boolean;\n  value?: string;\n};\n\ntype SelectMenuContentProps = {\n  activeIndex: number;\n  closeMenu: (restoreFocus?: boolean) => void;\n  itemVariants: ReturnType<typeof getItemVariants>;\n  listboxId: string;\n  listboxRef: MutableRefObject<HTMLDivElement | null>;\n  menuPanelRef: MutableRefObject<HTMLDivElement | null>;\n  menuPosition: MenuPosition | null;\n  options: SelectOption[];\n  optionRefs: MutableRefObject<Array<HTMLDivElement | null>>;\n  panelTransition: {\n    duration: number;\n    ease: \"easeOut\" | typeof SOFT_EASE;\n  };\n  runTypeahead: (key: string) => void;\n  sections: SelectSection[];\n  selectOption: (index: number, restoreFocus?: boolean) => void;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n  setShowActiveHighlight: Dispatch<SetStateAction<boolean>>;\n  showActiveHighlight: boolean;\n  triggerId: string;\n  value?: string;\n};\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max);\n}\n\nfunction isPrintableKey(\n  event: Pick<\n    KeyboardEvent<HTMLElement>,\n    \"altKey\" | \"ctrlKey\" | \"metaKey\" | \"key\"\n  >\n) {\n  return (\n    !(event.altKey || event.ctrlKey || event.metaKey) && event.key.length === 1\n  );\n}\n\nfunction getSelectSections(options: SelectOption[]) {\n  const nextSections: SelectSection[] = [];\n\n  options.forEach((option, index) => {\n    const groupLabel = option.group?.trim();\n    const previousSection = nextSections.at(-1);\n\n    if (groupLabel) {\n      if (previousSection?.label === groupLabel) {\n        previousSection.items.push({ index, option });\n        return;\n      }\n\n      nextSections.push({\n        items: [{ index, option }],\n        key: `${groupLabel}-${nextSections.length}`,\n        label: groupLabel,\n      });\n      return;\n    }\n\n    nextSections.push({\n      items: [{ index, option }],\n      key: `ungrouped-${index}`,\n    });\n  });\n\n  return nextSections;\n}\n\nfunction normalizeTypeaheadValue(value: string) {\n  return value\n    .normalize(\"NFKD\")\n    .replace(/[\\u0300-\\u036f]/g, \"\")\n    .toLowerCase()\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\nfunction findTypeaheadMatch(\n  options: SelectOption[],\n  query: string,\n  startIndex: number\n) {\n  const normalizedQuery = normalizeTypeaheadValue(query);\n\n  if (!normalizedQuery) {\n    return -1;\n  }\n\n  for (let offset = 1; offset <= options.length; offset += 1) {\n    const index = (startIndex + offset) % options.length;\n    const candidate = normalizeTypeaheadValue(options[index]?.label ?? \"\");\n\n    if (candidate.startsWith(normalizedQuery)) {\n      return index;\n    }\n  }\n\n  return -1;\n}\n\nfunction getNextIndex(\n  currentIndex: number,\n  optionCount: number,\n  direction: 1 | -1\n) {\n  if (optionCount === 0) {\n    return 0;\n  }\n\n  return (currentIndex + direction + optionCount) % optionCount;\n}\n\nfunction scrollOptionIntoView(\n  container: HTMLDivElement | null,\n  option: HTMLDivElement | null\n) {\n  if (!(container && option)) {\n    return;\n  }\n\n  const containerRect = container.getBoundingClientRect();\n  const optionRect = option.getBoundingClientRect();\n\n  if (optionRect.top < containerRect.top) {\n    container.scrollTop -= containerRect.top - optionRect.top;\n    return;\n  }\n\n  if (optionRect.bottom > containerRect.bottom) {\n    container.scrollTop += optionRect.bottom - containerRect.bottom;\n  }\n}\n\nfunction getItemVariants() {\n  return {\n    exit: (index: number) => ({\n      opacity: 0,\n      y: -2,\n      transition: {\n        delay: Math.min(index, 4) * 0.01,\n        duration: 0.12,\n        ease: EXIT_EASE,\n      },\n    }),\n    hidden: {\n      opacity: 0,\n      y: -4,\n    },\n    visible: (index: number) => ({\n      opacity: 1,\n      y: 0,\n      transition: {\n        delay: Math.min(index, 4) * 0.02,\n        duration: 0.18,\n        ease: SOFT_EASE,\n      },\n    }),\n  };\n}\n\nfunction getMenuPosition({\n  optionCount,\n  panel,\n  triggerRect,\n  viewportHeight,\n  viewportWidth,\n}: {\n  optionCount: number;\n  panel: HTMLDivElement | null;\n  triggerRect: DOMRect;\n  viewportHeight: number;\n  viewportWidth: number;\n}) {\n  const width = triggerRect.width;\n  const availableBelow = Math.max(\n    viewportHeight - triggerRect.bottom - MENU_OFFSET - VIEWPORT_MARGIN,\n    0\n  );\n  const availableAbove = Math.max(\n    triggerRect.top - MENU_OFFSET - VIEWPORT_MARGIN,\n    0\n  );\n  const measuredHeight = Math.max(\n    panel?.scrollHeight ?? 0,\n    panel?.getBoundingClientRect().height ?? 0,\n    Math.min(optionCount * 52, MAX_MENU_HEIGHT)\n  );\n  const preferredHeight = Math.min(\n    measuredHeight || MAX_MENU_HEIGHT,\n    MAX_MENU_HEIGHT\n  );\n  const shouldPlaceAbove =\n    availableBelow < preferredHeight && availableAbove > availableBelow;\n  const maxHeight = Math.max(\n    Math.min(\n      MAX_MENU_HEIGHT,\n      shouldPlaceAbove ? availableAbove : availableBelow\n    ),\n    0\n  );\n  const left = clamp(\n    triggerRect.left,\n    VIEWPORT_MARGIN,\n    Math.max(VIEWPORT_MARGIN, viewportWidth - width - VIEWPORT_MARGIN)\n  );\n  const resolvedHeight = Math.min(\n    preferredHeight,\n    maxHeight || preferredHeight\n  );\n  const top = shouldPlaceAbove\n    ? Math.max(VIEWPORT_MARGIN, triggerRect.top - MENU_OFFSET - resolvedHeight)\n    : triggerRect.bottom + MENU_OFFSET;\n  const horizontalOrigin =\n    left <= VIEWPORT_MARGIN + 0.5\n      ? \"left\"\n      : left >= viewportWidth - width - VIEWPORT_MARGIN - 0.5\n        ? \"right\"\n        : \"left\";\n\n  return {\n    left,\n    maxHeight,\n    top,\n    transformOrigin: `${horizontalOrigin} ${\n      shouldPlaceAbove ? \"bottom\" : \"top\"\n    }`,\n    width,\n  };\n}\n\nfunction handleSelectTriggerKeyDown({\n  closeMenu,\n  event,\n  open,\n  openMenu,\n  optionsLength,\n  pendingTypeaheadRef,\n  selectedIndex,\n}: {\n  closeMenu: (restoreFocus?: boolean) => void;\n  event: KeyboardEvent<HTMLButtonElement>;\n  open: boolean;\n  openMenu: (nextActiveIndex?: number) => void;\n  optionsLength: number;\n  pendingTypeaheadRef: MutableRefObject<string | null>;\n  selectedIndex: number;\n}) {\n  switch (event.key) {\n    case \"ArrowDown\":\n      event.preventDefault();\n      openMenu(selectedIndex >= 0 ? selectedIndex : 0);\n      return;\n    case \"ArrowUp\":\n      event.preventDefault();\n      openMenu(selectedIndex >= 0 ? selectedIndex : optionsLength - 1);\n      return;\n    case \"Home\":\n      event.preventDefault();\n      openMenu(0);\n      return;\n    case \"End\":\n      event.preventDefault();\n      openMenu(optionsLength - 1);\n      return;\n    case \"Enter\":\n    case \" \":\n      event.preventDefault();\n\n      if (open) {\n        closeMenu(false);\n        return;\n      }\n\n      openMenu(selectedIndex >= 0 ? selectedIndex : 0);\n      return;\n    default:\n      if (!isPrintableKey(event)) {\n        return;\n      }\n\n      event.preventDefault();\n      pendingTypeaheadRef.current = event.key;\n      openMenu(selectedIndex >= 0 ? selectedIndex : 0);\n  }\n}\n\nfunction handleSelectListboxKeyDown({\n  activeIndex,\n  closeMenu,\n  event,\n  optionCount,\n  runTypeahead,\n  selectOption,\n  setActiveIndex,\n}: {\n  activeIndex: number;\n  closeMenu: (restoreFocus?: boolean) => void;\n  event: KeyboardEvent<HTMLDivElement>;\n  optionCount: number;\n  runTypeahead: (key: string) => void;\n  selectOption: (index: number, restoreFocus?: boolean) => void;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n}) {\n  switch (event.key) {\n    case \"ArrowDown\":\n      event.preventDefault();\n      setActiveIndex((current) => getNextIndex(current, optionCount, 1));\n      return;\n    case \"ArrowUp\":\n      event.preventDefault();\n      setActiveIndex((current) => getNextIndex(current, optionCount, -1));\n      return;\n    case \"Home\":\n      event.preventDefault();\n      setActiveIndex(0);\n      return;\n    case \"End\":\n      event.preventDefault();\n      setActiveIndex(Math.max(optionCount - 1, 0));\n      return;\n    case \"Enter\":\n    case \" \":\n      event.preventDefault();\n      selectOption(activeIndex);\n      return;\n    case \"Escape\":\n      event.preventDefault();\n      closeMenu(true);\n      return;\n    case \"Tab\":\n      closeMenu(false);\n      return;\n    default:\n      if (!isPrintableKey(event)) {\n        return;\n      }\n\n      event.preventDefault();\n      runTypeahead(event.key);\n  }\n}\n\nfunction SelectOptionRow({\n  activeHighlightId,\n  index,\n  isActive,\n  isSelected,\n  itemVariants,\n  listboxId,\n  onSelect,\n  option,\n  setActiveIndex,\n  setOptionRef,\n  setShowActiveHighlight,\n  showActiveHighlight,\n}: SelectOptionRowProps) {\n  return (\n    <motion.div\n      animate=\"visible\"\n      aria-selected={isSelected}\n      className={cn(\n        \"group relative isolate flex min-h-11 cursor-pointer touch-manipulation select-none items-center gap-3 px-3 py-2.5 text-foreground text-sm outline-none transition-colors\",\n        controlCornerClassName,\n        !isActive && \"hover:bg-accent/60\"\n      )}\n      custom={index}\n      exit=\"exit\"\n      id={`${listboxId}-option-${index}`}\n      initial=\"hidden\"\n      onClick={() => onSelect(index)}\n      onMouseDown={(event) => {\n        event.preventDefault();\n      }}\n      onMouseEnter={() => {\n        setActiveIndex(index);\n        setShowActiveHighlight(true);\n      }}\n      ref={(node) => {\n        setOptionRef(index, node);\n      }}\n      role=\"option\"\n      title={option.label}\n      transition={PRESS_SPRING}\n      variants={itemVariants}\n      whileTap={{ scale: 0.96 }}\n    >\n      {showActiveHighlight && isActive ? (\n        <motion.span\n          className={cn(\n            \"absolute inset-0 -z-10 bg-accent/70\",\n            controlCornerInheritClassName\n          )}\n          layoutId={activeHighlightId}\n          transition={ACTIVE_SPRING}\n        />\n      ) : null}\n      {option.icon ? (\n        <span className=\"flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground\">\n          {option.icon}\n        </span>\n      ) : null}\n      <span className=\"min-w-0 flex-1 truncate text-left\">{option.label}</span>\n      <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">\n        <AnimatePresence>\n          {isSelected ? (\n            <motion.span\n              animate={{ opacity: 1, scale: 1, y: 0 }}\n              className=\"text-primary\"\n              exit={{ opacity: 0, scale: 0.8, y: 1 }}\n              initial={{ opacity: 0, scale: 0.8, y: 1 }}\n              transition={CHECK_SPRING}\n            >\n              <Check className=\"h-4 w-4\" />\n            </motion.span>\n          ) : null}\n        </AnimatePresence>\n      </span>\n    </motion.div>\n  );\n}\n\nfunction SelectMenuSection({\n  activeHighlightId,\n  activeIndex,\n  itemVariants,\n  listboxId,\n  optionRefs,\n  section,\n  sectionIndex,\n  selectOption,\n  setActiveIndex,\n  setShowActiveHighlight,\n  showActiveHighlight,\n  value,\n}: SelectMenuSectionProps) {\n  const groupLabelId = `${listboxId}-group-${sectionIndex}`;\n  const sectionItems = section.items.map(({ index, option }) => (\n    <SelectOptionRow\n      activeHighlightId={activeHighlightId}\n      index={index}\n      isActive={index === activeIndex}\n      isSelected={option.value === value}\n      itemVariants={itemVariants}\n      key={option.value}\n      listboxId={listboxId}\n      onSelect={selectOption}\n      option={option}\n      setActiveIndex={setActiveIndex}\n      setOptionRef={(nextIndex, node) => {\n        optionRefs.current[nextIndex] = node;\n      }}\n      setShowActiveHighlight={setShowActiveHighlight}\n      showActiveHighlight={showActiveHighlight}\n    />\n  ));\n\n  if (!section.label) {\n    return <div className=\"space-y-1\">{sectionItems}</div>;\n  }\n\n  return (\n    <div className=\"pt-2 first:pt-0\">\n      <div\n        className=\"px-3 pb-1.5 font-medium text-[10px] text-muted-foreground uppercase tracking-[0.16em]\"\n        id={groupLabelId}\n      >\n        {section.label}\n      </div>\n      <div className=\"space-y-1\">{sectionItems}</div>\n    </div>\n  );\n}\n\nfunction SelectMenuContent({\n  activeIndex,\n  closeMenu,\n  itemVariants,\n  listboxId,\n  listboxRef,\n  menuPanelRef,\n  menuPosition,\n  options,\n  optionRefs,\n  panelTransition,\n  runTypeahead,\n  sections,\n  selectOption,\n  setActiveIndex,\n  setShowActiveHighlight,\n  showActiveHighlight,\n  triggerId,\n  value,\n}: SelectMenuContentProps) {\n  const panelStyle: CSSProperties | undefined = menuPosition\n    ? {\n        left: menuPosition.left,\n        position: \"fixed\",\n        top: menuPosition.top,\n        transformOrigin: menuPosition.transformOrigin,\n        width: menuPosition.width,\n      }\n    : undefined;\n\n  return (\n    <motion.div\n      animate={{ opacity: 1, y: 0 }}\n      className={cn(\n        componentThemeClassName,\n        controlCornerClassName,\n        \"z-[300] overflow-hidden border border-border bg-card shadow-lg\"\n      )}\n      exit={{ opacity: 0, y: -4 }}\n      initial={{ opacity: 0, y: -4 }}\n      key=\"select-dropdown\"\n      ref={menuPanelRef}\n      style={panelStyle}\n      transition={panelTransition}\n    >\n      <ScrollAreaPrimitive.Root\n        className=\"relative overflow-hidden\"\n        style={{\n          maxHeight: menuPosition?.maxHeight ?? MAX_MENU_HEIGHT,\n        }}\n      >\n        <ScrollAreaPrimitive.Viewport className=\"max-h-[inherit] min-h-0 overscroll-contain outline-none\">\n          <div\n            aria-activedescendant={\n              options[activeIndex]\n                ? `${listboxId}-option-${activeIndex}`\n                : undefined\n            }\n            aria-labelledby={triggerId}\n            aria-orientation=\"vertical\"\n            className=\"p-1.5 outline-none\"\n            id={listboxId}\n            onKeyDown={(event) => {\n              setShowActiveHighlight(true);\n              handleSelectListboxKeyDown({\n                activeIndex,\n                closeMenu,\n                event,\n                optionCount: options.length,\n                runTypeahead,\n                selectOption,\n                setActiveIndex,\n              });\n            }}\n            ref={listboxRef}\n            role=\"listbox\"\n            tabIndex={-1}\n          >\n            {options.length === 0 ? (\n              <div className=\"px-3 py-3 text-muted-foreground text-sm\">\n                No options available.\n              </div>\n            ) : (\n              sections.map((section, sectionIndex) => (\n                <SelectMenuSection\n                  activeHighlightId={`${listboxId}-active-option`}\n                  activeIndex={activeIndex}\n                  itemVariants={itemVariants}\n                  key={section.key}\n                  listboxId={listboxId}\n                  optionRefs={optionRefs}\n                  section={section}\n                  sectionIndex={sectionIndex}\n                  selectOption={selectOption}\n                  setActiveIndex={setActiveIndex}\n                  setShowActiveHighlight={setShowActiveHighlight}\n                  showActiveHighlight={showActiveHighlight}\n                  value={value}\n                />\n              ))\n            )}\n          </div>\n        </ScrollAreaPrimitive.Viewport>\n        <ScrollAreaPrimitive.Scrollbar\n          className={selectListScrollbarClassName}\n          orientation=\"vertical\"\n        >\n          <ScrollAreaPrimitive.Thumb className={selectListThumbClassName} />\n        </ScrollAreaPrimitive.Scrollbar>\n      </ScrollAreaPrimitive.Root>\n    </motion.div>\n  );\n}\n\nexport function Select({\n  options,\n  value,\n  onChange,\n  placeholder = \"Select an option…\",\n  className,\n}: SelectProps) {\n  const generatedId = useId();\n  const [activeIndex, setActiveIndex] = useState(0);\n  const [mounted, setMounted] = useState(false);\n  const [menuPosition, setMenuPosition] = useState<MenuPosition | null>(null);\n  const [open, setOpen] = useState(false);\n  const [showActiveHighlight, setShowActiveHighlight] = useState(false);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const menuPanelRef = useRef<HTMLDivElement>(null);\n  const listboxRef = useRef<HTMLDivElement>(null);\n  const openInteractionRef = useRef<\"keyboard\" | \"pointer\">(\"pointer\");\n  const optionRefs = useRef<Array<HTMLDivElement | null>>([]);\n  const pendingTypeaheadRef = useRef<string | null>(null);\n  const typeaheadRef = useRef(\"\");\n  const typeaheadTimeoutRef = useRef<number | null>(null);\n  const listboxId = `${generatedId}-listbox`;\n  const triggerId = `${generatedId}-trigger`;\n  const selectedIndex = options.findIndex((option) => option.value === value);\n  const selected = selectedIndex >= 0 ? options[selectedIndex] : undefined;\n  const itemVariants = useMemo(() => getItemVariants(), []);\n  const buttonTransition = PRESS_SPRING;\n  const panelTransition = { duration: 0.22, ease: SOFT_EASE };\n  const chevronTransition = { duration: 0.2, ease: SOFT_EASE };\n  const sections = useMemo(() => getSelectSections(options), [options]);\n\n  const clearTypeahead = useCallback(() => {\n    if (typeaheadTimeoutRef.current !== null) {\n      window.clearTimeout(typeaheadTimeoutRef.current);\n      typeaheadTimeoutRef.current = null;\n    }\n\n    typeaheadRef.current = \"\";\n  }, []);\n\n  const focusTrigger = useCallback(() => {\n    window.requestAnimationFrame(() => {\n      triggerRef.current?.focus({ preventScroll: true });\n    });\n  }, []);\n\n  const updateMenuPosition = useCallback(() => {\n    const trigger = triggerRef.current;\n\n    if (!(trigger && typeof window !== \"undefined\")) {\n      return;\n    }\n\n    const nextPosition = getMenuPosition({\n      optionCount: options.length,\n      panel: menuPanelRef.current,\n      triggerRect: trigger.getBoundingClientRect(),\n      viewportHeight: window.innerHeight,\n      viewportWidth: window.innerWidth,\n    });\n\n    setMenuPosition((previous) => {\n      if (\n        previous &&\n        previous.left === nextPosition.left &&\n        previous.maxHeight === nextPosition.maxHeight &&\n        previous.top === nextPosition.top &&\n        previous.transformOrigin === nextPosition.transformOrigin &&\n        previous.width === nextPosition.width\n      ) {\n        return previous;\n      }\n\n      return nextPosition;\n    });\n  }, [options.length]);\n\n  const openMenu = useCallback(\n    (nextActiveIndex = selectedIndex >= 0 ? selectedIndex : 0) => {\n      setActiveIndex(\n        clamp(nextActiveIndex, 0, Math.max(options.length - 1, 0))\n      );\n      setShowActiveHighlight(openInteractionRef.current === \"keyboard\");\n      setOpen(true);\n    },\n    [options.length, selectedIndex]\n  );\n\n  const closeMenu = useCallback(\n    (restoreFocus = false) => {\n      setOpen(false);\n      setShowActiveHighlight(false);\n      clearTypeahead();\n      pendingTypeaheadRef.current = null;\n\n      if (restoreFocus) {\n        focusTrigger();\n      }\n    },\n    [clearTypeahead, focusTrigger]\n  );\n\n  const selectOption = useCallback(\n    (index: number, restoreFocus = true) => {\n      const option = options[index];\n\n      if (!option) {\n        return;\n      }\n\n      onChange?.(option.value);\n      setActiveIndex(index);\n      closeMenu(restoreFocus);\n    },\n    [closeMenu, onChange, options]\n  );\n\n  const runTypeahead = useCallback(\n    (key: string) => {\n      if (options.length === 0) {\n        return;\n      }\n\n      const normalizedKey = normalizeTypeaheadValue(key);\n\n      if (!normalizedKey) {\n        return;\n      }\n\n      const bufferedQuery = `${typeaheadRef.current}${normalizedKey}`;\n      const bufferedMatch = findTypeaheadMatch(\n        options,\n        bufferedQuery,\n        activeIndex\n      );\n      const singleKeyMatch =\n        bufferedMatch === -1\n          ? findTypeaheadMatch(options, normalizedKey, activeIndex)\n          : bufferedMatch;\n      const nextIndex = bufferedMatch !== -1 ? bufferedMatch : singleKeyMatch;\n\n      typeaheadRef.current =\n        bufferedMatch !== -1 ? bufferedQuery : normalizedKey;\n\n      if (typeaheadTimeoutRef.current !== null) {\n        window.clearTimeout(typeaheadTimeoutRef.current);\n      }\n\n      typeaheadTimeoutRef.current = window.setTimeout(() => {\n        typeaheadRef.current = \"\";\n        typeaheadTimeoutRef.current = null;\n      }, TYPEAHEAD_RESET_MS);\n\n      if (nextIndex >= 0) {\n        setActiveIndex(nextIndex);\n        setShowActiveHighlight(true);\n      }\n    },\n    [activeIndex, options]\n  );\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  useEffect(() => {\n    return () => clearTypeahead();\n  }, [clearTypeahead]);\n\n  useLayoutEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    updateMenuPosition();\n  }, [open, updateMenuPosition]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    setActiveIndex((current) => {\n      if (selectedIndex >= 0) {\n        return selectedIndex;\n      }\n\n      return clamp(current, 0, Math.max(options.length - 1, 0));\n    });\n  }, [open, options.length, selectedIndex]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    const handleLayoutChange = () => updateMenuPosition();\n    window.addEventListener(\"resize\", handleLayoutChange);\n    window.addEventListener(\"scroll\", handleLayoutChange, true);\n\n    if (typeof ResizeObserver === \"undefined\") {\n      return () => {\n        window.removeEventListener(\"resize\", handleLayoutChange);\n        window.removeEventListener(\"scroll\", handleLayoutChange, true);\n      };\n    }\n\n    const observer = new ResizeObserver(handleLayoutChange);\n    const observedNodes = [triggerRef.current, menuPanelRef.current].filter(\n      Boolean\n    );\n\n    observedNodes.forEach((node) => {\n      observer.observe(node as Element);\n    });\n\n    return () => {\n      observer.disconnect();\n      window.removeEventListener(\"resize\", handleLayoutChange);\n      window.removeEventListener(\"scroll\", handleLayoutChange, true);\n    };\n  }, [open, updateMenuPosition]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    if (openInteractionRef.current !== \"keyboard\") {\n      return;\n    }\n\n    const focusFrame = window.requestAnimationFrame(() => {\n      listboxRef.current?.focus({ preventScroll: true });\n\n      if (pendingTypeaheadRef.current) {\n        runTypeahead(pendingTypeaheadRef.current);\n        pendingTypeaheadRef.current = null;\n      }\n    });\n\n    return () => window.cancelAnimationFrame(focusFrame);\n  }, [open, runTypeahead]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    scrollOptionIntoView(\n      listboxRef.current,\n      optionRefs.current[activeIndex] ?? null\n    );\n  }, [activeIndex, open]);\n\n  useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    function handlePointerDown(event: PointerEvent) {\n      const target = event.target as Node;\n\n      if (triggerRef.current?.contains(target)) {\n        return;\n      }\n\n      if (menuPanelRef.current?.contains(target)) {\n        return;\n      }\n\n      closeMenu(false);\n    }\n\n    document.addEventListener(\"pointerdown\", handlePointerDown, true);\n    return () =>\n      document.removeEventListener(\"pointerdown\", handlePointerDown, true);\n  }, [closeMenu, open]);\n\n  return (\n    <div\n      className={cn(\n        componentThemeClassName,\n        \"relative w-72 max-w-full\",\n        className\n      )}\n    >\n      <motion.button\n        aria-controls={open ? listboxId : undefined}\n        aria-expanded={open}\n        aria-haspopup=\"listbox\"\n        className={cn(\n          \"flex min-h-11 w-full touch-manipulation items-center justify-between gap-2 border border-border bg-card px-4 py-3 text-left font-medium text-foreground text-sm transition-colors hover:bg-accent/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          controlCornerClassName\n        )}\n        data-state={open ? \"open\" : \"closed\"}\n        id={triggerId}\n        onClick={() => {\n          if (open) {\n            closeMenu(false);\n            return;\n          }\n\n          openInteractionRef.current = \"pointer\";\n          openMenu(selectedIndex >= 0 ? selectedIndex : 0);\n        }}\n        onKeyDown={(event) => {\n          openInteractionRef.current = \"keyboard\";\n          handleSelectTriggerKeyDown({\n            closeMenu,\n            event,\n            open,\n            openMenu,\n            optionsLength: options.length,\n            pendingTypeaheadRef,\n            selectedIndex,\n          });\n        }}\n        ref={triggerRef}\n        transition={buttonTransition}\n        type=\"button\"\n        whileTap={{ scale: 0.96 }}\n      >\n        <span\n          className={cn(\n            \"min-w-0 flex-1 truncate\",\n            selected ? \"text-foreground\" : \"text-muted-foreground\"\n          )}\n          title={selected ? selected.label : placeholder}\n        >\n          {selected ? selected.label : placeholder}\n        </span>\n        <motion.span\n          animate={{ rotate: open ? 180 : 0 }}\n          className=\"shrink-0\"\n          transition={chevronTransition}\n        >\n          <ChevronDown className=\"h-4 w-4 text-muted-foreground\" />\n        </motion.span>\n      </motion.button>\n\n      {mounted\n        ? createPortal(\n            <AnimatePresence>\n              {open ? (\n                <SelectMenuContent\n                  activeIndex={activeIndex}\n                  closeMenu={closeMenu}\n                  itemVariants={itemVariants}\n                  listboxId={listboxId}\n                  listboxRef={listboxRef}\n                  menuPanelRef={menuPanelRef}\n                  menuPosition={menuPosition}\n                  optionRefs={optionRefs}\n                  options={options}\n                  panelTransition={panelTransition}\n                  runTypeahead={runTypeahead}\n                  sections={sections}\n                  selectOption={selectOption}\n                  setActiveIndex={setActiveIndex}\n                  setShowActiveHighlight={setShowActiveHighlight}\n                  showActiveHighlight={showActiveHighlight}\n                  triggerId={triggerId}\n                  value={value}\n                />\n              ) : null}\n            </AnimatePresence>,\n            document.body\n          )\n        : null}\n    </div>\n  );\n}\n\nexport { Select as select };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Select",
  "description": "Animated single-select dropdown with staggered options, chevron rotation, scaleY panel, row hover slide, and check mark on the active item. Motion + Lucide."
}
