| | | 1 | | import { useEffect } from "react"; |
| | | 2 | | |
| | 0 | 3 | | const SECTIONS: { title: string; rows: [string, string][] }[] = [ |
| | | 4 | | { |
| | | 5 | | title: "Move", |
| | | 6 | | rows: [ |
| | | 7 | | ["j / ↓", "next item"], |
| | | 8 | | ["k / ↑", "previous item"], |
| | | 9 | | ["g g", "first item"], |
| | | 10 | | ["G", "last item"], |
| | | 11 | | ], |
| | | 12 | | }, |
| | | 13 | | { |
| | | 14 | | title: "Items", |
| | | 15 | | rows: [ |
| | | 16 | | ["Enter", "open / view"], |
| | | 17 | | ["e", "edit"], |
| | | 18 | | ["d", "delete"], |
| | | 19 | | ["o", "new item"], |
| | | 20 | | ], |
| | | 21 | | }, |
| | | 22 | | { |
| | | 23 | | title: "Search", |
| | | 24 | | rows: [ |
| | | 25 | | ["/", "filter items"], |
| | | 26 | | ["Esc", "close panel / clear filter"], |
| | | 27 | | ], |
| | | 28 | | }, |
| | | 29 | | { |
| | | 30 | | title: "Panes", |
| | | 31 | | rows: [ |
| | | 32 | | ["Alt+1", "focus rooms sidebar"], |
| | | 33 | | ["Alt+2", "focus items list"], |
| | | 34 | | ["Alt+3", "focus detail / editor"], |
| | | 35 | | ["Tab", "next focusable"], |
| | | 36 | | ], |
| | | 37 | | }, |
| | | 38 | | { |
| | | 39 | | title: "Terminal", |
| | | 40 | | rows: [ |
| | | 41 | | [":", "open terminal (vim ex)"], |
| | | 42 | | ["Alt+4", "toggle terminal"], |
| | | 43 | | ["Esc", "close terminal"], |
| | | 44 | | ["clear", "wipe scrollback"], |
| | | 45 | | ], |
| | | 46 | | }, |
| | | 47 | | { |
| | | 48 | | title: "Account", |
| | | 49 | | rows: [ |
| | | 50 | | ["[user ▌]", "click chip top-right (TUI)"], |
| | | 51 | | ["s", "sign out (in user panel)"], |
| | | 52 | | ["whoami", "show profile (terminal)"], |
| | | 53 | | ["logout", "sign out (terminal)"], |
| | | 54 | | ], |
| | | 55 | | }, |
| | | 56 | | { |
| | | 57 | | title: "Edit form", |
| | | 58 | | rows: [ |
| | | 59 | | ["Ctrl+S", "save"], |
| | | 60 | | ["Ctrl+Enter", "save"], |
| | | 61 | | ["Esc", "cancel"], |
| | | 62 | | ], |
| | | 63 | | }, |
| | | 64 | | { |
| | | 65 | | title: "Other", |
| | | 66 | | rows: [ |
| | | 67 | | ["?", "this help"], |
| | | 68 | | ], |
| | | 69 | | }, |
| | | 70 | | ]; |
| | | 71 | | |
| | 0 | 72 | | export function HelpOverlay({ onClose }: { onClose: () => void }) { |
| | 0 | 73 | | useEffect(() => { |
| | 0 | 74 | | function onKey(e: KeyboardEvent) { |
| | 0 | 75 | | if (e.key === "Escape" || e.key === "?") { |
| | 0 | 76 | | e.preventDefault(); |
| | 0 | 77 | | onClose(); |
| | | 78 | | } |
| | | 79 | | } |
| | 0 | 80 | | window.addEventListener("keydown", onKey); |
| | 0 | 81 | | return () => window.removeEventListener("keydown", onKey); |
| | | 82 | | }, [onClose]); |
| | | 83 | | |
| | 0 | 84 | | return ( |
| | | 85 | | <div |
| | | 86 | | role="dialog" |
| | | 87 | | aria-modal="true" |
| | | 88 | | aria-label="Keyboard shortcuts" |
| | | 89 | | className="cs-help-overlay" |
| | 0 | 90 | | onClick={(e) => { if (e.target === e.currentTarget) onClose(); }} |
| | | 91 | | > |
| | | 92 | | <div className="cs-help-panel tui-panel"> |
| | | 93 | | <span className="tui-panel-title">─[ keyboard shortcuts ]─</span> |
| | | 94 | | <div className="cs-help-grid"> |
| | 0 | 95 | | {SECTIONS.map(s => ( |
| | 0 | 96 | | <section key={s.title}> |
| | | 97 | | <h3 className="cs-help-heading">── {s.title.toLowerCase()} ──</h3> |
| | | 98 | | <dl> |
| | 0 | 99 | | {s.rows.map(([k, v]) => ( |
| | 0 | 100 | | <div key={k} className="cs-help-row"> |
| | | 101 | | <dt><kbd>{k}</kbd></dt> |
| | | 102 | | <dd>{v}</dd> |
| | | 103 | | </div> |
| | | 104 | | ))} |
| | | 105 | | </dl> |
| | | 106 | | </section> |
| | | 107 | | ))} |
| | | 108 | | </div> |
| | | 109 | | <div className="cs-help-footer"> |
| | | 110 | | <button type="button" onClick={onClose} className="cs-help-close"> |
| | | 111 | | [Esc] close |
| | | 112 | | </button> |
| | | 113 | | </div> |
| | | 114 | | </div> |
| | | 115 | | </div> |
| | | 116 | | ); |
| | | 117 | | } |