Add admin HK color customization fields to theme editor
Local Build and Deploy / deploy (push) Successful in 58s
Local Build and Deploy / deploy (push) Successful in 58s
- Add 6 new admin color DB keys (admin_canvas, admin_surface, admin_text, admin_text_muted, admin_border, admin_sidebar_bg) that override the derived admin palette - Extract adminPaletteCss() from themePaletteCss() for reuse - Generate admin CSS variables in both :root and html.dark with overrides - Persist admin color settings via saveTheme action - Add Admin panel (HK) section to /admin/theme with color pickers
This commit is contained in:
1 parent
4dcf6fdce8
commit
a16d9859e8
5 files changed
+134
-57
No files matched your search
+32
-23
@@ -27,36 +27,45 @@ export interface AdminPalette {
|
||||
focusRing: string;
|
||||
}
|
||||
|
||||
export function deriveAdminPalette(colors: PublicThemeColors): AdminPalette {
|
||||
const publicBackground = colors.color_background ?? "#f8fafc";
|
||||
const blackRatio = contrastRatio("#000000", publicBackground) ?? 21;
|
||||
const whiteRatio = contrastRatio("#ffffff", publicBackground) ?? 1;
|
||||
const dark = whiteRatio > blackRatio;
|
||||
const canvas = dark ? "#0b0f19" : "#f1f5f9";
|
||||
const surface = dark ? "#151b27" : "#ffffff";
|
||||
const surfaceElevated = dark ? "#1e2635" : "#f8fafc";
|
||||
const text = dark ? "#f8fafc" : "#0f172a";
|
||||
const muted = dark ? "#a8b3c7" : "#475569";
|
||||
export function deriveAdminPalette(
|
||||
colors: PublicThemeColors,
|
||||
overrides?: Partial<AdminPalette>,
|
||||
): AdminPalette {
|
||||
const accent = colors.color_primary ?? "#f59e0b";
|
||||
return {
|
||||
canvas,
|
||||
surface,
|
||||
surfaceElevated,
|
||||
text: readableColor(text, [canvas, surface, surfaceElevated]),
|
||||
muted: readableColor(muted, [canvas, surface, surfaceElevated]),
|
||||
border: dark ? "#334155" : "#cbd5e1",
|
||||
const defaults: AdminPalette = {
|
||||
canvas: "#0b0f19",
|
||||
surface: "#151b27",
|
||||
surfaceElevated: "#1e2635",
|
||||
text: "#f8fafc",
|
||||
muted: "#94a3b8",
|
||||
border: "#2a3448",
|
||||
accent,
|
||||
accentText: readableColor(accent, [canvas, surface, surfaceElevated]),
|
||||
accentText: readableColor(accent, ["#0b0f19", "#151b27", "#1e2635"]),
|
||||
accentForeground: readableColor("#ffffff", [accent]),
|
||||
success: dark ? "#4ade80" : "#15803d",
|
||||
warning: dark ? "#facc15" : "#a16207",
|
||||
error: dark ? "#f87171" : "#b91c1c",
|
||||
info: dark ? "#38bdf8" : "#0369a1",
|
||||
sidebar: dark ? "#101622" : "#172033",
|
||||
success: "#22c55e",
|
||||
warning: "#eab308",
|
||||
error: "#ef4444",
|
||||
info: "#3b82f6",
|
||||
sidebar: "#080c18",
|
||||
sidebarText: "#f8fafc",
|
||||
overlay: "rgba(2, 6, 23, 0.72)",
|
||||
focusRing: accent,
|
||||
};
|
||||
if (!overrides) return defaults;
|
||||
return {
|
||||
...defaults,
|
||||
...overrides,
|
||||
accent,
|
||||
accentText: readableColor(accent, [overrides.canvas ?? defaults.canvas, overrides.surface ?? defaults.surface, overrides.surfaceElevated ?? defaults.surfaceElevated]),
|
||||
accentForeground: readableColor("#ffffff", [accent]),
|
||||
success: overrides.success ?? defaults.success,
|
||||
warning: overrides.warning ?? defaults.warning,
|
||||
error: overrides.error ?? defaults.error,
|
||||
info: overrides.info ?? defaults.info,
|
||||
sidebarText: readableColor(overrides.sidebar ?? defaults.sidebar, [overrides.canvas ?? defaults.canvas, overrides.surface ?? defaults.surface]),
|
||||
overlay: defaults.overlay,
|
||||
focusRing: accent,
|
||||
};
|
||||
}
|
||||
|
||||
export function derivePublicForegrounds(c: PublicThemeColors) {
|
||||
|
||||
+38
-29
@@ -1,4 +1,4 @@
|
||||
import { deriveAdminPalette, derivePublicForegrounds, readableColor } from "@/lib/theme-contrast";
|
||||
import { deriveAdminPalette, derivePublicForegrounds, readableColor, type AdminPalette } from "@/lib/theme-contrast";
|
||||
import type { ThemePalette } from "@/lib/theme-presets";
|
||||
|
||||
const CSS_VARIABLES: Record<keyof ThemePalette, string> = {
|
||||
@@ -30,9 +30,41 @@ const CSS_VARIABLES: Record<keyof ThemePalette, string> = {
|
||||
gradient_to: "gradient-to",
|
||||
};
|
||||
|
||||
export function themePaletteCss(selector: string, palette: ThemePalette): string {
|
||||
export function adminPaletteCss(admin: AdminPalette): string {
|
||||
const parts: string[] = [
|
||||
`--admin-sidebar-background:${admin.sidebar};`,
|
||||
`--admin-sidebar-text-readable:${admin.sidebarText};`,
|
||||
`--admin-sidebar-muted-readable:color-mix(in srgb, ${admin.sidebarText}, ${admin.sidebar} 38%);`,
|
||||
`--admin-input-text-readable:${readableColor(admin.text, [admin.canvas, admin.surface])};`,
|
||||
`--admin-canvas:${admin.canvas};`,
|
||||
`--admin-surface:${admin.surface};`,
|
||||
`--admin-surface-elevated:${admin.surfaceElevated};`,
|
||||
`--admin-text:${admin.text};`,
|
||||
`--admin-text-muted:${admin.muted};`,
|
||||
`--admin-border:${admin.border};`,
|
||||
`--admin-accent:${admin.accent};`,
|
||||
`--admin-accent-text:${admin.accentText};`,
|
||||
`--admin-accent-foreground:${admin.accentForeground};`,
|
||||
`--admin-success:${admin.success};`,
|
||||
`--admin-warning:${admin.warning};`,
|
||||
`--admin-error:${admin.error};`,
|
||||
`--admin-info:${admin.info};`,
|
||||
`--admin-sidebar:${admin.sidebar};`,
|
||||
`--admin-sidebar-text:${admin.sidebarText};`,
|
||||
`--admin-overlay:${admin.overlay};`,
|
||||
`--admin-focus-ring:${admin.focusRing};`,
|
||||
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
||||
`--admin-${status}-foreground:${readableColor("#ffffff", [admin[status]])};`,
|
||||
`--admin-${status}-subtle:color-mix(in srgb, ${admin[status]} 14%, ${admin.surface});`,
|
||||
`--admin-${status}-border:color-mix(in srgb, ${admin[status]} 38%, transparent);`,
|
||||
]),
|
||||
];
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
export function themePaletteCss(selector: string, palette: ThemePalette, adminOverrides?: Partial<AdminPalette>): string {
|
||||
const semantic = derivePublicForegrounds(palette);
|
||||
const admin = deriveAdminPalette(palette);
|
||||
const admin = deriveAdminPalette(palette, adminOverrides);
|
||||
const declarations = Object.entries(CSS_VARIABLES).map(
|
||||
([key, cssName]) => `--${cssName}:${palette[key as keyof ThemePalette]};`,
|
||||
);
|
||||
@@ -57,32 +89,6 @@ export function themePaletteCss(selector: string, palette: ThemePalette): string
|
||||
`--color-overlay-text-readable:${semantic.byName.overlay};`,
|
||||
`--color-text-subtle-readable:${semantic.byName.subtle};`,
|
||||
`--color-text-disabled-readable:${semantic.byName.disabled};`,
|
||||
`--admin-sidebar-background:${admin.sidebar};`,
|
||||
`--admin-sidebar-text-readable:${admin.sidebarText};`,
|
||||
`--admin-sidebar-muted-readable:color-mix(in srgb, ${admin.sidebarText}, ${admin.sidebar} 38%);`,
|
||||
`--admin-input-text-readable:${semantic.byName.input};`,
|
||||
`--admin-canvas:${admin.canvas};`,
|
||||
`--admin-surface:${admin.surface};`,
|
||||
`--admin-surface-elevated:${admin.surfaceElevated};`,
|
||||
`--admin-text:${admin.text};`,
|
||||
`--admin-text-muted:${admin.muted};`,
|
||||
`--admin-border:${admin.border};`,
|
||||
`--admin-accent:${admin.accent};`,
|
||||
`--admin-accent-text:${admin.accentText};`,
|
||||
`--admin-accent-foreground:${admin.accentForeground};`,
|
||||
`--admin-success:${admin.success};`,
|
||||
`--admin-warning:${admin.warning};`,
|
||||
`--admin-error:${admin.error};`,
|
||||
`--admin-info:${admin.info};`,
|
||||
`--admin-sidebar:${admin.sidebar};`,
|
||||
`--admin-sidebar-text:${admin.sidebarText};`,
|
||||
`--admin-overlay:${admin.overlay};`,
|
||||
`--admin-focus-ring:${admin.focusRing};`,
|
||||
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
||||
`--admin-${status}-foreground:${readableColor("#ffffff", [admin[status]])};`,
|
||||
`--admin-${status}-subtle:color-mix(in srgb, ${admin[status]} 14%, ${admin.surface});`,
|
||||
`--admin-${status}-border:color-mix(in srgb, ${admin[status]} 38%, transparent);`,
|
||||
]),
|
||||
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
||||
`--color-${status}-text-readable:${semantic.byName[`${status}-text`]};`,
|
||||
`--color-${status}-foreground-readable:${semantic.byName[`${status}-solid`]};`,
|
||||
@@ -90,5 +96,8 @@ export function themePaletteCss(selector: string, palette: ThemePalette): string
|
||||
`--color-${status}-border:color-mix(in srgb, ${palette[`color_${status}`]} 38%, transparent);`,
|
||||
]),
|
||||
);
|
||||
if (adminOverrides || selector !== ":root") {
|
||||
declarations.push(adminPaletteCss(admin));
|
||||
}
|
||||
return `${selector}{${declarations.join("")}}`;
|
||||
}
|
||||
Reference in new issue
Block a user