a11y: convert all modals to native dialog with focus management

This commit is contained in:
TypoGenie
2026-02-18 23:27:55 +02:00
parent 7d5af9e39c
commit 242e16f75d
5 changed files with 244 additions and 156 deletions

View File

@@ -7,6 +7,7 @@ import { Preview } from './components/Preview';
import { ZoomControl } from './components/ZoomControl'; import { ZoomControl } from './components/ZoomControl';
import { useSettings } from './hooks/useSettings'; import { useSettings } from './hooks/useSettings';
import { useTemplates } from './hooks/useTemplates'; import { useTemplates } from './hooks/useTemplates';
import { useDialog } from './hooks/useDialog';
// @ts-ignore // @ts-ignore
import { parse } from 'marked'; import { parse } from 'marked';
import { Sparkles, Loader2, FileType, Keyboard, X, RefreshCw } from 'lucide-react'; 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'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
// Keyboard shortcuts help component // 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 = [ const shortcuts = [
{ key: '↑ / ↓', description: 'Navigate styles' }, { key: '↑ / ↓', description: 'Navigate styles' },
{ key: '← / →', description: 'Navigate categories (when focused)' }, { key: '← / →', description: 'Navigate categories (when focused)' },
@@ -26,56 +28,56 @@ const KeyboardShortcutsHelp: React.FC<{ onClose: () => void }> = ({ onClose }) =
{ key: 'Escape', description: 'Go back / Close' }, { key: 'Escape', description: 'Go back / Close' },
]; ];
if (!isOpen) return null;
return ( return (
<motion.div <dialog
initial={{ opacity: 0 }} ref={dialogRef}
animate={{ opacity: 1 }} onClick={handleBackdropClick}
exit={{ opacity: 0 }} aria-labelledby="shortcuts-title"
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4" className="fixed inset-0 z-50 p-4"
onClick={onClose}
> >
<motion.div <motion.div
initial={{ scale: 0.9, opacity: 0 }} initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} 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" className="bg-zinc-900 border border-zinc-700 rounded-2xl p-6 max-w-md w-full shadow-2xl"
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<div className="flex items-center gap-3"> <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} /> <Keyboard size={20} />
</div> </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> </div>
<button <button
onClick={onClose} onClick={close}
className="p-1 hover:bg-zinc-800 rounded-lg text-zinc-400 hover:text-white transition-colors" 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} /> <X size={20} />
</button> </button>
</div> </div>
<div className="space-y-3"> <dl className="space-y-3">
{shortcuts.map((shortcut, index) => ( {shortcuts.map((shortcut) => (
<motion.div <div
key={shortcut.key} 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" className="flex justify-between items-center py-2 border-b border-zinc-800 last:border-0"
> >
<span className="text-zinc-400">{shortcut.description}</span> <dt className="text-zinc-400">{shortcut.description}</dt>
<kbd className="px-2 py-1 bg-zinc-800 rounded text-sm font-mono text-zinc-300 border border-zinc-700"> <dd className="ml-4">
{shortcut.key} <kbd className="px-2 py-1 bg-zinc-800 rounded text-sm font-mono text-zinc-300 border border-zinc-700">
</kbd> {shortcut.key}
</motion.div> </kbd>
</dd>
</div>
))} ))}
</div> </dl>
<p className="mt-6 text-xs text-zinc-400 text-center"> <p className="mt-6 text-xs text-zinc-400 text-center">
Press Escape or click outside to close Press Escape or click outside to close
</p> </p>
</motion.div> </motion.div>
</motion.div> </dialog>
); );
}; };
@@ -194,11 +196,7 @@ const App: React.FC = () => {
> >
{/* Keyboard Shortcuts Modal */} {/* Keyboard Shortcuts Modal */}
<AnimatePresence> <KeyboardShortcutsHelp isOpen={showShortcuts} onClose={() => setShowShortcuts(false)} />
{showShortcuts && (
<KeyboardShortcutsHelp onClose={() => setShowShortcuts(false)} />
)}
</AnimatePresence>
{/* Header - Fixed height */} {/* Header - Fixed height */}
<motion.header <motion.header

View File

@@ -1,5 +1,6 @@
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { useDialog } from '../hooks/useDialog';
interface ExportOptionsModalProps { interface ExportOptionsModalProps {
isOpen: boolean; isOpen: boolean;
@@ -8,6 +9,7 @@ interface ExportOptionsModalProps {
} }
export default function ExportOptionsModal({ isOpen, onClose, onExport }: ExportOptionsModalProps) { export default function ExportOptionsModal({ isOpen, onClose, onExport }: ExportOptionsModalProps) {
const { dialogRef, handleBackdropClick, close } = useDialog(isOpen, { onClose });
const [selectedMode, setSelectedMode] = useState<'table' | 'semantic'>('semantic'); const [selectedMode, setSelectedMode] = useState<'table' | 'semantic'>('semantic');
if (!isOpen) return null; if (!isOpen) return null;
@@ -17,15 +19,23 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export
}; };
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-zinc-950/80 backdrop-blur-sm"> <dialog
<div className="relative w-full max-w-2xl bg-zinc-900 rounded-2xl shadow-2xl m-4 overflow-hidden border border-zinc-700"> ref={dialogRef}
onClick={handleBackdropClick}
aria-labelledby="export-title"
className="fixed inset-0 z-50 p-4"
>
<div
className="relative w-full max-w-2xl bg-zinc-900 rounded-2xl shadow-2xl overflow-hidden border border-zinc-700"
onClick={e => e.stopPropagation()}
>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-zinc-800 bg-zinc-900/50"> <div className="flex items-center justify-between px-6 py-4 border-b border-zinc-800 bg-zinc-900/50">
<h2 className="text-xl font-semibold text-white">Export Options</h2> <h2 id="export-title" className="text-xl font-semibold text-white">Export Options</h2>
<button <button
onClick={onClose} onClick={close}
className="p-1 text-zinc-400 hover:text-white transition-colors rounded-md hover:bg-zinc-800" className="p-2 min-w-[44px] min-h-[44px] flex items-center justify-center text-zinc-400 hover:text-white transition-colors rounded-md hover:bg-zinc-800"
aria-label="Close" aria-label="Close export options"
> >
<X size={20} /> <X size={20} />
</button> </button>
@@ -37,109 +47,113 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export
Choose how headers should be rendered in your Word document: Choose how headers should be rendered in your Word document:
</p> </p>
{/* Option 1: High-Fidelity */} <fieldset>
<label <legend className="sr-only">Header rendering mode</legend>
className={`block p-4 border-2 rounded-lg cursor-pointer transition-all ${selectedMode === 'table'
? 'border-indigo-500 bg-indigo-500/10'
: 'border-zinc-700 hover:border-zinc-600 bg-zinc-800/50'
}`}
>
<div className="flex items-start">
<input
type="radio"
name="exportMode"
value="table"
checked={selectedMode === 'table'}
onChange={() => setSelectedMode('table')}
className="mt-1 mr-3 text-indigo-600 focus:ring-indigo-500 bg-zinc-700 border-zinc-600"
/>
<div className="flex-1">
<div className="font-semibold text-white mb-2">High-Fidelity Layout</div>
<div className="space-y-1 text-sm">
<div className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-zinc-300">Perfect padding and border alignment</span>
</div>
<div className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-zinc-300">Backgrounds contained precisely</span>
</div>
<div className="flex items-start">
<span className="text-red-500 mr-2"></span>
<span className="text-zinc-300">No automatic Table of Contents</span>
</div>
<div className="flex items-start">
<span className="text-red-500 mr-2"></span>
<span className="text-zinc-300">Document outline/navigation disabled</span>
</div>
</div>
<div className="mt-3 text-xs text-zinc-400 italic">
Best for: Portfolios, brochures, print-ready designs
</div>
</div>
</div>
</label>
{/* Option 2: Semantic */} {/* Option 1: High-Fidelity */}
<label <label
className={`block p-4 border-2 rounded-lg cursor-pointer transition-all ${selectedMode === 'semantic' className={`block p-4 border-2 rounded-lg cursor-pointer transition-all ${selectedMode === 'table'
? 'border-indigo-500 bg-indigo-500/10' ? 'border-indigo-500 bg-indigo-500/10'
: 'border-zinc-700 hover:border-zinc-600 bg-zinc-800/50' : 'border-zinc-700 hover:border-zinc-600 bg-zinc-800/50'
}`} }`}
> >
<div className="flex items-start"> <div className="flex items-start">
<input <input
type="radio" type="radio"
name="exportMode" name="exportMode"
value="semantic" value="table"
checked={selectedMode === 'semantic'} checked={selectedMode === 'table'}
onChange={() => setSelectedMode('semantic')} onChange={() => setSelectedMode('table')}
className="mt-1 mr-3 text-indigo-600 focus:ring-indigo-500 bg-zinc-700 border-zinc-600" className="mt-1 mr-3 text-indigo-600 focus:ring-indigo-500 bg-zinc-700 border-zinc-600"
/> />
<div className="flex-1"> <div className="flex-1">
<div className="font-semibold text-white mb-2">Semantic Structure</div> <div className="font-semibold text-white mb-2">High-Fidelity Layout</div>
<div className="space-y-1 text-sm"> <div className="space-y-1 text-sm">
<div className="flex items-start"> <div className="flex items-start">
<span className="text-green-500 mr-2"></span> <span className="text-green-500 mr-2" aria-hidden="true">&#10003;</span>
<span className="text-zinc-300">Auto-generated Table of Contents</span> <span className="text-zinc-300">Perfect padding and border alignment</span>
</div>
<div className="flex items-start">
<span className="text-green-500 mr-2" aria-hidden="true">&#10003;</span>
<span className="text-zinc-300">Backgrounds contained precisely</span>
</div>
<div className="flex items-start">
<span className="text-red-500 mr-2" aria-hidden="true">&#10007;</span>
<span className="text-zinc-300">No automatic Table of Contents</span>
</div>
<div className="flex items-start">
<span className="text-red-500 mr-2" aria-hidden="true">&#10007;</span>
<span className="text-zinc-300">Document outline/navigation disabled</span>
</div>
</div> </div>
<div className="flex items-start"> <div className="mt-3 text-xs text-zinc-400 italic">
<span className="text-green-500 mr-2"></span> Best for: Portfolios, brochures, print-ready designs
<span className="text-zinc-300">Document navigation panel works</span>
</div> </div>
<div className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-zinc-300">Screen reader accessible</span>
</div>
<div className="flex items-start">
<span className="text-yellow-500 mr-2"></span>
<span className="text-zinc-300">Minor padding/border alignment issues</span>
</div>
</div>
<div className="mt-3 text-xs text-zinc-400 italic">
Best for: Academic papers, reports, accessible documents
</div> </div>
</div> </div>
</div> </label>
</label>
{/* Option 2: Semantic */}
<label
className={`block p-4 mt-4 border-2 rounded-lg cursor-pointer transition-all ${selectedMode === 'semantic'
? 'border-indigo-500 bg-indigo-500/10'
: 'border-zinc-700 hover:border-zinc-600 bg-zinc-800/50'
}`}
>
<div className="flex items-start">
<input
type="radio"
name="exportMode"
value="semantic"
checked={selectedMode === 'semantic'}
onChange={() => setSelectedMode('semantic')}
className="mt-1 mr-3 text-indigo-600 focus:ring-indigo-500 bg-zinc-700 border-zinc-600"
/>
<div className="flex-1">
<div className="font-semibold text-white mb-2">Semantic Structure</div>
<div className="space-y-1 text-sm">
<div className="flex items-start">
<span className="text-green-500 mr-2" aria-hidden="true">&#10003;</span>
<span className="text-zinc-300">Auto-generated Table of Contents</span>
</div>
<div className="flex items-start">
<span className="text-green-500 mr-2" aria-hidden="true">&#10003;</span>
<span className="text-zinc-300">Document navigation panel works</span>
</div>
<div className="flex items-start">
<span className="text-green-500 mr-2" aria-hidden="true">&#10003;</span>
<span className="text-zinc-300">Screen reader accessible</span>
</div>
<div className="flex items-start">
<span className="text-yellow-500 mr-2" aria-hidden="true">&#9888;</span>
<span className="text-zinc-300">Minor padding/border alignment issues</span>
</div>
</div>
<div className="mt-3 text-xs text-zinc-400 italic">
Best for: Academic papers, reports, accessible documents
</div>
</div>
</div>
</label>
</fieldset>
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-zinc-800 bg-zinc-900"> <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-zinc-800 bg-zinc-900">
<button <button
onClick={onClose} onClick={close}
className="px-4 py-2 text-sm font-medium text-zinc-300 bg-zinc-800 border border-zinc-700 rounded-md hover:bg-zinc-700 transition-colors" className="px-5 py-3 text-sm font-medium text-zinc-300 bg-zinc-800 border border-zinc-700 rounded-md hover:bg-zinc-700 transition-colors"
> >
Cancel Cancel
</button> </button>
<button <button
onClick={handleExport} onClick={handleExport}
className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-500 transition-colors" className="px-5 py-3 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-500 transition-colors"
> >
Export to Word Export to Word
</button> </button>
</div> </div>
</div> </div>
</div> </dialog>
); );
} }

View File

@@ -1,8 +1,10 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { StyleOption } from '../types'; import { StyleOption } from '../types';
import { useDialog } from '../hooks/useDialog';
interface StylePreviewModalProps { interface StylePreviewModalProps {
isOpen: boolean;
style: StyleOption; style: StyleOption;
onClose: () => void; onClose: () => void;
} }
@@ -10,42 +12,35 @@ interface StylePreviewModalProps {
const SAMPLE_CONTENT = ` const SAMPLE_CONTENT = `
<h1>The Art of Typography</h1> <h1>The Art of Typography</h1>
<p>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.</p> <p>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.</p>
<h2>1. Visual Hierarchy</h2> <h2>1. Visual Hierarchy</h2>
<p>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.</p> <p>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.</p>
<blockquote> <blockquote>
"Design is not just what it looks like and feels like. Design is how it works." "Design is not just what it looks like and feels like. Design is how it works."
</blockquote> </blockquote>
<h2>2. Key Elements</h2> <h2>2. Key Elements</h2>
<ul> <ul>
<li><strong>Typeface:</strong> The design of the letters.</li> <li><strong>Typeface:</strong> The design of the letters.</li>
<li><strong>Contrast:</strong> Distinguishing elements effectively.</li> <li><strong>Contrast:</strong> Distinguishing elements effectively.</li>
<li><strong>Consistency:</strong> Maintaining a coherent structure.</li> <li><strong>Consistency:</strong> Maintaining a coherent structure.</li>
</ul> </ul>
<p>Good typography is invisible. It should not distract the reader but rather enhance the reading experience, ensuring the message is delivered clearly and effectively.</p> <p>Good typography is invisible. It should not distract the reader but rather enhance the reading experience, ensuring the message is delivered clearly and effectively.</p>
`; `;
export const StylePreviewModal: React.FC<StylePreviewModalProps> = ({ style, onClose }) => { export const StylePreviewModal: React.FC<StylePreviewModalProps> = ({ isOpen, style, onClose }) => {
const { dialogRef, handleBackdropClick, close } = useDialog(isOpen, { onClose });
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => { useEffect(() => {
const handleEscape = (e: KeyboardEvent) => { if (!isOpen || !iframeRef.current) return;
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [onClose]);
useEffect(() => {
if (!iframeRef.current) return;
const doc = iframeRef.current.contentDocument; const doc = iframeRef.current.contentDocument;
if (doc) { if (doc) {
const html = ` const html = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -74,29 +69,31 @@ export const StylePreviewModal: React.FC<StylePreviewModalProps> = ({ style, onC
doc.write(html); doc.write(html);
doc.close(); doc.close();
} }
}, [style]); }, [isOpen, style]);
if (!isOpen) return null;
return ( return (
<div <dialog
className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm animate-in fade-in duration-200" ref={dialogRef}
onClick={(e) => { onClick={handleBackdropClick}
// Close if clicking the background overlay directly aria-labelledby="preview-title"
if (e.target === e.currentTarget) { className="fixed inset-0 z-[100] p-4"
onClose();
}
}}
> >
<div className="relative w-full max-w-3xl bg-zinc-900 rounded-2xl border border-zinc-700 shadow-2xl overflow-hidden flex flex-col h-[85vh]"> <div
className="relative w-full max-w-3xl bg-zinc-900 rounded-2xl border border-zinc-700 shadow-2xl overflow-hidden flex flex-col h-[85vh]"
onClick={e => e.stopPropagation()}
>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-4 border-b border-zinc-800 bg-zinc-900/50"> <div className="flex items-center justify-between p-4 border-b border-zinc-800 bg-zinc-900/50">
<div> <div>
<h3 className="text-xl font-bold text-white">{style.name}</h3> <h3 id="preview-title" className="text-xl font-bold text-white">{style.name}</h3>
<p className="text-sm text-zinc-400">{style.description}</p> <p className="text-sm text-zinc-400">{style.description}</p>
</div> </div>
<button <button
onClick={onClose} onClick={close}
className="p-2 text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-full transition-colors" className="p-2 min-w-[44px] min-h-[44px] flex items-center justify-center text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-full transition-colors"
aria-label="Close preview"
> >
<X size={24} /> <X size={24} />
</button> </button>
@@ -105,24 +102,24 @@ export const StylePreviewModal: React.FC<StylePreviewModalProps> = ({ style, onC
{/* Content */} {/* Content */}
<div className="flex-grow bg-zinc-800 p-1 overflow-hidden relative"> <div className="flex-grow bg-zinc-800 p-1 overflow-hidden relative">
<div className="w-full h-full bg-white rounded-lg overflow-hidden shadow-inner"> <div className="w-full h-full bg-white rounded-lg overflow-hidden shadow-inner">
<iframe <iframe
ref={iframeRef} ref={iframeRef}
title="Style Preview" title="Style Preview"
className="w-full h-full border-0" className="w-full h-full border-0"
/> />
</div> </div>
</div> </div>
{/* Footer */} {/* Footer */}
<div className="p-4 border-t border-zinc-800 bg-zinc-900 flex justify-end"> <div className="p-4 border-t border-zinc-800 bg-zinc-900 flex justify-end">
<button <button
onClick={onClose} onClick={close}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg transition-colors" className="px-5 py-3 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg transition-colors"
> >
Close Preview Close Preview
</button> </button>
</div> </div>
</div> </div>
</div> </dialog>
); );
}; };

58
src/hooks/useDialog.ts Normal file
View File

@@ -0,0 +1,58 @@
import { useRef, useEffect, useCallback } from 'react';
interface UseDialogOptions {
onClose: () => void;
}
export function useDialog(isOpen: boolean, options: UseDialogOptions) {
const dialogRef = useRef<HTMLDialogElement>(null);
const triggerRef = useRef<Element | null>(null);
const close = useCallback(() => {
dialogRef.current?.close();
options.onClose();
}, [options.onClose]);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
triggerRef.current = document.activeElement;
if (!dialog.open) {
dialog.showModal();
}
} else {
if (dialog.open) {
dialog.close();
}
// Restore focus to trigger
if (triggerRef.current instanceof HTMLElement) {
triggerRef.current.focus();
}
}
}, [isOpen]);
// Handle native cancel event (Escape key)
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
const handleCancel = (e: Event) => {
e.preventDefault();
close();
};
dialog.addEventListener('cancel', handleCancel);
return () => dialog.removeEventListener('cancel', handleCancel);
}, [close]);
// Handle backdrop click
const handleBackdropClick = useCallback((e: React.MouseEvent<HTMLDialogElement>) => {
if (e.target === e.currentTarget) {
close();
}
}, [close]);
return { dialogRef, handleBackdropClick, close };
}

View File

@@ -138,3 +138,24 @@ body ::-webkit-scrollbar-thumb:hover,
white-space: nowrap; white-space: nowrap;
border-width: 0; border-width: 0;
} }
/* Native dialog styles */
dialog {
background: transparent;
border: none;
padding: 0;
max-width: 100vw;
max-height: 100vh;
overflow: visible;
}
dialog::backdrop {
background: rgba(9, 9, 11, 0.8);
backdrop-filter: blur(4px);
}
dialog[open] {
display: flex;
align-items: center;
justify-content: center;
}