import { useEffect, useMemo, useRef, useState, type InputHTMLAttributes, } from 'react'; import { cn } from '../../lib/classname'; import { useOutsideClick } from '../../hooks/use-outside-click'; import { useDebounceValue } from '../../hooks/use-debounce'; import { httpGet } from '../../lib/http'; import { useToast } from '../../hooks/use-toast'; type GetTopAIRoadmapTermResponse = { _id: string; term: string; title: string; }[]; type AITermSuggestionInputProps = { value: string; onValueChange: (value: string) => void; onSelect?: (roadmapId: string) => void; inputClassName?: string; wrapperClassName?: string; placeholder?: string; } & Omit, 'onSelect' | 'onChange'>; export function AITermSuggestionInput(props: AITermSuggestionInputProps) { const { value: defaultValue, onValueChange, onSelect, inputClassName, wrapperClassName, placeholder, ...inputProps } = props; const termCache = useMemo( () => new Map(), [], ); const toast = useToast(); const searchInputRef = useRef(null); const dropdownRef = useRef(null); const [isActive, setIsActive] = useState(false); const [searchResults, setSearchResults] = useState([]); const [searchedText, setSearchedText] = useState(defaultValue); const [activeCounter, setActiveCounter] = useState(0); const debouncedSearchValue = useDebounceValue(searchedText, 500); const loadTopAIRoadmapTerm = async () => { if (debouncedSearchValue.length === 0) { return []; } if (termCache.has(debouncedSearchValue)) { const cachedData = termCache.get(debouncedSearchValue); return cachedData || []; } const { response, error } = await httpGet( `${import.meta.env.PUBLIC_API_URL}/v1-get-top-ai-roadmap-term`, { term: debouncedSearchValue, }, ); if (error || !response) { toast.error(error?.message || 'Something went wrong'); setSearchResults([]); return []; } termCache.set(debouncedSearchValue, response); return response; }; useEffect(() => { if (debouncedSearchValue.length === 0) { setSearchResults([]); return; } setIsActive(true); loadTopAIRoadmapTerm().then((results) => { setSearchResults(results?.slice(0, 5) || []); setActiveCounter(0); }); }, [debouncedSearchValue]); useOutsideClick(dropdownRef, () => { setIsActive(false); }); return (
{ const value = (e.target as HTMLInputElement).value.trim(); setSearchedText(value); onValueChange(value); }} onFocus={() => { setIsActive(true); }} onKeyDown={(e) => { if (e.key === 'ArrowDown') { const canGoNext = activeCounter < searchResults.length - 1; setActiveCounter(canGoNext ? activeCounter + 1 : 0); } else if (e.key === 'ArrowUp') { const canGoPrev = activeCounter > 0; setActiveCounter( canGoPrev ? activeCounter - 1 : searchResults.length - 1, ); } else if (e.key === 'Tab') { if (isActive) { e.preventDefault(); } } else if (e.key === 'Escape') { setSearchedText(''); setIsActive(false); } else if (e.key === 'Enter') { e.preventDefault(); const activeData = searchResults[activeCounter]; if (activeData) { onValueChange(activeData.term); onSelect?.(activeData._id); setIsActive(false); } } }} /> {isActive && searchResults.length > 0 && (
{searchResults.map((result, counter) => { return ( ); })}
)}
); }