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
+99 -59
+99 -59
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) => {
const val = toHex(current[f.key], f.def);
return (
<div
key={f.key}
style={{
display: "flex",
alignItems: "center",
gap: "0.6rem",
padding: "0.4rem 0.5rem",
borderRadius: 6,
}} }}
> >
{fields.map((f) => (
<label key={f.key} style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
<input <input
type="color" type="color"
name={`${f.key}${suffix}`} name={`${f.key}${suffix}`}
defaultValue={toHex(current[f.key], f.def)} defaultValue={val}
style={{ width: 40, height: 30, padding: 0, border: "none", background: "none" }} style={{ width: 40, height: 32, padding: 0, border: "none", background: "none", cursor: "pointer", flexShrink: 0 }}
/> />
<span style={{ fontSize: "0.88rem" }}>{f.label}</span> <div style={{ display: "flex", flexDirection: "column", minWidth: 0, flex: 1 }}>
</label> <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>
<section className="mt-4"> <p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
<p className="text-xs text-[var(--admin-text-muted)] mb-2"> Override colors for the Housekeeping admin panel. These apply across both light and dark modes. Leave empty to
Colors for the Housekeeping admin panel. These override the defaults derived from light/dark mode palettes. use auto-derived defaults.
</p> </p>
<section className="mt-4">
<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>