Files
cesizen-individuel/frontend/src/components/ui/Modal.tsx
T
Azmog f6e150ddae feat(frontend): convertit l'ensemble des pages et composants au DSFR
- composants UI (Input, Card, Badge, Modal, ConfirmDialog, Slider, Skeleton,
  ThemeToggle) + Sidebar
- pages auth (connexion, inscription)
- pages publiques (informations, détail, contacts urgence, mentions, RGPD)
- espace membre (dashboard, journal, wizard saisie via fr-stepper, rapports, profil)
  + composants tracker (EditSaisieModal, EmotionPicker, TrackerTimeline, EmotionChart)
- back-office admin (dashboard, utilisateurs, contenus, émotions)

Suppression de Tailwind candlelight/malachite + lucide-react. Build OK (20 routes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:34:01 +02:00

95 lines
2.3 KiB
TypeScript

"use client";
import React, { useEffect, useCallback } from "react";
type ModalSize = "sm" | "md" | "lg";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
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",
};
export default function Modal({
isOpen,
onClose,
title,
children,
size = "md",
}: ModalProps) {
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
},
[onClose]
);
useEffect(() => {
if (isOpen) {
document.addEventListener("keydown", handleKeyDown);
document.body.style.overflow = "hidden";
}
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.body.style.overflow = "";
};
}, [isOpen, handleKeyDown]);
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.
return (
<dialog
className="fr-modal fr-modal--opened"
aria-modal="true"
role="dialog"
open
style={{
position: "fixed",
inset: 0,
zIndex: 1000,
display: "block",
background: "transparent",
border: "none",
maxWidth: "none",
maxHeight: "none",
width: "100%",
height: "100%",
}}
>
<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>
</div>
</dialog>
);
}