From 633b1fd3a52bffdf98e8f39a1b45a3369d77a4d6 Mon Sep 17 00:00:00 2001 From: Alula Date: Mon, 24 Nov 2025 02:04:54 +0100 Subject: [PATCH] Fix dark/light theme bug and introduce automatic option --- src/components/ThemeSelector.tsx | 86 +++++++++++++++++++++++++++++--- src/index.css | 3 -- src/main.tsx | 18 +++++++ 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/src/components/ThemeSelector.tsx b/src/components/ThemeSelector.tsx index 0ea96ab..a9c8d37 100644 --- a/src/components/ThemeSelector.tsx +++ b/src/components/ThemeSelector.tsx @@ -1,6 +1,23 @@ import { useEffect, useState } from "react"; -type Theme = "dark" | "light"; +type Theme = "dark" | "light" | "automatic"; + +const DiagonalSplitPreview = () => { + return ( + + {/* Upper triangle (light) */} + + {/* Lower triangle (dark) */} + + + ); +}; const ThemeOption = ({ value, @@ -12,7 +29,7 @@ const ThemeOption = ({ }: { value: Theme; label: string; - previewColor: string; + previewColor: string | "diagonalSplit"; theme: Theme; setTheme: (theme: Theme) => void; isLast?: boolean; @@ -40,10 +57,16 @@ const ThemeOption = ({ >
{/* Preview Box */} -
+
+ {previewColor === "diagonalSplit" ? ( + + ) : ( +
+ )} +
{/* Label */} {label} @@ -68,19 +91,66 @@ const ThemeOption = ({ ); }; +function getSystemTheme(): "dark" | "light" { + if (typeof window === "undefined") return "dark"; + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +function getEffectiveTheme(theme: Theme): "dark" | "light" { + if (theme === "automatic") { + return getSystemTheme(); + } + return theme; +} + export function ThemeSelector() { const [theme, setTheme] = useState(() => { const saved = localStorage.getItem("theme") as Theme | null; - return saved || "dark"; + // Default to "automatic" if no saved preference + return saved || "automatic"; }); useEffect(() => { - document.documentElement.setAttribute("data-theme", theme); + const effectiveTheme = getEffectiveTheme(theme); + document.documentElement.setAttribute("data-theme", effectiveTheme); localStorage.setItem("theme", theme); }, [theme]); + // Listen to system theme changes when "automatic" is selected + useEffect(() => { + if (theme !== "automatic") return; + + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = () => { + if (theme === "automatic") { + const newSystemTheme = getSystemTheme(); + document.documentElement.setAttribute("data-theme", newSystemTheme); + } + }; + + // Modern browsers + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); + } + // Fallback for older browsers + else if (mediaQuery.addListener) { + mediaQuery.addListener(handleChange); + return () => mediaQuery.removeListener(handleChange); + } + }, [theme]); + return (
+