@orange-halo/tokens 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ {
2
+ "$description": "Token pairs that must meet WCAG contrast. Used by `pnpm check:contrast` and the Storybook Tokens page. Paths are token paths in dist/tokens.json. `over` composites a translucent background onto a solid one. `knownIssue` reports a failure as a warning until it's resolved.",
3
+ "pairs": [
4
+ { "fg": "color.text.primary", "bg": "color.surface.base", "min": 4.5, "use": "Body text on page" },
5
+ { "fg": "color.text.primary", "bg": "color.surface.raised", "min": 4.5, "use": "Body text on cards" },
6
+ { "fg": "color.text.secondary", "bg": "color.surface.base", "min": 4.5, "use": "Secondary text on page" },
7
+ { "fg": "color.text.secondary", "bg": "color.surface.inset", "min": 4.5, "use": "Placeholder / helper text in wells" },
8
+ { "fg": "color.text.secondary", "bg": "color.surface.raised", "min": 4.5, "use": "Secondary text on raised surfaces (cards, panels, keyboard hints)" },
9
+ { "fg": "color.text.primary", "bg": "color.surface.highlight", "min": 4.5, "use": "Text in a focused input" },
10
+ { "fg": "color.text.link", "bg": "color.surface.base", "min": 4.5, "use": "Links on page" },
11
+ { "fg": "color.text.linkRaised", "bg": "color.surface.raised", "min": 4.5, "use": "Links on raised surfaces (cards, skip link)" },
12
+ { "fg": "color.text.onBrand", "bg": "color.brand.primary", "min": 4.5, "use": "Primary button, hero text" },
13
+ { "fg": "color.text.onBrand", "bg": "color.brand.primaryHover", "min": 4.5, "use": "Primary button hover" },
14
+ { "fg": "color.text.onAccent", "bg": "color.accent.default", "min": 4.5, "use": "Accent button, footer text" },
15
+ { "fg": "color.text.onAccent", "bg": "color.accent.hover", "min": 4.5, "use": "Accent button hover" },
16
+ { "fg": "color.brand.primary", "bg": "color.brand.primarySubtle", "min": 4.5, "use": "Brand badge" },
17
+ { "fg": "color.accent.default", "bg": "color.accent.subtle", "min": 4.5, "use": "Accent badge" },
18
+ { "fg": "color.feedback.success", "bg": "color.feedback.successSubtle", "min": 4.5, "use": "Success badge / alert text" },
19
+ { "fg": "color.feedback.error", "bg": "color.feedback.errorSubtle", "min": 4.5, "use": "Error text on error background" },
20
+ { "fg": "color.feedback.error", "bg": "color.surface.base", "min": 4.5, "use": "Error messages" },
21
+ { "fg": "color.feedback.warning", "bg": "color.feedback.warningSubtle", "min": 4.5, "use": "Warning badge / alert text" },
22
+ { "fg": "color.feedback.info", "bg": "color.feedback.infoSubtle", "min": 4.5, "use": "Info badge / alert text" },
23
+ { "fg": "color.text.onSuccess", "bg": "color.feedback.success", "min": 4.5, "use": "Filled success badge" },
24
+ { "fg": "color.text.onError", "bg": "color.feedback.error", "min": 4.5, "use": "Filled error badge" },
25
+ { "fg": "color.text.onWarning", "bg": "color.feedback.warning", "min": 4.5, "use": "Filled warning badge" },
26
+ { "fg": "color.text.onInfo", "bg": "color.feedback.info", "min": 4.5, "use": "Filled info badge" },
27
+ { "fg": "color.border.emphasis", "bg": "color.surface.base", "min": 3, "use": "Control borders at rest" },
28
+ { "fg": "color.border.hover", "bg": "color.surface.base", "min": 3, "use": "Control borders on hover" },
29
+ { "fg": "focus.ringColor", "bg": "color.surface.highlight", "min": 3, "use": "Focused input border" },
30
+ { "fg": "focus.ringColor", "bg": "color.surface.base", "min": 3, "use": "Focus outline" },
31
+ { "fg": "color.brand.primary", "bg": "color.surface.base", "min": 3, "use": "Checked controls, link underlines" },
32
+ { "fg": "color.feedback.success", "bg": "color.surface.inset", "min": 3, "use": "Progress fill (success)" },
33
+ { "fg": "color.brand.primary", "bg": "color.surface.inset", "min": 3, "use": "Progress fill (brand)" },
34
+ { "fg": "progress.inverse.fill", "bg": "progress.inverse.trackBackground", "over": "color.brand.primary", "min": 3, "use": "Inverse progress fill in the hero" }
35
+ ]
36
+ }
package/contrast.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WCAG 2.x contrast helpers, shared by scripts/check-contrast.mjs and the
3
+ * Storybook Tokens page. No dependencies.
4
+ */
5
+
6
+ /** Parse #rgb, #rrggbb, #rrggbbaa or rgb()/rgba() into [r, g, b, a]. */
7
+ export function parseColor(value) {
8
+ const v = String(value).trim().toLowerCase();
9
+ let m = v.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/);
10
+ if (m) {
11
+ const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
12
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
13
+ const a = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
14
+ return [r, g, b, a];
15
+ }
16
+ m = v.match(/^rgba?\(([^)]+)\)$/);
17
+ if (m) {
18
+ const [r, g, b, a = "1"] = m[1].split(/[\s,/]+/).filter(Boolean);
19
+ return [Number(r), Number(g), Number(b), Number(a)];
20
+ }
21
+ throw new Error(`Can't parse colour: ${value}`);
22
+ }
23
+
24
+ /** Alpha-composite a colour onto an opaque background. */
25
+ export function composite(fg, bg) {
26
+ const [r, g, b, a] = fg;
27
+ return [r * a + bg[0] * (1 - a), g * a + bg[1] * (1 - a), b * a + bg[2] * (1 - a), 1];
28
+ }
29
+
30
+ function luminance([r, g, b]) {
31
+ const lin = (c) => {
32
+ const s = c / 255;
33
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
34
+ };
35
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
36
+ }
37
+
38
+ /** Contrast ratio between two opaque colours. */
39
+ export function ratio(a, b) {
40
+ const [l1, l2] = [luminance(a), luminance(b)].sort((x, y) => y - x);
41
+ return (l1 + 0.05) / (l2 + 0.05);
42
+ }
43
+
44
+ /** Look up a dotted token path (e.g. "color.brand.primary") in dist/tokens.json. */
45
+ export function resolve(tokens, path) {
46
+ const value = path.split(".").reduce((node, key) => node?.[key], tokens);
47
+ if (typeof value !== "string") throw new Error(`Token not found: ${path}`);
48
+ return value;
49
+ }
50
+
51
+ /** Evaluate one pair from contrast-pairs.json. */
52
+ export function checkPair(tokens, pair) {
53
+ let bg = parseColor(resolve(tokens, pair.bg));
54
+ if (pair.over) bg = composite(bg, parseColor(resolve(tokens, pair.over)));
55
+ let fg = parseColor(resolve(tokens, pair.fg));
56
+ if (fg[3] < 1) fg = composite(fg, bg);
57
+ const value = ratio(fg, bg);
58
+ return { ...pair, ratio: Math.round(value * 100) / 100, pass: value >= pair.min };
59
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Do not edit directly, this file was auto-generated.
3
+ */
4
+
5
+ [data-theme='dark'] {
6
+ --oh-color-surface-overlay: rgba(0, 0, 0, 0.7);
7
+ --oh-color-surface-sheen: rgba(255, 255, 255, 0.03);
8
+ --oh-color-surface-backdrop: rgba(0, 0, 0, 0.75);
9
+ --oh-color-border-brand: rgba(255, 255, 255, 0.25);
10
+ --oh-color-border-brand-hover: rgba(255, 255, 255, 0.45);
11
+ --oh-focus-glow-color: rgba(255, 255, 255, 0); /** No glow: the outline is the indicator */
12
+ --oh-color-brand-primary: var(--oh-color-white);
13
+ --oh-color-brand-primary-hover: var(--oh-color-grey-200);
14
+ --oh-color-brand-primary-active: var(--oh-color-grey-300);
15
+ --oh-color-brand-primary-subtle: var(--oh-color-grey-800);
16
+ --oh-color-brand-highlight: var(--oh-color-grey-100);
17
+ --oh-color-brand-highlight-subtle: var(--oh-color-grey-750);
18
+ --oh-color-accent-default: var(--oh-color-blue-300);
19
+ --oh-color-accent-hover: var(--oh-color-blue-200);
20
+ --oh-color-accent-active: var(--oh-color-blue-300);
21
+ --oh-color-accent-subtle: var(--oh-color-blue-900);
22
+ --oh-color-accent-shadow: var(--oh-color-blue-900);
23
+ --oh-color-feedback-success: var(--oh-color-green-300);
24
+ --oh-color-feedback-success-subtle: var(--oh-color-green-900);
25
+ --oh-color-feedback-error: var(--oh-color-red-300);
26
+ --oh-color-feedback-error-hover: var(--oh-color-red-100);
27
+ --oh-color-feedback-error-subtle: var(--oh-color-red-900);
28
+ --oh-color-feedback-error-shadow: var(--oh-color-red-900);
29
+ --oh-color-feedback-warning: var(--oh-color-amber-300);
30
+ --oh-color-feedback-warning-subtle: var(--oh-color-amber-900);
31
+ --oh-color-feedback-info: var(--oh-color-blue-300);
32
+ --oh-color-feedback-info-subtle: var(--oh-color-blue-900);
33
+ --oh-color-surface-base: var(--oh-color-grey-850);
34
+ --oh-color-surface-raised: var(--oh-color-grey-800);
35
+ --oh-color-surface-sunlit: var(--oh-color-grey-800);
36
+ --oh-color-surface-inset: var(--oh-color-grey-900);
37
+ --oh-color-surface-highlight: var(--oh-color-grey-750);
38
+ --oh-color-surface-disabled: var(--oh-color-grey-800);
39
+ --oh-color-surface-hover: var(--oh-color-grey-750);
40
+ --oh-color-surface-active-pressed: var(--oh-color-grey-700);
41
+ --oh-color-surface-featured-start: var(--oh-color-grey-750);
42
+ --oh-color-surface-featured-mid: var(--oh-color-grey-800);
43
+ --oh-color-surface-featured-end: var(--oh-color-grey-800);
44
+ --oh-color-border-default: var(--oh-color-grey-750);
45
+ --oh-color-border-subtle: var(--oh-color-grey-800);
46
+ --oh-color-border-emphasis: var(--oh-color-grey-500);
47
+ --oh-color-border-strong: var(--oh-color-grey-650);
48
+ --oh-color-border-hover: var(--oh-color-grey-100);
49
+ --oh-color-border-disabled: var(--oh-color-grey-800);
50
+ --oh-color-border-divider: var(--oh-color-grey-750);
51
+ --oh-color-text-primary: var(--oh-color-grey-100);
52
+ --oh-color-text-secondary: var(--oh-color-grey-500);
53
+ --oh-color-text-disabled: var(--oh-color-grey-650);
54
+ --oh-color-text-inverse: var(--oh-color-grey-850);
55
+ --oh-color-text-link: var(--oh-color-white);
56
+ --oh-color-text-link-hover: var(--oh-color-grey-300);
57
+ --oh-color-text-link-raised: var(--oh-color-white);
58
+ --oh-color-text-brand: var(--oh-color-white);
59
+ --oh-color-text-on-brand: var(--oh-color-grey-850);
60
+ --oh-color-text-on-accent: var(--oh-color-grey-850);
61
+ --oh-color-text-on-success: var(--oh-color-grey-850);
62
+ --oh-color-text-on-error: var(--oh-color-grey-850);
63
+ --oh-color-text-on-warning: var(--oh-color-grey-850);
64
+ --oh-color-text-on-info: var(--oh-color-grey-850);
65
+ --oh-shadow-overlay: var(--oh-color-black);
66
+ --oh-shadow-primary: var(--oh-color-black);
67
+ --oh-shadow-secondary: var(--oh-color-black);
68
+ --oh-shadow-accent: var(--oh-color-black);
69
+ --oh-shadow-error: var(--oh-color-black);
70
+ --oh-shadow-neutral: var(--oh-color-black);
71
+ --oh-shadow-neutral-hover: var(--oh-color-black);
72
+ --oh-shadow-field: var(--oh-color-black);
73
+ --oh-focus-ring-color: var(--oh-color-white); /** Solid focus outline, as on the site: white on dark, near-black on light */
74
+ }