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:
Michael Chihlas
2026-01-28 21:19:57 -05:00
parent 4cee013733
commit 5a0dff1da9
9 changed files with 455 additions and 40 deletions

View 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