import React, { useState, useEffect, useRef } from 'react' import { useNavigate } from '@tanstack/react-router' interface PaletteProps { open: boolean onClose: () => void } interface JumpItem { kind: 'jump' label: string hint: string path: string } const JUMP_ITEMS: JumpItem[] = [ { kind: 'jump', label: 'Recent', hint: 'g h', path: '/' }, { kind: 'jump', label: 'Projects', hint: 'g p', path: '/projects' }, { kind: 'jump', label: 'Stats', hint: 'g s', path: '/stats' }, { kind: 'jump', label: 'Health', hint: 'g i', path: '/health' }, { kind: 'jump', label: 'Settings', hint: '', path: '/settings' }, ] export function Palette({ open, onClose }: PaletteProps): React.JSX.Element { const navigate = useNavigate() const [query, setQuery] = useState('') const [cursor, setCursor] = useState(0) const inputRef = useRef(null) // Autofocus when palette opens; reset state when it closes useEffect(() => { if (open) { setQuery('') setCursor(0) // Small defer so the element is visible before focus requestAnimationFrame(() => { inputRef.current?.focus() }) } }, [open]) // Reset cursor when query changes useEffect(() => { setCursor(0) }, [query]) if (!open) return <> const q = query.trim() const filtered: JumpItem[] = q === '' ? JUMP_ITEMS : JUMP_ITEMS.filter(item => item.label.toLowerCase().includes(q.toLowerCase()), ) const showSearch = q !== '' && filtered.length === 0 const total = filtered.length + (showSearch ? 1 : 0) function fire(idx: number): void { if (showSearch && idx === 0) { void navigate({ to: '/search', search: { q: query } }) onClose() return } const item = filtered[idx] if (item) { void navigate({ to: item.path }) onClose() } } function onKeyDown(e: React.KeyboardEvent): void { if (e.key === 'Escape') { e.preventDefault() onClose() } else if (e.key === 'ArrowDown') { e.preventDefault() setCursor(c => Math.min(total - 1, c + 1)) } else if (e.key === 'ArrowUp') { e.preventDefault() setCursor(c => Math.max(0, c - 1)) } else if (e.key === 'Enter') { e.preventDefault() fire(cursor) } } return (
e.stopPropagation()}> {/* Input row */}
⌘K setQuery(e.target.value)} onKeyDown={onKeyDown} placeholder="search turns, jump to a page, pick a session…" />
{/* Item list */}
{showSearch && (
fire(0)} > search "{query}"
)} {filtered.map((item, i) => { const idx = showSearch ? i + 1 : i return (
fire(idx)} > {item.kind} {item.label} {item.hint !== '' && ( {item.hint} )}
) })} {filtered.length === 0 && !showSearch && (
no matches
)}
{/* Footer */}
↑↓ navigate ↵ open esc close g h · g p · g s · g i
) }