mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-09-19 01:35:51 +08:00
139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useEffect } from "react";
|
|
import { useEditorStore } from "../store/editor-store";
|
|
import { colorFormatter } from "../utils/color-converter";
|
|
import { setShadowVariables } from "@/utils/shadows";
|
|
import { applyStyleToElement } from "@/utils/apply-style-to-element";
|
|
import { ThemeStyleProps, ThemeStyles } from "@/types/theme";
|
|
import { useThemePresetFromUrl } from "@/hooks/use-theme-preset-from-url";
|
|
import { COMMON_STYLES } from "@/config/theme";
|
|
|
|
type Theme = "dark" | "light";
|
|
|
|
type ThemeProviderProps = {
|
|
children: React.ReactNode;
|
|
defaultTheme?: Theme;
|
|
};
|
|
|
|
type Coords = { x: number; y: number };
|
|
|
|
type ThemeProviderState = {
|
|
theme: Theme;
|
|
setTheme: (theme: Theme) => void;
|
|
toggleTheme: (coords?: Coords) => void;
|
|
};
|
|
|
|
const COMMON_NON_COLOR_KEYS = COMMON_STYLES;
|
|
|
|
const initialState: ThemeProviderState = {
|
|
theme: "light",
|
|
setTheme: () => null,
|
|
toggleTheme: () => null,
|
|
};
|
|
|
|
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
|
|
// Helper functions
|
|
const applyCommonStyles = (root: HTMLElement, themeStyles: ThemeStyleProps) => {
|
|
Object.entries(themeStyles)
|
|
.filter(([key]) =>
|
|
COMMON_NON_COLOR_KEYS.includes(key as (typeof COMMON_NON_COLOR_KEYS)[number])
|
|
)
|
|
.forEach(([key, value]) => {
|
|
if (typeof value === "string") {
|
|
applyStyleToElement(root, key, value);
|
|
}
|
|
});
|
|
};
|
|
|
|
const applyThemeColors = (
|
|
root: HTMLElement,
|
|
themeStyles: ThemeStyles,
|
|
mode: Theme
|
|
) => {
|
|
Object.entries(themeStyles[mode]).forEach(([key, value]) => {
|
|
if (
|
|
typeof value === "string" &&
|
|
!COMMON_NON_COLOR_KEYS.includes(key as (typeof COMMON_NON_COLOR_KEYS)[number])
|
|
) {
|
|
const hslValue = colorFormatter(value, "hsl", "4");
|
|
applyStyleToElement(root, key, hslValue);
|
|
}
|
|
});
|
|
};
|
|
|
|
const updateThemeClass = (root: HTMLElement, mode: Theme) => {
|
|
if (mode === "light") {
|
|
root.classList.remove("dark");
|
|
} else {
|
|
root.classList.add("dark");
|
|
}
|
|
};
|
|
|
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
const { themeState, setThemeState } = useEditorStore();
|
|
|
|
// Handle theme preset from URL
|
|
useThemePresetFromUrl();
|
|
|
|
useEffect(() => {
|
|
const root = window.document.documentElement;
|
|
const { currentMode: mode, styles: themeStyles } = themeState;
|
|
|
|
updateThemeClass(root, mode);
|
|
applyCommonStyles(root, themeStyles.light);
|
|
applyThemeColors(root, themeStyles, mode);
|
|
setShadowVariables(themeState);
|
|
}, [themeState]);
|
|
|
|
const handleThemeChange = (newMode: Theme) => {
|
|
setThemeState({ ...themeState, currentMode: newMode });
|
|
};
|
|
|
|
const handleThemeToggle = (coords?: Coords) => {
|
|
const root = document.documentElement;
|
|
const newMode = themeState.currentMode === "light" ? "dark" : "light";
|
|
|
|
const prefersReducedMotion = window.matchMedia(
|
|
"(prefers-reduced-motion: reduce)"
|
|
).matches;
|
|
|
|
if (!document.startViewTransition || prefersReducedMotion) {
|
|
handleThemeChange(newMode);
|
|
return;
|
|
}
|
|
|
|
if (coords) {
|
|
root.style.setProperty("--x", `${coords.x}px`);
|
|
root.style.setProperty("--y", `${coords.y}px`);
|
|
}
|
|
|
|
document.startViewTransition(() => {
|
|
handleThemeChange(newMode);
|
|
});
|
|
};
|
|
|
|
const value: ThemeProviderState = {
|
|
theme: themeState.currentMode,
|
|
setTheme: handleThemeChange,
|
|
toggleTheme: handleThemeToggle,
|
|
};
|
|
|
|
return (
|
|
<ThemeProviderContext.Provider {...props} value={value}>
|
|
{children}
|
|
</ThemeProviderContext.Provider>
|
|
);
|
|
}
|
|
|
|
export const useTheme = () => {
|
|
const context = useContext(ThemeProviderContext);
|
|
|
|
if (context === undefined) {
|
|
throw new Error("useTheme must be used within a ThemeProvider");
|
|
}
|
|
|
|
return context;
|
|
};
|