1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98import { useState } from 'react';
import { useHotkey } from '@tanstack/react-hotkeys';
import { Modal } from '@/components/ui/modal';
import { X, Keyboard } from 'lucide-react';
const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
const MOD_LABEL = isMac ? '⌘' : 'Ctrl';
interface ShortcutDef {
keys: string;
label: string;
description: string;
}
const SHORTCUTS: ShortcutDef[] = [
{ keys: `${MOD_LABEL}+0`, label: 'Skip Menu', description: 'Open the skip-navigation menu' },
{ keys: `${MOD_LABEL}+1`, label: 'Page Picker', description: 'Jump to the page navigation sidebar' },
{ keys: `${MOD_LABEL}+2`, label: 'Stage Picker', description: 'Jump to the pipeline stage tabs' },
{ keys: `${MOD_LABEL}+3`, label: 'Rendered Preview', description: 'Jump to the rendered markdown preview' },
{ keys: `${MOD_LABEL}+4`, label: 'Changes Panel', description: 'Jump to the changes sidebar' },
{ keys: `${MOD_LABEL}+/`, label: 'Keyboard Shortcuts', description: 'Open this help dialog' },
];
export type FocusRegion = 'skip' | 'pages' | 'stages' | 'preview' | 'changes';
interface KeyboardShortcutsProps {
onFocusRegion: (region: FocusRegion) => void;
}
export function KeyboardShortcuts({ onFocusRegion }: KeyboardShortcutsProps) {
const [helpOpen, setHelpOpen] = useState(false);
useHotkey('Mod+0', (e) => {
e.preventDefault();
onFocusRegion('skip');
});
useHotkey('Mod+1', (e) => {
e.preventDefault();
onFocusRegion('pages');
});
useHotkey('Mod+2', (e) => {
e.preventDefault();
onFocusRegion('stages');
});
useHotkey('Mod+3', (e) => {
e.preventDefault();
onFocusRegion('preview');
});
useHotkey('Mod+4', (e) => {
e.preventDefault();
onFocusRegion('changes');
});
useHotkey('Mod+/', (e) => {
e.preventDefault();
setHelpOpen((prev) => !prev);
});
return (
<>
{helpOpen && (
<Modal onClose={() => setHelpOpen(false)} aria-label="Keyboard Shortcuts" className="bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
<div className="flex items-center justify-between px-6 py-4 border-b">
<div className="flex items-center gap-2">
<Keyboard className="w-4 h-4 text-uic-blue" />
<h2 className="text-base font-semibold text-gray-800">Keyboard Shortcuts</h2>
</div>
<button
onClick={() => setHelpOpen(false)}
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
aria-label="Close"
>
<X className="w-4 h-4 text-muted-foreground" />
</button>
</div>
<div className="px-6 py-4 space-y-3">
{SHORTCUTS.map((s) => (
<div key={s.keys} className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-800">{s.label}</p>
<p className="text-xs text-muted-foreground">{s.description}</p>
</div>
<kbd className="px-2.5 py-1 rounded-md bg-gray-100 border text-xs font-mono font-medium text-gray-700 shrink-0 ml-4">
{s.keys}
</kbd>
</div>
))}
</div>
</Modal>
)}
</>
);
}