Add more button/navbar colors and gradient mix section to theme editor

This commit is contained in:
openhands committed 2026-07-07 20:04:16 +02:00
1 parent e43a768ce4
commit f386ae2b25
4 files changed
+53 -8

No files matched your search

+9 -1
View File
@@ -10,12 +10,20 @@ import { FONTS, PRESETS, THEME_COLOR_KEYS } from "@/lib/theme-presets";
// Only hex/keyword colour values are accepted (matches ThemeVars' sanitiser). // Only hex/keyword colour values are accepted (matches ThemeVars' sanitiser).
const COLOR_RE = /^[#a-zA-Z0-9(),.\s%-]+$/; const COLOR_RE = /^[#a-zA-Z0-9(),.\s%-]+$/;
// Extra colour settings beyond the preset palette (buttons + links). // Extra colour settings beyond the preset palette (buttons + links + gradients).
const EXTRA_COLOR_KEYS = [ const EXTRA_COLOR_KEYS = [
"button_secondary_color", "button_secondary_color",
"button_secondary_text_color",
"button_danger_color", "button_danger_color",
"button_danger_text_color",
"button_outline_color",
"button_outline_text_color",
"button_secondary_hover_color",
"button_danger_hover_color",
"link_color", "link_color",
"link_hover_color", "link_hover_color",
"gradient_from",
"gradient_to",
]; ];
const HEADING_KEYS = ["size_heading_h1", "size_heading_h2", "size_heading_h3"]; const HEADING_KEYS = ["size_heading_h1", "size_heading_h2", "size_heading_h3"];
const CUSTOM_CSS_MAX = 20000; const CUSTOM_CSS_MAX = 20000;
+21 -4
View File
@@ -24,21 +24,30 @@ const COLOR_FIELDS: { key: string; label: string; def: string }[] = [
]; ];
const BUTTON_FIELDS: { key: string; label: string; def: string }[] = [ const BUTTON_FIELDS: { key: string; label: string; def: string }[] = [
{ key: "button_primary_color", label: "Button", def: "#f59e0b" }, { key: "button_primary_color", label: "Primary button", def: "#f59e0b" },
{ key: "button_text_color", label: "Button text", def: "#1e293b" }, { key: "button_text_color", label: "Button text", def: "#1e293b" },
{ key: "button_secondary_color", label: "Secondary", def: "#22c55e" }, { key: "button_secondary_color", label: "Secondary button", def: "#22c55e" },
{ key: "button_danger_color", label: "Danger", def: "#ef4444" }, { key: "button_secondary_text_color", label: "Secondary text", def: "#ffffff" },
{ key: "button_danger_color", label: "Danger button", def: "#ef4444" },
{ key: "button_danger_text_color", label: "Danger text", def: "#ffffff" },
{ key: "button_outline_color", label: "Outline button", def: "#eeb425" },
{ key: "button_outline_text_color", label: "Outline text", def: "#1a1a2e" },
{ key: "link_color", label: "Link", def: "#eeb425" }, { key: "link_color", label: "Link", def: "#eeb425" },
{ key: "link_hover_color", label: "Link hover", def: "#cf9d15" }, { key: "link_hover_color", label: "Link hover", def: "#cf9d15" },
]; ];
const GRADIENT_FIELDS: { key: string; label: string; def: string }[] = [
{ key: "gradient_from", label: "Gradient from", def: "#f59e0b" },
{ key: "gradient_to", label: "Gradient to", def: "#10b981" },
];
const HEADINGS: { key: string; label: string; def: string }[] = [ const HEADINGS: { key: string; label: string; def: string }[] = [
{ key: "size_heading_h1", label: "H1", def: "30" }, { key: "size_heading_h1", label: "H1", def: "30" },
{ key: "size_heading_h2", label: "H2", def: "22" }, { key: "size_heading_h2", label: "H2", def: "22" },
{ key: "size_heading_h3", label: "H3", def: "18" }, { key: "size_heading_h3", label: "H3", def: "18" },
]; ];
const ALL_COLORS = [...COLOR_FIELDS, ...BUTTON_FIELDS]; const ALL_COLORS = [...COLOR_FIELDS, ...BUTTON_FIELDS, ...GRADIENT_FIELDS];
function toHex(value: string | null, def: string): string { function toHex(value: string | null, def: string): string {
return value && /^#[0-9a-fA-F]{6}$/.test(value) ? value : def; return value && /^#[0-9a-fA-F]{6}$/.test(value) ? value : def;
@@ -166,6 +175,14 @@ export default async function AdminTheme({
</div> </div>
</section> </section>
<section className="mt-6">
<h2 className="admin-section-title">Gradient mix</h2>
<p className="text-xs text-[var(--color-text-muted)] mb-2">Control the card header and hero background gradients.</p>
<div className="admin-card">
{colorGrid(GRADIENT_FIELDS, current)}
</div>
</section>
<section className="mt-6"> <section className="mt-6">
<h2 className="admin-section-title">{t("typographyLayout")}</h2> <h2 className="admin-section-title">{t("typographyLayout")}</h2>
<div className="admin-card"> <div className="admin-card">
+5 -3
View File
@@ -35,6 +35,8 @@
--color-error: #ef4444; --color-error: #ef4444;
--color-info: #0ea5e9; --color-info: #0ea5e9;
--color-danger: #ef4444; --color-danger: #ef4444;
--gradient-from: #f59e0b;
--gradient-to: #10b981;
--button-color: #eeb425; --button-color: #eeb425;
--button-text-color: #1a1a2e; --button-text-color: #1a1a2e;
--button-secondary-color: #22c55e; --button-secondary-color: #22c55e;
@@ -288,7 +290,7 @@ a:hover {
font-size: 0.875rem; font-size: 0.875rem;
} }
.hero { .hero {
background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 14%, var(--color-navbar)), color-mix(in srgb, var(--color-accent) 8%, var(--color-navbar))); background: linear-gradient(135deg, color-mix(in srgb, var(--gradient-from) 14%, var(--color-navbar)), color-mix(in srgb, var(--gradient-to) 8%, var(--color-navbar)));
border: 1px solid color-mix(in srgb, var(--color-primary) 20%, transparent); border: 1px solid color-mix(in srgb, var(--color-primary) 20%, transparent);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 2rem 2.25rem; padding: 2rem 2.25rem;
@@ -593,7 +595,7 @@ tbody tr:hover {
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding: 0.9rem 1.1rem; padding: 0.9rem 1.1rem;
background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 10%, var(--color-navbar)), color-mix(in srgb, var(--color-primary) 5%, var(--color-navbar))); background: linear-gradient(135deg, color-mix(in srgb, var(--gradient-from) 10%, var(--color-navbar)), color-mix(in srgb, var(--gradient-to) 5%, var(--color-navbar)));
border-bottom: 1px solid color-mix(in srgb, var(--color-text-muted) 8%, transparent); border-bottom: 1px solid color-mix(in srgb, var(--color-text-muted) 8%, transparent);
} }
.content-card-icon { .content-card-icon {
@@ -604,7 +606,7 @@ tbody tr:hover {
display: grid; display: grid;
place-items: center; place-items: center;
font-size: 1.25rem; font-size: 1.25rem;
background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 22%, var(--color-navbar)), color-mix(in srgb, var(--color-primary) 12%, var(--color-navbar))); background: linear-gradient(135deg, color-mix(in srgb, var(--gradient-from) 22%, var(--color-navbar)), color-mix(in srgb, var(--gradient-to) 12%, var(--color-navbar)));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 30%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 30%, transparent);
} }
.content-card-head-text { .content-card-head-text {
+18
View File
@@ -23,10 +23,16 @@ export async function ThemeVars() {
buttonColor, buttonColor,
buttonText, buttonText,
buttonSecondary, buttonSecondary,
buttonSecondaryText,
buttonDanger, buttonDanger,
buttonDangerText,
buttonOutline,
buttonOutlineText,
linkColor, linkColor,
linkHover, linkHover,
borderColor, borderColor,
gradientFrom,
gradientTo,
borderRadius, borderRadius,
fontKey, fontKey,
h1, h1,
@@ -50,10 +56,16 @@ export async function ThemeVars() {
g("button_primary_color", "#f59e0b"), g("button_primary_color", "#f59e0b"),
g("button_text_color", "#1e293b"), g("button_text_color", "#1e293b"),
g("button_secondary_color", "#22c55e"), g("button_secondary_color", "#22c55e"),
g("button_secondary_text_color", "#ffffff"),
g("button_danger_color", "#ef4444"), g("button_danger_color", "#ef4444"),
g("button_danger_text_color", "#ffffff"),
g("button_outline_color", "#eeb425"),
g("button_outline_text_color", "#1a1a2e"),
g("link_color", "#eeb425"), g("link_color", "#eeb425"),
g("link_hover_color", "#cf9d15"), g("link_hover_color", "#cf9d15"),
g("border_color", "#eeb425"), g("border_color", "#eeb425"),
g("gradient_from", "#f59e0b"),
g("gradient_to", "#10b981"),
g("border_radius", "12"), g("border_radius", "12"),
g("font_family", "nunito"), g("font_family", "nunito"),
g("size_heading_h1", "30"), g("size_heading_h1", "30"),
@@ -85,10 +97,16 @@ export async function ThemeVars() {
--button-color:${safe(buttonColor, "#f59e0b")}; --button-color:${safe(buttonColor, "#f59e0b")};
--button-text-color:${safe(buttonText, "#1e293b")}; --button-text-color:${safe(buttonText, "#1e293b")};
--button-secondary-color:${safe(buttonSecondary, "#22c55e")}; --button-secondary-color:${safe(buttonSecondary, "#22c55e")};
--button-secondary-text-color:${safe(buttonSecondaryText, "#ffffff")};
--button-danger-color:${safe(buttonDanger, "#ef4444")}; --button-danger-color:${safe(buttonDanger, "#ef4444")};
--button-danger-text-color:${safe(buttonDangerText, "#ffffff")};
--button-outline-color:${safe(buttonOutline, "#eeb425")};
--button-outline-text-color:${safe(buttonOutlineText, "#1a1a2e")};
--link-color:${safe(linkColor, "#eeb425")}; --link-color:${safe(linkColor, "#eeb425")};
--link-hover-color:${safe(linkHover, "#cf9d15")}; --link-hover-color:${safe(linkHover, "#cf9d15")};
--border-color:${safe(borderColor, "#eeb425")}; --border-color:${safe(borderColor, "#eeb425")};
--gradient-from:${safe(gradientFrom, "#f59e0b")};
--gradient-to:${safe(gradientTo, "#10b981")};
--border-radius:${px(borderRadius, "12")}px; --border-radius:${px(borderRadius, "12")}px;
--font-family:${font.stack}; --font-family:${font.stack};
--size-heading-h1:${px(h1, "30")}px; --size-heading-h1:${px(h1, "30")}px;