This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// 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 (
|
||||
<section aria-labelledby="brand-preview-title" className="brand-preview">
|
||||
<h2 id="brand-preview-title">{labels.title}</h2>
|
||||
<div className="brand-preview-grid">
|
||||
{(["dark", "light"] as const).map((theme) => {
|
||||
const numbers = report[theme]
|
||||
if (!numbers) return null
|
||||
const heading = `brand-preview-${theme}`
|
||||
return (
|
||||
<article
|
||||
key={theme}
|
||||
aria-labelledby={heading}
|
||||
className={theme === "light" ? "brand-preview-panel light" : "brand-preview-panel"}
|
||||
style={style as CSSProperties}
|
||||
>
|
||||
<h3 id={heading}>{theme === "light" ? labels.light : labels.dark}</h3>
|
||||
<p>{labels.sample}</p>
|
||||
<button type="button" className="brand-preview-action">
|
||||
{labels.action}
|
||||
</button>{" "}
|
||||
<a href="#brand-preview-title" className="brand-preview-link">
|
||||
{labels.link}
|
||||
</a>
|
||||
<ul>
|
||||
<li>
|
||||
{ratio(labels.fillContrast, numbers.fillContrast)} (
|
||||
{numbers.fillContrast >= AA ? labels.meets : labels.below})
|
||||
</li>
|
||||
<li>
|
||||
{ratio(labels.inkContrast, numbers.inkContrast)} (
|
||||
{numbers.inkContrast >= AA ? labels.meets : labels.below})
|
||||
</li>
|
||||
<li>
|
||||
{ratio(labels.edgeContrast, numbers.edgeContrast)} (
|
||||
{numbers.edgeContrast >= NON_TEXT ? labels.meets : labels.below})
|
||||
</li>
|
||||
</ul>
|
||||
{numbers.ink.toLowerCase() !== numbers.fill.toLowerCase() ? <p>{labels.adjusted}</p> : null}
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user