Retour au catalogue
Modal Cookie Consent
Modal de consentement cookies avec toggles de preferences.
modal-pagessimple Both Responsive a11y
minimaluniversalstacked
Theme
"use client";
import { useState } from "react";
import { Cookie, X, Shield } from "lucide-react";
interface CookieCategory {
id: string;
label: string;
description: string;
required: boolean;
enabled: boolean;
}
const initialCategories: CookieCategory[] = [
{ id: "essential", label: "Essential", description: "Required for core site functionality. Cannot be disabled.", required: true, enabled: true },
{ id: "analytics", label: "Analytics", description: "Help us understand how visitors interact with our site.", required: false, enabled: false },
{ id: "marketing", label: "Marketing", description: "Used to deliver relevant ads and track campaign performance.", required: false, enabled: false },
{ id: "preferences", label: "Preferences", description: "Remember your settings, language, and personalization choices.", required: false, enabled: true },
];
export default function ModalCookieConsent() {
const [isOpen, setIsOpen] = useState(true);
const [showPrefs, setShowPrefs] = useState(false);
const [categories, setCategories] = useState(initialCategories);
const toggleCategory = (id: string) => {
setCategories((prev) =>
prev.map((c) => (c.id === id && !c.required ? { ...c, enabled: !c.enabled } : c))
);
};
const acceptAll = () => {
setCategories((prev) => prev.map((c) => ({ ...c, enabled: true })));
setIsOpen(false);
};
const savePrefs = () => setIsOpen(false);
return (
<div className="min-h-[500px] flex items-center justify-center px-6" style={{ background: "var(--color-background)" }}>
<button
onClick={() => { setIsOpen(true); setShowPrefs(false); }}
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)" }}
>
Cookie settings
</button>
{isOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-40 transition-opacity duration-300"
style={{ background: "rgba(0,0,0,0.5)" }}
onClick={() => setIsOpen(false)}
/>
{/* Modal */}
<div
className="fixed bottom-0 left-0 right-0 z-50 md:bottom-auto md:top-1/2 md:left-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:max-w-lg md:rounded-2xl rounded-t-2xl p-6 shadow-2xl"
style={{ background: "var(--color-background)", border: "1px solid var(--color-border)" }}
>
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-xl flex items-center justify-center"
style={{ background: "color-mix(in srgb, var(--color-accent) 12%, transparent)" }}
>
<Cookie size={20} style={{ color: "var(--color-accent)" }} />
</div>
<div>
<h3 className="text-base font-semibold" style={{ color: "var(--color-foreground)" }}>Cookie preferences</h3>
<p className="text-xs" style={{ color: "var(--color-foreground-muted)" }}>Manage your privacy settings</p>
</div>
</div>
<button onClick={() => setIsOpen(false)} className="p-1">
<X size={16} style={{ color: "var(--color-foreground-light)" }} />
</button>
</div>
<p className="text-sm leading-relaxed mb-5" style={{ color: "var(--color-foreground-muted)" }}>
We use cookies to enhance your experience. You can customize your preferences or accept all cookies.
</p>
{/* Preference toggles */}
{showPrefs && (
<div className="mb-5 flex flex-col gap-2">
{categories.map((cat) => (
<div
key={cat.id}
className="flex items-center justify-between p-3 rounded-xl"
style={{ background: "var(--color-background-alt)" }}
>
<div className="flex-1 mr-3">
<div className="flex items-center gap-1.5">
<span className="text-sm font-medium" style={{ color: "var(--color-foreground)" }}>{cat.label}</span>
{cat.required && (
<Shield size={10} style={{ color: "var(--color-accent)" }} />
)}
</div>
<p className="text-[11px] mt-0.5" style={{ color: "var(--color-foreground-muted)" }}>{cat.description}</p>
</div>
<button
onClick={() => toggleCategory(cat.id)}
disabled={cat.required}
className="relative w-10 h-5 rounded-full transition-colors duration-200 flex-shrink-0"
style={{
background: cat.enabled ? "var(--color-accent)" : "var(--color-border)",
opacity: cat.required ? 0.7 : 1,
cursor: cat.required ? "not-allowed" : "pointer",
}}
>
<div
className="absolute top-0.5 w-4 h-4 rounded-full transition-transform duration-200"
style={{
background: "var(--color-background)",
transform: cat.enabled ? "translateX(22px)" : "translateX(2px)",
}}
/>
</button>
</div>
))}
</div>
)}
{/* Actions */}
<div className="flex items-center gap-3">
{!showPrefs ? (
<>
<button
onClick={() => setShowPrefs(true)}
className="flex-1 px-4 py-2.5 text-sm font-medium rounded-lg transition-colors"
style={{ background: "var(--color-background-alt)", color: "var(--color-foreground-muted)" }}
>
Customize
</button>
<button
onClick={acceptAll}
className="flex-1 px-4 py-2.5 text-sm font-medium rounded-lg transition-opacity hover:opacity-90"
style={{ background: "var(--color-accent)", color: "var(--color-background)" }}
>
Accept all
</button>
</>
) : (
<>
<button
onClick={() => setShowPrefs(false)}
className="flex-1 px-4 py-2.5 text-sm font-medium rounded-lg"
style={{ background: "var(--color-background-alt)", color: "var(--color-foreground-muted)" }}
>
Back
</button>
<button
onClick={savePrefs}
className="flex-1 px-4 py-2.5 text-sm font-medium rounded-lg transition-opacity hover:opacity-90"
style={{ background: "var(--color-accent)", color: "var(--color-background)" }}
>
Save preferences
</button>
</>
)}
</div>
</div>
</>
)}
</div>
);
}