fix(gallery): WCAG-safe Download button text + extract HeaderDownloadButton (#401 follow-ups)
Two follow-ups from PR #401's review: 1. Download button text was hardcoded `color: '#ffffff'`. Once admins start picking palettes via #400's expanded customizer, a pale accent (yellow, pastel blue, etc.) leaves the button unreadable — white text on near-white background. Fix: derive the foreground colour from the accent's WCAG relative luminance and expose it as the new `--color-accent-fg` CSS variable in ThemeContext.applyTheme. Light backgrounds (L >= 0.5) get black text; dark backgrounds get white. Same treatment applied to `--color-accent-dark-fg` for the filled-CTA token. The Download button now reads `var(--color-accent-fg, #ffffff)` so any future component that paints on accent gets the same treatment for free, and legacy deployments before the variable is set fall back to the previous hardcoded white. Threshold-based (rather than "highest contrast ratio") to preserve how saturated mid-tone accents have always rendered. The Picpeak default green (#5C8762, L≈0.20) keeps white text — same visual identity as before. Only genuinely pale accents flip to black, which is the actual scenario the review flagged. 2. The Download button JSX was duplicated three times in GalleryLayout.tsx (standard/banner, minimal, hero — ~15 lines each). Extracted into a small inline `HeaderDownloadButton` component above the GalleryLayout export. Three call sites now collapse to a 5-line component invocation each. Markup, accessibility, and styling live in one place — future tweaks only need to happen once. ## Files - `frontend/src/utils/contrast.ts` — new helper module: `relativeLuminance(hex)` (WCAG 2.x sRGB luminance) and `getReadableForeground(hex)` (white-or-black picker). - `frontend/src/utils/__tests__/contrast.test.ts` — 10 cases: fallbacks, saturated mid-tones, pale accents, near-black, shorthand `#RGB`, no-leading-`#`, case-insensitive, anchors (black/white luminance). - `frontend/src/contexts/ThemeContext.tsx` — wire the helper into `applyTheme`: set `--color-accent-fg` from `accentColor` and `--color-accent-dark-fg` from `accentDarkColor`/`primaryColor`. - `frontend/src/components/gallery/GalleryLayout.tsx` — extract `HeaderDownloadButton` component above `GalleryLayout`, replace three inline button blocks with the component, update its inline style to read `--color-accent-fg` (with the legacy `#ffffff` as the CSS-variable fallback). ## Verified - `npx vitest run src/utils/__tests__/contrast.test.ts` — 10/10 pass - `npx tsc --noEmit` — clean - `npx eslint` clean on every touched file - Default PicPeak green still renders white text (no regression) - Pale accent (#fef9c3 yellow-100) now correctly renders black text
This commit is contained in:
@@ -3,6 +3,7 @@ import type { ReactNode } from 'react';
|
||||
import { ThemeConfig, EventTheme, GALLERY_THEME_PRESETS } from '../types/theme.types';
|
||||
import { fontsService, extractFamilyName, type FontDefinition } from '../services/fonts.service';
|
||||
import { applyForceColorMode } from '../utils/themeMigration';
|
||||
import { getReadableForeground } from '../utils/contrast';
|
||||
import { usePublicSettings } from '../hooks/usePublicSettings';
|
||||
|
||||
// Self-hosted font loader. Resolves the available-fonts list once (cached for
|
||||
@@ -136,6 +137,11 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
|
||||
if (themeConfig.accentColor) {
|
||||
root.style.setProperty('--color-accent', themeConfig.accentColor);
|
||||
// Pick a readable foreground (white or black) for text/icons sitting
|
||||
// on top of `--color-accent`. The gallery header Download CTA reads
|
||||
// this via `var(--color-accent-fg, #ffffff)` so a pale accent doesn't
|
||||
// leave the button text unreadable (PR #401 review follow-up).
|
||||
root.style.setProperty('--color-accent-fg', getReadableForeground(themeConfig.accentColor));
|
||||
}
|
||||
|
||||
// Accent-dark: filled CTA background. Falls back to primaryColor for
|
||||
@@ -144,6 +150,9 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
const accentDark = themeConfig.accentDarkColor || themeConfig.primaryColor;
|
||||
if (accentDark) {
|
||||
root.style.setProperty('--color-accent-dark', accentDark);
|
||||
// Same readable-foreground treatment for filled CTAs (.btn-primary
|
||||
// and .tile-selected) that paint on top of accent-dark.
|
||||
root.style.setProperty('--color-accent-dark-fg', getReadableForeground(accentDark));
|
||||
}
|
||||
|
||||
if (themeConfig.backgroundColor) {
|
||||
|
||||
Reference in New Issue
Block a user