jan/web-app/src/providers/AppearanceProvider.tsx
2025-10-06 10:55:17 +07:00

318 lines
9.7 KiB
TypeScript

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
}