📦 EqualifyEverything / equalify-reflow

📄 KeyboardShortcuts.tsx · 98 lines
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>
      )}
    </>
  );
}