fix(theme): white icon glyphs on solid tiles + single theme storage key
Two issues: 1. Icon badges read as a coloured glyph on a same-hue LIGHT tile (bgcolor X.light + color X.main) — low contrast, "badly visible". The AttendanceHistory month tile and the Dashboard 2FA-banner icon now use a SOLID tile (X.main) + white glyph, matching the StatCard badge convention. Readable in both schemes (verified: info.main #1d4ed8 light / #3b82f6 dark, error.main, white glyph). 2. Theme reverted to light on F5 while the toggle still showed dark. Cause: two independent stores — our ThemeContext key (boha-theme) and MUI's own cssVariables mode key (mui-mode). The toggle only wrote boha-theme, so on mount MUI restored its stale mui-mode and overrode data-theme. Collapsed to a SINGLE source of truth: ThemeContext now reads/writes MUI's `mui-mode` key directly (with a one-time migration from the legacy boha-theme, which is then removed). Verified: toggle dark -> F5 -> stays dark; only `mui-mode` remains in localStorage. tsc -b --noEmit, npm run build, vitest 152/152 clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -540,8 +540,8 @@ export default function AttendanceHistory() {
|
|||||||
width: 44,
|
width: 44,
|
||||||
height: 44,
|
height: 44,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
bgcolor: "info.light",
|
bgcolor: "info.main",
|
||||||
color: "info.main",
|
color: "#fff",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ export default function Dashboard() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
bgcolor: "error.light",
|
bgcolor: "error.main",
|
||||||
color: "error.main",
|
color: "#fff",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ type ViewTransitionDocument = Document & {
|
|||||||
|
|
||||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||||
const [theme, setTheme] = useState(() => {
|
const [theme, setTheme] = useState(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window === "undefined") return "dark";
|
||||||
return localStorage.getItem("boha-theme") || "dark";
|
// Single source of truth = MUI's own mode key. MUI's cssVariables provider
|
||||||
}
|
// reads `mui-mode` on mount and sets `data-theme` from it, so we read/write
|
||||||
return "dark";
|
// that SAME key (no separate boha-theme) — page and toggle stay in sync
|
||||||
|
// across reloads. Fall back to the legacy key for pre-existing sessions.
|
||||||
|
const stored =
|
||||||
|
localStorage.getItem("mui-mode") || localStorage.getItem("boha-theme");
|
||||||
|
return stored === "light" || stored === "dark" ? stored : "dark";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply synchronously (layout effect) so the `data-theme` attribute flips
|
// Apply synchronously (layout effect) so the `data-theme` attribute flips
|
||||||
@@ -31,7 +35,8 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||||||
// the "after" snapshot and cross-fade to it.
|
// the "after" snapshot and cross-fade to it.
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
localStorage.setItem("boha-theme", theme);
|
localStorage.setItem("mui-mode", theme);
|
||||||
|
localStorage.removeItem("boha-theme"); // retire the duplicate legacy key
|
||||||
const themeColor = theme === "dark" ? "#12121a" : "#ffffff";
|
const themeColor = theme === "dark" ? "#12121a" : "#ffffff";
|
||||||
document
|
document
|
||||||
.querySelector('meta[name="theme-color"]')
|
.querySelector('meta[name="theme-color"]')
|
||||||
|
|||||||
Reference in New Issue
Block a user