{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "Combobox",
  "description": "Searchable single-select built from Popover + Command. Trigger follows the Input contract: 3 variants (outline, soft, ghost), 3 sizes (sm, md, lg), 3 tones (neutral, success, danger). Controlled or uncontrolled via value/defaultValue and onValueChange; options as {value, label} pairs.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/button",
    "@ai2/command",
    "@ai2/popover",
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/ui/combobox.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ChevronsUpDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/ai2/ui/button\"\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/registry/ai2/ui/command\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/registry/ai2/ui/popover\"\n\ninterface ComboboxOption {\n  value: string\n  label: string\n  disabled?: boolean\n}\n\ninterface ComboboxProps\n  extends Omit<\n    React.ComponentProps<typeof Button>,\n    \"value\" | \"defaultValue\" | \"onChange\" | \"variant\" | \"size\" | \"tone\"\n  > {\n  options: ComboboxOption[]\n  value?: string\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n  placeholder?: string\n  searchPlaceholder?: string\n  emptyText?: string\n  variant?: \"outline\" | \"soft\" | \"ghost\"\n  size?: \"sm\" | \"md\" | \"lg\"\n  tone?: \"neutral\" | \"success\" | \"danger\"\n}\n\nconst comboboxToneClasses: Record<NonNullable<ComboboxProps[\"tone\"]>, string> = {\n  neutral: \"\",\n  success:\n    \"border border-success focus-visible:border-success focus-visible:ring-success/20 dark:focus-visible:ring-success/40\",\n  danger:\n    \"border border-danger focus-visible:border-danger focus-visible:ring-danger/20 dark:focus-visible:ring-danger/40\",\n}\n\nfunction Combobox({\n  options,\n  value: valueProp,\n  defaultValue = \"\",\n  onValueChange,\n  placeholder = \"Select an option…\",\n  searchPlaceholder = \"Search…\",\n  emptyText = \"No results found.\",\n  className,\n  disabled,\n  variant = \"outline\",\n  size = \"md\",\n  tone = \"neutral\",\n  ...props\n}: ComboboxProps) {\n  const [open, setOpen] = React.useState(false)\n  const [internal, setInternal] = React.useState(defaultValue)\n  const value = valueProp ?? internal\n\n  const select = (next: string) => {\n    const resolved = next === value ? \"\" : next\n    setInternal(resolved)\n    onValueChange?.(resolved)\n    setOpen(false)\n  }\n\n  const selected = options.find((o) => o.value === value)\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <Button\n          variant={variant}\n          size={size}\n          role=\"combobox\"\n          aria-expanded={open}\n          disabled={disabled}\n          data-slot=\"combobox-trigger\"\n          data-tone={tone}\n          className={cn(\n            \"w-56 justify-between font-normal\",\n            comboboxToneClasses[tone],\n            className\n          )}\n          {...props}\n        >\n          <span className={cn(\"truncate\", !selected && \"text-muted-foreground\")}>\n            {selected ? selected.label : placeholder}\n          </span>\n          <ChevronsUpDown className=\"text-muted-foreground\" />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-(--radix-popover-trigger-width) p-0\">\n        <Command>\n          <CommandInput placeholder={searchPlaceholder} />\n          <CommandList>\n            <CommandEmpty>{emptyText}</CommandEmpty>\n            <CommandGroup>\n              {options.map((option) => (\n                <CommandItem\n                  key={option.value}\n                  value={option.value}\n                  disabled={option.disabled}\n                  onSelect={select}\n                >\n                  <Check\n                    className={cn(\n                      \"size-4\",\n                      value === option.value ? \"opacity-100\" : \"opacity-0\"\n                    )}\n                  />\n                  {option.label}\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  )\n}\n\nexport { Combobox, type ComboboxOption, type ComboboxProps }\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Pass options as an array of {value, label, disabled?}. Selecting the active option again clears the value.",
  "categories": [
    "form"
  ],
  "type": "registry:ui"
}