diff --git a/src/components/KeyboardShortcutsModal.tsx b/src/components/KeyboardShortcutsModal.tsx index ba52379..2c642a0 100644 --- a/src/components/KeyboardShortcutsModal.tsx +++ b/src/components/KeyboardShortcutsModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { X } from 'lucide-react'; interface KeyboardShortcutsModalProps { @@ -21,7 +21,19 @@ const shortcuts = [ { key: '?', description: 'Show this help' }, ]; +const FOCUSABLE_SELECTORS = [ + 'a[href]', + 'button:not([disabled])', + 'textarea:not([disabled])', + 'input:not([disabled])', + 'select:not([disabled])', + '[tabindex]:not([tabindex="-1"])', +].join(', '); + export default function KeyboardShortcutsModal({ open, onClose }: KeyboardShortcutsModalProps) { + const dialogRef = useRef(null); + + // Escape / ? key handler useEffect(() => { if (!open) return; const handleKey = (e: KeyboardEvent) => { @@ -33,6 +45,49 @@ export default function KeyboardShortcutsModal({ open, onClose }: KeyboardShortc return () => window.removeEventListener('keydown', handleKey); }, [open, onClose]); + // Focus trapping + useEffect(() => { + if (!open) return; + + const dialog = dialogRef.current; + if (!dialog) return; + + // Move focus into the dialog on open + const firstFocusable = dialog.querySelector(FOCUSABLE_SELECTORS); + firstFocusable?.focus(); + + const handleTabKey = (e: KeyboardEvent) => { + if (e.key !== 'Tab') return; + + const focusableElements = Array.from( + dialog.querySelectorAll(FOCUSABLE_SELECTORS) + ).filter((el) => !el.closest('[disabled]')); + + if (focusableElements.length === 0) { + e.preventDefault(); + return; + } + + const first = focusableElements[0]; + const last = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey) { + if (document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + } else { + if (document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }; + + dialog.addEventListener('keydown', handleTabKey); + return () => dialog.removeEventListener('keydown', handleTabKey); + }, [open]); + if (!open) return null; return ( @@ -41,11 +96,20 @@ export default function KeyboardShortcutsModal({ open, onClose }: KeyboardShortc onClick={onClose} >
e.stopPropagation()} >
-

Keyboard Shortcuts

+

+ Keyboard Shortcuts +