import './Modal.css'; import { useEffect, useRef, type ReactNode } from 'react'; interface ModalProps { title: string; onClose: () => void; children: ReactNode; /** Contenu du pied (boutons d'action). */ footer?: ReactNode; } const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; /** * Boîte modale minimale : overlay, fermeture par Échap / clic extérieur, focus * piégé à l'intérieur. Base pour ConfirmDialog et tout dialogue personnalisé. */ export default function Modal({ title, onClose, children, footer }: ModalProps) { const ref = useRef(null); useEffect(() => { const previouslyFocused = document.activeElement as HTMLElement | null; ref.current?.focus(); function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') { onClose(); return; } if (e.key !== 'Tab') return; const focusables = ref.current?.querySelectorAll(FOCUSABLE); if (!focusables || focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('keydown', onKeyDown); previouslyFocused?.focus(); }; }, [onClose]); return (
e.stopPropagation()} >

{title}

{children}
{footer &&
{footer}
}
); }