{ "name": "autocomplete", "type": "registry:component", "title": "AutoComplete", "description": "Autocomplete input with keyboard navigation and loading state", "registryDependencies": [ "command", "skeleton" ], "dependencies": [ "cmdk", "lucide-react" ], "files": [ { "path": "components/autocomplete.tsx", "type": "registry:component", "content": "import {\n CommandGroup,\n CommandItem,\n CommandList,\n CommandInput,\n} from \"./ui/command\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { useState, useRef, useCallback, type KeyboardEvent } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { Skeleton } from \"./ui/skeleton\";\nimport { cn } from \"@barddev/ui-shared\";\n\n/**\n * AutoComplete component props\n */\ntype AutoCompleteProps = {\n /** Available options to select from */\n options: string[];\n /** Message shown when no options match */\n emptyMessage: string;\n /** Current input value */\n value: string;\n /** Callback when value changes */\n onValueChange: (value: string) => void;\n /** Shows loading skeleton */\n isLoading?: boolean;\n /** Disables the input */\n disabled?: boolean;\n /** Additional CSS classes */\n className?: string;\n /** Placeholder text */\n placeholder?: string;\n};\n\n/**\n * AutoComplete component\n * Provides searchable dropdown with keyboard navigation\n * Built on top of cmdk library\n */\nexport const AutoComplete = ({\n options,\n placeholder,\n emptyMessage,\n value,\n onValueChange,\n disabled,\n className,\n isLoading = false,\n}: AutoCompleteProps) => {\n const inputRef = useRef(null);\n\n const [isOpen, setOpen] = useState(false);\n const [selected, setSelected] = useState(value);\n\n /**\n * Handle keyboard events for navigation\n */\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n const input = inputRef.current;\n if (!input) {\n return;\n }\n\n // Keep the options displayed when the user is typing\n if (!isOpen) {\n setOpen(true);\n }\n\n // Select option on Enter\n if (event.key === \"Enter\" && input.value !== \"\") {\n const optionToSelect = options.find((option) => option === input.value);\n if (optionToSelect) {\n setSelected(optionToSelect);\n onValueChange(optionToSelect ?? \"\");\n }\n }\n\n // Close on Escape\n if (event.key === \"Escape\") {\n input.blur();\n }\n },\n [isOpen, options, onValueChange]\n );\n\n /**\n * Handle blur event - close dropdown\n */\n const handleBlur = useCallback(() => {\n setOpen(false);\n onValueChange(selected);\n }, [selected, onValueChange]);\n\n /**\n * Handle option selection\n */\n const handleSelectOption = useCallback(\n (selectedOption: string) => {\n onValueChange(selectedOption);\n setSelected(selectedOption);\n onValueChange(selectedOption);\n\n // Blur input after selection\n setTimeout(() => {\n inputRef?.current?.blur();\n }, 0);\n },\n [onValueChange]\n );\n\n return (\n \n
\n setOpen(true)}\n placeholder={placeholder}\n disabled={disabled}\n className=\"text-base\"\n containterClassName={className}\n />\n
\n
\n \n \n {isLoading ? (\n \n
\n \n
\n
\n ) : null}\n {options.length > 0 && !isLoading ? (\n \n {options.map((option) => {\n const isSelected = selected === option;\n return (\n {\n event.preventDefault();\n event.stopPropagation();\n }}\n onSelect={() => handleSelectOption(option)}\n className=\"flex w-full items-center gap-2\"\n >\n {option}\n {isSelected ? : null}\n \n );\n })}\n \n ) : null}\n {!isLoading ? (\n \n {emptyMessage}\n \n ) : null}\n
\n
\n \n
\n );\n};\n" } ] }