Files
resolutionflow/frontend/src/hooks/useKeyboardShortcuts.ts
chihlasm 402cdea063 feat: session quick wins (#51-#55) (#72)
* feat: add session quick wins (#51-#55)

- Session timer showing elapsed time in header (#51)
- Tab keyboard shortcut to focus notes textarea (#52)
- Repeat Last Session button on tree library page (#53)
- Auto-recovery banner for incomplete sessions (#54)
- Copy individual step to clipboard on session detail (#55)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing delete button to table and list tree views

The onDeleteTree prop was accepted but never used in TreeTableView and
TreeListView. Now both views show a trash icon (permission-gated) matching
the existing grid view behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:40:45 -05:00

98 lines
2.4 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
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,
},
// Tab to focus notes
{
key: 'Tab',
handler: () => {
document.getElementById('session-notes')?.focus()
},
},
]
useKeyboardShortcuts(shortcuts)
}
export default useKeyboardShortcuts