import React, { useState, useRef, useCallback, useMemo, useEffect } from "react" import { Command, CommandGroup, CommandItem, CommandList } from "@/components/ui/command" import { Command as CommandPrimitive } from "cmdk" import { motion, AnimatePresence } from "framer-motion" import { cn, searchSafeRegExp, shuffleArray } from "@/lib/utils" import { useMountedState } from "react-use" interface GraphNode { name: string prettyName: string connectedTopics: string[] } interface AutocompleteProps { topics: GraphNode[] onSelect: (topic: string) => void onInputChange: (value: string) => void } export function Autocomplete({ topics = [], onSelect, onInputChange }: AutocompleteProps): JSX.Element { const inputRef = useRef(null) const [open, setOpen] = useState(false) const isMounted = useMountedState() const [inputValue, setInputValue] = useState("") const [hasInteracted, setHasInteracted] = useState(false) const [showDropdown, setShowDropdown] = useState(false) const initialShuffledTopics = useMemo(() => shuffleArray(topics).slice(0, 5), [topics]) const filteredTopics = useMemo(() => { if (!inputValue) { return initialShuffledTopics } const regex = searchSafeRegExp(inputValue) return topics .filter( topic => regex.test(topic.name) || regex.test(topic.prettyName) || topic.connectedTopics.some(connectedTopic => regex.test(connectedTopic)) ) .sort((a, b) => a.prettyName.localeCompare(b.prettyName)) .slice(0, 10) }, [inputValue, topics, initialShuffledTopics]) const handleSelect = useCallback( (topic: GraphNode) => { setOpen(false) onSelect(topic.name) }, [onSelect] ) const handleInputChange = useCallback( (value: string) => { setInputValue(value) setShowDropdown(true) setHasInteracted(true) onInputChange(value) }, [onInputChange] ) const handleFocus = useCallback(() => { setHasInteracted(true) }, []) const handleClick = useCallback(() => { setShowDropdown(true) setHasInteracted(true) }, []) const commandKey = useMemo(() => { return filteredTopics .map(topic => `${topic.name}:${topic.prettyName}:${topic.connectedTopics.join(",")}`) .join("__") }, [filteredTopics]) useEffect(() => { if (inputRef.current && isMounted() && hasInteracted) { inputRef.current.focus() } }, [commandKey, isMounted, hasInteracted]) const animationProps = { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, transition: { duration: 0.1 } } return (
{ setTimeout(() => setShowDropdown(false), 100) }} onFocus={handleFocus} onClick={handleClick} placeholder={filteredTopics[0]?.prettyName} className={cn("placeholder:text-muted-foreground flex-1 bg-transparent px-2 outline-none")} autoFocus // Add this line />
{showDropdown && hasInteracted && ( {filteredTopics.map((topic, index) => ( handleSelect(topic)} className="min-h-10 rounded-none px-3 py-1.5" > {topic.prettyName} {topic.connectedTopics.length > 0 && topic.connectedTopics.join(", ")} ))} )}
) } export default Autocomplete