{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react", "motion", "lucide-react"],
  "devDependencies": [],
  "files": [
    {
      "path": "dropdown.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 * as React from \"react\";\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 dropdownItemHighlightBeforeClassName =\n  \"before:absolute before:inset-x-1 before:inset-y-0.5 before:-z-10 before:bg-transparent before:transition-colors before:rounded-[inherit] before: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\nexport type DropdownVariant = \"select\" | \"action\";\n\ntype DropdownAlign = \"center\" | \"end\" | \"start\";\ntype DropdownFocusStrategy = \"first\" | \"last\" | \"selected\";\n\nconst SOFT_EASE = [0.22, 1, 0.36, 1] as const;\nconst MAX_CONTENT_HEIGHT = 384;\n\nconst dropdownListScrollbarClassName =\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 dropdownListThumbClassName =\n  \"relative rounded-full bg-muted-foreground/50\";\nconst TYPEAHEAD_RESET_MS = 500;\nconst VIEWPORT_MARGIN = 12;\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max);\n}\n\nfunction getContentMotion() {\n  return {\n    animate: { opacity: 1, y: 0 },\n    exit: { opacity: 0, y: -4 },\n    initial: { opacity: 0, y: -4 },\n    transition: {\n      duration: 0.25,\n      ease: SOFT_EASE,\n    },\n  };\n}\n\nfunction getHorizontalOrigin(align: DropdownAlign) {\n  if (align === \"center\") {\n    return \"center\";\n  }\n\n  if (align === \"end\") {\n    return \"right\";\n  }\n\n  return \"left\";\n}\n\nfunction getResolvedPlacement(\n  contentHeight: number,\n  triggerRect: DOMRect,\n  viewportHeight: number,\n  sideOffset: number\n) {\n  const availableBelow = Math.max(\n    viewportHeight - triggerRect.bottom - sideOffset - VIEWPORT_MARGIN,\n    0\n  );\n  const maxHeight =\n    availableBelow > 0\n      ? Math.min(MAX_CONTENT_HEIGHT, Math.min(contentHeight, availableBelow))\n      : null;\n\n  return { maxHeight };\n}\n\nfunction getInnerContentMotion() {\n  return {\n    animate: { opacity: 1, y: 0 },\n    exit: { opacity: 0, y: -2 },\n    initial: { opacity: 0, y: 3 },\n    transition: {\n      delay: 0.03,\n      duration: 0.18,\n      ease: SOFT_EASE,\n    },\n  };\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\ntype DropdownContextValue = {\n  activeItemId: string | null;\n  contentId: string;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  labels: Record<string, string>;\n  open: boolean;\n  setActiveItemId: (id: string | null) => void;\n  setFocusStrategy: (strategy: DropdownFocusStrategy) => void;\n  setOpen: (open: boolean) => void;\n  setValue: (value: string | undefined) => void;\n  triggerRef: React.RefObject<HTMLButtonElement | null>;\n  value: string | undefined;\n  variant: DropdownVariant;\n};\n\nconst DropdownContext = React.createContext<DropdownContextValue | null>(null);\n\nfunction useDropdownContext(componentName: string) {\n  const context = React.useContext(DropdownContext);\n\n  if (!context) {\n    throw new Error(`${componentName} must be used within Dropdown.`);\n  }\n\n  return context;\n}\n\nfunction setRefValue<T>(\n  ref: React.ForwardedRef<T> | undefined,\n  value: T | null\n) {\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n\n  if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction getTextContent(node: React.ReactNode): string {\n  if (typeof node === \"string\" || typeof node === \"number\") {\n    return String(node);\n  }\n\n  if (Array.isArray(node)) {\n    return node.map(getTextContent).join(\"\");\n  }\n\n  if (React.isValidElement(node)) {\n    const element = node as React.ReactElement<{ children?: React.ReactNode }>;\n    return getTextContent(element.props.children);\n  }\n\n  return \"\";\n}\n\nfunction getResolvedHorizontalPosition({\n  align,\n  containerRect,\n  contentWidth,\n  triggerRect,\n  viewportWidth,\n}: {\n  align: DropdownAlign;\n  containerRect: DOMRect;\n  contentWidth: number;\n  triggerRect: DOMRect;\n  viewportWidth: number;\n}) {\n  const desiredLeft =\n    align === \"center\"\n      ? triggerRect.left + (triggerRect.width - contentWidth) / 2\n      : align === \"end\"\n        ? triggerRect.right - contentWidth\n        : triggerRect.left;\n  const maxLeft = Math.max(\n    VIEWPORT_MARGIN,\n    viewportWidth - contentWidth - VIEWPORT_MARGIN\n  );\n  const clampedLeft = clamp(desiredLeft, VIEWPORT_MARGIN, maxLeft);\n  const horizontalOrigin: \"center\" | \"left\" | \"right\" =\n    Math.abs(clampedLeft - VIEWPORT_MARGIN) < 0.5\n      ? \"left\"\n      : Math.abs(clampedLeft - maxLeft) < 0.5\n        ? \"right\"\n        : getHorizontalOrigin(align);\n\n  return {\n    horizontalOrigin,\n    left: clampedLeft - containerRect.left,\n  };\n}\n\nfunction collectDropdownLabels(node: React.ReactNode): Record<string, string> {\n  const labels: Record<string, string> = {};\n\n  const visit = (childNode: React.ReactNode) => {\n    if (!React.isValidElement(childNode)) {\n      if (Array.isArray(childNode)) {\n        childNode.forEach(visit);\n      }\n\n      return;\n    }\n\n    const element = childNode as React.ReactElement<{\n      children?: React.ReactNode;\n      textValue?: string;\n      value?: string;\n    }>;\n    const displayName =\n      typeof element.type === \"string\"\n        ? element.type\n        : (element.type as { displayName?: string }).displayName;\n\n    if (displayName === \"DropdownItem\" && element.props.value) {\n      labels[element.props.value] = (\n        element.props.textValue ?? getTextContent(element.props.children)\n      ).trim();\n    }\n\n    if (element.props.children) {\n      React.Children.forEach(element.props.children, visit);\n    }\n  };\n\n  React.Children.forEach(node, visit);\n  return labels;\n}\n\nfunction useControllableState<T>({\n  defaultProp,\n  onChange,\n  prop,\n}: {\n  defaultProp: T;\n  onChange?: (value: T) => void;\n  prop?: T;\n}) {\n  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultProp);\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolledValue;\n\n  const setValue = React.useCallback(\n    (nextValue: T) => {\n      if (!isControlled) {\n        setUncontrolledValue(nextValue);\n      }\n\n      onChange?.(nextValue);\n    },\n    [isControlled, onChange]\n  );\n\n  return [value, setValue] as const;\n}\n\nexport interface DropdownProps {\n  children: React.ReactNode;\n  className?: string;\n  defaultOpen?: boolean;\n  defaultValue?: string;\n  onOpenChange?: (open: boolean) => void;\n  onValueChange?: (value: string | undefined) => void;\n  open?: boolean;\n  value?: string;\n  variant?: DropdownVariant;\n}\n\nexport function Dropdown({\n  children,\n  className,\n  defaultOpen = false,\n  defaultValue,\n  onOpenChange,\n  onValueChange,\n  open: openProp,\n  value: valueProp,\n  variant = \"select\",\n}: DropdownProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const triggerRef = React.useRef<HTMLButtonElement>(null);\n  const contentId = React.useId();\n  const focusStrategyRef = React.useRef<DropdownFocusStrategy>(\"selected\");\n  const typeaheadRef = React.useRef(\"\");\n  const typeaheadTimeoutRef = React.useRef<number | null>(null);\n  const [open, setOpen] = useControllableState<boolean>({\n    defaultProp: defaultOpen,\n    onChange: onOpenChange,\n    prop: openProp,\n  });\n  const [activeItemId, setActiveItemId] = React.useState<string | null>(null);\n  const [value, setValue] = useControllableState<string | undefined>({\n    defaultProp: defaultValue,\n    onChange: onValueChange,\n    prop: valueProp,\n  });\n  const labels = React.useMemo(\n    () => collectDropdownLabels(children),\n    [children]\n  );\n\n  const clearTypeahead = React.useCallback(() => {\n    if (typeaheadTimeoutRef.current !== null) {\n      window.clearTimeout(typeaheadTimeoutRef.current);\n      typeaheadTimeoutRef.current = null;\n    }\n\n    typeaheadRef.current = \"\";\n  }, []);\n\n  const getEnabledItems = React.useCallback(() => {\n    if (!containerRef.current) {\n      return [];\n    }\n\n    return Array.from(\n      containerRef.current.querySelectorAll<HTMLElement>(\n        \"[data-dropdown-item]:not([data-disabled])\"\n      )\n    );\n  }, []);\n\n  const focusItem = React.useCallback(\n    (index: number) => {\n      const items = getEnabledItems();\n\n      if (!items.length) {\n        return;\n      }\n\n      const nextIndex = Math.min(Math.max(index, 0), items.length - 1);\n      const nextItem = items[nextIndex];\n      nextItem?.focus();\n      nextItem?.scrollIntoView({ block: \"nearest\" });\n    },\n    [getEnabledItems]\n  );\n\n  const getActiveDropdownElement = React.useCallback(() => {\n    const activeElement = document.activeElement;\n\n    if (\n      !(\n        containerRef.current &&\n        activeElement &&\n        containerRef.current.contains(activeElement)\n      )\n    ) {\n      return null;\n    }\n\n    return activeElement;\n  }, []);\n\n  const findMatchingItemIndex = React.useCallback(\n    (query: string) => {\n      const normalizedQuery = normalizeTypeaheadValue(query);\n\n      if (!normalizedQuery) {\n        return null;\n      }\n\n      const items = getEnabledItems();\n\n      if (!items.length) {\n        return null;\n      }\n\n      const activeElement = getActiveDropdownElement();\n      const currentIndex = items.indexOf(activeElement as HTMLElement);\n      const startIndex =\n        currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;\n\n      for (let offset = 0; offset < items.length; offset += 1) {\n        const index = (startIndex + offset) % items.length;\n        const candidate = items[index];\n        const textValue = normalizeTypeaheadValue(\n          candidate?.dataset.textValue ?? candidate?.textContent ?? \"\"\n        );\n\n        if (textValue.startsWith(normalizedQuery)) {\n          return index;\n        }\n      }\n\n      return null;\n    },\n    [getActiveDropdownElement, getEnabledItems]\n  );\n\n  const handleMenuNavigation = React.useCallback(\n    (key: \"ArrowDown\" | \"ArrowUp\" | \"Home\" | \"End\") => {\n      const activeElement = getActiveDropdownElement();\n\n      if (!activeElement) {\n        return false;\n      }\n\n      const items = getEnabledItems();\n\n      if (!items.length) {\n        return false;\n      }\n\n      if (key === \"Home\") {\n        focusItem(0);\n        return true;\n      }\n\n      if (key === \"End\") {\n        focusItem(items.length - 1);\n        return true;\n      }\n\n      const currentIndex = items.indexOf(activeElement as HTMLElement);\n\n      if (currentIndex === -1) {\n        focusItem(key === \"ArrowUp\" ? items.length - 1 : 0);\n        return true;\n      }\n\n      const nextIndex =\n        key === \"ArrowUp\"\n          ? (currentIndex - 1 + items.length) % items.length\n          : (currentIndex + 1) % items.length;\n\n      focusItem(nextIndex);\n      return true;\n    },\n    [focusItem, getActiveDropdownElement, getEnabledItems]\n  );\n\n  const handleTypeaheadKey = React.useCallback(\n    (key: string) => {\n      const normalizedKey = normalizeTypeaheadValue(key);\n\n      if (!normalizedKey) {\n        return false;\n      }\n\n      const nextQuery =\n        typeaheadRef.current === normalizedKey\n          ? normalizedKey\n          : `${typeaheadRef.current}${normalizedKey}`;\n      const matchIndex = findMatchingItemIndex(nextQuery);\n\n      typeaheadRef.current = nextQuery;\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 (matchIndex !== null) {\n        focusItem(matchIndex);\n        return true;\n      }\n\n      if (nextQuery !== normalizedKey) {\n        typeaheadRef.current = normalizedKey;\n        const fallbackMatchIndex = findMatchingItemIndex(normalizedKey);\n\n        if (fallbackMatchIndex !== null) {\n          focusItem(fallbackMatchIndex);\n          return true;\n        }\n      }\n\n      return false;\n    },\n    [findMatchingItemIndex, focusItem]\n  );\n\n  React.useEffect(() => {\n    if (open) {\n      return;\n    }\n\n    setActiveItemId(null);\n    clearTypeahead();\n  }, [clearTypeahead, open]);\n\n  React.useEffect(() => clearTypeahead, [clearTypeahead]);\n\n  React.useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    const handlePointerDown = (event: MouseEvent) => {\n      if (\n        containerRef.current &&\n        !containerRef.current.contains(event.target as Node)\n      ) {\n        setOpen(false);\n      }\n    };\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === \"Escape\") {\n        setOpen(false);\n        triggerRef.current?.focus();\n        return;\n      }\n\n      if (event.key === \"Tab\") {\n        setOpen(false);\n        return;\n      }\n\n      if (\n        event.key.length === 1 &&\n        !event.altKey &&\n        !event.ctrlKey &&\n        !event.metaKey &&\n        event.key.trim() &&\n        handleTypeaheadKey(event.key)\n      ) {\n        event.preventDefault();\n        return;\n      }\n\n      if (\n        (event.key === \"ArrowDown\" ||\n          event.key === \"ArrowUp\" ||\n          event.key === \"Home\" ||\n          event.key === \"End\") &&\n        handleMenuNavigation(event.key)\n      ) {\n        event.preventDefault();\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handlePointerDown);\n    document.addEventListener(\"keydown\", handleKeyDown);\n\n    return () => {\n      document.removeEventListener(\"mousedown\", handlePointerDown);\n      document.removeEventListener(\"keydown\", handleKeyDown);\n    };\n  }, [handleMenuNavigation, handleTypeaheadKey, open, setOpen]);\n\n  React.useEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    const frame = window.requestAnimationFrame(() => {\n      const items = getEnabledItems();\n\n      if (!items.length) {\n        return;\n      }\n\n      const selectedIndex =\n        value === undefined\n          ? -1\n          : items.findIndex((item) => item.dataset.value === value);\n\n      if (focusStrategyRef.current === \"first\") {\n        focusItem(0);\n        return;\n      }\n\n      if (focusStrategyRef.current === \"last\") {\n        focusItem(items.length - 1);\n        return;\n      }\n\n      focusItem(selectedIndex >= 0 ? selectedIndex : 0);\n    });\n\n    return () => {\n      window.cancelAnimationFrame(frame);\n    };\n  }, [focusItem, getEnabledItems, open, value]);\n\n  const contextValue = React.useMemo(\n    () => ({\n      activeItemId,\n      contentId,\n      containerRef,\n      labels,\n      open,\n      setActiveItemId,\n      setFocusStrategy: (strategy: DropdownFocusStrategy) => {\n        focusStrategyRef.current = strategy;\n      },\n      setOpen,\n      setValue,\n      triggerRef,\n      value,\n      variant,\n    }),\n    [activeItemId, contentId, labels, open, setOpen, setValue, value, variant]\n  );\n\n  return (\n    <DropdownContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          componentThemeClassName,\n          \"relative inline-block\",\n          className\n        )}\n        ref={containerRef}\n      >\n        {children}\n      </div>\n    </DropdownContext.Provider>\n  );\n}\n\nconst dropdownTriggerBaseClassName =\n  \"flex min-h-11 w-full 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\nexport interface DropdownTriggerProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<typeof motion.button>,\n    \"children\"\n  > {\n  children?: React.ReactNode;\n  showChevron?: boolean;\n  /** Keep circular avatar triggers unchanged — skips squircle corner styling. */\n  triggerShape?: \"default\" | \"avatar\";\n}\n\nexport const DropdownTrigger = React.forwardRef<\n  HTMLButtonElement,\n  DropdownTriggerProps\n>(\n  (\n    {\n      children,\n      className,\n      disabled,\n      onClick,\n      onKeyDown,\n      showChevron = true,\n      triggerShape = \"default\",\n      ...props\n    },\n    ref\n  ) => {\n    const { contentId, open, setFocusStrategy, setOpen, triggerRef, variant } =\n      useDropdownContext(\"DropdownTrigger\");\n\n    return (\n      <motion.button\n        aria-controls={contentId}\n        aria-expanded={open}\n        aria-haspopup={variant === \"action\" ? \"menu\" : \"listbox\"}\n        className={cn(\n          dropdownTriggerBaseClassName,\n          triggerShape !== \"avatar\" && controlCornerClassName,\n          disabled && \"cursor-not-allowed opacity-60\",\n          className\n        )}\n        data-state={open ? \"open\" : \"closed\"}\n        disabled={disabled}\n        layout=\"size\"\n        onClick={(event) => {\n          onClick?.(event);\n\n          if (event.defaultPrevented || disabled) {\n            return;\n          }\n\n          setFocusStrategy(\"selected\");\n          setOpen(!open);\n        }}\n        onKeyDown={(event) => {\n          onKeyDown?.(event);\n\n          if (event.defaultPrevented || disabled) {\n            return;\n          }\n\n          if (event.key === \"ArrowDown\") {\n            event.preventDefault();\n            setFocusStrategy(\"first\");\n            setOpen(true);\n            return;\n          }\n\n          if (event.key === \"ArrowUp\") {\n            event.preventDefault();\n            setFocusStrategy(\"last\");\n            setOpen(true);\n            return;\n          }\n\n          if (event.key === \"Enter\" || event.key === \" \") {\n            event.preventDefault();\n            setFocusStrategy(\"selected\");\n            setOpen(!open);\n          }\n        }}\n        ref={(node) => {\n          triggerRef.current = node;\n          setRefValue(ref, node);\n        }}\n        type=\"button\"\n        whileTap={disabled ? undefined : { scale: 0.96 }}\n        {...props}\n      >\n        <motion.span\n          className=\"flex min-w-0 flex-1 items-center gap-2\"\n          layout=\"position\"\n        >\n          {children}\n        </motion.span>\n        {showChevron ? (\n          <motion.span\n            animate={{ rotate: open ? 180 : 0 }}\n            className=\"text-muted-foreground\"\n            transition={{\n              type: \"spring\",\n              stiffness: 260,\n              damping: 22,\n              mass: 0.72,\n            }}\n          >\n            <ChevronDown className=\"h-4 w-4\" />\n          </motion.span>\n        ) : null}\n      </motion.button>\n    );\n  }\n);\nDropdownTrigger.displayName = \"DropdownTrigger\";\n\nexport interface DropdownValueProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  placeholder?: string;\n}\n\nexport const DropdownValue = React.forwardRef<\n  HTMLSpanElement,\n  DropdownValueProps\n>(({ className, placeholder = \"Select an option\", ...props }, ref) => {\n  const { labels, value } = useDropdownContext(\"DropdownValue\");\n  const label = value ? (labels[value] ?? value) : undefined;\n\n  return (\n    <span\n      className={cn(\"truncate\", !label && \"text-muted-foreground\", className)}\n      ref={ref}\n      {...props}\n    >\n      {label ?? placeholder}\n    </span>\n  );\n});\nDropdownValue.displayName = \"DropdownValue\";\n\nexport interface DropdownContentProps\n  extends React.ComponentPropsWithoutRef<typeof motion.div> {\n  align?: DropdownAlign;\n  sideOffset?: number;\n}\n\nexport const DropdownContent = React.forwardRef<\n  HTMLDivElement,\n  DropdownContentProps\n>(\n  (\n    { align = \"start\", children, className, sideOffset = 8, style, ...props },\n    ref\n  ) => {\n    const { containerRef, contentId, open, triggerRef, variant } =\n      useDropdownContext(\"DropdownContent\");\n    const shellRef = React.useRef<HTMLDivElement | null>(null);\n    const [position, setPosition] = React.useState<{\n      horizontalOrigin: \"center\" | \"left\" | \"right\";\n      left: number;\n      maxHeight: number | null;\n    }>({\n      horizontalOrigin: getHorizontalOrigin(align),\n      left: 0,\n      maxHeight: null,\n    });\n\n    const setContentRef = React.useCallback(\n      (node: HTMLDivElement | null) => {\n        shellRef.current = node;\n        setRefValue(ref, node);\n      },\n      [ref]\n    );\n\n    const updatePosition = React.useCallback(() => {\n      const container = containerRef.current;\n      const shell = shellRef.current;\n      const trigger = triggerRef.current;\n\n      if (!(container && shell && trigger)) {\n        return;\n      }\n\n      const containerRect = container.getBoundingClientRect();\n      const shellRect = shell.getBoundingClientRect();\n      const triggerRect = trigger.getBoundingClientRect();\n      const contentWidth = shellRect.width || triggerRect.width;\n      const { maxHeight } = getResolvedPlacement(\n        shellRect.height || Math.min(MAX_CONTENT_HEIGHT, 240),\n        triggerRect,\n        window.innerHeight,\n        sideOffset\n      );\n      const { horizontalOrigin, left } = getResolvedHorizontalPosition({\n        align,\n        containerRect,\n        contentWidth,\n        triggerRect,\n        viewportWidth: window.innerWidth,\n      });\n\n      setPosition({\n        horizontalOrigin,\n        left,\n        maxHeight,\n      });\n    }, [align, containerRef, sideOffset, triggerRef]);\n\n    React.useEffect(() => {\n      if (!open) {\n        return;\n      }\n\n      const frame = window.requestAnimationFrame(updatePosition);\n      const handleViewportChange = () => updatePosition();\n      window.addEventListener(\"resize\", handleViewportChange);\n      window.addEventListener(\"scroll\", handleViewportChange, true);\n\n      const observer = new ResizeObserver(() => updatePosition());\n      const observedNodes = [\n        containerRef.current,\n        shellRef.current,\n        triggerRef.current,\n      ].filter(Boolean);\n\n      observedNodes.forEach((node) => {\n        observer.observe(node as Element);\n      });\n\n      return () => {\n        window.cancelAnimationFrame(frame);\n        window.removeEventListener(\"resize\", handleViewportChange);\n        window.removeEventListener(\"scroll\", handleViewportChange, true);\n        observer.disconnect();\n      };\n    }, [containerRef, open, triggerRef, updatePosition]);\n\n    const contentMotion = getContentMotion();\n    const innerContentMotion = getInnerContentMotion();\n\n    return (\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            animate={contentMotion.animate}\n            aria-orientation=\"vertical\"\n            className={cn(\n              controlCornerClassName,\n              \"absolute z-[300] min-w-[12rem] max-w-[calc(100vw-1.5rem)] overflow-hidden border border-border/60 bg-card shadow-[0_14px_34px_-22px_hsl(var(--ic-foreground)/0.28)]\",\n              className\n            )}\n            exit={contentMotion.exit}\n            id={contentId}\n            initial={contentMotion.initial}\n            ref={setContentRef}\n            role={variant === \"action\" ? \"menu\" : \"listbox\"}\n            style={{\n              ...style,\n              left: position.left,\n              top: `calc(100% + ${sideOffset}px)`,\n              transformOrigin: `top ${position.horizontalOrigin}`,\n            }}\n            transition={contentMotion.transition}\n            {...props}\n          >\n            <motion.div\n              animate={innerContentMotion.animate}\n              className=\"overflow-hidden\"\n              exit={innerContentMotion.exit}\n              initial={innerContentMotion.initial}\n              transition={innerContentMotion.transition}\n            >\n              <ScrollAreaPrimitive.Root\n                className={cn(\n                  componentThemeClassName,\n                  \"relative overflow-hidden\"\n                )}\n                style={{\n                  maxHeight: position.maxHeight ?? MAX_CONTENT_HEIGHT,\n                }}\n              >\n                <ScrollAreaPrimitive.Viewport className=\"max-h-[inherit] min-h-0 scroll-py-1 overscroll-contain p-1.5 outline-none\">\n                  {children as React.ReactNode}\n                </ScrollAreaPrimitive.Viewport>\n                <ScrollAreaPrimitive.Scrollbar\n                  className={dropdownListScrollbarClassName}\n                  orientation=\"vertical\"\n                >\n                  <ScrollAreaPrimitive.Thumb\n                    className={dropdownListThumbClassName}\n                  />\n                </ScrollAreaPrimitive.Scrollbar>\n              </ScrollAreaPrimitive.Root>\n            </motion.div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    );\n  }\n);\nDropdownContent.displayName = \"DropdownContent\";\n\nexport interface DropdownItemProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  disabled?: boolean;\n  textValue?: string;\n  value?: string;\n}\n\nexport const DropdownItem = React.forwardRef<HTMLDivElement, DropdownItemProps>(\n  (\n    {\n      children,\n      className,\n      disabled,\n      onClick,\n      onKeyDown,\n      textValue,\n      value,\n      ...props\n    },\n    ref\n  ) => {\n    const {\n      activeItemId,\n      setActiveItemId,\n      setOpen,\n      setValue,\n      value: currentValue,\n      variant,\n    } = useDropdownContext(\"DropdownItem\");\n    const itemId = React.useId();\n    const isActive = activeItemId === itemId && !disabled;\n    const isSelected =\n      variant === \"select\" && value !== undefined && currentValue === value;\n    const resolvedTextValue = React.useMemo(\n      () => (textValue ?? getTextContent(children)).trim(),\n      [children, textValue]\n    );\n\n    const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {\n      onClick?.(event);\n\n      if (event.defaultPrevented || disabled) {\n        return;\n      }\n\n      if (variant === \"select\" && value !== undefined) {\n        setValue(value);\n      }\n\n      setOpen(false);\n    };\n\n    const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n      onKeyDown?.(event);\n\n      if (event.defaultPrevented || disabled) {\n        return;\n      }\n\n      if (event.key === \"Enter\" || event.key === \" \") {\n        event.preventDefault();\n        event.currentTarget.click();\n      }\n    };\n\n    const itemContent = (\n      <>\n        <motion.span\n          className=\"relative z-10 flex min-w-0 flex-1 items-center gap-2 truncate\"\n          transition={{\n            type: \"spring\",\n            stiffness: 360,\n            damping: 28,\n            mass: 0.55,\n          }}\n        >\n          {children}\n        </motion.span>\n        {isSelected ? (\n          <motion.span\n            animate={{ opacity: 1, scale: 1, y: 0 }}\n            className=\"relative z-10 flex size-5 shrink-0 items-center justify-center text-foreground\"\n            initial={{ opacity: 0, scale: 0.96, y: 1 }}\n            transition={{\n              type: \"spring\",\n              stiffness: 460,\n              damping: 24,\n              mass: 0.5,\n            }}\n          >\n            <Check className=\"h-4 w-4\" />\n          </motion.span>\n        ) : null}\n      </>\n    );\n\n    const sharedProps = {\n      ...props,\n      \"aria-disabled\": disabled || undefined,\n      className: cn(\n        controlCornerClassName,\n        \"group relative isolate flex min-h-11 w-full scroll-m-1 items-center justify-between gap-3 px-3 py-2.5 text-left text-foreground text-sm transition-colors\",\n        dropdownItemHighlightBeforeClassName,\n        \"hover:before:bg-accent/65 focus-visible:text-foreground focus-visible:outline-none focus-visible:before:bg-accent/65\",\n        isActive && \"before:bg-accent/65\",\n        isSelected &&\n          variant === \"select\" &&\n          \"text-foreground before:bg-accent/45\",\n        isSelected && isActive && variant === \"select\" && \"before:bg-accent/75\",\n        disabled && \"pointer-events-none opacity-50\",\n        className\n      ),\n      \"data-disabled\": disabled ? \"\" : undefined,\n      \"data-dropdown-item\": \"\",\n      \"data-state\": isSelected ? \"checked\" : \"unchecked\",\n      \"data-text-value\": resolvedTextValue,\n      \"data-value\": value,\n      id: itemId,\n      onBlur: () => setActiveItemId(null),\n      onClick: handleClick,\n      onFocus: () => setActiveItemId(itemId),\n      onKeyDown: handleKeyDown,\n      onMouseEnter: () => setActiveItemId(itemId),\n      onMouseLeave: () => setActiveItemId(null),\n      ref,\n    };\n\n    if (variant === \"select\") {\n      return (\n        <div\n          {...sharedProps}\n          aria-selected={isSelected}\n          role=\"option\"\n          tabIndex={disabled ? undefined : -1}\n        >\n          {itemContent}\n        </div>\n      );\n    }\n\n    return (\n      <div\n        {...sharedProps}\n        role=\"menuitem\"\n        tabIndex={disabled ? undefined : -1}\n      >\n        {itemContent}\n      </div>\n    );\n  }\n);\nDropdownItem.displayName = \"DropdownItem\";\n\nexport interface DropdownGroupProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  label?: React.ReactNode;\n  labelClassName?: string;\n}\n\nexport const DropdownGroup = React.forwardRef<\n  HTMLDivElement,\n  DropdownGroupProps\n>(\n  (\n    {\n      \"aria-label\": ariaLabel,\n      \"aria-labelledby\": ariaLabelledby,\n      children,\n      className,\n      label,\n      labelClassName,\n      ...props\n    },\n    ref\n  ) => {\n    const generatedLabelId = React.useId();\n    const groupLabelId = label ? generatedLabelId : ariaLabelledby;\n    const hasAccessibleLabel = Boolean(label || ariaLabel || groupLabelId);\n\n    return (\n      <div\n        className={cn(\"mt-2 space-y-0.5 first:mt-0\", className)}\n        ref={ref}\n        {...(hasAccessibleLabel\n          ? {\n              \"aria-label\": ariaLabel,\n              \"aria-labelledby\": groupLabelId,\n              role: \"group\" as const,\n            }\n          : {})}\n        {...props}\n      >\n        {label ? (\n          <DropdownLabel className={labelClassName} id={generatedLabelId}>\n            {label}\n          </DropdownLabel>\n        ) : null}\n        {children}\n      </div>\n    );\n  }\n);\nDropdownGroup.displayName = \"DropdownGroup\";\n\nexport const DropdownLabel = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    className={cn(\n      \"px-3 pt-1 pb-1 font-medium text-[0.68rem] text-muted-foreground/80 uppercase tracking-[0.12em]\",\n      className\n    )}\n    ref={ref}\n    {...props}\n  />\n));\nDropdownLabel.displayName = \"DropdownLabel\";\n\nexport const DropdownSeparator = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    aria-hidden=\"true\"\n    className={cn(\"my-1 h-1 border-0\", className)}\n    ref={ref}\n    {...props}\n  />\n));\nDropdownSeparator.displayName = \"DropdownSeparator\";\n",
      "type": "registry:ui"
    }
  ],
  "title": "Dropdown",
  "description": "Animated dropdown with a selectable value mode and an action-menu mode, plus chevron rotation and spring-driven menu entry."
}
