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(); // Check if required modifiers match (only check if explicitly required) const ctrlMatches = shortcut.ctrl === true ? event.ctrlKey : true; const shiftMatches = shortcut.shift === true ? event.shiftKey : true; const altMatches = shortcut.alt === true ? event.altKey : true; // Handle meta/cmd key with cross-platform support let metaMatches = true; if (shortcut.meta === true) { // On Mac: require Cmd key // On Windows/Linux: accept Ctrl key as Cmd equivalent const isMac = navigator.platform.includes('Mac'); metaMatches = isMac ? event.metaKey : event.ctrlKey; } // Ensure unwanted modifiers are not pressed (unless explicitly required) const noExtraCtrl = shortcut.ctrl === true || shortcut.meta === true || !event.ctrlKey; const noExtraShift = shortcut.shift === true || !event.shiftKey; const noExtraAlt = shortcut.alt === true || !event.altKey; const noExtraMeta = shortcut.meta === true || !event.metaKey; if ( keyMatches && ctrlMatches && shiftMatches && altMatches && metaMatches && noExtraCtrl && noExtraShift && noExtraAlt && noExtraMeta ) { 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, }, ]); }