- Add copy-to-clipboard buttons on command blocks (action + custom step nodes) - Add keyboard shortcuts modal (?) with option loading spinners and [Esc] hint - Restyle session timer as a pill badge for better visibility - Add prominent "Create Tree" CTA to MyTreesPage header and empty state - Make breadcrumb items clickable to rewind navigation to any previous step Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
2.6 KiB
TypeScript
107 lines
2.6 KiB
TypeScript
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
|
|
onShowShortcuts?: () => void
|
|
optionCount: number
|
|
canGoBack: boolean
|
|
canContinue: boolean
|
|
}
|
|
|
|
export function useTreeNavigationShortcuts({
|
|
onSelectOption,
|
|
onGoBack,
|
|
onContinue,
|
|
onShowShortcuts,
|
|
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,
|
|
},
|
|
// Tab to focus notes
|
|
{
|
|
key: 'Tab',
|
|
handler: () => {
|
|
document.getElementById('session-notes')?.focus()
|
|
},
|
|
},
|
|
// ? to show shortcuts modal
|
|
{
|
|
key: '?',
|
|
shift: true,
|
|
handler: () => onShowShortcuts?.(),
|
|
enabled: !!onShowShortcuts,
|
|
},
|
|
]
|
|
|
|
useKeyboardShortcuts(shortcuts)
|
|
}
|
|
|
|
export default useKeyboardShortcuts
|