mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Merge pull request #42 from phranck/fix/theme-flash
Fix: Eliminate theme flash (FOUC) on page reload
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
@@ -25,19 +24,21 @@ const ThemeContext = createContext<ThemeContextValue>({
|
||||
|
||||
const STORAGE_KEY = "tuikit-theme";
|
||||
|
||||
/**
|
||||
* Reads the theme that the blocking script already applied to the DOM.
|
||||
* Falls back to "amber" if nothing is set yet (SSR or first visit).
|
||||
*/
|
||||
function getInitialTheme(): Theme {
|
||||
if (typeof document !== "undefined") {
|
||||
const attr = document.documentElement.getAttribute("data-theme") as Theme | null;
|
||||
if (attr && themes.includes(attr)) return attr;
|
||||
}
|
||||
return "amber";
|
||||
}
|
||||
|
||||
/** Provides theme state and applies it to the document root via data-theme attribute. */
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>("amber");
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
|
||||
if (stored && themes.includes(stored)) {
|
||||
setThemeState(stored);
|
||||
document.documentElement.setAttribute("data-theme", stored);
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", "amber");
|
||||
}
|
||||
}, []);
|
||||
const [theme, setThemeState] = useState<Theme>(getInitialTheme);
|
||||
|
||||
const setTheme = useCallback((next: Theme) => {
|
||||
setThemeState(next);
|
||||
|
||||
+20
-1
@@ -31,8 +31,27 @@ export default function RootLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
/** Inline script that runs before first paint to prevent theme flash (FOUC). */
|
||||
const themeInitScript = `
|
||||
(function() {
|
||||
try {
|
||||
var stored = localStorage.getItem('tuikit-theme');
|
||||
if (stored && ['green','amber','white','red'].includes(stored)) {
|
||||
document.documentElement.setAttribute('data-theme', stored);
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'amber');
|
||||
}
|
||||
} catch(e) {
|
||||
document.documentElement.setAttribute('data-theme', 'amber');
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
return (
|
||||
<html lang="en" className="dark">
|
||||
<html lang="en" className="dark" suppressHydrationWarning>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
|
||||
</head>
|
||||
<body
|
||||
className={`${geistMono.variable} antialiased`}
|
||||
>
|
||||
|
||||
+2
-2
@@ -253,7 +253,7 @@ export default function Home() {
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-border bg-card/30 backdrop-blur-sm">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-center gap-2 px-6 py-8 text-center">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-center gap-0.5 px-6 py-8 text-center">
|
||||
<span className="text-base text-muted">
|
||||
Made with ❤️ in Bregenz
|
||||
</span>
|
||||
@@ -267,7 +267,7 @@ export default function Home() {
|
||||
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-2 text-xs text-muted/70 transition-colors hover:text-foreground"
|
||||
className="mt-6 text-xs text-muted/70 transition-colors hover:text-foreground"
|
||||
>
|
||||
CC BY-NC-SA 4.0
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user