theme-o-rama
Version:
A TypeScript library for dynamic theme management in react + shadcn + tailwind applications
88 lines (87 loc) • 3.4 kB
JavaScript
"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useContext, useEffect, useRef, useState } from "react";
import colorTheme from "./color.json" with { type: "json" };
import darkTheme from "./dark.json" with { type: "json" };
import { applyTheme } from "./index.js";
import lightTheme from "./light.json" with { type: "json" };
import { ThemeLoader } from "./theme-loader.js";
// Browser detection for SSR compatibility
const isBrowser = typeof window !== "undefined";
const SimpleThemeContext = createContext(undefined);
export function SimpleThemeProvider({ children, imageResolver, onThemeChange, }) {
const [currentTheme, setCurrentTheme] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isSettingTheme, setIsSettingTheme] = useState(false);
const [error, setError] = useState(null);
// Use refs for stable instances that don't need to trigger re-renders
const themeLoader = useRef(new ThemeLoader()).current;
const imageResolverRef = useRef(imageResolver);
// Store callbacks and functions in refs to avoid re-running effects when they change
const onThemeChangeRef = useRef(onThemeChange);
useEffect(() => {
onThemeChangeRef.current = onThemeChange;
imageResolverRef.current = imageResolver;
}, [onThemeChange]);
const setTheme = async (theme) => {
if (isSettingTheme)
return; // Prevent concurrent calls
setIsSettingTheme(true);
try {
setCurrentTheme(theme);
if (isBrowser) {
applyTheme(theme, document.documentElement);
}
// Notify app of theme change (app handles storage)
if (onThemeChangeRef.current) {
onThemeChangeRef.current(theme);
}
setError(null); // Clear any previous errors
}
catch (err) {
console.error("Error setting theme:", err);
setError("Failed to set theme");
}
finally {
setIsSettingTheme(false);
}
};
useEffect(() => {
const initializeThemes = async () => {
try {
setIsLoading(true);
setError(null);
// Always load built-in themes so the can be inherited from
await themeLoader.loadTheme(lightTheme);
await themeLoader.loadTheme(darkTheme);
await themeLoader.loadTheme(colorTheme);
}
catch (err) {
console.error("Error loading themes:", err);
setError("Failed to load themes");
setCurrentTheme(null);
}
finally {
setIsLoading(false);
}
};
initializeThemes();
}, []);
const initializeTheme = async (theme) => {
return await themeLoader.initializeTheme(theme, imageResolverRef.current);
};
return (_jsx(SimpleThemeContext.Provider, { value: {
currentTheme,
setTheme,
isLoading,
error,
initializeTheme,
}, children: children }));
}
export function useSimpleTheme() {
const context = useContext(SimpleThemeContext);
if (context === undefined) {
throw new Error("useSimpleTheme must be used within a SimpleThemeProvider");
}
return context;
}