forked from LF-Decentralized-Trust-labs/gitmesh
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseKeyboardShortcuts.ts
More file actions
114 lines (100 loc) · 3.26 KB
/
Copy pathuseKeyboardShortcuts.ts
File metadata and controls
114 lines (100 loc) · 3.26 KB
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { useEffect, useRef } from "react";
interface ShortcutHandlers {
onNewIssue?: () => void;
onToggleSidebar?: () => void;
onTogglePanel?: () => void;
onSwitchProject?: (index: number) => void;
onNavigate?: (path: string) => void;
onShowShortcuts?: () => void;
}
const CHORD_TIMEOUT = 500; // ms to wait for second key in a chord
export function useKeyboardShortcuts({
onNewIssue,
onToggleSidebar,
onTogglePanel,
onSwitchProject,
onNavigate,
onShowShortcuts,
}: ShortcutHandlers) {
const pendingChord = useRef<string | null>(null);
const chordTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
function clearChord() {
pendingChord.current = null;
if (chordTimer.current) {
clearTimeout(chordTimer.current);
chordTimer.current = null;
}
}
function handleKeyDown(e: KeyboardEvent) {
const target = e.target as HTMLElement;
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
return;
}
// Escape → cancel pending chord
if (e.key === "Escape") {
clearChord();
return;
}
// G-chord navigation (GitHub-style: G then D/I/A/O/S)
if (e.key === "g" && !e.metaKey && !e.ctrlKey && !e.altKey) {
if (pendingChord.current === "g") {
// GG → go to dashboard
clearChord();
onNavigate?.("/dashboard");
return;
}
pendingChord.current = "g";
if (chordTimer.current) clearTimeout(chordTimer.current);
chordTimer.current = setTimeout(clearChord, CHORD_TIMEOUT);
return;
}
if (pendingChord.current === "g") {
clearChord();
switch (e.key) {
case "d": onNavigate?.("/dashboard"); break;
case "i": onNavigate?.("/issues"); break;
case "a": onNavigate?.("/agents/all"); break;
case "o": onNavigate?.("/org"); break;
case "s": onNavigate?.("/instance-settings"); break;
}
return;
}
// Cmd+1..9 → Switch project
if ((e.metaKey || e.ctrlKey) && e.key >= "1" && e.key <= "9") {
e.preventDefault();
onSwitchProject?.(parseInt(e.key, 10) - 1);
return;
}
// C → New Issue
if (e.key === "c" && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
onNewIssue?.();
return;
}
// [ → Toggle Sidebar (mobile)
if (e.key === "[" && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
onToggleSidebar?.();
return;
}
// ] → Toggle Properties Panel
if (e.key === "]" && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
onTogglePanel?.();
return;
}
// ? → Show keyboard shortcuts
if (e.key === "?" && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
onShowShortcuts?.();
return;
}
}
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
if (chordTimer.current) clearTimeout(chordTimer.current);
};
}, [onNewIssue, onToggleSidebar, onTogglePanel, onSwitchProject, onNavigate, onShowShortcuts]);
}