import { useKeyboard } from "@opentui/solid"; import { For, createSignal, createMemo, onMount, onCleanup } from "solid-js"; import type { KeyEvent } from "@opentui/core"; import type { PersonaSummary } from "../../../src/core/types.js"; import { useKeyboardNav } from "../context/keyboard.js"; interface PersonaListOverlayProps { personas: PersonaSummary[]; activePersonaId: string | null; title?: string; onSelect: (personaId: string) => void; onDismiss: () => void; } export function PersonaListOverlay(props: PersonaListOverlayProps) { const { setOverlayActive } = useKeyboardNav(); const [selectedIndex, setSelectedIndex] = createSignal(0); const [filterText, setFilterText] = createSignal(""); const [filterMode, setFilterMode] = createSignal(false); onMount(() => setOverlayActive(true)); onCleanup(() => setOverlayActive(false)); const filteredPersonas = createMemo(() => { const filter = filterText().toLowerCase(); if (!filter) return props.personas; return props.personas.filter((p) => p.display_name.toLowerCase().includes(filter) ); }); createMemo(() => { const list = filteredPersonas(); if (selectedIndex() >= list.length) { setSelectedIndex(Math.max(0, list.length - 1)); } }); useKeyboard((event: KeyEvent) => { const key = event.name; const listLength = filteredPersonas().length; if (filterMode()) { if (key === "escape") { event.preventDefault(); setFilterText(""); setFilterMode(false); return; } if (key === "backspace") { event.preventDefault(); setFilterText((prev) => prev.slice(0, -1)); return; } if (key === "return") { event.preventDefault(); if (listLength > 0) { const selected = filteredPersonas()[selectedIndex()]; props.onSelect(selected.id); } return; } if (key.length === 1 && !event.ctrl && !event.meta) { event.preventDefault(); setFilterText((prev) => prev + key); return; } } else { if (key === "j" || key === "down") { event.preventDefault(); setSelectedIndex((prev) => Math.min(prev + 1, listLength - 1)); return; } if (key === "k" || key === "up") { event.preventDefault(); setSelectedIndex((prev) => Math.max(prev - 1, 0)); return; } if (key === "return") { event.preventDefault(); if (listLength > 0) { const selected = filteredPersonas()[selectedIndex()]; props.onSelect(selected.id); } return; } if (key === "escape") { event.preventDefault(); props.onDismiss(); return; } if (key === "/") { event.preventDefault(); setFilterMode(true); return; } } }); const truncateDescription = (desc: string | undefined, maxLen: number = 40): string => { if (!desc) return ""; return desc.length > maxLen ? desc.slice(0, maxLen - 3) + "..." : desc; }; const title = props.title || "Select Persona"; return ( {title} {(persona, index) => { const isActive = () => props.activePersonaId === persona.id; const isSelected = () => selectedIndex() === index(); const description = truncateDescription(persona.short_description); const label = () => { const prefix = isActive() ? "> " : " "; const descText = description ? ` - ${description}` : ""; return `${prefix}${persona.display_name}${descText}`; }; return ( {label()} ); }} {filterMode() ? ( Filter: {filterText()}| ) : ( j/k: navigate | Enter: select | Esc: cancel | /: filter )} ); }