From 8e687fe176c893d5f364ed360cb924926c87730d Mon Sep 17 00:00:00 2001 From: "D. Rimron-Soutter" Date: Fri, 17 Oct 2025 12:38:22 +0100 Subject: [PATCH] Attempt 2 to add a darkmode --- src/components/ThemeDropdown.tsx | 93 ++++++++++---------------------- 1 file changed, 27 insertions(+), 66 deletions(-) diff --git a/src/components/ThemeDropdown.tsx b/src/components/ThemeDropdown.tsx index 641813b..730223f 100644 --- a/src/components/ThemeDropdown.tsx +++ b/src/components/ThemeDropdown.tsx @@ -1,84 +1,66 @@ "use client"; -import { useEffect, useMemo, useState, useCallback } from "react"; +import { useEffect, useState, useCallback } from "react"; import * as Icon from "react-bootstrap-icons"; import { Nav, Dropdown } from "react-bootstrap"; type Theme = "light" | "dark" | "auto"; -const COOKIE = "theme"; +const COOKIE = "NBN-theme"; -function getCookie(name: string): string | null { - if (typeof document === "undefined") return null; +const getCookie = (name: string) => { const m = document.cookie.match(new RegExp("(^|; )" + name + "=([^;]+)")); return m ? decodeURIComponent(m[2]) : null; -} -function setCookie(name: string, value: string) { - document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax`; -} +}; + +const setCookie = (name: string, value: string) => { + document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax; Domain=specnext.dev`; +}; -// Use a single function to read current system preference (works on iOS) const prefersDark = () => - typeof window !== "undefined" && - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches; + window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; export default function ThemeDropdown() { const [mounted, setMounted] = useState(false); - const [theme, setThemeState] = useState("auto"); + const [theme, setTheme] = useState("auto"); - const applyTheme = useCallback((t: Theme) => { + const apply = useCallback((t: Theme) => { const effective = t === "auto" ? (prefersDark() ? "dark" : "light") : t; document.documentElement.setAttribute("data-bs-theme", effective); }, []); - // Initial mount: read cookie and APPLY immediately (important for iOS) useEffect(() => { setMounted(true); const v = getCookie(COOKIE); + console.log("Cookie:", v); const initial: Theme = v === "light" || v === "dark" || v === "auto" ? (v as Theme) : "auto"; - setThemeState(initial); - applyTheme(initial); // ensure render matches auto right away - }, [applyTheme]); + setTheme(initial); + // Important: apply immediately so “auto” reflects system after hydration + apply(initial); + }, [apply]); - // Follow system changes while in auto; include iOS visibility/page-show events useEffect(() => { if (!mounted || theme !== "auto") return; - const mql = window.matchMedia("(prefers-color-scheme: dark)"); - const onChange = () => applyTheme("auto"); + const onChange = () => apply("auto"); - // Safari <14 uses addListener/removeListener - if (typeof mql.addEventListener === "function") { - mql.addEventListener("change", onChange); - } else if (typeof mql.addListener === "function") { - mql.addListener(onChange); - } + mql.addEventListener("change", onChange); - const onVisibility = () => applyTheme("auto"); - const onPageShow = () => applyTheme("auto"); - - document.addEventListener("visibilitychange", onVisibility); + const onPageShow = () => apply("auto"); window.addEventListener("pageshow", onPageShow); return () => { - if (typeof mql.removeEventListener === "function") { - mql.removeEventListener("change", onChange); - } else if (typeof mql.removeListener === "function") { - mql.removeListener(onChange); - } - document.removeEventListener("visibilitychange", onVisibility); + mql.removeEventListener("change", onChange); window.removeEventListener("pageshow", onPageShow); }; - }, [mounted, theme, applyTheme]); + }, [mounted, theme, apply]); - const handleSetTheme = (t: Theme) => { + const choose = (t: Theme) => { setCookie(COOKIE, t); - setThemeState(t); - applyTheme(t); + setTheme(t); + apply(t); }; const isActive = (t: Theme) => theme === t; - const ToggleIcon = !mounted ? Icon.CircleHalf : theme === "dark" @@ -94,39 +76,18 @@ export default function ThemeDropdown() { Toggle theme - - handleSetTheme("light")} - > + choose("light")}> Light {isActive("light") && - - handleSetTheme("dark")} - > + choose("dark")}> Dark {isActive("dark") && - - handleSetTheme("auto")} - > + choose("auto")}> Auto {isActive("auto") &&