fix: refactor keyboard shortcut context

This commit is contained in:
Jan-Henrik Bruhn 2025-10-10 11:54:51 +02:00
parent 75cb26a991
commit 4e335a8fde
5 changed files with 41 additions and 161 deletions

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import KeyboardIcon from '@mui/icons-material/Keyboard'; import KeyboardIcon from '@mui/icons-material/Keyboard';
import { useKeyboardShortcuts } from '../../contexts/KeyboardShortcutContext'; import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts';
import type { ShortcutCategory } from '../../hooks/useKeyboardShortcutManager'; import type { ShortcutCategory, KeyboardShortcut } from '../../hooks/useKeyboardShortcutManager';
/** /**
* KeyboardShortcutsHelp Component * KeyboardShortcutsHelp Component
@ -55,7 +55,7 @@ const KeyboardShortcutsHelp: React.FC<KeyboardShortcutsHelpProps> = ({
{categories.map(category => { {categories.map(category => {
const categoryShortcuts = shortcuts const categoryShortcuts = shortcuts
.getShortcutsByCategory(category) .getShortcutsByCategory(category)
.filter(s => s.enabled !== false); .filter((s: KeyboardShortcut) => s.enabled !== false);
if (categoryShortcuts.length === 0) return null; if (categoryShortcuts.length === 0) return null;
@ -65,7 +65,7 @@ const KeyboardShortcutsHelp: React.FC<KeyboardShortcutsHelpProps> = ({
{category} {category}
</h3> </h3>
<div className="space-y-2"> <div className="space-y-2">
{categoryShortcuts.map(shortcut => ( {categoryShortcuts.map((shortcut: KeyboardShortcut) => (
<div <div
key={shortcut.id} key={shortcut.id}
className="flex items-center justify-between py-2 hover:bg-gray-50 px-2 rounded" className="flex items-center justify-between py-2 hover:bg-gray-50 px-2 rounded"

View file

@ -1,26 +1,14 @@
import React, { createContext, useContext, ReactNode } from 'react'; import React, { ReactNode } from "react";
import { useKeyboardShortcutManager } from '../hooks/useKeyboardShortcutManager'; import { useKeyboardShortcutManager } from "../hooks/useKeyboardShortcutManager";
import { KeyboardShortcutContext } from "./keyboardShortcut";
/**
* Keyboard Shortcut Context
*
* Provides centralized keyboard shortcut management throughout the application.
* Components can register shortcuts and the system handles conflicts and priorities.
*/
interface KeyboardShortcutContextValue {
shortcuts: ReturnType<typeof useKeyboardShortcutManager>;
}
const KeyboardShortcutContext = createContext<KeyboardShortcutContextValue | null>(null);
interface KeyboardShortcutProviderProps { interface KeyboardShortcutProviderProps {
children: ReactNode; children: ReactNode;
} }
export const KeyboardShortcutProvider: React.FC<KeyboardShortcutProviderProps> = ({ export const KeyboardShortcutProvider: React.FC<
children, KeyboardShortcutProviderProps
}) => { > = ({ children }) => {
const shortcuts = useKeyboardShortcutManager(); const shortcuts = useKeyboardShortcutManager();
return ( return (
@ -29,26 +17,3 @@ export const KeyboardShortcutProvider: React.FC<KeyboardShortcutProviderProps> =
</KeyboardShortcutContext.Provider> </KeyboardShortcutContext.Provider>
); );
}; };
/**
* Hook to access keyboard shortcut manager
*
* Usage:
* ```tsx
* const { shortcuts } = useKeyboardShortcuts();
*
* useEffect(() => {
* shortcuts.register({...});
* return () => shortcuts.unregister('id');
* }, []);
* ```
*/
export function useKeyboardShortcuts() {
const context = useContext(KeyboardShortcutContext);
if (!context) {
throw new Error(
'useKeyboardShortcuts must be used within KeyboardShortcutProvider'
);
}
return context;
}

View file

@ -0,0 +1,9 @@
import { createContext } from "react";
import { useKeyboardShortcutManager } from "../hooks/useKeyboardShortcutManager";
export interface KeyboardShortcutContextValue {
shortcuts: ReturnType<typeof useKeyboardShortcutManager>;
}
export const KeyboardShortcutContext =
createContext<KeyboardShortcutContextValue | null>(null);

View file

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useKeyboardShortcuts } from "../contexts/KeyboardShortcutContext"; import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts";
import { useWorkspaceStore } from "../stores/workspaceStore"; import { useWorkspaceStore } from "../stores/workspaceStore";
import type { KeyboardShortcut } from "./useKeyboardShortcutManager"; import type { KeyboardShortcut } from "./useKeyboardShortcutManager";

View file

@ -1,119 +1,25 @@
import { useEffect } from 'react'; import { useContext } from "react";
import { useWorkspaceStore } from '../stores/workspaceStore'; import { KeyboardShortcutContext } from "../contexts/keyboardShortcut";
/** /**
* useKeyboardShortcuts Hook * Hook to access keyboard shortcut manager
* *
* Global keyboard shortcuts for the application: * Usage:
* - Ctrl/Cmd + Tab: Next document * ```tsx
* - Ctrl/Cmd + Shift + Tab: Previous document * const { shortcuts } = useKeyboardShortcuts();
* - Ctrl/Cmd + W: Close current document *
* - Ctrl/Cmd + N: New document * useEffect(() => {
* - Ctrl/Cmd + S: Save current document (trigger save immediately) * shortcuts.register({...});
* - Ctrl/Cmd + O: Open document manager * return () => shortcuts.unregister('id');
* - Ctrl/Cmd + Z: Undo (per-document) * }, []);
* - Ctrl/Cmd + Y / Ctrl/Cmd + Shift + Z: Redo (per-document) * ```
*/ */
export function useKeyboardShortcuts() {
interface UseKeyboardShortcutsOptions { const context = useContext(KeyboardShortcutContext);
onOpenDocumentManager?: () => void; if (!context) {
onUndo?: () => void; throw new Error(
onRedo?: () => void; "useKeyboardShortcuts must be used within KeyboardShortcutProvider",
} );
}
export function useKeyboardShortcuts(options?: UseKeyboardShortcutsOptions) { return context;
const {
documentOrder,
activeDocumentId,
switchToDocument,
closeDocument,
createDocument,
saveDocument,
} = useWorkspaceStore();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const modifier = isMac ? e.metaKey : e.ctrlKey;
// Ctrl/Cmd + Tab - Next document
if (modifier && e.key === 'Tab' && !e.shiftKey) {
e.preventDefault();
const currentIndex = documentOrder.findIndex(id => id === activeDocumentId);
if (currentIndex !== -1) {
const nextIndex = (currentIndex + 1) % documentOrder.length;
switchToDocument(documentOrder[nextIndex]);
}
return;
}
// Ctrl/Cmd + Shift + Tab - Previous document
if (modifier && e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
const currentIndex = documentOrder.findIndex(id => id === activeDocumentId);
if (currentIndex !== -1) {
const prevIndex = (currentIndex - 1 + documentOrder.length) % documentOrder.length;
switchToDocument(documentOrder[prevIndex]);
}
return;
}
// Ctrl/Cmd + W - Close current document
if (modifier && e.key === 'w') {
e.preventDefault();
if (activeDocumentId && documentOrder.length > 1) {
closeDocument(activeDocumentId);
}
return;
}
// Ctrl/Cmd + N - New document
if (modifier && e.key === 'n') {
e.preventDefault();
createDocument();
return;
}
// Ctrl/Cmd + S - Save current document
if (modifier && e.key === 's') {
e.preventDefault();
if (activeDocumentId) {
saveDocument(activeDocumentId);
}
return;
}
// Ctrl/Cmd + O - Open document manager
if (modifier && e.key === 'o') {
e.preventDefault();
options?.onOpenDocumentManager?.();
return;
}
// Ctrl/Cmd + Z - Undo
if (modifier && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
options?.onUndo?.();
return;
}
// Ctrl/Cmd + Y or Ctrl/Cmd + Shift + Z - Redo
if (modifier && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
e.preventDefault();
options?.onRedo?.();
return;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [
documentOrder,
activeDocumentId,
switchToDocument,
closeDocument,
createDocument,
saveDocument,
options,
]);
} }