Download apps/vscode/webview-ui/src/components/SlashCommandMenu.tsx from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 2.64 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/SlashCommandMenu.tsx
- Command line
-
hf download hf://SaylorTwift/kimi-code/apps/vscode/webview-ui/src/components/SlashCommandMenu.tsx
-
curl -L -o SlashCommandMenu.tsx https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/SlashCommandMenu.tsx
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> | |
| ); | |
| } | |