feat: MVP-5 Sprint 2 - Command Palette and Global Shortcuts

- Add Command Palette (Ctrl+K / Cmd+K) with search and navigation
- Add global keyboard shortcuts: g h (dashboard), g n (notes), n (new note)
- Add keyboard shortcuts help dialog (?)
- Add shortcuts provider component
- Shortcuts ignore inputs/textareas for proper UX
This commit is contained in:
2026-03-22 18:22:28 -03:00
parent 8c80a12b81
commit cde0a143a5
6 changed files with 215 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
const SHORTCUTS: Record<string, string> = {
'g h': '/', // go to dashboard
'g n': '/notes', // go to notes
'n': '/new', // new note
'/': '/notes', // focus search (navigate to notes)
'?': 'show-help', // show shortcuts dialog
}
export function useGlobalShortcuts() {
const router = useRouter()
const [showHelp, setShowHelp] = useState(false)
useEffect(() => {
let keystroke = ''
let timeout: NodeJS.Timeout
const handleKeyDown = (e: KeyboardEvent) => {
// Ignore if in input/textarea/contenteditable
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable) {
return
}
// Handle ? for help
if (e.key === '?' && !e.shiftKey) {
e.preventDefault()
setShowHelp(true)
return
}
// Track g + key combos
if (e.key === 'g' && !e.metaKey && !e.ctrlKey) {
keystroke = 'g'
clearTimeout(timeout)
timeout = setTimeout(() => { keystroke = '' }, 500)
return
}
if (keystroke === 'g') {
const combo = 'g ' + e.key
if (SHORTCUTS[combo]) {
e.preventDefault()
router.push(SHORTCUTS[combo])
keystroke = ''
}
}
// Direct shortcuts
if (e.key === 'n' && !e.metaKey && !e.ctrlKey) {
e.preventDefault()
router.push('/new')
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [router])
return { showHelp, setShowHelp }
}