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