import { useEffect } from 'react' import { useAppearance, useBlurSupport } from '@/hooks/useAppearance' import { useTheme } from '@/hooks/useTheme' import { isDefaultColor, getDefaultTextColor, isDefaultColorMainView, isDefaultColorPrimary, isDefaultColorAccent, isDefaultColorDestructive, } from '@/hooks/useAppearance' /** * AppearanceProvider ensures appearance settings are applied on every page load * This component should be mounted at the root level of the application */ export function AppearanceProvider() { const { fontSize, appBgColor, appLeftPanelTextColor, appMainViewBgColor, appMainViewTextColor, appPrimaryBgColor, appPrimaryTextColor, appAccentBgColor, appAccentTextColor, appDestructiveBgColor, appDestructiveTextColor, } = useAppearance() const { isDark } = useTheme() const showAlphaSlider = useBlurSupport() // Force re-apply appearance on mount to fix theme desync issues on Windows // This ensures that when navigating to routes (like logs), the theme is properly applied useEffect(() => { const { setAppBgColor, setAppMainViewBgColor, appBgColor, appMainViewBgColor, } = useAppearance.getState() // Re-trigger setters to ensure CSS variables are applied with correct theme setAppBgColor(appBgColor) setAppMainViewBgColor(appMainViewBgColor) }, []) // Run once on mount // Update colors when blur support changes (important for Windows/Linux) useEffect(() => { const { setAppBgColor, appBgColor } = useAppearance.getState() // Re-apply color to update alpha based on blur support setAppBgColor(appBgColor) }, [showAlphaSlider]) // Apply appearance settings on mount and when they change useEffect(() => { // Apply font size document.documentElement.style.setProperty('--font-size-base', fontSize) // Hide alpha slider when blur is not supported const shouldHideAlpha = !showAlphaSlider let alphaStyleElement = document.getElementById('alpha-slider-style') if (shouldHideAlpha) { if (!alphaStyleElement) { alphaStyleElement = document.createElement('style') alphaStyleElement.id = 'alpha-slider-style' document.head.appendChild(alphaStyleElement) } alphaStyleElement.textContent = '.react-colorful__alpha { display: none !important; }' } else if (alphaStyleElement) { alphaStyleElement.remove() } // Apply app background color // Import culori functions dynamically to avoid SSR issues import('culori').then(({ rgb, oklch, formatCss }) => { // Convert RGBA to a format culori can work with // Use alpha = 1 when blur is not supported const culoriRgb = rgb({ mode: 'rgb', r: appBgColor.r / 255, g: appBgColor.g / 255, b: appBgColor.b / 255, alpha: showAlphaSlider ? appBgColor.a : 1, }) const culoriRgbMainView = rgb({ mode: 'rgb', r: appMainViewBgColor.r / 255, g: appMainViewBgColor.g / 255, b: appMainViewBgColor.b / 255, alpha: appMainViewBgColor.a, }) const culoriRgbPrimary = rgb({ mode: 'rgb', r: appPrimaryBgColor.r / 255, g: appPrimaryBgColor.g / 255, b: appPrimaryBgColor.b / 255, alpha: appPrimaryBgColor.a, }) const culoriRgbAccent = rgb({ mode: 'rgb', r: appAccentBgColor.r / 255, g: appAccentBgColor.g / 255, b: appAccentBgColor.b / 255, alpha: appAccentBgColor.a, }) const culoriRgbDestructive = rgb({ mode: 'rgb', r: appDestructiveBgColor.r / 255, g: appDestructiveBgColor.g / 255, b: appDestructiveBgColor.b / 255, alpha: appDestructiveBgColor.a, }) // Convert to OKLCH for CSS variable const oklchColor = oklch(culoriRgb) const oklchColormainViewApp = oklch(culoriRgbMainView) const oklchColorPrimary = oklch(culoriRgbPrimary) const oklchColorAccent = oklch(culoriRgbAccent) const oklchColorDestructive = oklch(culoriRgbDestructive) if (oklchColor) { document.documentElement.style.setProperty( '--app-bg', formatCss(oklchColor) ) } if (oklchColormainViewApp) { document.documentElement.style.setProperty( '--app-main-view', formatCss(oklchColormainViewApp) ) } if (oklchColorPrimary) { document.documentElement.style.setProperty( '--app-primary', formatCss(oklchColorPrimary) ) } if (oklchColorAccent) { document.documentElement.style.setProperty( '--app-accent', formatCss(oklchColorAccent) ) } if (oklchColorDestructive) { document.documentElement.style.setProperty( '--app-destructive', formatCss(oklchColorDestructive) ) } }) // Apply text color based on background brightness document.documentElement.style.setProperty( '--app-left-panel-fg', appLeftPanelTextColor ) // Apply text color based on background brightness document.documentElement.style.setProperty( '--app-main-view-fg', appMainViewTextColor ) // Apply text color based on background brightness for primary document.documentElement.style.setProperty( '--app-primary-fg', appPrimaryTextColor ) // Apply text color based on background brightness for accent document.documentElement.style.setProperty( '--app-accent-fg', appAccentTextColor ) // Apply text color based on background brightness for destructive document.documentElement.style.setProperty( '--app-destructive-fg', appDestructiveTextColor ) }, [ fontSize, appBgColor, appLeftPanelTextColor, isDark, appMainViewBgColor, appMainViewTextColor, appPrimaryBgColor, appPrimaryTextColor, appAccentBgColor, appAccentTextColor, appDestructiveBgColor, appDestructiveTextColor, showAlphaSlider, ]) // Update appearance when theme changes useEffect(() => { // Get the current appearance state const { appBgColor, appMainViewBgColor, appPrimaryBgColor, appAccentBgColor, appDestructiveBgColor, setAppBgColor, setAppMainViewBgColor, setAppPrimaryBgColor, setAppAccentBgColor, setAppDestructiveBgColor, } = useAppearance.getState() // Force re-apply all colors when theme changes to ensure correct dark/light defaults // This is especially important on Windows where the theme might not be properly // synchronized when navigating to different routes (e.g., logs page) // If using default background color, update it when theme changes if (isDefaultColor(appBgColor)) { // This will trigger the appropriate updates for both background and text color setAppBgColor(appBgColor) } else { // If using custom background, just update the text color if needed const textColor = isDefaultColor(appBgColor) ? getDefaultTextColor(isDark) : appLeftPanelTextColor document.documentElement.style.setProperty( '--app-left-panel-fg', textColor ) } // If using default background color, update it when theme changes if (isDefaultColorMainView(appMainViewBgColor)) { // This will trigger the appropriate updates for both background and text color setAppMainViewBgColor(appMainViewBgColor) } else { // If using custom background, just update the text color if needed const textColorMainView = isDefaultColor(appMainViewBgColor) ? getDefaultTextColor(isDark) : appMainViewTextColor document.documentElement.style.setProperty( '--app-main-view-fg', textColorMainView ) } // If using default primary color, update it when theme changes if (isDefaultColorPrimary(appPrimaryBgColor)) { // This will trigger the appropriate updates for both background and text color setAppPrimaryBgColor(appPrimaryBgColor) } else { // If using custom background, just update the text color if needed const textColorPrimary = isDefaultColorPrimary(appPrimaryBgColor) ? getDefaultTextColor(isDark) : appPrimaryTextColor document.documentElement.style.setProperty( '--app-primary-fg', textColorPrimary ) } // If using default accent color, update it when theme changes if (isDefaultColorAccent(appAccentBgColor)) { // This will trigger the appropriate updates for both background and text color setAppAccentBgColor(appAccentBgColor) } else { // If using custom background, just update the text color if needed const textColorAccent = isDefaultColorAccent(appAccentBgColor) ? getDefaultTextColor(isDark) : appAccentTextColor document.documentElement.style.setProperty( '--app-accent-fg', textColorAccent ) } // If using default destructive color, update it when theme changes if (isDefaultColorDestructive(appDestructiveBgColor)) { // This will trigger the appropriate updates for both background and text color setAppDestructiveBgColor(appDestructiveBgColor) } else { // If using custom background, just update the text color if needed const textColorDestructive = isDefaultColorDestructive( appDestructiveBgColor ) ? getDefaultTextColor(isDark) : appDestructiveTextColor document.documentElement.style.setProperty( '--app-destructive-fg', textColorDestructive ) } }, [ isDark, appLeftPanelTextColor, appMainViewTextColor, appPrimaryTextColor, appAccentTextColor, appDestructiveTextColor, ]) return null }