Add theme system with color presets, fix login page styling, persist auth session
Theme system with 5 color presets (Slate, Emerald, Violet, Amber, Rose) and light/dark/system mode. User menu in sidebar with theme picker and sign out. Login page uses standalone dark branded styling with autofill override. Auth persists in sessionStorage across refreshes.
This commit is contained in:
379
packages/admin/src/lib/themes.ts
Normal file
379
packages/admin/src/lib/themes.ts
Normal file
@@ -0,0 +1,379 @@
|
||||
export interface ThemeColors {
|
||||
background: string
|
||||
foreground: string
|
||||
card: string
|
||||
cardForeground: string
|
||||
popover: string
|
||||
popoverForeground: string
|
||||
primary: string
|
||||
primaryForeground: string
|
||||
secondary: string
|
||||
secondaryForeground: string
|
||||
muted: string
|
||||
mutedForeground: string
|
||||
accent: string
|
||||
accentForeground: string
|
||||
destructive: string
|
||||
destructiveForeground: string
|
||||
border: string
|
||||
input: string
|
||||
ring: string
|
||||
sidebar: string
|
||||
sidebarForeground: string
|
||||
sidebarPrimary: string
|
||||
sidebarPrimaryForeground: string
|
||||
sidebarAccent: string
|
||||
sidebarAccentForeground: string
|
||||
sidebarBorder: string
|
||||
}
|
||||
|
||||
export interface ThemePreset {
|
||||
name: string
|
||||
label: string
|
||||
light: ThemeColors
|
||||
dark: ThemeColors
|
||||
}
|
||||
|
||||
export const themes: ThemePreset[] = [
|
||||
{
|
||||
name: 'slate',
|
||||
label: 'Slate',
|
||||
light: {
|
||||
background: '210 20% 97%',
|
||||
foreground: '222 47% 11%',
|
||||
card: '0 0% 100%',
|
||||
cardForeground: '222 47% 11%',
|
||||
popover: '0 0% 100%',
|
||||
popoverForeground: '222 47% 11%',
|
||||
primary: '215 25% 27%',
|
||||
primaryForeground: '210 40% 98%',
|
||||
secondary: '210 40% 94%',
|
||||
secondaryForeground: '222 47% 11%',
|
||||
muted: '210 40% 94%',
|
||||
mutedForeground: '215 16% 47%',
|
||||
accent: '210 40% 94%',
|
||||
accentForeground: '222 47% 11%',
|
||||
destructive: '0 72% 51%',
|
||||
destructiveForeground: '210 40% 98%',
|
||||
border: '214 32% 89%',
|
||||
input: '214 32% 89%',
|
||||
ring: '215 25% 27%',
|
||||
sidebar: '210 25% 95%',
|
||||
sidebarForeground: '215 16% 47%',
|
||||
sidebarPrimary: '215 25% 27%',
|
||||
sidebarPrimaryForeground: '210 40% 98%',
|
||||
sidebarAccent: '210 40% 92%',
|
||||
sidebarAccentForeground: '222 47% 11%',
|
||||
sidebarBorder: '214 32% 89%',
|
||||
},
|
||||
dark: {
|
||||
background: '222 30% 8%',
|
||||
foreground: '210 20% 86%',
|
||||
card: '222 30% 10%',
|
||||
cardForeground: '210 20% 86%',
|
||||
popover: '222 30% 10%',
|
||||
popoverForeground: '210 20% 86%',
|
||||
primary: '215 20% 72%',
|
||||
primaryForeground: '222 30% 8%',
|
||||
secondary: '217 20% 15%',
|
||||
secondaryForeground: '210 20% 86%',
|
||||
muted: '217 20% 15%',
|
||||
mutedForeground: '215 15% 55%',
|
||||
accent: '217 20% 15%',
|
||||
accentForeground: '210 20% 86%',
|
||||
destructive: '0 55% 48%',
|
||||
destructiveForeground: '210 20% 92%',
|
||||
border: '217 20% 17%',
|
||||
input: '217 20% 17%',
|
||||
ring: '215 20% 55%',
|
||||
sidebar: '222 30% 7%',
|
||||
sidebarForeground: '215 15% 65%',
|
||||
sidebarPrimary: '215 20% 72%',
|
||||
sidebarPrimaryForeground: '0 0% 100%',
|
||||
sidebarAccent: '217 20% 13%',
|
||||
sidebarAccentForeground: '210 20% 86%',
|
||||
sidebarBorder: '217 20% 15%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'emerald',
|
||||
label: 'Emerald',
|
||||
light: {
|
||||
background: '150 20% 97%',
|
||||
foreground: '150 40% 10%',
|
||||
card: '0 0% 100%',
|
||||
cardForeground: '150 40% 10%',
|
||||
popover: '0 0% 100%',
|
||||
popoverForeground: '150 40% 10%',
|
||||
primary: '160 84% 29%',
|
||||
primaryForeground: '150 20% 98%',
|
||||
secondary: '150 20% 93%',
|
||||
secondaryForeground: '150 40% 10%',
|
||||
muted: '150 20% 93%',
|
||||
mutedForeground: '150 10% 45%',
|
||||
accent: '150 20% 93%',
|
||||
accentForeground: '150 40% 10%',
|
||||
destructive: '0 72% 51%',
|
||||
destructiveForeground: '150 20% 98%',
|
||||
border: '150 15% 88%',
|
||||
input: '150 15% 88%',
|
||||
ring: '160 84% 29%',
|
||||
sidebar: '150 20% 95%',
|
||||
sidebarForeground: '150 10% 45%',
|
||||
sidebarPrimary: '160 84% 29%',
|
||||
sidebarPrimaryForeground: '150 20% 98%',
|
||||
sidebarAccent: '150 20% 91%',
|
||||
sidebarAccentForeground: '150 40% 10%',
|
||||
sidebarBorder: '150 15% 88%',
|
||||
},
|
||||
dark: {
|
||||
background: '155 30% 7%',
|
||||
foreground: '150 15% 85%',
|
||||
card: '155 30% 9%',
|
||||
cardForeground: '150 15% 85%',
|
||||
popover: '155 30% 9%',
|
||||
popoverForeground: '150 15% 85%',
|
||||
primary: '160 72% 42%',
|
||||
primaryForeground: '155 30% 7%',
|
||||
secondary: '155 20% 14%',
|
||||
secondaryForeground: '150 15% 85%',
|
||||
muted: '155 20% 14%',
|
||||
mutedForeground: '150 10% 52%',
|
||||
accent: '155 20% 14%',
|
||||
accentForeground: '150 15% 85%',
|
||||
destructive: '0 55% 48%',
|
||||
destructiveForeground: '150 15% 92%',
|
||||
border: '155 20% 16%',
|
||||
input: '155 20% 16%',
|
||||
ring: '160 72% 42%',
|
||||
sidebar: '155 30% 6%',
|
||||
sidebarForeground: '150 10% 62%',
|
||||
sidebarPrimary: '160 72% 42%',
|
||||
sidebarPrimaryForeground: '0 0% 100%',
|
||||
sidebarAccent: '155 20% 12%',
|
||||
sidebarAccentForeground: '150 15% 85%',
|
||||
sidebarBorder: '155 20% 14%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'violet',
|
||||
label: 'Violet',
|
||||
light: {
|
||||
background: '260 15% 97%',
|
||||
foreground: '260 40% 11%',
|
||||
card: '0 0% 100%',
|
||||
cardForeground: '260 40% 11%',
|
||||
popover: '0 0% 100%',
|
||||
popoverForeground: '260 40% 11%',
|
||||
primary: '262 83% 58%',
|
||||
primaryForeground: '260 15% 98%',
|
||||
secondary: '260 20% 94%',
|
||||
secondaryForeground: '260 40% 11%',
|
||||
muted: '260 20% 94%',
|
||||
mutedForeground: '260 10% 47%',
|
||||
accent: '260 20% 94%',
|
||||
accentForeground: '260 40% 11%',
|
||||
destructive: '0 72% 51%',
|
||||
destructiveForeground: '260 15% 98%',
|
||||
border: '260 15% 89%',
|
||||
input: '260 15% 89%',
|
||||
ring: '262 83% 58%',
|
||||
sidebar: '260 18% 95%',
|
||||
sidebarForeground: '260 10% 47%',
|
||||
sidebarPrimary: '262 83% 58%',
|
||||
sidebarPrimaryForeground: '260 15% 98%',
|
||||
sidebarAccent: '260 20% 92%',
|
||||
sidebarAccentForeground: '260 40% 11%',
|
||||
sidebarBorder: '260 15% 89%',
|
||||
},
|
||||
dark: {
|
||||
background: '260 30% 8%',
|
||||
foreground: '260 15% 86%',
|
||||
card: '260 30% 10%',
|
||||
cardForeground: '260 15% 86%',
|
||||
popover: '260 30% 10%',
|
||||
popoverForeground: '260 15% 86%',
|
||||
primary: '262 83% 65%',
|
||||
primaryForeground: '260 30% 8%',
|
||||
secondary: '260 20% 15%',
|
||||
secondaryForeground: '260 15% 86%',
|
||||
muted: '260 20% 15%',
|
||||
mutedForeground: '260 10% 54%',
|
||||
accent: '260 20% 15%',
|
||||
accentForeground: '260 15% 86%',
|
||||
destructive: '0 55% 48%',
|
||||
destructiveForeground: '260 15% 92%',
|
||||
border: '260 20% 17%',
|
||||
input: '260 20% 17%',
|
||||
ring: '262 83% 65%',
|
||||
sidebar: '260 30% 7%',
|
||||
sidebarForeground: '260 10% 64%',
|
||||
sidebarPrimary: '262 83% 65%',
|
||||
sidebarPrimaryForeground: '0 0% 100%',
|
||||
sidebarAccent: '260 20% 13%',
|
||||
sidebarAccentForeground: '260 15% 86%',
|
||||
sidebarBorder: '260 20% 15%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'amber',
|
||||
label: 'Amber',
|
||||
light: {
|
||||
background: '36 20% 97%',
|
||||
foreground: '28 40% 10%',
|
||||
card: '0 0% 100%',
|
||||
cardForeground: '28 40% 10%',
|
||||
popover: '0 0% 100%',
|
||||
popoverForeground: '28 40% 10%',
|
||||
primary: '25 95% 40%',
|
||||
primaryForeground: '36 20% 98%',
|
||||
secondary: '36 25% 93%',
|
||||
secondaryForeground: '28 40% 10%',
|
||||
muted: '36 25% 93%',
|
||||
mutedForeground: '28 10% 47%',
|
||||
accent: '36 25% 93%',
|
||||
accentForeground: '28 40% 10%',
|
||||
destructive: '0 72% 51%',
|
||||
destructiveForeground: '36 20% 98%',
|
||||
border: '36 20% 88%',
|
||||
input: '36 20% 88%',
|
||||
ring: '25 95% 40%',
|
||||
sidebar: '36 22% 95%',
|
||||
sidebarForeground: '28 10% 47%',
|
||||
sidebarPrimary: '25 95% 40%',
|
||||
sidebarPrimaryForeground: '36 20% 98%',
|
||||
sidebarAccent: '36 25% 91%',
|
||||
sidebarAccentForeground: '28 40% 10%',
|
||||
sidebarBorder: '36 20% 88%',
|
||||
},
|
||||
dark: {
|
||||
background: '28 25% 7%',
|
||||
foreground: '36 15% 85%',
|
||||
card: '28 25% 9%',
|
||||
cardForeground: '36 15% 85%',
|
||||
popover: '28 25% 9%',
|
||||
popoverForeground: '36 15% 85%',
|
||||
primary: '30 90% 52%',
|
||||
primaryForeground: '28 25% 7%',
|
||||
secondary: '28 18% 14%',
|
||||
secondaryForeground: '36 15% 85%',
|
||||
muted: '28 18% 14%',
|
||||
mutedForeground: '28 10% 52%',
|
||||
accent: '28 18% 14%',
|
||||
accentForeground: '36 15% 85%',
|
||||
destructive: '0 55% 48%',
|
||||
destructiveForeground: '36 15% 92%',
|
||||
border: '28 18% 16%',
|
||||
input: '28 18% 16%',
|
||||
ring: '30 90% 52%',
|
||||
sidebar: '28 25% 6%',
|
||||
sidebarForeground: '28 10% 62%',
|
||||
sidebarPrimary: '30 90% 52%',
|
||||
sidebarPrimaryForeground: '0 0% 100%',
|
||||
sidebarAccent: '28 18% 12%',
|
||||
sidebarAccentForeground: '36 15% 85%',
|
||||
sidebarBorder: '28 18% 14%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'rose',
|
||||
label: 'Rose',
|
||||
light: {
|
||||
background: '350 15% 97%',
|
||||
foreground: '350 40% 10%',
|
||||
card: '0 0% 100%',
|
||||
cardForeground: '350 40% 10%',
|
||||
popover: '0 0% 100%',
|
||||
popoverForeground: '350 40% 10%',
|
||||
primary: '347 77% 50%',
|
||||
primaryForeground: '350 15% 98%',
|
||||
secondary: '350 20% 94%',
|
||||
secondaryForeground: '350 40% 10%',
|
||||
muted: '350 20% 94%',
|
||||
mutedForeground: '350 10% 47%',
|
||||
accent: '350 20% 94%',
|
||||
accentForeground: '350 40% 10%',
|
||||
destructive: '0 72% 51%',
|
||||
destructiveForeground: '350 15% 98%',
|
||||
border: '350 15% 89%',
|
||||
input: '350 15% 89%',
|
||||
ring: '347 77% 50%',
|
||||
sidebar: '350 18% 95%',
|
||||
sidebarForeground: '350 10% 47%',
|
||||
sidebarPrimary: '347 77% 50%',
|
||||
sidebarPrimaryForeground: '350 15% 98%',
|
||||
sidebarAccent: '350 20% 92%',
|
||||
sidebarAccentForeground: '350 40% 10%',
|
||||
sidebarBorder: '350 15% 89%',
|
||||
},
|
||||
dark: {
|
||||
background: '350 25% 7%',
|
||||
foreground: '350 12% 86%',
|
||||
card: '350 25% 9%',
|
||||
cardForeground: '350 12% 86%',
|
||||
popover: '350 25% 9%',
|
||||
popoverForeground: '350 12% 86%',
|
||||
primary: '347 77% 58%',
|
||||
primaryForeground: '350 25% 7%',
|
||||
secondary: '350 18% 14%',
|
||||
secondaryForeground: '350 12% 86%',
|
||||
muted: '350 18% 14%',
|
||||
mutedForeground: '350 10% 54%',
|
||||
accent: '350 18% 14%',
|
||||
accentForeground: '350 12% 86%',
|
||||
destructive: '0 55% 48%',
|
||||
destructiveForeground: '350 12% 92%',
|
||||
border: '350 18% 16%',
|
||||
input: '350 18% 16%',
|
||||
ring: '347 77% 58%',
|
||||
sidebar: '350 25% 6%',
|
||||
sidebarForeground: '350 10% 62%',
|
||||
sidebarPrimary: '347 77% 58%',
|
||||
sidebarPrimaryForeground: '0 0% 100%',
|
||||
sidebarAccent: '350 18% 12%',
|
||||
sidebarAccentForeground: '350 12% 86%',
|
||||
sidebarBorder: '350 18% 14%',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const CSS_VAR_MAP: Record<keyof ThemeColors, string> = {
|
||||
background: '--color-background',
|
||||
foreground: '--color-foreground',
|
||||
card: '--color-card',
|
||||
cardForeground: '--color-card-foreground',
|
||||
popover: '--color-popover',
|
||||
popoverForeground: '--color-popover-foreground',
|
||||
primary: '--color-primary',
|
||||
primaryForeground: '--color-primary-foreground',
|
||||
secondary: '--color-secondary',
|
||||
secondaryForeground: '--color-secondary-foreground',
|
||||
muted: '--color-muted',
|
||||
mutedForeground: '--color-muted-foreground',
|
||||
accent: '--color-accent',
|
||||
accentForeground: '--color-accent-foreground',
|
||||
destructive: '--color-destructive',
|
||||
destructiveForeground: '--color-destructive-foreground',
|
||||
border: '--color-border',
|
||||
input: '--color-input',
|
||||
ring: '--color-ring',
|
||||
sidebar: '--sidebar',
|
||||
sidebarForeground: '--sidebar-foreground',
|
||||
sidebarPrimary: '--sidebar-primary',
|
||||
sidebarPrimaryForeground: '--sidebar-primary-foreground',
|
||||
sidebarAccent: '--sidebar-accent',
|
||||
sidebarAccentForeground: '--sidebar-accent-foreground',
|
||||
sidebarBorder: '--sidebar-border',
|
||||
}
|
||||
|
||||
export function applyThemeColors(colors: ThemeColors) {
|
||||
const root = document.documentElement
|
||||
for (const [key, cssVar] of Object.entries(CSS_VAR_MAP)) {
|
||||
const value = colors[key as keyof ThemeColors]
|
||||
root.style.setProperty(cssVar, `hsl(${value})`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getThemeByName(name: string): ThemePreset {
|
||||
return themes.find((t) => t.name === name) ?? themes[0]
|
||||
}
|
||||
Reference in New Issue
Block a user