{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "type": "registry:ui",
  "title": "Combobox",
  "description": "Select with search for long option lists.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@wibo/utils",
    "@wibo/button",
    "@wibo/command",
    "@wibo/popover"
  ],
  "files": [
    {
      "path": "src/components/combobox.tsx",
      "type": "registry:ui",
      "content": "import { useId, useState, type ReactNode } from \"react\";\nimport { Check, ChevronsUpDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\n\ninterface ComboboxOption {\n  value: string;\n  label: string;\n  description?: string;\n  disabled?: boolean;\n  /** Extra search terms. */\n  keywords?: string[];\n}\n\ninterface ComboboxProps {\n  options: ComboboxOption[];\n  value?: string | null;\n  defaultValue?: string | null;\n  onValueChange?: (value: string | null) => void;\n  placeholder?: string;\n  searchPlaceholder?: string;\n  emptyText?: ReactNode;\n  /** Selecting the current option again clears the value. */\n  clearable?: boolean;\n  disabled?: boolean;\n  /** Submits the value with a form. */\n  name?: string;\n  id?: string;\n  className?: string;\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string;\n  \"aria-describedby\"?: string;\n  \"aria-invalid\"?: boolean;\n}\n\n/** Select with search, for long option lists (people, projects, tenants). */\nfunction Combobox({\n  options,\n  value,\n  defaultValue = null,\n  onValueChange,\n  placeholder = \"Select...\",\n  searchPlaceholder = \"Search...\",\n  emptyText = \"No results.\",\n  clearable = false,\n  disabled,\n  name,\n  id,\n  className,\n  ...aria\n}: ComboboxProps) {\n  const [open, setOpen] = useState(false);\n  const [internal, setInternal] = useState<string | null>(defaultValue);\n  const current = value === undefined ? internal : value;\n  const selected = options.find((option) => option.value === current);\n  const listId = useId();\n\n  const choose = (next: string) => {\n    const resolved = clearable && next === current ? null : next;\n    if (value === undefined) setInternal(resolved);\n    onValueChange?.(resolved);\n    setOpen(false);\n  };\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <Button\n          id={id}\n          type=\"button\"\n          variant=\"outline\"\n          role=\"combobox\"\n          aria-expanded={open}\n          aria-controls={listId}\n          aria-haspopup=\"listbox\"\n          disabled={disabled}\n          data-slot=\"combobox-trigger\"\n          className={cn(\n            \"w-full justify-between px-3 font-normal aria-invalid:border-danger\",\n            !selected && \"text-muted-foreground\",\n            className,\n          )}\n          {...aria}\n        >\n          <span className=\"truncate\">{selected ? selected.label : placeholder}</span>\n          <ChevronsUpDown aria-hidden=\"true\" className=\"text-muted-foreground\" />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-(--radix-popover-trigger-width) min-w-48 p-0\" align=\"start\">\n        <Command>\n          <CommandInput placeholder={searchPlaceholder} />\n          <CommandList id={listId}>\n            <CommandEmpty>{emptyText}</CommandEmpty>\n            <CommandGroup>\n              {options.map((option) => (\n                <CommandItem\n                  key={option.value}\n                  value={option.value}\n                  keywords={[option.label, ...(option.keywords ?? [])]}\n                  disabled={option.disabled}\n                  onSelect={() => choose(option.value)}\n                >\n                  <Check\n                    aria-hidden=\"true\"\n                    className={cn(\"size-4\", option.value === current ? \"opacity-100\" : \"opacity-0\")}\n                  />\n                  <div className=\"grid min-w-0\">\n                    <span className=\"truncate\">{option.label}</span>\n                    {option.description ? (\n                      <span className=\"truncate text-xs text-muted-foreground\">{option.description}</span>\n                    ) : null}\n                  </div>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n      {name ? <input type=\"hidden\" name={name} value={current ?? \"\"} /> : null}\n    </Popover>\n  );\n}\n\nexport { Combobox };\nexport type { ComboboxOption, ComboboxProps };\n"
    }
  ]
}
