Retour au catalogue

Banners Alert

Banniere d'alerte systeme. 4 variantes : info, success, warning, error. Avec icone contextuelle.

bannerssimple Both Responsive a11y
corporateuniversalstacked
Theme
"use client";

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

type AlertType = "info" | "success" | "warning" | "error";

interface BannersAlertProps {
  text?: string;
  type?: AlertType;
  linkLabel?: string;
  linkUrl?: string;
  dismissible?: boolean;
}

const alertConfig: Record<
  AlertType,
  { bg: string; border: string; text: string; icon: typeof Info }
> = {
  info: {
    bg: "rgba(59, 130, 246, 0.08)",
    border: "rgba(59, 130, 246, 0.2)",
    text: "rgb(59, 130, 246)",
    icon: Info,
  },
  success: {
    bg: "rgba(34, 197, 94, 0.08)",
    border: "rgba(34, 197, 94, 0.2)",
    text: "rgb(34, 197, 94)",
    icon: CheckCircle,
  },
  warning: {
    bg: "rgba(234, 179, 8, 0.08)",
    border: "rgba(234, 179, 8, 0.2)",
    text: "rgb(180, 140, 10)",
    icon: AlertTriangle,
  },
  error: {
    bg: "rgba(239, 68, 68, 0.08)",
    border: "rgba(239, 68, 68, 0.2)",
    text: "rgb(239, 68, 68)",
    icon: XCircle,
  },
};

export default function BannersAlert({
  text = "Maintenance prevue prochainement.",
  type = "info",
  linkLabel,
  linkUrl,
  dismissible = true,
}: BannersAlertProps) {
  const [visible, setVisible] = useState(true);
  const config = alertConfig[type];
  const Icon = config.icon;

  return (
    <AnimatePresence>
      {visible && (
        <motion.div
          initial={{ height: 0, opacity: 0 }}
          animate={{ height: "auto", opacity: 1 }}
          exit={{ height: 0, opacity: 0 }}
          transition={{ duration: 0.3 }}
          className="overflow-hidden"
        >
          <div
            className="mx-auto"
            style={{
              maxWidth: "var(--container-max-width)",
              paddingLeft: "var(--container-padding-x)",
              paddingRight: "var(--container-padding-x)",
              paddingTop: "0.75rem",
              paddingBottom: "0.75rem",
            }}
          >
            <div
              className="flex items-center gap-3 px-4 py-3 rounded-xl"
              style={{
                backgroundColor: config.bg,
                border: `1px solid ${config.border}`,
              }}
            >
              <Icon
                className="h-4 w-4 shrink-0"
                style={{ color: config.text }}
              />

              <p className="text-sm flex-1" style={{ color: config.text }}>
                {text}
                {linkLabel && (
                  <a
                    href={linkUrl}
                    className="ml-2 font-semibold underline underline-offset-2"
                    style={{ color: config.text }}
                  >
                    {linkLabel}
                  </a>
                )}
              </p>

              {dismissible && (
                <button
                  onClick={() => setVisible(false)}
                  className="w-6 h-6 flex items-center justify-center cursor-pointer rounded-md transition-opacity hover:opacity-60 shrink-0"
                  style={{ color: config.text }}
                  aria-label="Fermer"
                >
                  <X className="h-3.5 w-3.5" />
                </button>
              )}
            </div>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

Avis