import { useEffect, useState } from 'react'; import { Keyboard } from 'lucide-react'; import { isShortcutsChord, SHORTCUT_SECTIONS } from '@/state/shortcuts'; /* `Ctrl+?` — every shortcut the shell owns, on one screen. Ported in shape from `vimazing`, where the controls are declared as data and the help screen renders them. Its renderer was a recursive `any` walk over arbitrary content, which is right for a game whose help also carries rules and scoring; here the content is one shape, so the renderer is typed and boring and the interesting part is `state/shortcuts.ts`. Card per section, two columns on anything wider than a phone — the same arrangement vimazing used for its controls grid, and for the same reason: these are short lists that read badly as one long column. */ const Row = ({ keys, description }: { keys: string; description: string }) => (
{/* Fixed width, tabular: the chords line up into a column you can scan, which is the whole point of putting them on the left. */} {keys} {description}
); export const ShortcutsModal = () => { const [open, setOpen] = useState(false); useEffect(() => { const onKey = (ev: KeyboardEvent) => { if (isShortcutsChord(ev)) { // No focus check: `Ctrl+?` types nothing in a text field, and a help list you cannot open from the // composer is a help list you cannot open — the lesson from the Spotlight guard. ev.preventDefault(); setOpen((was) => !was); return; } if (ev.key === 'Escape' && open) setOpen(false); }; // Capture, so a panel with its own key handling cannot swallow it first. window.addEventListener('keydown', onKey, true); return () => window.removeEventListener('keydown', onKey, true); }, [open]); if (!open) return null; return (
setOpen(false)} >
ev.stopPropagation()} >

Keyboard shortcuts

Ctrl+? · Esc to close
{/* Only this scrolls — the header stays, for the same reason the theme dropdown's does. */}
{SHORTCUT_SECTIONS.map((section) => (

{section.title}

{section.note &&

{section.note}

}
{section.shortcuts.map((shortcut) => ( ))}
))}
); };