import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; import type { SlashCommandInfo } from "shared/legacy-sdk"; interface SlashCommandMenuProps { commands: SlashCommandInfo[]; query: string; selectedIndex: number; onSelect: (name: string) => void; onHover: (index: number) => void; } function highlightMatch(text: string, query: string): React.ReactNode { if (!query) { return text; } const lowerText = text.toLowerCase(); const lowerQuery = query.toLowerCase(); const parts: React.ReactNode[] = []; let lastIdx = 0; let qi = 0; for (let i = 0; i < text.length && qi < lowerQuery.length; i++) { if (lowerText[i] === lowerQuery[qi]) { if (i > lastIdx) { parts.push(text.slice(lastIdx, i)); } parts.push( {text[i]} , ); lastIdx = i + 1; qi++; } } if (lastIdx < text.length) { parts.push(text.slice(lastIdx)); } return parts.length > 0 ? parts : text; } export function SlashCommandMenu({ commands, query, selectedIndex, onSelect, onHover }: SlashCommandMenuProps) { const selectedRef = useRef(null); const hoverSelectionRef = useRef(null); useEffect(() => { if (hoverSelectionRef.current === selectedIndex) { hoverSelectionRef.current = null; return; } hoverSelectionRef.current = null; selectedRef.current?.scrollIntoView({ block: "nearest" }); }, [selectedIndex]); const handleHover = (index: number) => { hoverSelectionRef.current = index; onHover(index); }; if (commands.length === 0) { return No commands found; } return ( {commands.map((cmd, idx) => ( onSelect(cmd.name)} onMouseMove={() => handleHover(idx)} className={cn("w-full px-2 py-1.5 text-left flex items-center justify-between gap-3", idx === selectedIndex ? "bg-accent" : "hover:bg-accent/50")} > {highlightMatch(`/${cmd.name}`, query)} {cmd.description} ))} ); }