import { CommandGroup, CommandItem, CommandList } from "./Command"; import { Command as CommandPrimitive } from "cmdk"; import { useState, useRef, useCallback, type KeyboardEvent, type ReactNode, } from "react"; import { Popover, PopoverContent, PopoverTrigger } from "./Popover"; import { Skeleton } from "./Skeleton"; import styles from "./AutoComplete.module.css"; export type Option = { value: string; label: ReactNode; displayText?: string; metadata?: T; }; const getDisplayText = (option: Option): string => { if (option.displayText) { return option.displayText; } if (typeof option.label === "string") { return option.label; } return option.value; }; type AutoCompleteProps = { options: Option[]; emptyMessage: string; value?: Option; onValueChange?: (value: Option) => void; inputValue: string; onInputValueChange: (value: string) => void; isLoading?: boolean; disabled?: boolean; placeholder?: string; allowFreeForm?: boolean; }; export const AutoComplete = ({ options, placeholder, emptyMessage, value, onValueChange, inputValue, onInputValueChange, disabled, isLoading = false, allowFreeForm = false, }: AutoCompleteProps) => { const inputRef = useRef(null); const [isOpen, setOpen] = useState(false); const [selected, setSelected] = useState | undefined>(value); const handleKeyDown = useCallback( (event: KeyboardEvent) => { const input = inputRef.current; if (!input) { return; } if (event.key === "Enter" && input.value !== "") { const optionToSelect = options.find( (option) => getDisplayText(option) === input.value, ); if (optionToSelect) { setSelected(optionToSelect); onValueChange?.(optionToSelect); } } if (event.key === "Escape") { input.blur(); setOpen(false); } }, [options, onValueChange], ); const handleBlur = useCallback(() => { if (!isOpen && !allowFreeForm) { onInputValueChange(selected ? getDisplayText(selected) : ""); } }, [selected, isOpen, onInputValueChange, allowFreeForm]); const handleSelectOption = useCallback( (selectedOption: Option) => { onInputValueChange(getDisplayText(selectedOption)); setSelected(selectedOption); onValueChange?.(selectedOption); setOpen(false); setTimeout(() => { inputRef?.current?.blur(); }, 0); }, [onValueChange, onInputValueChange], ); return (
{!!options.length && ( e.preventDefault()} > {options.length > 0 && ( {options.map((option) => { return ( { event.preventDefault(); event.stopPropagation(); }} onSelect={() => handleSelectOption(option)} > {option.label} ); { isLoading ? (
) : null; } })}
)}
)}
); };