diff --git a/app/src/api/shortcuts.ts b/app/src/api/shortcuts.ts new file mode 100644 index 000000000..8f1b3fb0d --- /dev/null +++ b/app/src/api/shortcuts.ts @@ -0,0 +1,14 @@ +export type ShortcutConfig = { + key: string; + ctrlKey: boolean; + description: string; + action: string; +}; + +export const DEFAULT_SHORTCUTS: ShortcutConfig[] = [ + { key: 'e', ctrlKey: true, description: 'Go to Expenses', action: 'navigate:/expenses' }, + { key: 'b', ctrlKey: true, description: 'Go to Bills', action: 'navigate:/bills' }, + { key: 'd', ctrlKey: true, description: 'Go to Dashboard', action: 'navigate:/dashboard' }, + { key: 'k', ctrlKey: true, description: 'Open Search', action: 'open:search' }, + { key: 'Escape', ctrlKey: false, description: 'Close current dialog', action: 'close:modal' }, +]; diff --git a/app/src/hooks/useKeyboardShortcuts.ts b/app/src/hooks/useKeyboardShortcuts.ts new file mode 100644 index 000000000..d68b0fb87 --- /dev/null +++ b/app/src/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,33 @@ +import { useEffect, useCallback } from 'react'; +import { ShortcutConfig, DEFAULT_SHORTCUTS } from '../api/shortcuts'; + +export type ShortcutHandler = (action: string) => void; + +export function useKeyboardShortcuts(onAction: ShortcutHandler) { + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + for (const shortcut of DEFAULT_SHORTCUTS) { + const ctrlMatch = shortcut.ctrlKey + ? event.ctrlKey || event.metaKey + : true; + const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase(); + + if (ctrlMatch && keyMatch && (shortcut.ctrlKey === (event.ctrlKey || event.metaKey))) { + event.preventDefault(); + onAction(shortcut.action); + return; + } + } + }, + [onAction], + ); + + useEffect(() => { + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [handleKeyDown]); + + return DEFAULT_SHORTCUTS; +}