kimi-code / apps /vscode /webview-ui /src /components /SlashCommandMenu.tsx
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f59fbe2 verified
Raw
History Blame Contribute Delete
2.64 kB
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(
<span key={i} className="text-foreground font-semibold">
{text[i]}
</span>,
);
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<HTMLButtonElement>(null);
const hoverSelectionRef = useRef<number | null>(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 <div className="rounded-md border bg-popover shadow-md p-3 text-xs text-muted-foreground text-center">No commands found</div>;
}
return (
<div className="rounded-md border bg-popover shadow-md overflow-hidden">
<div className="max-h-70 overflow-y-auto">
{commands.map((cmd, idx) => (
<button
key={cmd.name}
ref={idx === selectedIndex ? selectedRef : null}
onClick={() => 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")}
>
<span className="text-xs shrink-0">{highlightMatch(`/${cmd.name}`, query)}</span>
<span className="text-[10px] text-muted-foreground truncate">{cmd.description}</span>
</button>
))}
</div>
</div>
);
}