Add dark mode, export preview, and keyboard navigation
- Add theme store with light/dark/system modes and ThemeToggle component - Prevent flash of wrong theme on initial load via inline script - Add ExportPreviewModal for previewing session exports before download - Add copy-to-clipboard functionality to session export - Implement keyboard shortcuts for tree navigation (1-9 options, Esc back, Enter continue) - Display keyboard hints in tree navigation UI - Fix findNode to safely handle undefined structure parameter - Update page title to "Apoklisis" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
90
frontend/src/hooks/useKeyboardShortcuts.ts
Normal file
90
frontend/src/hooks/useKeyboardShortcuts.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useEffect, useCallback } from 'react'
|
||||
|
||||
interface ShortcutConfig {
|
||||
key: string
|
||||
ctrl?: boolean
|
||||
shift?: boolean
|
||||
alt?: boolean
|
||||
handler: () => void
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(shortcuts: ShortcutConfig[]) {
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
// Don't trigger shortcuts when typing in inputs
|
||||
const target = e.target as HTMLElement
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const shortcut of shortcuts) {
|
||||
if (shortcut.enabled === false) continue
|
||||
|
||||
const keyMatch = e.key === shortcut.key || e.key === shortcut.key.toLowerCase()
|
||||
const ctrlMatch = !!shortcut.ctrl === (e.ctrlKey || e.metaKey)
|
||||
const shiftMatch = !!shortcut.shift === e.shiftKey
|
||||
const altMatch = !!shortcut.alt === e.altKey
|
||||
|
||||
if (keyMatch && ctrlMatch && shiftMatch && altMatch) {
|
||||
e.preventDefault()
|
||||
shortcut.handler()
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
[shortcuts]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [handleKeyDown])
|
||||
}
|
||||
|
||||
// Convenience hook for tree navigation specifically
|
||||
export interface TreeNavigationShortcutsConfig {
|
||||
onSelectOption: (index: number) => void
|
||||
onGoBack: () => void
|
||||
onContinue: () => void
|
||||
optionCount: number
|
||||
canGoBack: boolean
|
||||
canContinue: boolean
|
||||
}
|
||||
|
||||
export function useTreeNavigationShortcuts({
|
||||
onSelectOption,
|
||||
onGoBack,
|
||||
onContinue,
|
||||
optionCount,
|
||||
canGoBack,
|
||||
canContinue,
|
||||
}: TreeNavigationShortcutsConfig) {
|
||||
const shortcuts: ShortcutConfig[] = [
|
||||
// Number keys 1-9 for options
|
||||
...Array.from({ length: Math.min(optionCount, 9) }, (_, i) => ({
|
||||
key: String(i + 1),
|
||||
handler: () => onSelectOption(i),
|
||||
})),
|
||||
// Escape to go back
|
||||
{
|
||||
key: 'Escape',
|
||||
handler: onGoBack,
|
||||
enabled: canGoBack,
|
||||
},
|
||||
// Enter to continue (for action nodes)
|
||||
{
|
||||
key: 'Enter',
|
||||
handler: onContinue,
|
||||
enabled: canContinue,
|
||||
},
|
||||
]
|
||||
|
||||
useKeyboardShortcuts(shortcuts)
|
||||
}
|
||||
|
||||
export default useKeyboardShortcuts
|
||||
Reference in New Issue
Block a user