76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { websiteConfig } from "@/config/website";
|
|
import {
|
|
ReactNode,
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useState,
|
|
} from "react";
|
|
|
|
const COOKIE_NAME = "active_theme";
|
|
const DEFAULT_THEME = websiteConfig.metadata.theme?.defaultTheme ?? "default";
|
|
|
|
function setThemeCookie(theme: string) {
|
|
if (typeof window === "undefined") return;
|
|
|
|
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""
|
|
}`;
|
|
}
|
|
|
|
type ThemeContextType = {
|
|
activeTheme: string;
|
|
setActiveTheme: (theme: string) => void;
|
|
};
|
|
|
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
|
|
/**
|
|
* This component is used to provide the active theme to the application
|
|
* It also sets the theme cookie and updates the body class when the theme changes.
|
|
*
|
|
* NOTICE: Since custom theme is set in useEffect,
|
|
* it will not be applied until the component is mounted,
|
|
* for better user experience, we recommend to replace the
|
|
* default theme with the custom theme in global.css.
|
|
*/
|
|
export function ActiveThemeProvider({
|
|
children,
|
|
initialTheme,
|
|
}: {
|
|
children: ReactNode;
|
|
initialTheme?: string;
|
|
}) {
|
|
const [activeTheme, setActiveTheme] = useState<string>(
|
|
() => initialTheme || DEFAULT_THEME
|
|
);
|
|
|
|
useEffect(() => {
|
|
setThemeCookie(activeTheme);
|
|
|
|
Array.from(document.body.classList)
|
|
.filter((className) => className.startsWith("theme-"))
|
|
.forEach((className) => {
|
|
document.body.classList.remove(className);
|
|
});
|
|
document.body.classList.add(`theme-${activeTheme}`);
|
|
}, [activeTheme]);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ activeTheme, setActiveTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useThemeConfig() {
|
|
const context = useContext(ThemeContext);
|
|
if (context === undefined) {
|
|
throw new Error(
|
|
"useThemeConfig must be used within an ActiveThemeProvider"
|
|
);
|
|
}
|
|
return context;
|
|
}
|