Improve theme editor clarity with labels and descriptions
Local Build and Deploy / deploy (push) Successful in 1m20s

- Add a description to every color field explaining what it affects
- Regroup colors into Page & text / Buttons & links / Gradients with
  explanatory section intros for both light and dark mode
- Add section intros for light, dark, and admin (HK) modes
- Widen color field layout and show descriptions under each label
This commit is contained in:
openhands committed 2026-07-13 19:58:38 +02:00
1 parent a16d9859e8
commit 17894db3e9
1 file changed
+103 -63
+103 -63
View File
@@ -6,39 +6,39 @@ import { FONTS, PRESETS } from "@/lib/theme-presets";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const metadata = { title: "Theme" }; export const metadata = { title: "Theme" };
const COLOR_FIELDS: { key: string; label: string; def: string }[] = [ const COLOR_FIELDS: { key: string; label: string; def: string; desc: string }[] = [
{ key: "color_primary", label: "Primary", def: "#f59e0b" }, { key: "color_background", label: "Page background", def: "#f8fafc", desc: "Main page background behind all content" },
{ key: "color_accent", label: "Accent", def: "#10b981" }, { key: "color_surface", label: "Card / surface", def: "#ffffff", desc: "Cards, panels, and elevated surfaces" },
{ key: "border_color", label: "Border (golden)", def: "#eeb425" }, { key: "color_dropdown", label: "Dropdown menu", def: "#ffffff", desc: "Dropdown menus and input backgrounds" },
{ key: "color_background", label: "Background", def: "#f8fafc" }, { key: "color_navbar", label: "Navbar bar", def: "#ffffff", desc: "Top navigation bar background" },
{ key: "color_surface", label: "Surface / cards", def: "#ffffff" }, { key: "color_navbar_text", label: "Navbar text", def: "#1e293b", desc: "Text color in the navigation bar" },
{ key: "color_dropdown", label: "Dropdown", def: "#ffffff" }, { key: "color_text", label: "Body text", def: "#0f172a", desc: "Main body text — must contrast with background/surface" },
{ key: "color_navbar", label: "Navbar", def: "#ffffff" }, { key: "color_text_muted", label: "Muted text", def: "#64748b", desc: "Secondary text, labels, hints — lighter than body text" },
{ key: "color_navbar_text", label: "Navbar text", def: "#1e293b" }, { key: "color_primary", label: "Primary accent", def: "#f59e0b", desc: "Main brand color, highlights, active elements" },
{ key: "color_text", label: "Text", def: "#0f172a" }, { key: "color_accent", label: "Secondary accent", def: "#10b981", desc: "Secondary brand color, success-oriented highlights" },
{ key: "color_text_muted", label: "Muted text", def: "#64748b" }, { key: "border_color", label: "Border (golden)", def: "#eeb425", desc: "Borders, dividers, outlines" },
{ key: "color_success", label: "Success", def: "#16a34a" }, { key: "color_success", label: "Success", def: "#16a34a", desc: "Positive status (green)" },
{ key: "color_warning", label: "Warning", def: "#eab308" }, { key: "color_warning", label: "Warning", def: "#eab308", desc: "Warning status (yellow)" },
{ key: "color_error", label: "Error", def: "#ef4444" }, { key: "color_error", label: "Error", def: "#ef4444", desc: "Error status (red)" },
{ key: "color_info", label: "Info", def: "#0ea5e9" }, { key: "color_info", label: "Info", def: "#0ea5e9", desc: "Info status (blue)" },
]; ];
const BUTTON_FIELDS: { key: string; label: string; def: string }[] = [ const BUTTON_FIELDS: { key: string; label: string; def: string; desc: string }[] = [
{ key: "button_primary_color", label: "Primary button", def: "#f59e0b" }, { key: "button_primary_color", label: "Primary button", def: "#f59e0b", desc: "Main call-to-action button background" },
{ key: "button_text_color", label: "Button text", def: "#1e293b" }, { key: "button_text_color", label: "Button text", def: "#1e293b", desc: "Text on primary buttons — must contrast with button color" },
{ key: "button_secondary_color", label: "Secondary button", def: "#22c55e" }, { key: "button_secondary_color", label: "Secondary button", def: "#22c55e", desc: "Secondary action button background" },
{ key: "button_secondary_text_color", label: "Secondary text", def: "#ffffff" }, { key: "button_secondary_text_color", label: "Secondary text", def: "#ffffff", desc: "Text on secondary buttons" },
{ key: "button_danger_color", label: "Danger button", def: "#ef4444" }, { key: "button_danger_color", label: "Danger button", def: "#ef4444", desc: "Destructive action button (delete, ban)" },
{ key: "button_danger_text_color", label: "Danger text", def: "#ffffff" }, { key: "button_danger_text_color", label: "Danger text", def: "#ffffff", desc: "Text on danger buttons" },
{ key: "button_outline_color", label: "Outline button", def: "#eeb425" }, { key: "button_outline_color", label: "Outline border", def: "#eeb425", desc: "Outline button border color" },
{ key: "button_outline_text_color", label: "Outline text", def: "#1a1a2e" }, { key: "button_outline_text_color", label: "Outline text", def: "#1a1a2e", desc: "Text on outline buttons" },
{ key: "link_color", label: "Link", def: "#eeb425" }, { key: "link_color", label: "Link", def: "#eeb425", desc: "Hyperlink text color" },
{ key: "link_hover_color", label: "Link hover", def: "#cf9d15" }, { key: "link_hover_color", label: "Link hover", def: "#cf9d15", desc: "Hyperlink text on hover" },
]; ];
const GRADIENT_FIELDS: { key: string; label: string; def: string }[] = [ const GRADIENT_FIELDS: { key: string; label: string; def: string; desc: string }[] = [
{ key: "gradient_from", label: "Gradient from", def: "#f59e0b" }, { key: "gradient_from", label: "Gradient start", def: "#f59e0b", desc: "Left/top color of card header and hero gradients" },
{ key: "gradient_to", label: "Gradient to", def: "#10b981" }, { key: "gradient_to", label: "Gradient end", def: "#10b981", desc: "Right/bottom color of card header and hero gradients" },
]; ];
const HEADINGS: { key: string; label: string; def: string }[] = [ const HEADINGS: { key: string; label: string; def: string }[] = [
@@ -47,13 +47,13 @@ const HEADINGS: { key: string; label: string; def: string }[] = [
{ key: "size_heading_h3", label: "H3", def: "18" }, { key: "size_heading_h3", label: "H3", def: "18" },
]; ];
const ADMIN_FIELDS: { key: string; label: string; def: string }[] = [ const ADMIN_FIELDS: { key: string; label: string; def: string; desc: string }[] = [
{ key: "admin_canvas", label: "Admin canvas", def: "#0f172a" }, { key: "admin_canvas", label: "Page background", def: "#0f172a", desc: "Main admin page background" },
{ key: "admin_surface", label: "Admin surface", def: "#1e293b" }, { key: "admin_surface", label: "Card / panel", def: "#1e293b", desc: "Admin cards, tables, and panel backgrounds" },
{ key: "admin_text", label: "Admin text", def: "#f1f5f9" }, { key: "admin_text", label: "Body text", def: "#f1f5f9", desc: "Main admin text — must contrast with canvas/surface" },
{ key: "admin_text_muted", label: "Admin muted text", def: "#94a3b8" }, { key: "admin_text_muted", label: "Muted text", def: "#94a3b8", desc: "Secondary labels, hints, descriptions" },
{ key: "admin_border", label: "Admin border", def: "#334155" }, { key: "admin_border", label: "Border", def: "#334155", desc: "Table borders, dividers, input outlines" },
{ key: "admin_sidebar_bg", label: "Admin sidebar", def: "#0f172a" }, { key: "admin_sidebar_bg", label: "Sidebar", def: "#0f172a", desc: "Left navigation sidebar background" },
]; ];
const ALL_COLORS = [...COLOR_FIELDS, ...BUTTON_FIELDS, ...GRADIENT_FIELDS]; const ALL_COLORS = [...COLOR_FIELDS, ...BUTTON_FIELDS, ...GRADIENT_FIELDS];
@@ -62,30 +62,55 @@ 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;
} }
interface ThemeField {
key: string;
label: string;
def: string;
desc: string;
}
function colorGrid( function colorGrid(
fields: { key: string; label: string; def: string }[], fields: ThemeField[],
current: Record<string, string>, current: Record<string, string>,
suffix = "", suffix = "",
bgHint?: string,
) { ) {
return ( return (
<div <div
style={{ style={{
display: "grid", display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))",
gap: "0.75rem 1.25rem", gap: "0.5rem",
}} }}
> >
{fields.map((f) => ( {fields.map((f) => {
<label key={f.key} style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}> const val = toHex(current[f.key], f.def);
<input return (
type="color" <div
name={`${f.key}${suffix}`} key={f.key}
defaultValue={toHex(current[f.key], f.def)} style={{
style={{ width: 40, height: 30, padding: 0, border: "none", background: "none" }} display: "flex",
/> alignItems: "center",
<span style={{ fontSize: "0.88rem" }}>{f.label}</span> gap: "0.6rem",
</label> padding: "0.4rem 0.5rem",
))} borderRadius: 6,
}}
>
<input
type="color"
name={`${f.key}${suffix}`}
defaultValue={val}
style={{ width: 40, height: 32, padding: 0, border: "none", background: "none", cursor: "pointer", flexShrink: 0 }}
/>
<div style={{ display: "flex", flexDirection: "column", minWidth: 0, flex: 1 }}>
<span style={{ fontSize: "0.82rem", fontWeight: 600, lineHeight: 1.3 }}>{f.label}</span>
<span style={{ fontSize: "0.7rem", color: "var(--admin-text-muted)", lineHeight: 1.3, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>
{f.desc}
</span>
</div>
</div>
);
})}
</div> </div>
); );
} }
@@ -182,44 +207,59 @@ export default async function AdminTheme({
</section> </section>
<form action={saveTheme}> <form action={saveTheme}>
<h2 className="mt-6 text-lg font-extrabold">Light mode</h2> <h2 className="mt-6 text-lg font-extrabold">☀ Light mode</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
These colors are used when visitors view your site in light mode (default). Make sure body text contrasts well
with both the page background and card surface.
</p>
<section className="mt-6"> <section className="mt-6">
<h2 className="admin-section-title">{t("colours")}</h2> <h2 className="admin-section-title">Page &amp; text colors</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-2">
Backgrounds, surfaces, and text colors. Text colors must contrast with their backgrounds for readability.
</p>
<div className="admin-card">{colorGrid(COLOR_FIELDS, current)}</div> <div className="admin-card">{colorGrid(COLOR_FIELDS, current)}</div>
</section> </section>
<section className="mt-6"> <section className="mt-6">
<h2 className="admin-section-title">{t("buttonsLinks")}</h2> <h2 className="admin-section-title">Buttons &amp; links</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-2">
Button backgrounds, text on buttons, and link colors. Button text must contrast with its button background.
</p>
<div className="admin-card">{colorGrid(BUTTON_FIELDS, current)}</div> <div className="admin-card">{colorGrid(BUTTON_FIELDS, current)}</div>
</section> </section>
<section className="mt-6"> <section className="mt-6">
<h2 className="admin-section-title">Gradient mix</h2> <h2 className="admin-section-title">Gradients</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-2"> <p className="text-xs text-[var(--admin-text-muted)] mb-2">
Control the card header and hero background gradients. Card header and hero section gradient. These blend from start → end across headings and banners.
</p> </p>
<div className="admin-card">{colorGrid(GRADIENT_FIELDS, current)}</div> <div className="admin-card">{colorGrid(GRADIENT_FIELDS, current)}</div>
</section> </section>
<h2 className="mt-8 text-lg font-extrabold">Admin panel (HK)</h2> <h2 className="mt-8 text-lg font-extrabold">🛠 Admin panel (HK)</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
Override colors for the Housekeeping admin panel. These apply across both light and dark modes. Leave empty to
use auto-derived defaults.
</p>
<section className="mt-4"> <section className="mt-4">
<p className="text-xs text-[var(--admin-text-muted)] mb-2">
Colors for the Housekeeping admin panel. These override the defaults derived from light/dark mode palettes.
</p>
<div className="admin-card">{colorGrid(ADMIN_FIELDS, current)}</div> <div className="admin-card">{colorGrid(ADMIN_FIELDS, current)}</div>
</section> </section>
<h2 className="mt-8 text-lg font-extrabold">Dark mode</h2> <h2 className="mt-8 text-lg font-extrabold">🌙 Dark mode</h2>
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
These colors are used when visitors switch to dark mode. Dark backgrounds need lighter text for good contrast.
</p>
<section className="mt-4"> <section className="mt-4">
<h3 className="admin-section-title">{t("colours")}</h3> <h3 className="admin-section-title">Page &amp; text colors</h3>
<div className="admin-card">{colorGrid(COLOR_FIELDS, currentDark, "_dark")}</div> <div className="admin-card">{colorGrid(COLOR_FIELDS, currentDark, "_dark")}</div>
</section> </section>
<section className="mt-6"> <section className="mt-6">
<h3 className="admin-section-title">{t("buttonsLinks")}</h3> <h3 className="admin-section-title">Buttons &amp; links</h3>
<div className="admin-card">{colorGrid(BUTTON_FIELDS, currentDark, "_dark")}</div> <div className="admin-card">{colorGrid(BUTTON_FIELDS, currentDark, "_dark")}</div>
</section> </section>
<section className="mt-6"> <section className="mt-6">
<h3 className="admin-section-title">Gradient mix</h3> <h3 className="admin-section-title">Gradients</h3>
<div className="admin-card">{colorGrid(GRADIENT_FIELDS, currentDark, "_dark")}</div> <div className="admin-card">{colorGrid(GRADIENT_FIELDS, currentDark, "_dark")}</div>
</section> </section>