SPB Git forge

spb/qc26

Public
10commits 1branches 0releases
2.8 MBsize
maindefault branch
17 days agolast push
Python 51.6% TypeScript 46.7% CSS 1.7%
2.3 KB · 36 lines tsx
Raw Blame History
1"use client";23import { useEffect, useState } from "react";45type Theme = "light" | "dark";67/** Script inline (avant hydratation) : applique le thème mémorisé ou celui du système, sans flash. */8export const THEME_INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('qc26-theme');if(t!=='light'&&t!=='dark'){t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.setAttribute('data-theme',t);var m=document.querySelector('meta[name="theme-color"]');if(m){m.setAttribute('content',t==='dark'?'#0e1013':'#f6f5f1')}}catch(e){}})();`;910function current(): Theme {11  if (typeof document === "undefined") return "light";12  return (document.documentElement.getAttribute("data-theme") as Theme) || "light";13}1415export function ThemeToggle({ className }: { className?: string }) {16  const [theme, setTheme] = useState<Theme>("light");17  // eslint-disable-next-line react-hooks/set-state-in-effect -- synchronisation unique avec l'attribut posé avant hydratation18  useEffect(() => { setTheme(current()); }, []);19  const toggle = () => {20    const next: Theme = current() === "dark" ? "light" : "dark";21    document.documentElement.setAttribute("data-theme", next);22    try { localStorage.setItem("qc26-theme", next); } catch { /* privé */ }23    document.querySelector('meta[name="theme-color"]')?.setAttribute("content", next === "dark" ? "#0e1013" : "#f6f5f1");24    setTheme(next);25  };26  return (27    <button onClick={toggle} className={className ?? "w-9 h-9 inline-flex items-center justify-center rounded-[9px] border border-border hover:bg-surface-2 text-ink-2"} aria-label={theme === "dark" ? "Passer au thème clair" : "Passer au thème sombre"} title={theme === "dark" ? "Thème clair" : "Thème sombre"}>28      {theme === "dark" ? (29        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></svg>30      ) : (31        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" /></svg>32      )}33    </button>34  );35}36