diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 8ef775d0..cd9505e5 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,12 +1,31 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import logo from "../assets/logo_light_160.png"; import { SideSheet } from "@douyinfe/semi-ui"; import { IconMenu } from "@douyinfe/semi-icons"; import { socials } from "../data/socials"; +import { getTheme, toggleTheme, onSystemPrefChange } from "../theme"; export default function Navbar() { const [openMenu, setOpenMenu] = useState(false); + const [isDark, setIsDark] = useState(() => getTheme() === "dark"); + + useEffect(() => { + // Keep local state synced when system preference changes (only when user hasn't chosen a theme) + const unsubscribe = onSystemPrefChange((prefersDark) => { + // if there's an explicit stored preference, ignore system changes + try { + const stored = window.localStorage && window.localStorage.getItem("drawdb:theme"); + if (!stored) { + setIsDark(prefersDark); + } + } catch (e) { + // ignore storage errors + } + }); + + return () => unsubscribe && unsubscribe(); + }, []); return ( <> @@ -75,12 +94,48 @@ export default function Navbar() { - +
+ + + +

system > default dark +export function getTheme() { + const stored = getStoredTheme(); + if (stored === "dark" || stored === "light") return stored; + const sys = isSystemDark() ? "dark" : "light"; + return sys || "dark"; +} + +export function applyTheme(theme) { + applyThemeClass(theme); +} + +// toggle and persist +export function toggleTheme() { + const current = getTheme(); + const next = current === "dark" ? "light" : "dark"; + try { + setStoredTheme(next); + } catch (e) { + // ignore + } + applyThemeClass(next); + return next; +} + +// Initialize on page load: apply stored or system preference, defaulting to dark +export function initTheme() { + const stored = getStoredTheme(); + const theme = stored === "dark" || stored === "light" ? stored : (isSystemDark() ? "dark" : "light"); + applyThemeClass(theme); +} + +// Listen to system preference changes and call callback(prefersDark) +// Returns an unsubscribe function +export function onSystemPrefChange(cb) { + try { + if (typeof window === "undefined" || !window.matchMedia) return () => {}; + const mq = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = (ev) => cb(!!ev.matches); + // modern API + if (mq.addEventListener) mq.addEventListener("change", handler); + else mq.addListener && mq.addListener(handler); + return () => { + if (mq.removeEventListener) mq.removeEventListener("change", handler); + else mq.removeListener && mq.removeListener(handler); + }; + } catch (e) { + return () => {}; + } +} + +// Auto-init if running in browser +if (typeof window !== "undefined") { + try { + initTheme(); + } catch (e) { + // ignore + } +} + +export default { + getTheme, + applyTheme, + toggleTheme, + initTheme, + onSystemPrefChange, +};