Fix automatic readable text contrast for public and admin themes.
Local Build and Deploy / deploy (push) Successful in 55s
Local Build and Deploy / deploy (push) Successful in 55s
Derive admin/public text colors from WCAG contrast, unify ThemeVars CSS emission, and keep navbar overrides in sync with readable vars. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
9d4d409b77
commit
d7278c77f9
9 files changed
+205
-176
No files matched your search
@@ -39,6 +39,10 @@ export function NavbarColorPicker() {
|
||||
if (!c) return;
|
||||
document.documentElement.style.setProperty("--color-navbar", c.value);
|
||||
document.documentElement.style.setProperty("--color-navbar-text", c.text);
|
||||
document.documentElement.style.setProperty(
|
||||
"--color-navbar-text-readable",
|
||||
c.text,
|
||||
);
|
||||
try {
|
||||
localStorage.setItem("navbarColor", c.value);
|
||||
localStorage.setItem("navbarTextColor", c.text);
|
||||
@@ -58,6 +62,10 @@ export function NavbarColorPicker() {
|
||||
"--color-navbar-text",
|
||||
savedText,
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--color-navbar-text-readable",
|
||||
savedText,
|
||||
);
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
+36
-134
@@ -1,10 +1,5 @@
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import {
|
||||
deriveAdminPalette,
|
||||
derivePublicForegrounds,
|
||||
readableColor,
|
||||
} from "@/lib/theme-contrast";
|
||||
import { adminPaletteCss, themePaletteCss } from "@/lib/theme-css";
|
||||
import { themePaletteCss } from "@/lib/theme-css";
|
||||
import {
|
||||
FONTS,
|
||||
PRESETS,
|
||||
@@ -12,9 +7,8 @@ import {
|
||||
type ThemePalette,
|
||||
} from "@/lib/theme-presets";
|
||||
|
||||
// Injects the DB-driven CSS custom properties into :root, exactly like
|
||||
// AtomCMS's app.blade.php. Falls back to the atom defaults when no DB. Covers
|
||||
// colours, typography, buttons, links + an optional raw custom-CSS block.
|
||||
// Injects the DB-driven CSS custom properties into :root / html.dark.
|
||||
// Readable (WCAG) text colors are derived centrally in themePaletteCss.
|
||||
export async function ThemeVars() {
|
||||
const g = (k: string, d: string) => siteSettings.get(k, d);
|
||||
const [
|
||||
@@ -103,68 +97,20 @@ export async function ThemeVars() {
|
||||
const px = (v: string | null, d: string) =>
|
||||
/^\d{1,3}$/.test(v ?? "") ? (v as string) : d;
|
||||
|
||||
const safeBackground = safe(background, "#f8fafc");
|
||||
const safeSurface = safe(surface, "#ffffff");
|
||||
const publicBackgrounds = [safeBackground, safeSurface];
|
||||
const readableText = readableColor(safe(text, "#0f172a"), publicBackgrounds);
|
||||
const readableMuted = readableColor(
|
||||
safe(textMuted, "#64748b"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readablePrimary = readableColor(
|
||||
safe(primary, "#f59e0b"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableAccent = readableColor(
|
||||
safe(accent, "#10b981"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableLink = readableColor(
|
||||
safe(linkColor, "#eeb425"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableLinkHover = readableColor(
|
||||
safe(linkHover, "#cf9d15"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableButtonText = readableColor(safe(buttonText, "#1e293b"), [
|
||||
safe(buttonColor, "#f59e0b"),
|
||||
]);
|
||||
const readableSecondaryText = readableColor(
|
||||
safe(buttonSecondaryText, "#ffffff"),
|
||||
[safe(buttonSecondary, "#22c55e")],
|
||||
);
|
||||
const readableDangerText = readableColor(safe(buttonDangerText, "#ffffff"), [
|
||||
safe(buttonDanger, "#ef4444"),
|
||||
]);
|
||||
const readableNavbarText = readableColor(safe(navbarText, "#1e293b"), [
|
||||
safe(navbar, "#ffffff"),
|
||||
]);
|
||||
const readablePrimaryForeground = readableColor(safe(buttonText, "#1e293b"), [
|
||||
safe(primary, "#f59e0b"),
|
||||
]);
|
||||
const readableAccentForeground = readableColor("#ffffff", [
|
||||
safe(accent, "#10b981"),
|
||||
]);
|
||||
const adminOverrides = {
|
||||
...(adminCanvas ? { canvas: safe(adminCanvas, "") } : {}),
|
||||
...(adminSurface ? { surface: safe(adminSurface, "") } : {}),
|
||||
...(adminText ? { text: safe(adminText, "") } : {}),
|
||||
...(adminTextMuted ? { muted: safe(adminTextMuted, "") } : {}),
|
||||
...(adminBorder ? { border: safe(adminBorder, "") } : {}),
|
||||
...(adminSidebarBg ? { sidebar: safe(adminSidebarBg, "") } : {}),
|
||||
};
|
||||
|
||||
const semantic = derivePublicForegrounds({
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
const lightPalette = {
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_background: safe(background, "#f8fafc"),
|
||||
color_surface: safe(surface, "#ffffff"),
|
||||
color_dropdown: safe(dropdown, "#ffffff"),
|
||||
color_navbar: safe(navbar, "#ffffff"),
|
||||
color_navbar_text: safe(navbarText, "#1e293b"),
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
color_success: safe(success, "#16a34a"),
|
||||
color_warning: safe(warning, "#eab308"),
|
||||
color_error: safe(error, "#ef4444"),
|
||||
color_info: safe(info, "#0ea5e9"),
|
||||
button_primary_color: safe(buttonColor, "#f59e0b"),
|
||||
button_text_color: safe(buttonText, "#1e293b"),
|
||||
button_secondary_color: safe(buttonSecondary, "#22c55e"),
|
||||
@@ -175,7 +121,19 @@ export async function ThemeVars() {
|
||||
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"),
|
||||
} satisfies ThemePalette;
|
||||
|
||||
const adminOverrides = {
|
||||
...(adminCanvas ? { canvas: safe(adminCanvas, "#0b0f19") } : {}),
|
||||
...(adminSurface ? { surface: safe(adminSurface, "#151b27") } : {}),
|
||||
...(adminText ? { text: safe(adminText, "#f8fafc") } : {}),
|
||||
...(adminTextMuted ? { muted: safe(adminTextMuted, "#94a3b8") } : {}),
|
||||
...(adminBorder ? { border: safe(adminBorder, "#2a3448") } : {}),
|
||||
...(adminSidebarBg ? { sidebar: safe(adminSidebarBg, "#080c18") } : {}),
|
||||
};
|
||||
|
||||
const font = FONTS[fontKey ?? "nunito"] ?? FONTS.nunito;
|
||||
|
||||
@@ -193,75 +151,19 @@ export async function ThemeVars() {
|
||||
),
|
||||
),
|
||||
) as ThemePalette;
|
||||
|
||||
const typography = [
|
||||
`--border-radius:${px(borderRadius, "12")}px`,
|
||||
`--font-family:${font.stack}`,
|
||||
`--size-heading-h1:${px(h1, "30")}px`,
|
||||
`--size-heading-h2:${px(h2, "22")}px`,
|
||||
`--size-heading-h3:${px(h3, "18")}px`,
|
||||
].join(";");
|
||||
|
||||
const lightCss = themePaletteCss(":root", lightPalette, adminOverrides);
|
||||
const darkCss = themePaletteCss("html.dark", darkPalette, adminOverrides);
|
||||
|
||||
const lightAdmin = deriveAdminPalette(
|
||||
{
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
color_success: safe(success, "#16a34a"),
|
||||
color_warning: safe(warning, "#eab308"),
|
||||
color_error: safe(error, "#ef4444"),
|
||||
color_info: safe(info, "#0ea5e9"),
|
||||
} as ThemePalette,
|
||||
adminOverrides,
|
||||
);
|
||||
|
||||
const css = `:root{${adminPaletteCss(lightAdmin)}
|
||||
--color-primary:${safe(primary, "#f59e0b")};
|
||||
--color-background:${safeBackground};
|
||||
--color-surface:${safeSurface};
|
||||
--color-dropdown:${safe(dropdown, "#ffffff")};
|
||||
--color-navbar:${safe(navbar, "#ffffff")};
|
||||
--color-navbar-text:${safe(navbarText, "#1e293b")};
|
||||
--color-text:${safe(text, "#0f172a")};
|
||||
--color-text-muted:${safe(textMuted, "#64748b")};
|
||||
--color-accent:${safe(accent, "#10b981")};
|
||||
--color-success:${safe(success, "#16a34a")};
|
||||
--color-warning:${safe(warning, "#eab308")};
|
||||
--color-error:${safe(error, "#ef4444")};
|
||||
--color-info:${safe(info, "#0ea5e9")};
|
||||
--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")};
|
||||
--color-text-readable:${readableText};
|
||||
--color-text-muted-readable:${readableMuted};
|
||||
--color-primary-readable:${readablePrimary};
|
||||
--color-accent-readable:${readableAccent};
|
||||
--color-primary-foreground-readable:${readablePrimaryForeground};
|
||||
--color-accent-foreground-readable:${readableAccentForeground};
|
||||
--color-navbar-text-readable:${readableNavbarText};
|
||||
--button-text-color-readable:${readableButtonText};
|
||||
--button-secondary-text-color-readable:${readableSecondaryText};
|
||||
--button-danger-text-color-readable:${readableDangerText};
|
||||
--link-color-readable:${readableLink};
|
||||
--link-hover-color-readable:${readableLinkHover};
|
||||
--color-surface-text-readable:${semantic.byName.surface};
|
||||
--color-dropdown-text-readable:${semantic.byName.dropdown};
|
||||
--button-outline-text-color-readable:${semantic.byName["button-outline"]};
|
||||
--color-pill-text-readable:${semantic.byName.pill};
|
||||
--color-badge-text-readable:${semantic.byName.badge};
|
||||
--color-overlay-text-readable:${semantic.byName.overlay};
|
||||
--border-radius:${px(borderRadius, "12")}px;
|
||||
--font-family:${font.stack};
|
||||
--size-heading-h1:${px(h1, "30")}px;
|
||||
--size-heading-h2:${px(h2, "22")}px;
|
||||
--size-heading-h3:${px(h3, "18")}px;
|
||||
}${darkCss}`;
|
||||
// Append typography inside :root block.
|
||||
const css = `${lightCss.slice(0, -1)}${typography};}${darkCss}`;
|
||||
|
||||
const googleHref = font.google
|
||||
? `https://fonts.googleapis.com/css2?family=${font.google}&display=swap`
|
||||
|
||||
Reference in new issue
Block a user