92 lines
2.1 KiB
TypeScript
92 lines
2.1 KiB
TypeScript
|
|
import { useEffect } from 'react';
|
||
|
|
|
||
|
|
export interface KeyboardShortcut {
|
||
|
|
key: string;
|
||
|
|
ctrl?: boolean;
|
||
|
|
shift?: boolean;
|
||
|
|
alt?: boolean;
|
||
|
|
meta?: boolean;
|
||
|
|
handler: (event: KeyboardEvent) => void;
|
||
|
|
description?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hook to register keyboard shortcuts
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```tsx
|
||
|
|
* useKeyboard([
|
||
|
|
* {
|
||
|
|
* key: 'c',
|
||
|
|
* meta: true, // Cmd on Mac, Ctrl on Windows
|
||
|
|
* handler: () => copyToClipboard(),
|
||
|
|
* description: 'Copy color',
|
||
|
|
* },
|
||
|
|
* {
|
||
|
|
* key: 'k',
|
||
|
|
* meta: true,
|
||
|
|
* handler: () => openCommandPalette(),
|
||
|
|
* description: 'Open command palette',
|
||
|
|
* },
|
||
|
|
* ]);
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function useKeyboard(shortcuts: KeyboardShortcut[]) {
|
||
|
|
useEffect(() => {
|
||
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||
|
|
for (const shortcut of shortcuts) {
|
||
|
|
const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase();
|
||
|
|
const ctrlMatches = shortcut.ctrl ? event.ctrlKey : !event.ctrlKey;
|
||
|
|
const shiftMatches = shortcut.shift ? event.shiftKey : !event.shiftKey;
|
||
|
|
const altMatches = shortcut.alt ? event.altKey : !event.altKey;
|
||
|
|
const metaMatches = shortcut.meta ? event.metaKey : !event.metaKey;
|
||
|
|
|
||
|
|
// On Windows/Linux, treat meta as ctrl for convenience
|
||
|
|
const modifierMatches = shortcut.meta
|
||
|
|
? event.metaKey || (event.ctrlKey && !navigator.platform.includes('Mac'))
|
||
|
|
: metaMatches;
|
||
|
|
|
||
|
|
if (
|
||
|
|
keyMatches &&
|
||
|
|
ctrlMatches &&
|
||
|
|
shiftMatches &&
|
||
|
|
altMatches &&
|
||
|
|
modifierMatches
|
||
|
|
) {
|
||
|
|
event.preventDefault();
|
||
|
|
shortcut.handler(event);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
window.addEventListener('keydown', handleKeyDown);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
window.removeEventListener('keydown', handleKeyDown);
|
||
|
|
};
|
||
|
|
}, [shortcuts]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hook to register a single keyboard shortcut (convenience wrapper)
|
||
|
|
*/
|
||
|
|
export function useKeyboardShortcut(
|
||
|
|
key: string,
|
||
|
|
handler: (event: KeyboardEvent) => void,
|
||
|
|
modifiers?: {
|
||
|
|
ctrl?: boolean;
|
||
|
|
shift?: boolean;
|
||
|
|
alt?: boolean;
|
||
|
|
meta?: boolean;
|
||
|
|
}
|
||
|
|
) {
|
||
|
|
useKeyboard([
|
||
|
|
{
|
||
|
|
key,
|
||
|
|
...modifiers,
|
||
|
|
handler,
|
||
|
|
},
|
||
|
|
]);
|
||
|
|
}
|