import React, { createContext, useContext, useState, useEffect } from 'react'; import { BrandColors, ColorTheme, PALETTE, colorThemes } from './theme-data'; import { hexToRgb } from '../utils/color-utils'; interface BrandColorsContextType { colors: BrandColors; currentTheme: string; themes: ColorTheme[]; setTheme: (themeId: string) => void; setBrandColor: (colorKey: keyof BrandColors, value: string) => void; radius: number; setRadius: (radius: number) => void; } const BrandColorsContext = createContext(undefined); interface BrandColorsProviderProps { children: React.ReactNode; defaultTheme?: string; primaryColor?: string; useCustomTokens?: boolean; } export const BrandColorsProvider: React.FC = ({ children, defaultTheme, primaryColor, useCustomTokens = false, }) => { const [currentTheme, setCurrentTheme] = useState(() => { // Se primaryColor for passado, ignoramos o nome do tema if (primaryColor) return 'custom'; return defaultTheme || 'xertica-original'; }); const [radius, setRadius] = useState(0.5); const [colors, setColors] = useState(() => { if (primaryColor) { // Criar um tema sob demanda baseado na cor primária return { ...colorThemes[0].colors, primary: primaryColor, primaryDarkMode: primaryColor, sidebarLight: primaryColor, sidebarDark: primaryColor, chart1: primaryColor, }; } const theme = colorThemes.find(t => t.id === currentTheme); return theme?.colors || colorThemes[0].colors; }); useEffect(() => { if (primaryColor) { setColors({ ...colorThemes[0].colors, primary: primaryColor, primaryDarkMode: primaryColor, sidebarLight: primaryColor, sidebarDark: primaryColor, chart1: primaryColor, }); setCurrentTheme('custom'); return; } const theme = colorThemes.find(t => t.id === currentTheme); if (theme) { setColors(theme.colors); } }, [currentTheme, primaryColor]); // Função para aplicar as cores baseadas no modo atual e tema const applyColors = React.useCallback(() => { if (useCustomTokens) return; // Skip if user wants to use local tokens.css if (typeof document === 'undefined') return; const root = document.documentElement; const isDark = root.classList.contains('dark'); // --- Determine Colors based on Mode --- const primary = isDark ? colors.primaryDarkMode : colors.primary; const primaryForeground = isDark ? colors.primaryForegroundDark : colors.primaryForeground; // Gradient Logic const gradientStart = isDark ? colors.gradientStartDark : colors.gradientStart; const gradientEnd = isDark ? colors.gradientEndDark : colors.gradientEnd; // --- Semantic Colors (Fixed Standard) --- // Success (Green), Warning (Amber/Yellow), Info (Blue), Destructive (Red) const semantic = { success: isDark ? '#4ade80' : '#047857', // Green 400 / 700 warning: isDark ? '#fbbf24' : '#b45309', // Amber 400 / 700 info: isDark ? '#60a5fa' : '#1d4ed8', // Blue 400 / 700 destructive: isDark ? '#f87171' : '#b91c1c', // Red 400 / 700 foreground: isDark ? '#050505' : '#ffffff', }; // --- Apply CSS Variables --- const cssVars: string[] = []; // 1. Core Primary Colors cssVars.push(`--primary: ${primary}`); cssVars.push(`--primary-foreground: ${primaryForeground}`); // 2. Semantic Colors cssVars.push(`--success: ${semantic.success}`); cssVars.push(`--success-foreground: ${semantic.foreground}`); cssVars.push(`--warning: ${semantic.warning}`); cssVars.push(`--warning-foreground: ${semantic.foreground}`); cssVars.push(`--info: ${semantic.info}`); cssVars.push(`--info-foreground: ${semantic.foreground}`); cssVars.push(`--destructive: ${semantic.destructive}`); cssVars.push(`--destructive-foreground: ${semantic.foreground}`); // 3. Secondary & Accents (Derived or Specific) const primaryRGB = hexToRgb(primary); if (primaryRGB) { cssVars.push( `--ring: ${isDark ? colors.primaryDarkMode : `rgba(${primaryRGB.r}, ${primaryRGB.g}, ${primaryRGB.b}, 0.5)`}` ); // Also update the Xertica specific variables cssVars.push(`--xertica-primary: ${primary}`); cssVars.push( `--primary-light: rgba(${primaryRGB.r}, ${primaryRGB.g}, ${primaryRGB.b}, 0.15)` ); cssVars.push(`--primary-light-foreground: ${primary}`); } // 3. Radius cssVars.push(`--radius: ${radius}rem`); // 4. Charts cssVars.push(`--chart-1: ${colors.chart1}`); cssVars.push(`--chart-2: ${colors.chart2}`); cssVars.push(`--chart-3: ${colors.chart3}`); cssVars.push(`--chart-4: ${colors.chart4}`); cssVars.push(`--chart-5: ${colors.chart5}`); // 5. Sidebar (Override) const sidebarBg = isDark ? colors.sidebarDark : colors.sidebarLight; const isSidebarLight = sidebarBg.toLowerCase() === '#ffffff' || sidebarBg.toLowerCase() === '#f3f4f6' || sidebarBg.toLowerCase() === '#fafafa'; const sidebarFg = isSidebarLight ? '#0F172A' : '#FFFFFF'; cssVars.push(`--sidebar: ${sidebarBg}`); cssVars.push(`--sidebar-foreground: ${sidebarFg}`); if (primaryRGB) { if (isSidebarLight) { cssVars.push( `--sidebar-accent: rgba(${primaryRGB.r}, ${primaryRGB.g}, ${primaryRGB.b}, 0.1)` ); cssVars.push(`--sidebar-accent-foreground: ${primary}`); cssVars.push(`--sidebar-border: rgba(0,0,0,0.1)`); } else { cssVars.push(`--sidebar-accent: rgba(255, 255, 255, 0.1)`); cssVars.push(`--sidebar-accent-foreground: #FFFFFF`); cssVars.push(`--sidebar-border: rgba(255,255,255,0.1)`); } cssVars.push(`--sidebar-ring: rgba(${primaryRGB.r}, ${primaryRGB.g}, ${primaryRGB.b}, 0.5)`); } if (!isSidebarLight) { cssVars.push(`--sidebar-primary: #FFFFFF`); cssVars.push(`--sidebar-primary-foreground: ${primary}`); } else { cssVars.push(`--sidebar-primary: ${primary}`); cssVars.push(`--sidebar-primary-foreground: ${primaryForeground}`); } // 6. Gradients cssVars.push( `--gradient-diagonal: linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)` ); // 7. Surface colors for dark mode (tinted toward the theme hue) // Only applied when dark surface tokens exist on the theme. const darkSurfaceVars: string[] = []; if (colors.darkBackground) { darkSurfaceVars.push(`--background: ${colors.darkBackground}`); darkSurfaceVars.push(`--card: ${colors.darkCard}`); darkSurfaceVars.push(`--popover: ${colors.darkCard}`); darkSurfaceVars.push(`--secondary: ${colors.darkMuted}`); darkSurfaceVars.push(`--muted: ${colors.darkMuted}`); darkSurfaceVars.push(`--accent: ${colors.darkMuted}`); darkSurfaceVars.push(`--border: ${colors.darkBorder}`); darkSurfaceVars.push(`--input: ${colors.darkMuted}`); darkSurfaceVars.push(`--input-background: ${colors.darkMuted}`); } // Apply primary-related tokens directly as inline styles on . // Inline styles have the highest possible specificity and always win over // any stylesheet rule, regardless of order or selector specificity. cssVars.forEach(declaration => { const eqIdx = declaration.indexOf(':'); if (eqIdx === -1) return; const prop = declaration.substring(0, eqIdx).trim(); const val = declaration.substring(eqIdx + 1).trim(); root.style.setProperty(prop, val); // `root` is document.documentElement, declared at line 79 }); // Surface tokens (background, card, muted, etc.) are injected via a style // tag appended last in so they win the cascade for their selectors. const styleId = 'xertica-brand-colors-injection'; let styleEl = document.getElementById(styleId); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = styleId; document.head.appendChild(styleEl); } else { document.head.appendChild(styleEl); } const darkSurfaceBlock = darkSurfaceVars.length > 0 ? `\n:root[data-mode='dark'], .dark {\n ${darkSurfaceVars.join(';\n ')};\n}` : ''; // The style tag handles only the surface tokens that vary per theme in dark mode. styleEl.innerHTML = darkSurfaceBlock.trim(); }, [colors, currentTheme, radius, useCustomTokens]); // Aplicar cores quando elas mudarem useEffect(() => { applyColors(); }, [applyColors]); // Observar mudanças na classe 'dark' do documento useEffect(() => { if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return; const root = document.documentElement; const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { applyColors(); } }); }); observer.observe(root, { attributes: true, attributeFilter: ['class'] }); return () => observer.disconnect(); }, [applyColors]); const setTheme = (themeId: string) => { setCurrentTheme(themeId); }; const setBrandColor = (colorKey: keyof BrandColors, value: string) => { setColors(prev => ({ ...prev, [colorKey]: value, })); }; return ( {children} ); }; export const useBrandColors = () => { const context = useContext(BrandColorsContext); if (context === undefined) { throw new Error('useBrandColors must be used within a BrandColorsProvider'); } return context; };