Repository navigation
Expand file tree
/
Copy pathuseKeyboardShortcuts.ts
More file actions
83 lines (70 loc) · 2.24 KB
/
Copy pathuseKeyboardShortcuts.ts
File metadata and controls
83 lines (70 loc) · 2.24 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
'use client';
import { useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
const SHORTCUT_ROUTES: Record<string, string> = {
d: '/',
c: '/contributors',
p: '/compare',
u: '/customize',
};
function isTypingTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false;
const tagName = target.tagName.toLowerCase();
return (
tagName === 'input' ||
tagName === 'textarea' ||
tagName === 'select' ||
target.isContentEditable
);
}
interface UseKeyboardShortcutsOptions {
onOpenShortcuts?: () => void;
}
// Global "g then key" quick-nav shortcuts. Navigates via the App Router, so it
// must be mounted within a Next.js App Router context (useRouter throws otherwise).
export function useKeyboardShortcuts(options?: UseKeyboardShortcutsOptions) {
const router = useRouter();
const waitingForSecondKey = useRef(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const resetShortcut = () => {
waitingForSecondKey.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (isTypingTarget(event.target)) return;
// ? opens shortcuts modal
if (!event.ctrlKey && !event.metaKey && !event.altKey && event.key === '?') {
event.preventDefault();
options?.onOpenShortcuts?.();
return;
}
if (event.ctrlKey || event.metaKey || event.altKey) return;
const key = event.key.toLowerCase();
if (!waitingForSecondKey.current) {
if (key === 'g') {
waitingForSecondKey.current = true;
timeoutRef.current = setTimeout(() => {
waitingForSecondKey.current = false;
timeoutRef.current = null;
}, 1000);
}
return;
}
const route = SHORTCUT_ROUTES[key];
if (route) {
event.preventDefault();
router.push(route);
}
resetShortcut();
};
window.addEventListener('keydown', handleKeyDown);
return () => {
resetShortcut();
window.removeEventListener('keydown', handleKeyDown);
};
}, [router, options]);
}