mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
refactor(emailclient): full a11y pass, component decomposition, keyboard shortcuts
Accessibility: - All components: data-testid, aria-label, aria-pressed, aria-current - ComposeWindow: role=dialog, aria-modal, focus trap, Escape handler - EmailCard: role=article, keyboard nav (Enter/Space), aria-current - ThreadList: role=list with listitem wrappers, role=status empty state - FolderNavigation: role=navigation, role=list, aria-current - RecipientInput: role=group, aria-label per type, onKeyPress→onKeyDown - BodyEditor: role=toolbar, aria-pressed on mode buttons - StarButton: MaterialIcon (star/star_border), dynamic aria-label - MarkAsReadCheckbox: dynamic label based on read state - EmailHeader: role=banner, <time> element, data-testids Component decomposition: - Extract useEmailClient hook (state + callbacks) - Extract demo-emails.ts (data constants) - EmailClientContent.tsx: 267→127 LOC (composition only) New: useKeyboardShortcuts hook (Gmail-style, 47 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,3 +10,4 @@ export { useMailboxes, type UseMailboxesResult, type Folder } from './useMailbox
|
||||
export { useAccounts, type UseAccountsResult, type EmailAccount } from './useAccounts'
|
||||
export { useCompose, type UseComposeResult, type EmailDraft } from './useCompose'
|
||||
export { useMessages, type UseMessagesResult, type Message } from './useMessages'
|
||||
export { useKeyboardShortcuts, type KeyboardShortcutMap } from './useKeyboardShortcuts'
|
||||
|
||||
47
hooks/email/useKeyboardShortcuts.ts
Normal file
47
hooks/email/useKeyboardShortcuts.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* Map of shortcut keys to handler functions.
|
||||
* Use '#' for Shift+3, '?' for Shift+/, etc.
|
||||
*/
|
||||
export interface KeyboardShortcutMap {
|
||||
[key: string]: () => void
|
||||
}
|
||||
|
||||
const IGNORED_TAGS = new Set([
|
||||
'INPUT', 'TEXTAREA', 'SELECT',
|
||||
])
|
||||
|
||||
function isEditable(el: EventTarget | null): boolean {
|
||||
if (!el || !(el instanceof HTMLElement)) return false
|
||||
if (IGNORED_TAGS.has(el.tagName)) return true
|
||||
return el.isContentEditable
|
||||
}
|
||||
|
||||
/**
|
||||
* Gmail-style keyboard shortcuts hook.
|
||||
* Fires mapped actions on keydown unless an
|
||||
* input/textarea/contenteditable is focused.
|
||||
* @param shortcuts - key-to-action map
|
||||
* @param enabled - toggle listener (default true)
|
||||
*/
|
||||
export function useKeyboardShortcuts(
|
||||
shortcuts: KeyboardShortcutMap,
|
||||
enabled = true
|
||||
): void {
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (isEditable(e.target)) return
|
||||
const action = shortcuts[e.key]
|
||||
if (action) {
|
||||
e.preventDefault()
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handler)
|
||||
return () => window.removeEventListener('keydown', handler)
|
||||
}, [shortcuts, enabled])
|
||||
}
|
||||
Reference in New Issue
Block a user