@crewlethq/tokens 0.8.1 → 0.10.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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/test/palette.d.mts +5 -0
- package/test/palette.mjs +22 -0
- package/test/palette.test.mjs +25 -0
package/README.md
CHANGED
|
@@ -424,6 +424,7 @@ Every other state and series step ships as approved: the dark inks, dark success
|
|
|
424
424
|
In light, the yellow series sits beside the warning ochre (`#c27600` and `#c17300`). The approved palette drew the two a few steps apart, and both were darkened onto the same floor, a mark that clears 3:1 on the frame. Nothing in the suite keeps a series from a state other than the reserved red, and a series is read only inside a figure that names it.
|
|
425
425
|
|
|
426
426
|
- **The danger fill carries a white label**, on a toast's destructive action and on an upload's remove button, so it is dark enough for 4.5:1 in both palettes (4.59:1 dark, 6.61:1 light), and its hover, `--color-feedback-danger-hover`, is a step darker again, the least a reader can see (dE 3.03 dark, 3.02 light). The palette suite holds the hover as it holds the primary action's: `a hovered destructive action is a visible step away from its label`.
|
|
427
|
+
- **A status glyph is held to 3:1 on its fill.** An `Avatar` status badge is the info, warning or danger fill carrying a small glyph in `--color-text-inverse`, the one ink that clears all three in both palettes (white sits at 1.6:1 on the dark info step). The pairs are `MARK_ON_FILL`, measured over every ground as a label is, under `a glyph clears 3:1 on its own fill`: the 3:1 a mark is held to, since a glyph is a shape rather than running text. The tightest is 3.33:1, the inverse ink on the light info step.
|
|
427
428
|
- **The reserved red is kept away from every series**: dE 14 under normal vision and 8 under protanopia and deuteranopia, where red, orange and green fall onto one axis. The approved chart green sat dE 0.6 from the shipped red under deuteranopia.
|
|
428
429
|
- **Neighbouring series** sit dE 9 apart under every vision and dE 15 under normal vision. The second floor is new: on its own, the dichromat floor let a pair sit dE 9 apart for every reader, which to full-colour vision is two shades of one hue.
|
|
429
430
|
|
|
@@ -448,7 +449,7 @@ The marketing root keeps its own state and series values (`tokens/color.json`),
|
|
|
448
449
|
|
|
449
450
|
## The palette suite
|
|
450
451
|
|
|
451
|
-
`test/palette.mjs` holds the rule table and the colour maths, and `test/palette.test.mjs` runs it over `dist/css` in import order, in every theme state: the base marketing root (`base`), the dark root (`dark`), light by media query (`light (media query)`) and light by attribute (`light (attribute)`), which are the keys `paletteStates()` returns. It measures every text step on every surface it can land on (the four opaque rungs in `OPAQUE_SURFACES`, and the translucent overlays composited over each of them), the step between a word and a dimmed one, every ink on its own soft tint, every fill as a mark, the primary action's three fills under its label and the steps between them, the destructive action's fill and its hover under the same label, the focus ring, the control boundary, the rail's current row, the steps between the rungs, the hairline that finds a card and the strong hairline round a raised chip, whether a hovered and a pressed row can be seen, the hue separations under normal, protan and deuteranopic vision (the states from each other, the chart series from their neighbours and from the danger red, and every hue from the accent), and the structure of the theme file itself. A component suite that measures a colour of its own measures it on `OPAQUE_SURFACES` too, rather than on a list of surface names it keeps itself.
|
|
452
|
+
`test/palette.mjs` holds the rule table and the colour maths, and `test/palette.test.mjs` runs it over `dist/css` in import order, in every theme state: the base marketing root (`base`), the dark root (`dark`), light by media query (`light (media query)`) and light by attribute (`light (attribute)`), which are the keys `paletteStates()` returns. It measures every text step on every surface it can land on (the four opaque rungs in `OPAQUE_SURFACES`, and the translucent overlays composited over each of them), the step between a word and a dimmed one, every ink on its own soft tint, every fill as a mark, each status glyph's ink on its fill, the primary action's three fills under its label and the steps between them, the destructive action's fill and its hover under the same label, the focus ring, the control boundary, the rail's current row, the steps between the rungs, the hairline that finds a card and the strong hairline round a raised chip, whether a hovered and a pressed row can be seen, the hue separations under normal, protan and deuteranopic vision (the states from each other, the chart series from their neighbours and from the danger red, and every hue from the accent), and the structure of the theme file itself. A component suite that measures a colour of its own measures it on `OPAQUE_SURFACES` too, rather than on a list of surface names it keeps itself.
|
|
452
453
|
|
|
453
454
|
The module is **published**, as `@crewlethq/tokens/test/palette`, so a consumer runs the same rules over the version it installed:
|
|
454
455
|
|
package/package.json
CHANGED
package/test/palette.d.mts
CHANGED
|
@@ -158,6 +158,11 @@ export const DATA_ADJACENT_NORMAL_DE: number;
|
|
|
158
158
|
* ACTION_LABEL on it.
|
|
159
159
|
*/
|
|
160
160
|
export const ACTION_STEPS: readonly (readonly [string, string, string])[];
|
|
161
|
+
/**
|
|
162
|
+
* Each state fill with the ink an avatar's status glyph is drawn in on it:
|
|
163
|
+
* [ink, fill], held to 3:1 over every ground the fill can sit on.
|
|
164
|
+
*/
|
|
165
|
+
export const MARK_ON_FILL: readonly (readonly [string, string])[];
|
|
161
166
|
export const ACTION_LABEL: string;
|
|
162
167
|
export const ACTION_DE: number;
|
|
163
168
|
|
package/test/palette.mjs
CHANGED
|
@@ -235,6 +235,23 @@ export const LABEL_ON_FILL = [
|
|
|
235
235
|
['--color-text-inverse', '--color-surface-inverse'],
|
|
236
236
|
];
|
|
237
237
|
|
|
238
|
+
/**
|
|
239
|
+
* A fill with the GLYPH a component draws inside it: an avatar's status badge,
|
|
240
|
+
* which is a state's fill step carrying a small play, question or stop mark in
|
|
241
|
+
* the inverse ink. A glyph is a graphic rather than running text, so it is
|
|
242
|
+
* held to the 3:1 a mark is held to rather than to a label's 4.5:1.
|
|
243
|
+
*
|
|
244
|
+
* THE INVERSE INK, because it is the one ink that clears every state fill in
|
|
245
|
+
* both palettes: white sits under 2:1 on the dark palette's info and warning
|
|
246
|
+
* steps, and the dark ink under 3:1 on none of them. Each fill is measured
|
|
247
|
+
* over every ground it can sit on, as a label's is.
|
|
248
|
+
*/
|
|
249
|
+
export const MARK_ON_FILL = [
|
|
250
|
+
['--color-text-inverse', '--color-feedback-info'],
|
|
251
|
+
['--color-text-inverse', '--color-feedback-warning'],
|
|
252
|
+
['--color-text-inverse', '--color-feedback-danger'],
|
|
253
|
+
];
|
|
254
|
+
|
|
238
255
|
/**
|
|
239
256
|
* THE PRIMARY ACTION'S THREE FILLS, at rest, under the pointer and pressed,
|
|
240
257
|
* each held against the one before it: [rule, the step, the fill it follows].
|
|
@@ -759,6 +776,11 @@ function checkState(state, values, profile, push) {
|
|
|
759
776
|
say('a label clears 4.5:1 on its own fill', low.ratio >= 4.5, `${label} on ${fill}`, low.ratio, `${low.ratio.toFixed(2)}:1 (worst on ${low.name})`);
|
|
760
777
|
}
|
|
761
778
|
|
|
779
|
+
for (const [ink, fill] of MARK_ON_FILL) {
|
|
780
|
+
const low = worst(colour(ink), opaque.map(([name, rgb]) => [`${fill} on ${name}`, flatten(values.get(fill), rgb)]));
|
|
781
|
+
say('a glyph clears 3:1 on its own fill', low.ratio >= 3, `${ink} on ${fill}`, low.ratio, `${low.ratio.toFixed(2)}:1 (worst on ${low.name})`);
|
|
782
|
+
}
|
|
783
|
+
|
|
762
784
|
// THE PRIMARY ACTION'S STEPS. See ACTION_STEPS.
|
|
763
785
|
for (const [rule, step, from] of ACTION_STEPS) {
|
|
764
786
|
const label = colour(ACTION_LABEL);
|
package/test/palette.test.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
DIMMED_TEXT,
|
|
26
26
|
fromOklab,
|
|
27
27
|
hex,
|
|
28
|
+
MARK_ON_FILL,
|
|
28
29
|
oklabToLinear,
|
|
29
30
|
OPAQUE_SURFACES,
|
|
30
31
|
OVERLAY_STEPS,
|
|
@@ -418,6 +419,30 @@ describe('the state and chart hues', () => {
|
|
|
418
419
|
const DESTRUCTIVE_RULE = ACTION_STEPS[2][0];
|
|
419
420
|
const summary = (check) => `${check.rule}: ${check.subject}: ${check.detail}`;
|
|
420
421
|
|
|
422
|
+
test('every status glyph is measured on its fill, in every state', () => {
|
|
423
|
+
// An avatar's status badge is a state fill carrying a glyph. A table that
|
|
424
|
+
// stopped measuring one of the three would pass the one badge nobody can
|
|
425
|
+
// read: white on the dark info step is 1.6:1.
|
|
426
|
+
const GLYPH_RULE = 'a glyph clears 3:1 on its own fill';
|
|
427
|
+
assert.deepEqual(
|
|
428
|
+
MARK_ON_FILL.map(([, fill]) => fill),
|
|
429
|
+
['--color-feedback-info', '--color-feedback-warning', '--color-feedback-danger'],
|
|
430
|
+
);
|
|
431
|
+
for (const state of THEME_STATES) {
|
|
432
|
+
for (const [ink, fill] of MARK_ON_FILL) {
|
|
433
|
+
const measured = checks.filter((check) => check.state === state && check.rule === GLYPH_RULE && check.subject === `${ink} on ${fill}`);
|
|
434
|
+
assert.equal(measured.length, 1, `${state}: ${fill}`);
|
|
435
|
+
assert.ok(measured[0].ok, `${state}: ${describeFailure(measured[0])}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// And it can fail: white glyphs on the dark info step.
|
|
439
|
+
const white = checksAfter('dark', '--color-text-inverse', '#ffffff').filter((check) => check.rule === GLYPH_RULE);
|
|
440
|
+
assert.deepEqual(
|
|
441
|
+
white.map((check) => check.subject),
|
|
442
|
+
['--color-text-inverse on --color-feedback-info', '--color-text-inverse on --color-feedback-warning'],
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
|
|
421
446
|
test('the chart ramp is four series, in every theme state and in the typed export', () => {
|
|
422
447
|
// A fifth series would have to sit between two of the four in a hue
|
|
423
448
|
// budget the reserved red and the accent have spent, so it is the
|