112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState, createContext, useContext } from 'react'
|
|
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
import { getBrandingSettings } from '@/lib/actions/branding'
|
|
import { PRESET_COLOR_SCHEMES, BrandingSettings } from '@/lib/constants/branding'
|
|
|
|
function hexToHsl(hex: string): string {
|
|
try {
|
|
let c = hex.replace('#', '')
|
|
if (c.length === 3) c = c.split('').map(x => x + x).join('')
|
|
const r = parseInt(c.substring(0, 2), 16) / 255
|
|
const g = parseInt(c.substring(2, 4), 16) / 255
|
|
const b = parseInt(c.substring(4, 6), 16) / 255
|
|
|
|
const max = Math.max(r, g, b)
|
|
const min = Math.min(r, g, b)
|
|
let h = 0, s = 0, l = (max + min) / 2
|
|
|
|
if (max !== min) {
|
|
const d = max - min
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
switch (max) {
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
|
case g: h = (b - r) / d + 2; break
|
|
case b: h = (r - g) / d + 4; break
|
|
}
|
|
h /= 6
|
|
}
|
|
|
|
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
|
|
} catch (e) {
|
|
return '217 91% 60%'
|
|
}
|
|
}
|
|
|
|
const ThemeContext = createContext<{
|
|
branding: BrandingSettings | null
|
|
refreshBranding: () => Promise<void>
|
|
}>({
|
|
branding: null,
|
|
refreshBranding: async () => {},
|
|
})
|
|
|
|
export function ThemeProvider({
|
|
children,
|
|
...props
|
|
}: {
|
|
children: React.ReactNode
|
|
[key: string]: any
|
|
}) {
|
|
const [branding, setBranding] = useState<BrandingSettings | null>(null)
|
|
|
|
const loadTheme = async () => {
|
|
try {
|
|
const settings = await getBrandingSettings()
|
|
setBranding(settings)
|
|
|
|
// Preset Ermittlung
|
|
const preset = PRESET_COLOR_SCHEMES.find(p => p.id === settings.colorScheme) || PRESET_COLOR_SCHEMES[3]
|
|
const isCustom = settings.colorScheme === 'custom'
|
|
|
|
const primary = isCustom ? (settings.primaryColor || '#2563eb') : preset.primary
|
|
const accent = isCustom ? (settings.accentColor || '#38bdf8') : preset.accent
|
|
|
|
const primaryHsl = hexToHsl(primary)
|
|
const accentHsl = hexToHsl(accent)
|
|
|
|
const root = document.documentElement
|
|
|
|
// 10 Color CSS Tokens applied globally onto :root
|
|
root.style.setProperty('--primary', primaryHsl)
|
|
root.style.setProperty('--ring', primaryHsl)
|
|
root.style.setProperty('--accent', accentHsl)
|
|
|
|
root.style.setProperty('--primary-custom', primary)
|
|
root.style.setProperty('--accent-custom', accent)
|
|
|
|
root.style.setProperty('--success-custom', settings.successColor || preset.success)
|
|
root.style.setProperty('--warning-custom', settings.warningColor || preset.warning)
|
|
root.style.setProperty('--destructive-custom', settings.destructiveColor || preset.destructive)
|
|
|
|
root.style.setProperty('--bg-glow-1', isCustom ? primary : preset.bgGlow1)
|
|
root.style.setProperty('--bg-glow-2', isCustom ? accent : preset.bgGlow2)
|
|
root.style.setProperty('--gradient-from', isCustom ? primary : preset.gradientFrom)
|
|
root.style.setProperty('--gradient-to', isCustom ? accent : preset.gradientTo)
|
|
root.style.setProperty('--card-border-glow', isCustom ? `${accent}40` : preset.cardBorder)
|
|
root.style.setProperty('--text-highlight', isCustom ? accent : preset.textHighlight)
|
|
root.style.setProperty('--button-bg', isCustom ? primary : preset.buttonBg)
|
|
root.style.setProperty('--ring-color', isCustom ? primary : preset.ringColor)
|
|
} catch (e) {
|
|
console.error('Failed to load branding theme:', e)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadTheme()
|
|
}, [])
|
|
|
|
return (
|
|
<NextThemesProvider {...props}>
|
|
<ThemeContext.Provider value={{ branding, refreshBranding: loadTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
</NextThemesProvider>
|
|
)
|
|
}
|
|
|
|
export function useTheme() {
|
|
return useContext(ThemeContext)
|
|
}
|