a11y: convert all modals to native dialog with focus management
This commit is contained in:
58
src/App.tsx
58
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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
<dialog
|
||||
ref={dialogRef}
|
||||
onClick={handleBackdropClick}
|
||||
aria-labelledby="shortcuts-title"
|
||||
className="fixed inset-0 z-50 p-4"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
className="bg-zinc-900 border border-zinc-700 rounded-2xl p-6 max-w-md w-full shadow-2xl"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-indigo-500/10 rounded-lg text-indigo-400">
|
||||
<div className="p-2 bg-indigo-500/10 rounded-lg text-indigo-400" aria-hidden="true">
|
||||
<Keyboard size={20} />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-white">Keyboard Shortcuts</h2>
|
||||
<h2 id="shortcuts-title" className="text-xl font-bold text-white">Keyboard Shortcuts</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 hover:bg-zinc-800 rounded-lg text-zinc-400 hover:text-white transition-colors"
|
||||
onClick={close}
|
||||
className="p-2 min-w-[44px] min-h-[44px] flex items-center justify-center hover:bg-zinc-800 rounded-lg text-zinc-400 hover:text-white transition-colors"
|
||||
aria-label="Close shortcuts"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{shortcuts.map((shortcut, index) => (
|
||||
<motion.div
|
||||
<dl className="space-y-3">
|
||||
{shortcuts.map((shortcut) => (
|
||||
<div
|
||||
key={shortcut.key}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="flex justify-between items-center py-2 border-b border-zinc-800 last:border-0"
|
||||
>
|
||||
<span className="text-zinc-400">{shortcut.description}</span>
|
||||
<kbd className="px-2 py-1 bg-zinc-800 rounded text-sm font-mono text-zinc-300 border border-zinc-700">
|
||||
{shortcut.key}
|
||||
</kbd>
|
||||
</motion.div>
|
||||
<dt className="text-zinc-400">{shortcut.description}</dt>
|
||||
<dd className="ml-4">
|
||||
<kbd className="px-2 py-1 bg-zinc-800 rounded text-sm font-mono text-zinc-300 border border-zinc-700">
|
||||
{shortcut.key}
|
||||
</kbd>
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</dl>
|
||||
<p className="mt-6 text-xs text-zinc-400 text-center">
|
||||
Press Escape or click outside to close
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -194,11 +196,7 @@ const App: React.FC = () => {
|
||||
>
|
||||
|
||||
{/* Keyboard Shortcuts Modal */}
|
||||
<AnimatePresence>
|
||||
{showShortcuts && (
|
||||
<KeyboardShortcutsHelp onClose={() => setShowShortcuts(false)} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<KeyboardShortcutsHelp isOpen={showShortcuts} onClose={() => setShowShortcuts(false)} />
|
||||
|
||||
{/* Header - Fixed height */}
|
||||
<motion.header
|
||||
|
||||
Reference in New Issue
Block a user