From f386ae2b25f14212700353efe88d863fb87ee1ff Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 7 Jul 2026 20:04:16 +0200 Subject: [PATCH] Add more button/navbar colors and gradient mix section to theme editor --- src/actions/admin-theme.ts | 10 +++++++++- src/app/admin/theme/page.tsx | 25 +++++++++++++++++++++---- src/app/globals.css | 8 +++++--- src/components/theme-vars.tsx | 18 ++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/actions/admin-theme.ts b/src/actions/admin-theme.ts index 555d8c7e..a37b835d 100644 --- a/src/actions/admin-theme.ts +++ b/src/actions/admin-theme.ts @@ -10,12 +10,20 @@ import { FONTS, PRESETS, THEME_COLOR_KEYS } from "@/lib/theme-presets"; // Only hex/keyword colour values are accepted (matches ThemeVars' sanitiser). 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 = [ "button_secondary_color", + "button_secondary_text_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_hover_color", + "gradient_from", + "gradient_to", ]; const HEADING_KEYS = ["size_heading_h1", "size_heading_h2", "size_heading_h3"]; const CUSTOM_CSS_MAX = 20000; diff --git a/src/app/admin/theme/page.tsx b/src/app/admin/theme/page.tsx index 4358665f..5b28d300 100644 --- a/src/app/admin/theme/page.tsx +++ b/src/app/admin/theme/page.tsx @@ -24,21 +24,30 @@ const COLOR_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_secondary_color", label: "Secondary", def: "#22c55e" }, - { key: "button_danger_color", label: "Danger", def: "#ef4444" }, + { key: "button_secondary_color", label: "Secondary button", def: "#22c55e" }, + { 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_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 }[] = [ { key: "size_heading_h1", label: "H1", def: "30" }, { key: "size_heading_h2", label: "H2", def: "22" }, { 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 { return value && /^#[0-9a-fA-F]{6}$/.test(value) ? value : def; @@ -166,6 +175,14 @@ export default async function AdminTheme({ +
+

Gradient mix

+

Control the card header and hero background gradients.

+
+ {colorGrid(GRADIENT_FIELDS, current)} +
+
+

{t("typographyLayout")}

diff --git a/src/app/globals.css b/src/app/globals.css index ffb3893d..75ccbe81 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -35,6 +35,8 @@ --color-error: #ef4444; --color-info: #0ea5e9; --color-danger: #ef4444; + --gradient-from: #f59e0b; + --gradient-to: #10b981; --button-color: #eeb425; --button-text-color: #1a1a2e; --button-secondary-color: #22c55e; @@ -288,7 +290,7 @@ a:hover { font-size: 0.875rem; } .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-radius: var(--radius-lg); padding: 2rem 2.25rem; @@ -593,7 +595,7 @@ tbody tr:hover { align-items: center; gap: 0.75rem; 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); } .content-card-icon { @@ -604,7 +606,7 @@ tbody tr:hover { display: grid; place-items: center; 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); } .content-card-head-text { diff --git a/src/components/theme-vars.tsx b/src/components/theme-vars.tsx index 7b88537e..0d65b65c 100644 --- a/src/components/theme-vars.tsx +++ b/src/components/theme-vars.tsx @@ -23,10 +23,16 @@ export async function ThemeVars() { buttonColor, buttonText, buttonSecondary, + buttonSecondaryText, buttonDanger, + buttonDangerText, + buttonOutline, + buttonOutlineText, linkColor, linkHover, borderColor, + gradientFrom, + gradientTo, borderRadius, fontKey, h1, @@ -50,10 +56,16 @@ export async function ThemeVars() { g("button_primary_color", "#f59e0b"), g("button_text_color", "#1e293b"), g("button_secondary_color", "#22c55e"), + g("button_secondary_text_color", "#ffffff"), 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_hover_color", "#cf9d15"), g("border_color", "#eeb425"), + g("gradient_from", "#f59e0b"), + g("gradient_to", "#10b981"), g("border_radius", "12"), g("font_family", "nunito"), g("size_heading_h1", "30"), @@ -85,10 +97,16 @@ export async function ThemeVars() { --button-color:${safe(buttonColor, "#f59e0b")}; --button-text-color:${safe(buttonText, "#1e293b")}; --button-secondary-color:${safe(buttonSecondary, "#22c55e")}; +--button-secondary-text-color:${safe(buttonSecondaryText, "#ffffff")}; --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-hover-color:${safe(linkHover, "#cf9d15")}; --border-color:${safe(borderColor, "#eeb425")}; +--gradient-from:${safe(gradientFrom, "#f59e0b")}; +--gradient-to:${safe(gradientTo, "#10b981")}; --border-radius:${px(borderRadius, "12")}px; --font-family:${font.stack}; --size-heading-h1:${px(h1, "30")}px;