fix(dsfr): Modal avec overlay visible et fermeture fiable

L'ancienne implémentation (<dialog> plein écran transparent) rendait la page
inutilisable au clic sur éditer/supprimer : overlay invisible captant tous les
clics, sans fond assombri ni fermeture au clic extérieur. Remplacé par un
overlay React maîtrisé (fond assombri, clic extérieur/Échap/bouton ferment).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Azmog
2026-06-19 00:39:32 +02:00
parent df911efde1
commit 43a149381f
+44 -43
View File
@@ -12,11 +12,10 @@ interface ModalProps {
size?: ModalSize;
}
// Largeur de la colonne DSFR selon la taille demandée.
const sizeColClasses: Record<ModalSize, string> = {
sm: "fr-col-12 fr-col-md-4",
md: "fr-col-12 fr-col-md-6",
lg: "fr-col-12 fr-col-md-8",
const maxWidthBySize: Record<ModalSize, string> = {
sm: "480px",
md: "672px",
lg: "896px",
};
export default function Modal({
@@ -34,10 +33,9 @@ export default function Modal({
);
useEffect(() => {
if (isOpen) {
document.addEventListener("keydown", handleKeyDown);
document.body.style.overflow = "hidden";
}
if (!isOpen) return;
document.addEventListener("keydown", handleKeyDown);
document.body.style.overflow = "hidden";
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.body.style.overflow = "";
@@ -46,49 +44,52 @@ export default function Modal({
if (!isOpen) return null;
// L'ouverture/fermeture est pilotée par React (isOpen/onClose), on applique
// donc la structure et les classes visuelles DSFR sans le JS d'ouverture DSFR.
// Overlay React maîtrisé (pas le JS DSFR) : fond assombri visible, fermeture
// au clic extérieur / Échap / bouton. Look DSFR via les classes fr-modal__*.
return (
<dialog
className="fr-modal fr-modal--opened"
aria-modal="true"
<div
role="dialog"
open
aria-modal="true"
onClick={onClose}
style={{
position: "fixed",
inset: 0,
zIndex: 1000,
display: "block",
background: "transparent",
border: "none",
maxWidth: "none",
maxHeight: "none",
width: "100%",
height: "100%",
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
overflowY: "auto",
padding: "2rem 1rem",
background: "rgba(22, 22, 22, 0.64)",
}}
>
<div className="fr-container fr-container--fluid fr-container-md">
<div className="fr-grid-row fr-grid-row--center">
<div className={sizeColClasses[size]}>
<div className="fr-modal__body">
<div className="fr-modal__header">
<button
type="button"
className="fr-btn--close fr-btn"
aria-label="Fermer"
onClick={onClose}
>
Fermer
</button>
</div>
<div className="fr-modal__content">
{title && <h1 className="fr-modal__title">{title}</h1>}
{children}
</div>
</div>
</div>
<div
className="fr-modal__body"
onClick={(e) => e.stopPropagation()}
style={{
width: "100%",
maxWidth: maxWidthBySize[size],
background: "var(--background-default-grey, #fff)",
borderRadius: "0.25rem",
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.32)",
padding: "1.5rem",
}}
>
<div className="fr-modal__header">
<button
type="button"
className="fr-btn--close fr-btn"
aria-label="Fermer"
onClick={onClose}
>
Fermer
</button>
</div>
<div className="fr-modal__content">
{title && <h1 className="fr-modal__title">{title}</h1>}
{children}
</div>
</div>
</dialog>
</div>
);
}