diff --git a/src/tui/browser/Browser.tsx b/src/tui/browser/Browser.tsx index a4f6841..0dc24ab 100644 --- a/src/tui/browser/Browser.tsx +++ b/src/tui/browser/Browser.tsx @@ -36,6 +36,7 @@ export function Browser({ project, readPou, writeSelection, onQuit }: BrowserPro const [cursorIdx, setCursorIdx] = React.useState(0); const [text, setText] = React.useState(null); const [scrollTop] = React.useState(0); + const [helpOpen, setHelpOpen] = React.useState(false); const rows = React.useMemo(() => flatten(project, expanded), [project, expanded]); const cursor = rows[Math.min(cursorIdx, rows.length - 1)]; @@ -63,6 +64,14 @@ export function Browser({ project, readPou, writeSelection, onQuit }: BrowserPro }, [cursor, readPou]); useInput((input, key) => { + if (input === '?') { + setHelpOpen((v) => !v); + return; + } + if (helpOpen) { + if (key.escape) setHelpOpen(false); + return; + } if (input === 'q') return onQuit(); if (input === 'j' || key.downArrow) { setCursorIdx((i) => Math.min(i + 1, rows.length - 1)); @@ -98,6 +107,7 @@ export function Browser({ project, readPou, writeSelection, onQuit }: BrowserPro ─ {project.rootDir.split(/[/\\]/).pop()} ─{stale ? ` mirror ${stale} old ` : ' '}─ + {helpOpen && } @@ -106,7 +116,22 @@ export function Browser({ project, readPou, writeSelection, onQuit }: BrowserPro - j/k nav l expand h collapse q quit + j/k nav l expand h collapse ? help q quit + + ); +} + +function HelpOverlay(): React.ReactElement { + return ( + + Keybindings + j / ↓ move cursor down + k / ↑ move cursor up + l / → expand device + h / ← collapse device + ? toggle this help + Esc close help + q quit ); } diff --git a/tests/tui/Browser.test.tsx b/tests/tui/Browser.test.tsx index 08c0ff5..e7382d1 100644 --- a/tests/tui/Browser.test.tsx +++ b/tests/tui/Browser.test.tsx @@ -79,4 +79,41 @@ describe('', () => { await flush(); expect(onQuit).toHaveBeenCalled(); }); + + it('toggles a help overlay on ?', async () => { + const { stdin, lastFrame } = render( + ''} + writeSelection={() => {}} + onQuit={() => {}} + /> + ); + await flush(); + expect(lastFrame()).not.toContain('Keybindings'); + stdin.write('?'); + await flush(); + expect(lastFrame()).toContain('Keybindings'); + stdin.write('?'); + await flush(); + expect(lastFrame()).not.toContain('Keybindings'); + }); + + it('closes the help overlay on Esc', async () => { + const { stdin, lastFrame } = render( + ''} + writeSelection={() => {}} + onQuit={() => {}} + /> + ); + await flush(); + stdin.write('?'); + await flush(); + expect(lastFrame()).toContain('Keybindings'); + stdin.write(String.fromCharCode(27)); + await flush(); + expect(lastFrame()).not.toContain('Keybindings'); + }); });