feat: centralize keyboard shortcuts through shortcut manager

All keyboard shortcut labels in the MenuBar now come from the
centralized keyboard shortcut manager via the new useShortcutLabels
hook, eliminating hardcoded duplicates.

Changes:
- Add useShortcutLabels hook for retrieving formatted shortcut labels
- Update MenuBar to use dynamic shortcut labels from manager
- Platform-aware display (Cmd on Mac, Ctrl elsewhere)
- Shortcuts automatically update if changed in manager
- Fix MUI icon fontSize prop issue in LeftPanel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-10-10 22:38:28 +02:00
parent cd1a93f88f
commit e778b29b56
3 changed files with 70 additions and 8 deletions

View file

@ -6,6 +6,7 @@ import DocumentManager from '../Workspace/DocumentManager';
import NodeTypeConfigModal from '../Config/NodeTypeConfig'; import NodeTypeConfigModal from '../Config/NodeTypeConfig';
import EdgeTypeConfigModal from '../Config/EdgeTypeConfig'; import EdgeTypeConfigModal from '../Config/EdgeTypeConfig';
import { useConfirm } from '../../hooks/useConfirm'; import { useConfirm } from '../../hooks/useConfirm';
import { useShortcutLabels } from '../../hooks/useShortcutLabels';
/** /**
* MenuBar Component * MenuBar Component
@ -30,6 +31,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
const [showEdgeConfig, setShowEdgeConfig] = useState(false); const [showEdgeConfig, setShowEdgeConfig] = useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const { confirm, ConfirmDialogComponent } = useConfirm(); const { confirm, ConfirmDialogComponent } = useConfirm();
const { getShortcutLabel } = useShortcutLabels();
const { const {
createDocument, createDocument,
@ -168,7 +170,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>New Document</span> <span>New Document</span>
<span className="text-xs text-gray-400">Ctrl+N</span> {getShortcutLabel('new-document') && (
<span className="text-xs text-gray-400">{getShortcutLabel('new-document')}</span>
)}
</button> </button>
<button <button
onClick={handleNewDocumentFromTemplate} onClick={handleNewDocumentFromTemplate}
@ -182,7 +186,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>Document Manager</span> <span>Document Manager</span>
<span className="text-xs text-gray-400">Ctrl+O</span> {getShortcutLabel('open-document-manager') && (
<span className="text-xs text-gray-400">{getShortcutLabel('open-document-manager')}</span>
)}
</button> </button>
<hr className="my-1 border-gray-200" /> <hr className="my-1 border-gray-200" />
@ -198,7 +204,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>Export Document</span> <span>Export Document</span>
<span className="text-xs text-gray-400">Ctrl+S</span> {getShortcutLabel('save-document') && (
<span className="text-xs text-gray-400">{getShortcutLabel('save-document')}</span>
)}
</button> </button>
<button <button
onClick={handleExportAll} onClick={handleExportAll}
@ -290,7 +298,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>Fit View to Content</span> <span>Fit View to Content</span>
<span className="text-xs text-gray-400">F</span> {getShortcutLabel('fit-view') && (
<span className="text-xs text-gray-400">{getShortcutLabel('fit-view')}</span>
)}
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -300,7 +310,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>Select All</span> <span>Select All</span>
<span className="text-xs text-gray-400">Ctrl+A</span> {getShortcutLabel('select-all') && (
<span className="text-xs text-gray-400">{getShortcutLabel('select-all')}</span>
)}
</button> </button>
</div> </div>
)} )}
@ -330,7 +342,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ onOpenHelp, onFitView, onSelectAll })
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between" className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
> >
<span>Keyboard Shortcuts</span> <span>Keyboard Shortcuts</span>
<span className="text-xs text-gray-400">?</span> {getShortcutLabel('show-help') && (
<span className="text-xs text-gray-400">{getShortcutLabel('show-help')}</span>
)}
</button> </button>
</div> </div>
)} )}

View file

@ -125,8 +125,8 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
title={nodeType.description} title={nodeType.description}
> >
{IconComponent ? ( {IconComponent ? (
<div className="w-5 h-5 flex items-center justify-center" style={{ color: textColor }}> <div className="w-5 h-5 flex items-center justify-center" style={{ color: textColor, fontSize: '1.25rem' }}>
<IconComponent style={{ fontSize: '1.25rem' }} /> <IconComponent />
</div> </div>
) : ( ) : (
<div <div

View file

@ -0,0 +1,48 @@
import { useCallback } from 'react';
import { useKeyboardShortcuts } from './useKeyboardShortcuts';
/**
* useShortcutLabels Hook
*
* Provides a convenient API for getting formatted keyboard shortcut labels
* from the centralized keyboard shortcut manager.
*
* This hook is useful for displaying shortcuts in:
* - Menu items
* - Tooltips
* - Context menus
* - Button labels
* - Any UI element that needs to show keyboard shortcuts
*
* Usage:
* ```tsx
* const { getShortcutLabel } = useShortcutLabels();
* const label = getShortcutLabel('new-document'); // Returns "Ctrl+N" or "Cmd+N" on Mac
* ```
*/
export function useShortcutLabels() {
const { shortcuts } = useKeyboardShortcuts();
/**
* Get the formatted label for a keyboard shortcut by its ID
*
* @param shortcutId - The unique identifier for the shortcut
* @returns Formatted shortcut string (e.g., "Ctrl+N") or null if shortcut doesn't exist or is disabled
*/
const getShortcutLabel = useCallback(
(shortcutId: string): string | null => {
const allShortcuts = shortcuts.getAllShortcuts();
const shortcut = allShortcuts.find((s) => s.id === shortcutId);
// Return null if shortcut doesn't exist or is disabled
if (!shortcut || shortcut.enabled === false) {
return null;
}
return shortcuts.formatShortcut(shortcut);
},
[shortcuts]
);
return { getShortcutLabel };
}