feat: Add CookieModal component with Toggle functionality
This commit is contained in:
parent
df859c0c06
commit
993089e8a0
44
app/components/common/Toggle.tsx
Normal file
44
app/components/common/Toggle.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type ToggleProps = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
onChange?: (checked: boolean) => void;
|
||||||
|
checked: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Toggle(props: ToggleProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={props.id}
|
||||||
|
className="sr-only"
|
||||||
|
onChange={(e) => props.onChange?.(e.target.checked)}
|
||||||
|
checked={props.checked}
|
||||||
|
aria-checked={props.checked}
|
||||||
|
role="switch"
|
||||||
|
aria-label={props.label}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label htmlFor={props.id} className="flex cursor-pointer items-center justify-between rounded-full">
|
||||||
|
<span className="relative flex h-6 w-10 items-center">
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"h-4 w-4 rounded-full bg-white shadow-md transition-transform duration-200 ease-in-out z-10",
|
||||||
|
props.checked ? "translate-x-full" : "translate-x-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"absolute top-1/2 left-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 rounded-full transition duration-200 ease-in-out z-0",
|
||||||
|
props.checked ? "bg-violet-500" : "bg-slate-300",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="ml-2 text-sm text-slate-700 dark:text-slate-300">{props.label}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ const variantStyles = {
|
|||||||
"bg-violet-300 font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500",
|
"bg-violet-300 font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-slate-800 font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400",
|
"bg-slate-800 font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400",
|
||||||
|
ghost:
|
||||||
|
"bg-transparent font-medium text-slate-900 dark:text-slate-400 hover:bg-slate-100 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-300/50 active:bg-slate-200",
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeStyles = {
|
const sizeStyles = {
|
||||||
|
|||||||
@ -2,11 +2,14 @@ import { MobileNavigation } from "@syntax/MobileNavigation";
|
|||||||
import { usePageContext } from "vike-react/usePageContext";
|
import { usePageContext } from "vike-react/usePageContext";
|
||||||
import { ThemeProvider } from "@/providers/ThemeProvider";
|
import { ThemeProvider } from "@/providers/ThemeProvider";
|
||||||
import { ThemeSelector } from "@syntax/ThemeSelector";
|
import { ThemeSelector } from "@syntax/ThemeSelector";
|
||||||
|
import { Button } from "@/components/syntax/Button";
|
||||||
|
import { Toggle } from "@/components/common/Toggle";
|
||||||
import { clientOnly } from "vike-react/clientOnly";
|
import { clientOnly } from "vike-react/clientOnly";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
import { Navigation } from "@syntax/Navigation";
|
import { Navigation } from "@syntax/Navigation";
|
||||||
import { Link } from "@/components/common/Link";
|
import { Link } from "@/components/common/Link";
|
||||||
|
import { reload } from "vike/client/router";
|
||||||
import { Hero } from "@syntax/Hero";
|
import { Hero } from "@syntax/Hero";
|
||||||
import { Logo } from "@syntax/Logo";
|
import { Logo } from "@syntax/Logo";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@ -74,12 +77,107 @@ function Header() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CookieModal() {
|
||||||
|
const { cookies } = usePageContext();
|
||||||
|
|
||||||
|
const [consentCookies, setConsentCookies] = useState(cookies.consent);
|
||||||
|
const [isSelectionOpen, setIsSelectionOpen] = useState(false);
|
||||||
|
const [isOpen, setIsOpen] = useState(() => {
|
||||||
|
return Object.keys(cookies.consent).every((value) => value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isSelectionOpen) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50">
|
||||||
|
<div className="flex flex-col gap-2 bg-slate-50 dark:bg-slate-800 rounded-md shadow-xl w-full max-w-sm p-4">
|
||||||
|
<p className="font-display dark:text-slate-300 font-bold text-lg">Personnalisation des cookies 🍪</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 w-full items-start">
|
||||||
|
<Toggle
|
||||||
|
id="cookies-analytics"
|
||||||
|
label="Cookies d'analyse (Umami et Google Analytics)"
|
||||||
|
checked={cookies.consent.analytics}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setConsentCookies((prev) => ({ ...prev, analytics: checked }));
|
||||||
|
reload();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
id="cookies-customization"
|
||||||
|
label="Cookie de personnalisation (thème)"
|
||||||
|
checked={cookies.consent.customization}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setConsentCookies((prev) => ({ ...prev, customization: checked }));
|
||||||
|
reload();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col fixed bottom-4 left-4 bg-slate-50 dark:bg-slate-800 z-50 rounded-md shadow-xl w-full max-w-sm overflow-hidden">
|
||||||
|
<div className="flex flex-col gap-2 p-4">
|
||||||
|
<p className="font-display dark:text-slate-300">
|
||||||
|
<span className="text-sm">Coucou c'est nous...</span>
|
||||||
|
<br />
|
||||||
|
<span className="font-bold text-lg">les cookies ! 🍪</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-slate-700 dark:text-slate-300">
|
||||||
|
On ne t'embête pas longtemps, on te laisse même le choix <em>(si ça c'est pas la classe 😎)</em>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-slate-700 dark:text-slate-300">
|
||||||
|
Si tu veux en savoir plus, tu peux consulter la page{" "}
|
||||||
|
<Link href="/politique-de-confidentialite" className="font-bold">
|
||||||
|
Politique de confidentialité
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid items-center grid-cols-3 justify-between bg-slate-100 dark:bg-slate-700">
|
||||||
|
<button
|
||||||
|
className="cursor-pointer px-2 py-1 text-slate-600 dark:text-slate-300"
|
||||||
|
onClick={async () => {
|
||||||
|
// TODO
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Non merci
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="cursor-pointer px-2 py-1 text-slate-600 dark:text-slate-300"
|
||||||
|
onClick={() => setIsSelectionOpen(true)}
|
||||||
|
>
|
||||||
|
Je choisis
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="cursor-pointer px-2 py-1 font-bold text-violet-600 dark:text-violet-300"
|
||||||
|
onClick={async () => {
|
||||||
|
// TODO
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Oui, j'ai faim !
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function DefaultLayout({ children }: { children: React.ReactNode }) {
|
export default function DefaultLayout({ children }: { children: React.ReactNode }) {
|
||||||
const { urlPathname, cookies } = usePageContext();
|
const { urlPathname, cookies } = usePageContext();
|
||||||
const isHomePage = urlPathname === "/";
|
const isHomePage = urlPathname === "/";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider defaultTheme={cookies.theme}>
|
<ThemeProvider defaultTheme={cookies.theme}>
|
||||||
|
<CookieModal />
|
||||||
|
|
||||||
<div className="flex w-full flex-col font-sans">
|
<div className="flex w-full flex-col font-sans">
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user