mirror of
https://github.com/alula/hbpatcher.git
synced 2025-11-24 10:34:55 +00:00
Fix dark/light theme bug and introduce automatic option
This commit is contained in:
parent
7205c16735
commit
633b1fd3a5
@ -1,6 +1,23 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Theme = "dark" | "light";
|
||||
type Theme = "dark" | "light" | "automatic";
|
||||
|
||||
const DiagonalSplitPreview = () => {
|
||||
return (
|
||||
<svg
|
||||
width="64"
|
||||
height="40"
|
||||
viewBox="0 0 64 40"
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
{/* Upper triangle (light) */}
|
||||
<polygon points="0,0 64,0 64,40" fill="#ebebeb" />
|
||||
{/* Lower triangle (dark) */}
|
||||
<polygon points="0,0 0,40 64,40" fill="#2d2d2d" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
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 = ({
|
||||
>
|
||||
<div className="flex items-center gap-6">
|
||||
{/* Preview Box */}
|
||||
<div
|
||||
className="w-16 h-10 border border-switch-line-sep shadow-sm"
|
||||
style={{ backgroundColor: previewColor }}
|
||||
/>
|
||||
<div className="w-16 h-10 border border-switch-line-sep overflow-hidden">
|
||||
{previewColor === "diagonalSplit" ? (
|
||||
<DiagonalSplitPreview />
|
||||
) : (
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{ backgroundColor: previewColor }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<span className="text-xl font-normal text-switch-text">{label}</span>
|
||||
@ -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<Theme>(() => {
|
||||
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 (
|
||||
<div className="space-y-0" role="radiogroup" aria-label="Theme Selection">
|
||||
<ThemeOption
|
||||
value="automatic"
|
||||
label="Automatic"
|
||||
previewColor="diagonalSplit"
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
/>
|
||||
<ThemeOption
|
||||
value="light"
|
||||
label="Basic White"
|
||||
|
||||
@ -80,9 +80,6 @@
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-switch-selected-bg);
|
||||
color: var(--switch-text);
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
color 0.3s;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
|
||||
18
src/main.tsx
18
src/main.tsx
@ -3,6 +3,24 @@ import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
|
||||
// Set initial theme before React renders to avoid flash
|
||||
function initializeTheme() {
|
||||
const saved = localStorage.getItem("theme");
|
||||
let effectiveTheme: "dark" | "light";
|
||||
|
||||
if (saved === "light" || saved === "dark") {
|
||||
effectiveTheme = saved;
|
||||
} else {
|
||||
// Default to automatic (system preference)
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
effectiveTheme = prefersDark ? "dark" : "light";
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute("data-theme", effectiveTheme);
|
||||
}
|
||||
|
||||
initializeTheme();
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user