// What a brand looks like on each theme, and whether it is legible there: the sample of a primary // action and of a link, drawn with the very declarations the organization layout will write, and the // contrast numbers in words. A colour that is chosen is shown before it is saved, so "it saves but // nobody can read it" is not a state a person can reach without being told. import type { CSSProperties, ReactElement } from "react" import { tenantStyle } from "../contract/style.js" export interface PreviewLabels { readonly title: string readonly dark: string readonly light: string readonly action: string readonly link: string readonly sample: string /** Contain `{ratio}`, replaced with the measured ratio to one decimal. */ readonly fillContrast: string readonly inkContrast: string readonly edgeContrast: string readonly meets: string readonly below: string readonly adjusted: string } /** The default brand's own fill, so a preview with no colour chosen shows what the app already has. */ export interface PreviewProps { readonly primary: string | null readonly accent: string | null /** The brand package's fill, which a preview with no colour of its own shows. */ readonly defaultPrimary: string readonly labels: PreviewLabels } const AA = 4.5 /** The 3:1 that a non-text boundary needs (WCAG 1.4.11). */ const NON_TEXT = 3 const ratio = (template: string, value: number): string => template.replace("{ratio}", value.toFixed(1)) export function BrandPreview({ primary, accent, defaultPrimary, labels }: PreviewProps): ReactElement { const { style, report } = tenantStyle({ primary: primary ?? defaultPrimary, accent }) return (

{labels.title}

{(["dark", "light"] as const).map((theme) => { const numbers = report[theme] if (!numbers) return null const heading = `brand-preview-${theme}` return (

{theme === "light" ? labels.light : labels.dark}

{labels.sample}

{" "} {labels.link}
  • {ratio(labels.fillContrast, numbers.fillContrast)} ( {numbers.fillContrast >= AA ? labels.meets : labels.below})
  • {ratio(labels.inkContrast, numbers.inkContrast)} ( {numbers.inkContrast >= AA ? labels.meets : labels.below})
  • {ratio(labels.edgeContrast, numbers.edgeContrast)} ( {numbers.edgeContrast >= NON_TEXT ? labels.meets : labels.below})
{numbers.ink.toLowerCase() !== numbers.fill.toLowerCase() ?

{labels.adjusted}

: null}
) })}
) }