|
|
@@ -1,347 +1,376 @@
|
|
|
-import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
|
+import React, { createContext, useContext, useState, useEffect } from "react";
|
|
|
|
|
|
-const API_BASE = 'https://goonblog.thevakhovske.eu.org/api';
|
|
|
+import { API_BASE } from "../config";
|
|
|
|
|
|
const ThemeContext = createContext();
|
|
|
|
|
|
export function useTheme() {
|
|
|
- const context = useContext(ThemeContext);
|
|
|
- if (context === undefined) {
|
|
|
- throw new Error('useTheme must be used within a ThemeProvider');
|
|
|
- }
|
|
|
- return context;
|
|
|
+ const context = useContext(ThemeContext);
|
|
|
+ if (context === undefined) {
|
|
|
+ throw new Error("useTheme must be used within a ThemeProvider");
|
|
|
+ }
|
|
|
+ return context;
|
|
|
}
|
|
|
|
|
|
export function ThemeProvider({ children }) {
|
|
|
- const [currentTheme, setCurrentTheme] = useState(null);
|
|
|
- const [allThemes, setAllThemes] = useState([]);
|
|
|
- const [activeThemeId, setActiveThemeId] = useState('default');
|
|
|
- const [loading, setLoading] = useState(true);
|
|
|
- const [error, setError] = useState(null);
|
|
|
-
|
|
|
- // Load themes on mount
|
|
|
- useEffect(() => {
|
|
|
- loadThemes();
|
|
|
- }, []);
|
|
|
-
|
|
|
- // Apply theme CSS variables when theme changes
|
|
|
- useEffect(() => {
|
|
|
- if (currentTheme) {
|
|
|
- applyThemeToCSS(currentTheme);
|
|
|
- }
|
|
|
- }, [currentTheme]);
|
|
|
-
|
|
|
- const loadThemes = async () => {
|
|
|
- try {
|
|
|
- setLoading(true);
|
|
|
- setError(null);
|
|
|
-
|
|
|
- console.log('Loading themes...');
|
|
|
-
|
|
|
- const response = await fetch(`${API_BASE}/themes`, {
|
|
|
- credentials: 'include',
|
|
|
- });
|
|
|
- console.log('Themes response status:', response.status, response.statusText);
|
|
|
-
|
|
|
- if (!response.ok) throw new Error('Failed to fetch themes');
|
|
|
-
|
|
|
- const data = await response.json();
|
|
|
- console.log('Themes data loaded:', data);
|
|
|
-
|
|
|
- setAllThemes(data.themes);
|
|
|
- setActiveThemeId(data.activeTheme);
|
|
|
-
|
|
|
- // Find and set current theme
|
|
|
- const activeTheme = data.themes.find(theme => theme.id === data.activeTheme);
|
|
|
- console.log('Looking for active theme:', data.activeTheme);
|
|
|
- console.log('Found active theme:', activeTheme);
|
|
|
-
|
|
|
- if (activeTheme) {
|
|
|
- setCurrentTheme(activeTheme);
|
|
|
- console.log('Applied theme:', activeTheme.name);
|
|
|
- } else {
|
|
|
- console.warn('Active theme not found, using default');
|
|
|
- const defaultTheme = data.themes.find(theme => theme.id === 'default');
|
|
|
- if (defaultTheme) {
|
|
|
- setCurrentTheme(defaultTheme);
|
|
|
+ const [currentTheme, setCurrentTheme] = useState(null);
|
|
|
+ const [allThemes, setAllThemes] = useState([]);
|
|
|
+ const [activeThemeId, setActiveThemeId] = useState("default");
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [error, setError] = useState(null);
|
|
|
+
|
|
|
+ // Load themes on mount
|
|
|
+ useEffect(() => {
|
|
|
+ loadThemes();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // Apply theme CSS variables when theme changes
|
|
|
+ useEffect(() => {
|
|
|
+ if (currentTheme) {
|
|
|
+ applyThemeToCSS(currentTheme);
|
|
|
}
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error('Error loading themes:', err);
|
|
|
- setError(err.message);
|
|
|
- } finally {
|
|
|
- setLoading(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const setActiveTheme = async (themeId) => {
|
|
|
- try {
|
|
|
- setError(null);
|
|
|
-
|
|
|
- console.log('Setting active theme:', themeId);
|
|
|
-
|
|
|
- const response = await fetch(`${API_BASE}/themes/active`, {
|
|
|
- method: 'PUT',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- },
|
|
|
- credentials: 'include',
|
|
|
- body: JSON.stringify({ themeId }),
|
|
|
- });
|
|
|
-
|
|
|
- console.log('Response status:', response.status, response.statusText);
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- let errorMessage = 'Failed to set active theme';
|
|
|
+ }, [currentTheme]);
|
|
|
+
|
|
|
+ const loadThemes = async () => {
|
|
|
try {
|
|
|
- const errorData = await response.json();
|
|
|
- errorMessage = errorData.error || errorMessage;
|
|
|
- } catch (e) {
|
|
|
- // If we can't parse error as JSON, use default message
|
|
|
+ setLoading(true);
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ console.log("Loading themes...");
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE}/themes`, {
|
|
|
+ credentials: "include",
|
|
|
+ });
|
|
|
+ console.log(
|
|
|
+ "Themes response status:",
|
|
|
+ response.status,
|
|
|
+ response.statusText,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!response.ok) throw new Error("Failed to fetch themes");
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ console.log("Themes data loaded:", data);
|
|
|
+
|
|
|
+ setAllThemes(data.themes);
|
|
|
+ setActiveThemeId(data.activeTheme);
|
|
|
+
|
|
|
+ // Find and set current theme
|
|
|
+ const activeTheme = data.themes.find(
|
|
|
+ (theme) => theme.id === data.activeTheme,
|
|
|
+ );
|
|
|
+ console.log("Looking for active theme:", data.activeTheme);
|
|
|
+ console.log("Found active theme:", activeTheme);
|
|
|
+
|
|
|
+ if (activeTheme) {
|
|
|
+ setCurrentTheme(activeTheme);
|
|
|
+ console.log("Applied theme:", activeTheme.name);
|
|
|
+ } else {
|
|
|
+ console.warn("Active theme not found, using default");
|
|
|
+ const defaultTheme = data.themes.find(
|
|
|
+ (theme) => theme.id === "default",
|
|
|
+ );
|
|
|
+ if (defaultTheme) {
|
|
|
+ setCurrentTheme(defaultTheme);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error("Error loading themes:", err);
|
|
|
+ setError(err.message);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
}
|
|
|
- throw new Error(`${errorMessage} (Status: ${response.status})`);
|
|
|
- }
|
|
|
-
|
|
|
- const data = await response.json();
|
|
|
- console.log('Theme set successfully:', data);
|
|
|
-
|
|
|
- setActiveThemeId(themeId);
|
|
|
- setCurrentTheme(data.theme);
|
|
|
-
|
|
|
- return { success: true };
|
|
|
- } catch (err) {
|
|
|
- console.error('Error setting active theme:', err);
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const createTheme = async (themeData) => {
|
|
|
- try {
|
|
|
- setError(null);
|
|
|
-
|
|
|
- const response = await fetch(`${API_BASE}/themes`, {
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- },
|
|
|
- credentials: 'include',
|
|
|
- body: JSON.stringify(themeData),
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- const errorData = await response.json();
|
|
|
- throw new Error(errorData.error || 'Failed to create theme');
|
|
|
- }
|
|
|
-
|
|
|
- const newTheme = await response.json();
|
|
|
- setAllThemes(prev => [...prev, newTheme]);
|
|
|
-
|
|
|
- return { success: true, theme: newTheme };
|
|
|
- } catch (err) {
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const updateTheme = async (themeId, themeData) => {
|
|
|
- try {
|
|
|
- setError(null);
|
|
|
-
|
|
|
- const response = await fetch(`${API_BASE}/themes/${themeId}`, {
|
|
|
- method: 'PUT',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- },
|
|
|
- credentials: 'include',
|
|
|
- body: JSON.stringify(themeData),
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- const errorData = await response.json();
|
|
|
- throw new Error(errorData.error || 'Failed to update theme');
|
|
|
- }
|
|
|
-
|
|
|
- const updatedTheme = await response.json();
|
|
|
-
|
|
|
- // Update in themes list
|
|
|
- setAllThemes(prev => prev.map(theme =>
|
|
|
- theme.id === themeId ? updatedTheme : theme
|
|
|
- ));
|
|
|
-
|
|
|
- // Update current theme if it's the active one
|
|
|
- if (activeThemeId === themeId) {
|
|
|
- setCurrentTheme(updatedTheme);
|
|
|
- }
|
|
|
-
|
|
|
- return { success: true, theme: updatedTheme };
|
|
|
- } catch (err) {
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const deleteTheme = async (themeId) => {
|
|
|
- try {
|
|
|
- setError(null);
|
|
|
-
|
|
|
- const response = await fetch(`${API_BASE}/themes/${themeId}`, {
|
|
|
- method: 'DELETE',
|
|
|
- credentials: 'include',
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- const errorData = await response.json();
|
|
|
- throw new Error(errorData.error || 'Failed to delete theme');
|
|
|
- }
|
|
|
-
|
|
|
- // Remove from themes list
|
|
|
- setAllThemes(prev => prev.filter(theme => theme.id !== themeId));
|
|
|
-
|
|
|
- return { success: true };
|
|
|
- } catch (err) {
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const exportTheme = async (themeId) => {
|
|
|
- try {
|
|
|
- const response = await fetch(`${API_BASE}/themes/${themeId}/export`, {
|
|
|
- credentials: 'include',
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) throw new Error('Failed to export theme');
|
|
|
-
|
|
|
- const themeData = await response.json();
|
|
|
-
|
|
|
- // Create and download file
|
|
|
- const blob = new Blob([JSON.stringify(themeData, null, 2)], {
|
|
|
- type: 'application/json'
|
|
|
- });
|
|
|
- const url = URL.createObjectURL(blob);
|
|
|
- const a = document.createElement('a');
|
|
|
- a.href = url;
|
|
|
- a.download = `theme-${themeId}.json`;
|
|
|
- document.body.appendChild(a);
|
|
|
- a.click();
|
|
|
- document.body.removeChild(a);
|
|
|
- URL.revokeObjectURL(url);
|
|
|
-
|
|
|
- return { success: true };
|
|
|
- } catch (err) {
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const importTheme = async (themeData) => {
|
|
|
- try {
|
|
|
- // Ensure unique ID
|
|
|
- const uniqueId = `${themeData.id}-${Date.now()}`;
|
|
|
- const importedTheme = {
|
|
|
- ...themeData,
|
|
|
- id: uniqueId,
|
|
|
- name: `${themeData.name} (Imported)`
|
|
|
- };
|
|
|
-
|
|
|
- return await createTheme(importedTheme);
|
|
|
- } catch (err) {
|
|
|
- setError(err.message);
|
|
|
- return { success: false, error: err.message };
|
|
|
- }
|
|
|
- };
|
|
|
+ };
|
|
|
|
|
|
- // Apply theme CSS variables to document root
|
|
|
- const applyThemeToCSS = (theme) => {
|
|
|
- if (!theme) {
|
|
|
- console.warn('No theme to apply');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- console.log('Applying theme to CSS:', theme.name, theme.colors);
|
|
|
-
|
|
|
- const root = document.documentElement;
|
|
|
-
|
|
|
- // Apply color variables
|
|
|
- Object.entries(theme.colors).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--color-${kebabCase(key)}`, value);
|
|
|
- });
|
|
|
-
|
|
|
- // Apply typography variables
|
|
|
- if (theme.typography) {
|
|
|
- root.style.setProperty('--font-family', theme.typography.fontFamily);
|
|
|
- root.style.setProperty('--font-family-heading', theme.typography.headingFontFamily);
|
|
|
-
|
|
|
- if (theme.typography.fontSize) {
|
|
|
- Object.entries(theme.typography.fontSize).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--font-size-${key}`, value);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if (theme.typography.lineHeight) {
|
|
|
- Object.entries(theme.typography.lineHeight).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--line-height-${key}`, value);
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Apply layout variables
|
|
|
- if (theme.layout) {
|
|
|
- if (theme.layout.borderRadius) {
|
|
|
- Object.entries(theme.layout.borderRadius).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--border-radius-${key}`, value);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if (theme.layout.spacing) {
|
|
|
- Object.entries(theme.layout.spacing).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--spacing-${key}`, value);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if (theme.layout.shadows) {
|
|
|
- Object.entries(theme.layout.shadows).forEach(([key, value]) => {
|
|
|
- root.style.setProperty(`--shadow-${key}`, value);
|
|
|
+ const setActiveTheme = async (themeId) => {
|
|
|
+ try {
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ console.log("Setting active theme:", themeId);
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE}/themes/active`, {
|
|
|
+ method: "PUT",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ credentials: "include",
|
|
|
+ body: JSON.stringify({ themeId }),
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ "Response status:",
|
|
|
+ response.status,
|
|
|
+ response.statusText,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ let errorMessage = "Failed to set active theme";
|
|
|
+ try {
|
|
|
+ const errorData = await response.json();
|
|
|
+ errorMessage = errorData.error || errorMessage;
|
|
|
+ } catch (e) {
|
|
|
+ // If we can't parse error as JSON, use default message
|
|
|
+ }
|
|
|
+ throw new Error(`${errorMessage} (Status: ${response.status})`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ console.log("Theme set successfully:", data);
|
|
|
+
|
|
|
+ setActiveThemeId(themeId);
|
|
|
+ setCurrentTheme(data.theme);
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+ } catch (err) {
|
|
|
+ console.error("Error setting active theme:", err);
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const createTheme = async (themeData) => {
|
|
|
+ try {
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE}/themes`, {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ credentials: "include",
|
|
|
+ body: JSON.stringify(themeData),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const errorData = await response.json();
|
|
|
+ throw new Error(errorData.error || "Failed to create theme");
|
|
|
+ }
|
|
|
+
|
|
|
+ const newTheme = await response.json();
|
|
|
+ setAllThemes((prev) => [...prev, newTheme]);
|
|
|
+
|
|
|
+ return { success: true, theme: newTheme };
|
|
|
+ } catch (err) {
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const updateTheme = async (themeId, themeData) => {
|
|
|
+ try {
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE}/themes/${themeId}`, {
|
|
|
+ method: "PUT",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ credentials: "include",
|
|
|
+ body: JSON.stringify(themeData),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const errorData = await response.json();
|
|
|
+ throw new Error(errorData.error || "Failed to update theme");
|
|
|
+ }
|
|
|
+
|
|
|
+ const updatedTheme = await response.json();
|
|
|
+
|
|
|
+ // Update in themes list
|
|
|
+ setAllThemes((prev) =>
|
|
|
+ prev.map((theme) =>
|
|
|
+ theme.id === themeId ? updatedTheme : theme,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+
|
|
|
+ // Update current theme if it's the active one
|
|
|
+ if (activeThemeId === themeId) {
|
|
|
+ setCurrentTheme(updatedTheme);
|
|
|
+ }
|
|
|
+
|
|
|
+ return { success: true, theme: updatedTheme };
|
|
|
+ } catch (err) {
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const deleteTheme = async (themeId) => {
|
|
|
+ try {
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ const response = await fetch(`${API_BASE}/themes/${themeId}`, {
|
|
|
+ method: "DELETE",
|
|
|
+ credentials: "include",
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const errorData = await response.json();
|
|
|
+ throw new Error(errorData.error || "Failed to delete theme");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove from themes list
|
|
|
+ setAllThemes((prev) =>
|
|
|
+ prev.filter((theme) => theme.id !== themeId),
|
|
|
+ );
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+ } catch (err) {
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const exportTheme = async (themeId) => {
|
|
|
+ try {
|
|
|
+ const response = await fetch(
|
|
|
+ `${API_BASE}/themes/${themeId}/export`,
|
|
|
+ {
|
|
|
+ credentials: "include",
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!response.ok) throw new Error("Failed to export theme");
|
|
|
+
|
|
|
+ const themeData = await response.json();
|
|
|
+
|
|
|
+ // Create and download file
|
|
|
+ const blob = new Blob([JSON.stringify(themeData, null, 2)], {
|
|
|
+ type: "application/json",
|
|
|
+ });
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ const a = document.createElement("a");
|
|
|
+ a.href = url;
|
|
|
+ a.download = `theme-${themeId}.json`;
|
|
|
+ document.body.appendChild(a);
|
|
|
+ a.click();
|
|
|
+ document.body.removeChild(a);
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+ } catch (err) {
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const importTheme = async (themeData) => {
|
|
|
+ try {
|
|
|
+ // Ensure unique ID
|
|
|
+ const uniqueId = `${themeData.id}-${Date.now()}`;
|
|
|
+ const importedTheme = {
|
|
|
+ ...themeData,
|
|
|
+ id: uniqueId,
|
|
|
+ name: `${themeData.name} (Imported)`,
|
|
|
+ };
|
|
|
+
|
|
|
+ return await createTheme(importedTheme);
|
|
|
+ } catch (err) {
|
|
|
+ setError(err.message);
|
|
|
+ return { success: false, error: err.message };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Apply theme CSS variables to document root
|
|
|
+ const applyThemeToCSS = (theme) => {
|
|
|
+ if (!theme) {
|
|
|
+ console.warn("No theme to apply");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("Applying theme to CSS:", theme.name, theme.colors);
|
|
|
+
|
|
|
+ const root = document.documentElement;
|
|
|
+
|
|
|
+ // Apply color variables
|
|
|
+ Object.entries(theme.colors).forEach(([key, value]) => {
|
|
|
+ root.style.setProperty(`--color-${kebabCase(key)}`, value);
|
|
|
});
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Apply custom CSS if present
|
|
|
- if (theme.customCSS) {
|
|
|
- let customStyleEl = document.getElementById('theme-custom-css');
|
|
|
- if (!customStyleEl) {
|
|
|
- customStyleEl = document.createElement('style');
|
|
|
- customStyleEl.id = 'theme-custom-css';
|
|
|
- document.head.appendChild(customStyleEl);
|
|
|
- }
|
|
|
- customStyleEl.textContent = theme.customCSS;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // Helper function to convert camelCase to kebab-case
|
|
|
- const kebabCase = (str) => {
|
|
|
- return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
|
- };
|
|
|
-
|
|
|
- const value = {
|
|
|
- currentTheme,
|
|
|
- allThemes,
|
|
|
- activeThemeId,
|
|
|
- loading,
|
|
|
- error,
|
|
|
- loadThemes,
|
|
|
- setActiveTheme,
|
|
|
- createTheme,
|
|
|
- updateTheme,
|
|
|
- deleteTheme,
|
|
|
- exportTheme,
|
|
|
- importTheme
|
|
|
- };
|
|
|
-
|
|
|
- return (
|
|
|
- <ThemeContext.Provider value={value}>
|
|
|
- {children}
|
|
|
- </ThemeContext.Provider>
|
|
|
- );
|
|
|
-}
|
|
|
+
|
|
|
+ // Apply typography variables
|
|
|
+ if (theme.typography) {
|
|
|
+ root.style.setProperty(
|
|
|
+ "--font-family",
|
|
|
+ theme.typography.fontFamily,
|
|
|
+ );
|
|
|
+ root.style.setProperty(
|
|
|
+ "--font-family-heading",
|
|
|
+ theme.typography.headingFontFamily,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (theme.typography.fontSize) {
|
|
|
+ Object.entries(theme.typography.fontSize).forEach(
|
|
|
+ ([key, value]) => {
|
|
|
+ root.style.setProperty(`--font-size-${key}`, value);
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (theme.typography.lineHeight) {
|
|
|
+ Object.entries(theme.typography.lineHeight).forEach(
|
|
|
+ ([key, value]) => {
|
|
|
+ root.style.setProperty(`--line-height-${key}`, value);
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply layout variables
|
|
|
+ if (theme.layout) {
|
|
|
+ if (theme.layout.borderRadius) {
|
|
|
+ Object.entries(theme.layout.borderRadius).forEach(
|
|
|
+ ([key, value]) => {
|
|
|
+ root.style.setProperty(`--border-radius-${key}`, value);
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (theme.layout.spacing) {
|
|
|
+ Object.entries(theme.layout.spacing).forEach(([key, value]) => {
|
|
|
+ root.style.setProperty(`--spacing-${key}`, value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (theme.layout.shadows) {
|
|
|
+ Object.entries(theme.layout.shadows).forEach(([key, value]) => {
|
|
|
+ root.style.setProperty(`--shadow-${key}`, value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply custom CSS if present
|
|
|
+ if (theme.customCSS) {
|
|
|
+ let customStyleEl = document.getElementById("theme-custom-css");
|
|
|
+ if (!customStyleEl) {
|
|
|
+ customStyleEl = document.createElement("style");
|
|
|
+ customStyleEl.id = "theme-custom-css";
|
|
|
+ document.head.appendChild(customStyleEl);
|
|
|
+ }
|
|
|
+ customStyleEl.textContent = theme.customCSS;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Helper function to convert camelCase to kebab-case
|
|
|
+ const kebabCase = (str) => {
|
|
|
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
|
+ };
|
|
|
+
|
|
|
+ const value = {
|
|
|
+ currentTheme,
|
|
|
+ allThemes,
|
|
|
+ activeThemeId,
|
|
|
+ loading,
|
|
|
+ error,
|
|
|
+ loadThemes,
|
|
|
+ setActiveTheme,
|
|
|
+ createTheme,
|
|
|
+ updateTheme,
|
|
|
+ deleteTheme,
|
|
|
+ exportTheme,
|
|
|
+ importTheme,
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
|
|
+ );
|
|
|
+}
|