Retour au catalogue

Modal Confirm

Modale de confirmation avec icone, message et boutons confirmer/annuler.

modal-pagessimple Both Responsive a11y
minimalcorporatesaasuniversalcentered
Theme
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { AlertTriangle, X } from "lucide-react";

interface ModalConfirmProps {
  title?: string;
  message?: string;
  confirmLabel?: string;
  cancelLabel?: string;
  variant?: "danger" | "warning" | "default";
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

export default function ModalConfirm({
  title = "Confirmer l'action",
  message = "Etes-vous sur de vouloir continuer ?",
  confirmLabel = "Confirmer",
  cancelLabel = "Annuler",
  variant = "danger",
}: ModalConfirmProps) {
  const [isOpen, setIsOpen] = useState(true);

  return (
    <div className="min-h-[400px] flex items-center justify-center px-6" style={{ background: "var(--color-background)" }}>
      <button
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 text-sm font-medium rounded-lg"
        style={{ background: "var(--color-background-alt)", color: "var(--color-foreground)", border: "1px solid var(--color-border)" }}
      >
        Ouvrir la modale
      </button>

      <AnimatePresence>
        {isOpen && (
          <>
            <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 z-40" style={{ background: "rgba(0,0,0,0.5)" }} onClick={() => setIsOpen(false)} />
            <motion.div
              initial={{ opacity: 0, scale: 0.95 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.95 }}
              transition={{ duration: 0.2, ease }}
              className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 w-full max-w-md rounded-2xl p-6 shadow-2xl"
              style={{ background: "var(--color-background)", border: "1px solid var(--color-border)" }}
            >
              <div className="flex items-start gap-4">
                <div className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: "var(--color-background-alt)" }}>
                  <AlertTriangle size={20} style={{ color: variant === "danger" ? "var(--color-accent)" : "var(--color-foreground-muted)" }} />
                </div>
                <div className="flex-1">
                  <div className="flex items-center justify-between">
                    <h3 className="text-base font-semibold" style={{ color: "var(--color-foreground)" }}>{title}</h3>
                    <button onClick={() => setIsOpen(false)}><X size={16} style={{ color: "var(--color-foreground-light)" }} /></button>
                  </div>
                  <p className="mt-2 text-sm" style={{ color: "var(--color-foreground-muted)" }}>{message}</p>
                </div>
              </div>
              <div className="flex items-center justify-end gap-3 mt-6">
                <button onClick={() => setIsOpen(false)} className="px-4 py-2 text-sm font-medium rounded-lg transition-colors" style={{ color: "var(--color-foreground-muted)", background: "var(--color-background-alt)" }}>
                  {cancelLabel}
                </button>
                <motion.button whileTap={{ scale: 0.97 }} onClick={() => setIsOpen(false)} className="px-4 py-2 text-sm font-medium rounded-lg" style={{ background: "var(--color-accent)", color: "var(--color-background)" }}>
                  {confirmLabel}
                </motion.button>
              </div>
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </div>
  );
}

Avis

Modal Confirm — React Modal-pages Section — Incubator