- Replace next-themes (38 KiB) with a tiny custom ThemeProvider (~< 1 KiB) using localStorage + classList.toggle for theme management - Add FOUC-prevention inline script in layout.tsx to apply saved theme before React hydrates - Remove framer-motion from Header.tsx: nav entry now uses CSS slideDown keyframe, mobile menu uses CSS opacity/translate transitions - Remove framer-motion from ThemeToggle.tsx: use Tailwind hover/active scale - Remove framer-motion from legal-notice and privacy-policy pages - Update useTheme import in ThemeToggle to use custom ThemeProvider - Add slideDown keyframe to tailwind.config.ts - Update tests to mock custom ThemeProvider instead of next-themes Result: framer-motion moves from "First Load JS shared by all" to lazy chunks; next-themes chunk eliminated entirely; -38 KiB from initial bundle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
39 lines
1.0 KiB
TypeScript
39 lines
1.0 KiB
TypeScript
"use client";
|
|
|
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
|
|
type Theme = "light" | "dark";
|
|
|
|
const ThemeCtx = createContext<{ theme: Theme; setTheme: (t: Theme) => void }>({
|
|
theme: "light",
|
|
setTheme: () => {},
|
|
});
|
|
|
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
const [theme, setThemeState] = useState<Theme>("light");
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const stored = localStorage.getItem("theme") as Theme | null;
|
|
if (stored === "dark" || stored === "light") {
|
|
setThemeState(stored);
|
|
document.documentElement.classList.toggle("dark", stored === "dark");
|
|
}
|
|
} catch {}
|
|
}, []);
|
|
|
|
const setTheme = (t: Theme) => {
|
|
setThemeState(t);
|
|
try {
|
|
localStorage.setItem("theme", t);
|
|
} catch {}
|
|
document.documentElement.classList.toggle("dark", t === "dark");
|
|
};
|
|
|
|
return <ThemeCtx.Provider value={{ theme, setTheme }}>{children}</ThemeCtx.Provider>;
|
|
}
|
|
|
|
export function useTheme() {
|
|
return useContext(ThemeCtx);
|
|
}
|