a11y: convert all modals to native dialog with focus management
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user