Add admin HK color customization fields to theme editor
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:
openhands committed 2026-07-13 19:49:19 +02:00
1 parent 4dcf6fdce8
commit a16d9859e8
5 files changed
+134 -57

No files matched your search

+38 -29
View File
@@ -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("")}}`;
}