From 242e16f75dfc8a9fdaac21c1116c4b4e656b116c Mon Sep 17 00:00:00 2001 From: TypoGenie Date: Wed, 18 Feb 2026 23:27:55 +0200 Subject: [PATCH] a11y: convert all modals to native dialog with focus management --- src/App.tsx | 58 ++++---- src/components/ExportOptionsModal.tsx | 194 ++++++++++++++------------ src/components/StylePreviewModal.tsx | 69 +++++---- src/hooks/useDialog.ts | 58 ++++++++ src/index.css | 21 +++ 5 files changed, 244 insertions(+), 156 deletions(-) create mode 100644 src/hooks/useDialog.ts diff --git a/src/App.tsx b/src/App.tsx index 7dfc71f..6e1ec26 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { Preview } from './components/Preview'; import { ZoomControl } from './components/ZoomControl'; import { useSettings } from './hooks/useSettings'; import { useTemplates } from './hooks/useTemplates'; +import { useDialog } from './hooks/useDialog'; // @ts-ignore import { parse } from 'marked'; import { Sparkles, Loader2, FileType, Keyboard, X, RefreshCw } from 'lucide-react'; @@ -14,7 +15,8 @@ import { Sparkles, Loader2, FileType, Keyboard, X, RefreshCw } from 'lucide-reac import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; // Keyboard shortcuts help component -const KeyboardShortcutsHelp: React.FC<{ onClose: () => void }> = ({ onClose }) => { +const KeyboardShortcutsHelp: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => { + const { dialogRef, handleBackdropClick, close } = useDialog(isOpen, { onClose }); const shortcuts = [ { key: '↑ / ↓', description: 'Navigate styles' }, { key: '← / →', description: 'Navigate categories (when focused)' }, @@ -26,56 +28,56 @@ const KeyboardShortcutsHelp: React.FC<{ onClose: () => void }> = ({ onClose }) = { key: 'Escape', description: 'Go back / Close' }, ]; + if (!isOpen) return null; + return ( - e.stopPropagation()} >
-
+ -

Keyboard Shortcuts

+

Keyboard Shortcuts

-
- {shortcuts.map((shortcut, index) => ( - + {shortcuts.map((shortcut) => ( +
- {shortcut.description} - - {shortcut.key} - - +
{shortcut.description}
+
+ + {shortcut.key} + +
+
))} -
+

Press Escape or click outside to close

- + ); }; @@ -194,11 +196,7 @@ const App: React.FC = () => { > {/* Keyboard Shortcuts Modal */} - - {showShortcuts && ( - setShowShortcuts(false)} /> - )} - + setShowShortcuts(false)} /> {/* Header - Fixed height */} ('semantic'); if (!isOpen) return null; @@ -17,15 +19,23 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export }; return ( -
-
+ +
e.stopPropagation()} + > {/* Header */}
-

Export Options

+

Export Options

@@ -37,109 +47,113 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export Choose how headers should be rendered in your Word document:

- {/* Option 1: High-Fidelity */} - +
+ Header rendering mode - {/* Option 2: Semantic */} -
{/* Footer */}
-
+ ); } diff --git a/src/components/StylePreviewModal.tsx b/src/components/StylePreviewModal.tsx index 1a2dd9f..46cfafb 100644 --- a/src/components/StylePreviewModal.tsx +++ b/src/components/StylePreviewModal.tsx @@ -1,8 +1,10 @@ import React, { useEffect, useRef } from 'react'; import { X } from 'lucide-react'; import { StyleOption } from '../types'; +import { useDialog } from '../hooks/useDialog'; interface StylePreviewModalProps { + isOpen: boolean; style: StyleOption; onClose: () => void; } @@ -10,42 +12,35 @@ interface StylePreviewModalProps { const SAMPLE_CONTENT = `

The Art of Typography

Typography is the art and technique of arranging type to make written language legible, readable, and appealing when displayed. The arrangement of type involves selecting typefaces, point sizes, line lengths, line-spacing, and letter-spacing.

- +

1. Visual Hierarchy

Visual hierarchy enables the reader to understand the importance of different sections. By using size, weight, and color, we guide the eye through the document in a logical flow.

- +
"Design is not just what it looks like and feels like. Design is how it works."
- +

2. Key Elements

  • Typeface: The design of the letters.
  • Contrast: Distinguishing elements effectively.
  • Consistency: Maintaining a coherent structure.
- +

Good typography is invisible. It should not distract the reader but rather enhance the reading experience, ensuring the message is delivered clearly and effectively.

`; -export const StylePreviewModal: React.FC = ({ style, onClose }) => { +export const StylePreviewModal: React.FC = ({ isOpen, style, onClose }) => { + const { dialogRef, handleBackdropClick, close } = useDialog(isOpen, { onClose }); const iframeRef = useRef(null); useEffect(() => { - const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape') onClose(); - }; - window.addEventListener('keydown', handleEscape); - return () => window.removeEventListener('keydown', handleEscape); - }, [onClose]); - - useEffect(() => { - if (!iframeRef.current) return; + if (!isOpen || !iframeRef.current) return; const doc = iframeRef.current.contentDocument; if (doc) { const html = ` - + @@ -74,29 +69,31 @@ export const StylePreviewModal: React.FC = ({ style, onC doc.write(html); doc.close(); } - }, [style]); + }, [isOpen, style]); + + if (!isOpen) return null; return ( -
{ - // Close if clicking the background overlay directly - if (e.target === e.currentTarget) { - onClose(); - } - }} + -
- +
e.stopPropagation()} + > {/* Header */}
-

{style.name}

+

{style.name}

{style.description}

- @@ -105,24 +102,24 @@ export const StylePreviewModal: React.FC = ({ style, onC {/* Content */}
-