{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "search",
  "title": "Search",
  "description": "Live command search with keyboard navigation, result callbacks, clearing, and an empty state.",
  "registryDependencies": [
    "https://beadkit.dev/r/ai-core.json"
  ],
  "files": [
    {
      "path": "components/ai/Search.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nexport interface SearchItem { id: string; label: string; description?: string; icon?: ReactNode; keywords?: readonly string[]; }\nexport interface SearchProps { items: readonly SearchItem[]; value?: string; defaultValue?: string; placeholder?: string; onChange?: (query: string) => void; onSelect?: (item: SearchItem) => void; className?: string; }\nconst SearchIcon = () => <svg aria-hidden=\"true\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m21 21-4.3-4.3\" /></svg>;\nexport function Search({ items, value, defaultValue = \"\", placeholder = \"Search…\", onChange, onSelect, className = \"\" }: SearchProps) {\n  const controlled = value !== undefined; const [draft, setDraft] = useState(defaultValue); const [active, setActive] = useState(0); const inputRef = useRef<HTMLInputElement>(null); const listboxId = useId(); const query = controlled ? value : draft; const results = query.trim() ? items.filter((item) => `${item.label} ${item.description ?? \"\"} ${(item.keywords ?? []).join(\" \")}`.toLowerCase().includes(query.toLowerCase())) : items;\n  const setQuery = (next: string) => { if (!controlled) setDraft(next); onChange?.(next); setActive(0); };\n  const keyDown = (event: KeyboardEvent<HTMLInputElement>) => { if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") { event.preventDefault(); setActive((index) => results.length ? (index + (event.key === \"ArrowDown\" ? 1 : -1) + results.length) % results.length : 0); } else if (event.key === \"Enter\" && results[active]) { event.preventDefault(); onSelect?.(results[active]); } else if (event.key === \"Escape\") { setQuery(\"\"); inputRef.current?.blur(); } };\n  useEffect(() => { if (active >= results.length) setActive(0); }, [active, results.length]);\n  return <div data-ai-state=\"search\" className={`bg-card border-border w-full max-w-md overflow-hidden rounded-xl border shadow-sm ${className}`}><div className=\"border-border flex min-h-11 items-center gap-2 border-b px-3\"><span className=\"text-muted-foreground\"><SearchIcon /></span><input ref={inputRef} role=\"combobox\" aria-autocomplete=\"list\" aria-expanded={results.length > 0} value={query} onChange={(event) => setQuery(event.target.value)} onKeyDown={keyDown} placeholder={placeholder} aria-label={placeholder} aria-controls={listboxId} aria-activedescendant={results[active] ? `${listboxId}-${active}` : undefined} className=\"min-w-0 flex-1 bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground\" />{query ? <button type=\"button\" aria-label=\"Clear search\" onClick={() => { setQuery(\"\"); inputRef.current?.focus(); }} className=\"text-muted-foreground hover:bg-muted rounded-full p-1\">×</button> : null}</div>{results.length ? <div id={listboxId} role=\"listbox\" aria-label=\"Search results\" className=\"max-h-72 overflow-y-auto p-1\">{results.map((item, index) => <button key={item.id} id={`${listboxId}-${index}`} type=\"button\" role=\"option\" aria-selected={index === active} onMouseEnter={() => setActive(index)} onClick={() => onSelect?.(item)} className={`hover:bg-muted flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 text-left text-sm ${index === active ? \"bg-muted\" : \"\"}`}><span className=\"text-muted-foreground shrink-0\">{item.icon ?? <SearchIcon />}</span><span className=\"min-w-0 flex-1 truncate\">{item.label}</span>{item.description ? <span className=\"text-muted-foreground max-w-[45%] truncate text-xs\">{item.description}</span> : null}</button>)}</div> : <div id={listboxId} role=\"status\" className=\"text-muted-foreground px-4 py-8 text-center text-sm\">No results found</div>}<span className=\"sr-only\" role=\"status\" aria-live=\"polite\">{results.length} {results.length === 1 ? \"result\" : \"results\"}</span></div>;\n}\nexport default Search;\n",
      "type": "registry:component",
      "target": "@components/ai/Search.tsx"
    }
  ],
  "type": "registry:component"
}
