@tristan2828/ui-foundation 3.26.0 → 3.27.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.
@@ -125,30 +125,38 @@ that lets people pick a colour stores the slot's name (an enum of
125
125
  `category-1` … `category-12` in its contract, with a CHECK on the column)
126
126
  and maps each to its whole class the same way.
127
127
 
128
- | Slots | What they are | Near info's blue |
129
- |---|---|---|
130
- | 1–8 | eight hues, about 25° apart | 1, 6, 8 |
131
- | 9–12 | deeper shades of four of those hues: 9 teal (4's), 10 plum (5's), 11 olive (3's), 12 violet (2's) | none |
128
+ | Slot | Colour | Slot | Colour |
129
+ |---|---|---|---|
130
+ | 1 | blue (near info) | 7 | teal |
131
+ | 2 | orange | 8 | red |
132
+ | 3 | green | 9 | gold |
133
+ | 4 | purple | 10 | navy |
134
+ | 5 | pink | 11 | lime |
135
+ | 6 | brown | 12 | magenta |
136
+
137
+ Every slot is a colour with its own name: no two blues, no two teals. So
138
+ in a picker, label each with its name ("Orange"), not its number.
132
139
 
133
140
  Five things about them:
134
141
 
135
- - **The hues avoid red, green and amber**, which the tones already spend. A
136
- category drawn in red reads as "this one is bad" even when nothing is
137
- wrong. Info's blue came later, and slots 1, 6 and 8 sit near it: a table
138
- that shows an info badge keeps its categories off those three, so a blue
139
- genre isn't read as "notice this". That still leaves nine.
140
- - **They are ordered by distinctness, not by hue angle.** An app using
141
- three categories gets three obviously different colours. Past about five,
142
- hue alone stops separating them — slots 3 and 7 are both yellow-greens —
143
- so the glyph has to carry the meaning and the colour only reinforces it.
144
- - **Past eight, it's lightness, not hue.** The hues left between the tones
145
- are spent, so slots 9–12 are darker shades of four of the first eight, in
146
- both themes. Each is at least as far from every other slot as the first
147
- eight are from each other (a unit test holds every pair to it), but a
148
- deep shade beside its own hue is told apart by how dark it is, which
149
- reads less readily than a hue. Use them for a set that really has more
150
- than eight values, and keep a shade away from its own hue where you can
151
- choose (10 isn't 5's neighbour in a list).
142
+ - **Chosen to be told apart, not spaced by hue.** They use the whole hue
143
+ circle and vary lightness and chroma as well, so a light lime sits next
144
+ to a deep navy rather than two mid-tone hues 25° apart. Every pair is at
145
+ least 17.9 apart in CIEDE2000 ΔE (the perceived difference; 10 and up
146
+ reads as a different colour), in both themes. A unit test holds every
147
+ pair to 15.
148
+ - **A category is not a tone.** Red, orange, brown and green are slots
149
+ because a category is a tinted glyph beside its name, never a status
150
+ badge, and doesn't read as good or bad there. Where it still could, keep
151
+ them apart: a column that shows tone badges doesn't tint its categories
152
+ in the same column, and a table that shows an info badge keeps its
153
+ categories off slot 1, the one near info's blue.
154
+ - **They are ordered for small sets.** An app using the first few gets the
155
+ most separable few, for everyone: the first six also stay apart (ΔE 10
156
+ or more, tested) for readers with deuteranopia or protanopia, the common
157
+ red-green deficiencies. Past six, some pairs merge for those readers
158
+ (gold and lime are nearly one colour), so the glyph and the name carry
159
+ the meaning and the colour only reinforces it.
152
160
  - **Tint the glyph, never fill behind text.** The label stays
153
161
  `text-foreground`, so readability never depends on the hue. A slot used
154
162
  as a background needs its own contrast check, which nothing here does
@@ -157,12 +165,13 @@ Five things about them:
157
165
  non-text minimum in both themes, on the page and on a card (recorded in
158
166
  `theme.css`). axe checks text contrast, not icon contrast, so the
159
167
  package's Storybook glyph check draws an icon in each slot on both
160
- surfaces (`patterns/CategoricalColour`, which also shows them side by
161
- side) and fails a change that takes one below 3:1.
168
+ surfaces (`patterns/CategoricalColour`) and fails a change that takes
169
+ one below 3:1. Its `SideBySide` story shows both themes at once, under
170
+ normal vision and simulated deuteranopia and protanopia.
162
171
 
163
172
  If you find yourself wanting a thirteenth, that is a sign the column should
164
173
  be showing a shape or a label rather than more colours: there is no more
165
- room in hue or lightness that stays readable.
174
+ room that stays readable.
166
175
 
167
176
  ## Typography
168
177
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tristan2828/ui-foundation",
3
- "version": "3.26.0",
3
+ "version": "3.27.0",
4
4
  "description": "Shared UI foundation for contract-first, database-backed React apps: shadcn primitives, DataTable/EntityForm composites, auth, the gateway error seam, design tokens, lint rules and the AI playbook.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/styles/theme.css CHANGED
@@ -66,54 +66,50 @@
66
66
  * to a slot in its own columns file, so a new enum value costs a line
67
67
  * there rather than three tokens here.
68
68
  *
69
- * Hues avoid the red and green bands that destructive/success already
70
- * spend, and the amber that warning does — a category drawn in red reads
71
- * as "bad". The blue slots (1, 6, 8) sit near info's hue, which came
72
- * later: a table using info keeps its categories off those three. They are ordered by distinctness, not by hue angle, so an app
73
- * using only the first three gets three obviously different colours.
74
- * Past about five, hue alone stops separating them and the glyph has to
75
- * carry the meaning; see docs/design-language.md. Twelve slots in all.
69
+ * Chosen to be told apart (#108), not spaced by hue: twelve colours a
70
+ * person names differently, from the whole hue circle, with lightness and
71
+ * chroma varied per slot as well as hue. Red, orange, brown and green are
72
+ * in: a category is a tinted glyph beside its name, never a status badge,
73
+ * so it doesn't read as a tone (design-language.md says where that still
74
+ * matters). Slot 1 (blue) sits near info's hue; a table using info keeps
75
+ * its categories off it.
76
76
  *
77
- * Ratios are against the page, measured (WCAG 2.x, sRGB), and all clear
78
- * the 3:1 non-text minimum in both themes with room to spare (4.5:1 at
79
- * the least, slots 9-12 on the dark card). axe does NOT check icon
80
- * contrast; the Storybook glyph check re-measures every slot on the
81
- * page and on a card (design-language/CategoricalColour).
82
- */
83
- --category-1-700: oklch(0.5 0.15 240);
84
- --category-1-400: oklch(0.78 0.12 240);
85
- --category-2-700: oklch(0.5 0.15 300);
86
- --category-2-400: oklch(0.78 0.12 300);
87
- --category-3-700: oklch(0.5 0.15 95);
88
- --category-3-400: oklch(0.78 0.12 95);
89
- --category-4-700: oklch(0.5 0.15 190);
90
- --category-4-400: oklch(0.78 0.12 190);
91
- --category-5-700: oklch(0.5 0.15 330);
92
- --category-5-400: oklch(0.78 0.12 330);
93
- --category-6-700: oklch(0.5 0.15 265);
94
- --category-6-400: oklch(0.78 0.12 265);
95
- --category-7-700: oklch(0.5 0.15 120);
96
- --category-7-400: oklch(0.78 0.12 120);
97
- --category-8-700: oklch(0.5 0.15 215);
98
- --category-8-400: oklch(0.78 0.12 215);
99
- /*
100
- * Slots 9-12 are a second channel, not new hues: the hues left between
101
- * the tones' are already spent ~25 degrees apart, so these are deeper
102
- * shades of the four that sit furthest from info (teal, plum, olive,
103
- * violet), darker than their slot in both themes. Each is at least as
104
- * far from its nearest slot (OKLab distance) as slots 1-8 are from each
105
- * other; tests/category-slots.test.ts holds every pair to that. A deep
106
- * shade of the blues or the yellow-green fell to half that distance, so
107
- * twelve is where it stops. Chroma is the most each holds in sRGB.
77
+ * Every pair is at least 17.9 apart in CIEDE2000 ΔE in both themes (the
78
+ * hue-spaced slots before them got to 8.8); tests/category-slots.test.ts
79
+ * holds every pair to 15. They are ordered so the first few stay apart
80
+ * under deuteranopia and protanopia too (the first six are 12 apart);
81
+ * past that, the glyph has to carry the meaning for those readers.
82
+ *
83
+ * Ratios are against the page, measured (WCAG 2.x, sRGB): all clear the
84
+ * 3:1 non-text minimum in both themes (3.75:1 at the least, orange and
85
+ * teal on white). axe does NOT check icon contrast; the Storybook glyph
86
+ * check re-measures every slot on the page and on a card
87
+ * (patterns/CategoricalColour).
108
88
  */
109
- --category-9-700: oklch(0.36 0.06 190);
110
- --category-9-400: oklch(0.62 0.1 190);
111
- --category-10-700: oklch(0.36 0.12 330);
112
- --category-10-400: oklch(0.62 0.15 330);
113
- --category-11-700: oklch(0.36 0.07 95);
114
- --category-11-400: oklch(0.62 0.12 95);
115
- --category-12-700: oklch(0.36 0.14 300);
116
- --category-12-400: oklch(0.62 0.17 300);
89
+ --category-1-700: oklch(0.44 0.11 245); /* blue */
90
+ --category-1-400: oklch(0.626 0.156 245);
91
+ --category-2-700: oklch(0.627 0.161 52); /* orange */
92
+ --category-2-400: oklch(0.722 0.16 52);
93
+ --category-3-700: oklch(0.492 0.105 152); /* green */
94
+ --category-3-400: oklch(0.763 0.17 152);
95
+ --category-4-700: oklch(0.55 0.254 305); /* purple */
96
+ --category-4-400: oklch(0.772 0.116 305);
97
+ --category-5-700: oklch(0.615 0.144 7); /* pink */
98
+ --category-5-400: oklch(0.812 0.102 7);
99
+ --category-6-700: oklch(0.445 0.1 56); /* brown */
100
+ --category-6-400: oklch(0.602 0.054 56);
101
+ --category-7-700: oklch(0.599 0.104 183); /* teal */
102
+ --category-7-400: oklch(0.849 0.139 183);
103
+ --category-8-700: oklch(0.503 0.176 27); /* red */
104
+ --category-8-400: oklch(0.646 0.152 27);
105
+ --category-9-700: oklch(0.573 0.115 84); /* gold */
106
+ --category-9-400: oklch(0.87 0.136 84);
107
+ --category-10-700: oklch(0.362 0.203 279); /* navy */
108
+ --category-10-400: oklch(0.601 0.164 279);
109
+ --category-11-700: oklch(0.593 0.121 119); /* lime */
110
+ --category-11-400: oklch(0.823 0.163 119);
111
+ --category-12-700: oklch(0.423 0.181 337); /* magenta */
112
+ --category-12-400: oklch(0.648 0.232 337);
117
113
  --blue-500: oklch(0.488 0.243 264.376);
118
114
 
119
115
  --radius-base: 0.625rem;