nandi/oripublic Fork 0
b6cd929ab1425a4ef6073ea9c189917cca02cf3c
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

CompletionPopup.tsx · 74 lines · 2.1 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1/**
2 * CompletionPopup is the list shown above the composer while an "@" or "/"
3 * selector is active: label, description, and a source tag for skills and
4 * agent commands. Selection happens with the keyboard (handled by the
5 * composer) or the mouse.
6 *
7 * @example
8 * <CompletionPopup items={items} active={0} onHover={setActive} onPick={pick} />
9 */
10
11import type { CompletionItem } from "../mentions";
12
13interface CompletionPopupProps {
14 items: CompletionItem[];
15 active: number;
16 onHover: (index: number) => void;
17 onPick: (item: CompletionItem) => void;
18}
19
20const sourceLabels: Record<CompletionItem["source"], string> = {
21 file: "",
22 skill: "skill",
23 command: "command",
24};
25
26// The popup is a combobox listbox driven from the textarea (which carries
27// aria-activedescendant), not a form <select>: hence the explicit roles on
28// focusable-but-unreachable divs.
29export function CompletionPopup({
30 items,
31 active,
32 onHover,
33 onPick,
34}: CompletionPopupProps) {
35 return (
36 <div
37 className="completion"
38 // biome-ignore lint/a11y/useSemanticElements: a <select> cannot host rich rows nor stay open while typing
39 role="listbox"
40 tabIndex={-1}
41 aria-label={items[0]?.source === "file" ? "files" : "skills"}
42 >
43 {items.map((item, index) => (
44 <div
45 key={item.id}
46 id={`completion-${index}`}
47 // biome-ignore lint/a11y/useSemanticElements: see the listbox above
48 role="option"
49 tabIndex={-1}
50 aria-selected={index === active}
51 className={`completion-item${index === active ? " completion-item-active" : ""}`}
52 onMouseEnter={() => onHover(index)}
53 // Fire before the textarea loses focus on click.
54 onMouseDown={(event) => {
55 event.preventDefault();
56 onPick(item);
57 }}
58 >
59 <span className="completion-label">{item.label}</span>
60 {item.description && (
61 <span className="completion-description">{item.description}</span>
62 )}
63 {sourceLabels[item.source] && (
64 <span
65 className={`completion-source completion-source-${item.source}`}
66 >
67 {sourceLabels[item.source]}
68 </span>
69 )}
70 </div>
71 ))}
72 </div>
73 );
74}