{
  "$schema": "https://www.cooki-ui.design/schema/registry-item.json",
  "name": "combobox",
  "type": "registry:ui",
  "title": "Combobox Basic",
  "description": "Autocomplete combobox with real-time fuzzy filtering, keyboard navigation, popover animations, and compound render prop item slots.",
  "category": "forms",
  "dependencies": [
    "framer-motion",
    "@phosphor-icons/react",
    "clsx",
    "tailwind-merge"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "tags": [
    "forms",
    "autocomplete",
    "select",
    "combobox",
    "input",
    "dropdown"
  ],
  "files": [
    {
      "path": "registry/forms/combobox/combobox.tsx",
      "type": "registry:ui",
      "target": "components/ui/combobox.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, AnimatePresence } from \"framer-motion\"\nimport { MagnifyingGlass, CaretUpDown, Check } from \"@phosphor-icons/react\"\nimport { cn } from \"@/lib/utils\"\n\ninterface ComboboxContextValue<T = any> {\n  items: readonly T[] | T[]\n  value?: T\n  selectedValue?: T\n  query: string\n  setQuery: (q: string) => void\n  isOpen: boolean\n  setIsOpen: (open: boolean) => void\n  filteredItems: T[]\n  selectItem: (item: T) => void\n  containerRef: React.RefObject<HTMLDivElement | null>\n  inputRef: React.RefObject<HTMLInputElement | null>\n}\n\nconst ComboboxContext = React.createContext<ComboboxContextValue | null>(null)\n\nfunction useComboboxContext<T = any>() {\n  const context = React.useContext(ComboboxContext)\n  if (!context) {\n    throw new Error(\"Combobox components must be used within a <Combobox /> provider\")\n  }\n  return context as ComboboxContextValue<T>\n}\n\nexport interface ComboboxProps<T = any> {\n  items: readonly T[] | T[]\n  value?: T\n  defaultValue?: T\n  onValueChange?: (value: T) => void\n  filter?: (item: T, query: string) => boolean\n  className?: string\n  children: React.ReactNode\n}\n\nexport function Combobox<T = any>({\n  items,\n  value: controlledValue,\n  defaultValue,\n  onValueChange,\n  filter,\n  className,\n  children,\n}: ComboboxProps<T>) {\n  const [internalValue, setInternalValue] = React.useState<T | undefined>(defaultValue)\n  const [query, setQuery] = React.useState(\"\")\n  const [isOpen, setIsOpen] = React.useState(false)\n  const containerRef = React.useRef<HTMLDivElement | null>(null)\n  const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n  const selectedValue = controlledValue !== undefined ? controlledValue : internalValue\n\n  const filteredItems = React.useMemo(() => {\n    if (!query.trim()) return items as T[]\n    if (filter) {\n      return (items as T[]).filter((item) => filter(item, query))\n    }\n    const lowerQuery = query.toLowerCase()\n    return (items as T[]).filter((item) => {\n      if (typeof item === \"string\") {\n        return item.toLowerCase().includes(lowerQuery)\n      }\n      if (typeof item === \"object\" && item !== null) {\n        return Object.values(item).some(\n          (val) => typeof val === \"string\" && val.toLowerCase().includes(lowerQuery)\n        )\n      }\n      return String(item).toLowerCase().includes(lowerQuery)\n    })\n  }, [items, query, filter])\n\n  const selectItem = React.useCallback(\n    (item: T) => {\n      if (controlledValue === undefined) {\n        setInternalValue(item)\n      }\n      onValueChange?.(item)\n      setQuery(\"\")\n      setIsOpen(false)\n    },\n    [controlledValue, onValueChange]\n  )\n\n  // Close on outside click\n  React.useEffect(() => {\n    function handleClickOutside(event: MouseEvent) {\n      if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n        setIsOpen(false)\n      }\n    }\n    document.addEventListener(\"mousedown\", handleClickOutside)\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside)\n  }, [])\n\n  return (\n    <ComboboxContext.Provider\n      value={{\n        items,\n        value: controlledValue,\n        selectedValue,\n        query,\n        setQuery,\n        isOpen,\n        setIsOpen,\n        filteredItems,\n        selectItem,\n        containerRef,\n        inputRef,\n      }}\n    >\n      <div ref={containerRef} className={cn(\"relative w-full max-w-sm\", isOpen && \"z-30\", className)}>\n        {children}\n      </div>\n    </ComboboxContext.Provider>\n  )\n}\n\nexport interface ComboboxInputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {\n  wrapperClassName?: string\n}\n\nexport const ComboboxInput = React.forwardRef<HTMLInputElement, ComboboxInputProps>(\n  ({ className, wrapperClassName, placeholder = \"Search...\", ...props }, forwardedRef) => {\n    const { query, setQuery, isOpen, setIsOpen, selectedValue, inputRef } = useComboboxContext()\n\n    React.useImperativeHandle(forwardedRef, () => inputRef.current as HTMLInputElement)\n\n    const displayValue = isOpen\n      ? query\n      : query || (selectedValue ? String(selectedValue) : \"\")\n\n    const effectivePlaceholder = (selectedValue && isOpen) ? String(selectedValue) : placeholder\n\n    return (\n      <div\n        className={cn(\n          \"relative flex items-center rounded-xl border border-zinc-200/90 dark:border-white/10 bg-white/90 dark:bg-zinc-900/90 shadow-xs backdrop-blur-md transition-all duration-200 focus-within:border-indigo-500/80 focus-within:ring-2 focus-within:ring-indigo-500/20\",\n          wrapperClassName\n        )}\n        onClick={() => {\n          inputRef.current?.focus()\n          setIsOpen(true)\n        }}\n      >\n        <MagnifyingGlass\n          size={16}\n          weight=\"bold\"\n          className=\"ml-3.5 shrink-0 text-zinc-400 dark:text-zinc-500\"\n        />\n        <input\n          ref={inputRef}\n          type=\"text\"\n          value={displayValue}\n          placeholder={effectivePlaceholder}\n          onChange={(e) => {\n            setQuery(e.target.value)\n            if (!isOpen) setIsOpen(true)\n          }}\n          onFocus={() => setIsOpen(true)}\n          onKeyDown={(e) => {\n            if (e.key === \"Escape\") {\n              setIsOpen(false)\n            }\n          }}\n          className={cn(\n            \"w-full bg-transparent px-3 py-2.5 text-sm text-zinc-900 placeholder:text-zinc-400 focus:outline-none dark:text-white dark:placeholder:text-zinc-500\",\n            className\n          )}\n          {...props}\n        />\n        <CaretUpDown\n          size={16}\n          weight=\"bold\"\n          className={cn(\n            \"mr-3 shrink-0 text-zinc-400 transition-transform duration-200 dark:text-zinc-500 cursor-pointer\",\n            isOpen && \"rotate-180 text-zinc-700 dark:text-zinc-300\"\n          )}\n          onClick={(e) => {\n            e.stopPropagation()\n            setIsOpen(!isOpen)\n          }}\n        />\n      </div>\n    )\n  }\n)\nComboboxInput.displayName = \"ComboboxInput\"\n\nexport interface ComboboxContentProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport function ComboboxContent({ className, children, ...props }: ComboboxContentProps) {\n  const { isOpen } = useComboboxContext()\n\n  return (\n    <AnimatePresence>\n      {isOpen && (\n        <motion.div\n          initial={{ opacity: 0, y: -6, scale: 0.98 }}\n          animate={{ opacity: 1, y: 0, scale: 1 }}\n          exit={{ opacity: 0, y: -6, scale: 0.98 }}\n          transition={{ duration: 0.15, ease: \"easeOut\" }}\n          className={cn(\n            \"absolute left-0 right-0 top-full z-50 mt-1.5 overflow-hidden rounded-xl border border-zinc-200 dark:border-white/10 bg-white/95 dark:bg-zinc-950/95 p-1.5 shadow-xl backdrop-blur-xl\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  )\n}\n\nexport interface ComboboxEmptyProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport function ComboboxEmpty({ className, children = \"No items found.\", ...props }: ComboboxEmptyProps) {\n  const { filteredItems } = useComboboxContext()\n\n  if (filteredItems.length > 0) return null\n\n  return (\n    <div\n      className={cn(\n        \"py-6 text-center text-xs font-mono text-zinc-400 dark:text-zinc-500\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport interface ComboboxListProps<T = any>\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  children?: React.ReactNode | ((item: T, index: number) => React.ReactNode)\n}\n\nexport function ComboboxList<T = any>({\n  className,\n  children,\n  ...props\n}: ComboboxListProps<T>) {\n  const { filteredItems } = useComboboxContext<T>()\n\n  return (\n    <div\n      className={cn(\"max-h-56 overflow-y-auto overflow-x-hidden space-y-0.5 scrollbar-thin\", className)}\n      {...props}\n    >\n      {typeof children === \"function\"\n        ? filteredItems.map((item, idx) => (children as (item: T, index: number) => React.ReactNode)(item, idx))\n        : children}\n    </div>\n  )\n}\n\nexport interface ComboboxItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  value: any\n  disabled?: boolean\n}\n\nexport function ComboboxItem({\n  value,\n  disabled = false,\n  className,\n  children,\n  ...props\n}: ComboboxItemProps) {\n  const { selectedValue, selectItem } = useComboboxContext()\n\n  const isSelected = selectedValue === value || String(selectedValue) === String(value)\n\n  return (\n    <div\n      onClick={() => {\n        if (!disabled) {\n          selectItem(value)\n        }\n      }}\n      className={cn(\n        \"group relative flex cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm text-zinc-700 transition-colors duration-150 hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800/80 dark:hover:text-white select-none\",\n        isSelected && \"bg-indigo-50 font-medium text-indigo-600 dark:bg-indigo-950/40 dark:text-indigo-400\",\n        disabled && \"pointer-events-none opacity-40\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"truncate\">{children}</span>\n      {isSelected && (\n        <Check size={14} weight=\"bold\" className=\"shrink-0 text-indigo-600 dark:text-indigo-400\" />\n      )}\n    </div>\n  )\n}\n"
    }
  ]
}