@microbit/ui 0.0.0-gray.ramp.90 → 0.0.0-gray.ramp.92

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 CHANGED
@@ -179,7 +179,7 @@ runtime lookup:
179
179
  import { token } from "@microbit/ui"; // re-exports styled-system/tokens
180
180
 
181
181
  token("colors.brand.500"); // "#007dbc" — raw value, safe for colour math
182
- token("colors.statusBarBg"); // "var(--colors-brand2-500)" — CSS contexts only
182
+ token("colors.statusBarBg"); // "var(--colors-gray-500)" — CSS contexts only
183
183
  ```
184
184
 
185
185
  Base tokens resolve to raw values; **semantic tokens resolve to `var()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microbit/ui",
3
- "version": "0.0.0-gray.ramp.90",
3
+ "version": "0.0.0-gray.ramp.92",
4
4
  "description": "micro:bit design-system primitives: react-aria-components + Panda CSS with a design language ported from Chakra UI v2. Ships as source; see README for the consumption setup.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,8 +17,17 @@ const transitionCommon =
17
17
  *
18
18
  * Focus matches both native `:focus-visible` (plain inputs; browsers treat any
19
19
  * focus in a text field as focus-visible) and react-aria's `data-focused`
20
- * (inputs inside RAC TextField). Focus is declared after invalid so a focused
21
- * invalid field shows the focus ring, as in Chakra.
20
+ * (inputs inside RAC TextField).
21
+ *
22
+ * Hover, invalid and focus all set `borderColor`, so their precedence has to be
23
+ * hover < invalid < focus. Declaration order will not buy that: Panda sorts a
24
+ * recipe's state rules itself, ranking selectors against a fixed
25
+ * link/visited/focus/hover/active table, which puts `_hover` *after* focus and
26
+ * after anything the table doesn't mention (`[data-invalid]`). Equal-specificity
27
+ * rules then leave hover winning. So the ladder is spelled with repeated `&`
28
+ * instead — `&&` and `&&&` emit `.input.input` and `.input.input.input`, making
29
+ * precedence specificity rather than order, which nothing downstream can
30
+ * resort. Variants still override freely; they land in a later cascade layer.
22
31
  *
23
32
  * Registered in the base preset (base-preset.ts), which also has the
24
33
  * `staticCss` entry that keeps the runtime-prop size variants generated.
@@ -42,11 +51,11 @@ export const input = defineRecipe({
42
51
  bg: "inherit",
43
52
  color: "inherit",
44
53
  _hover: { borderColor: "gray.500" },
45
- "&[data-invalid], &:user-invalid": {
54
+ "&&:is([data-invalid], :user-invalid)": {
46
55
  borderColor: "danger.500",
47
56
  boxShadow: "0 0 0 1px token(colors.danger.500)",
48
57
  },
49
- "&:is(:focus-visible, [data-focused])": {
58
+ "&&&:is(:focus-visible, [data-focused])": {
50
59
  zIndex: 1,
51
60
  borderColor: "focusBorder",
52
61
  boxShadow: "0 0 0 1px token(colors.focusBorder)",
@@ -85,9 +85,12 @@ export const select = defineSlotRecipe({
85
85
  // `> &` rather than a descendant selector, so an app's own invalid form
86
86
  // wrapper cannot paint every control inside it red.
87
87
  //
88
- // Declared after hover and before focus so red beats a hover tint and
89
- // the focus ring beats red, as in the input recipe.
90
- "[data-invalid] > &": {
88
+ // Doubled `&` for the same reason as the input recipe: hover, invalid and
89
+ // focus all set `borderColor`, and Panda sorts state rules by its own
90
+ // pseudo-class table rather than declaration order, so hover would win
91
+ // these ties. The repeated `&` makes the hover < invalid < focus ladder a
92
+ // matter of specificity instead.
93
+ "[data-invalid] > &&": {
91
94
  borderColor: "danger.500",
92
95
  boxShadow: "0 0 0 1px token(colors.danger.500)",
93
96
  },
@@ -101,7 +104,7 @@ export const select = defineSlotRecipe({
101
104
  // focus moves to an option (aria-activedescendant) — which strips RAC's
102
105
  // attribute for as long as the list has an active option, real focus
103
106
  // never having left. Select's trigger holds no input, so it can't match.
104
- "&[data-focus-visible], &:has(input:focus)": {
107
+ "&&&[data-focus-visible], &&&:has(input:focus)": {
105
108
  boxShadow: "0 0 0 1px token(colors.focusBorder)",
106
109
  borderColor: "focusBorder",
107
110
  outline: "2px solid transparent",
@@ -44,6 +44,35 @@ import { tooltip } from "./Tooltip.recipe";
44
44
  import { field } from "./TextField.recipe";
45
45
  import { toast } from "./Toast.recipe";
46
46
 
47
+ // The family gray ramp: pure neutrals (r=g=b throughout) as the OSS default
48
+ // of the per-app tinted ramps (see docs/gray-ramp.md). Stops keep Chakra's
49
+ // names, and 50–300 match Chakra's *luminance* exactly — big panel surfaces
50
+ // are made of these, and even a few bits of darkening at the near-white end
51
+ // is visible across a viewport (classroom's teacher page caught an earlier
52
+ // draft that darkened 50 for tidier numbering). Departures from Chakra:
53
+ // - 400 is re-graded darker to 3.05:1 on white, the accessible
54
+ // form-outline stop; Chakra's 400 (2.26:1) sat uselessly between
55
+ // roles and the ramp had nothing in the 3:1–4.5:1 band.
56
+ // - 500 is #767676 (4.54:1) rather than Chakra's 4.0:1, so
57
+ // placeholders and secondary text pass AA.
58
+ // The sub-100 stops are the family's *panel tints*, named by position:
59
+ // 10 (lighter than 50) and 75 (between 50 and 100 — renamed from a
60
+ // historical "25"; see docs/gray-ramp.md).
61
+ const gray = {
62
+ 10: { value: "#fcfcfc" },
63
+ 50: { value: "#f9f9f9" },
64
+ 75: { value: "#f5f5f5" },
65
+ 100: { value: "#f1f1f1" },
66
+ 200: { value: "#e7e7e7" },
67
+ 300: { value: "#d4d4d4" },
68
+ 400: { value: "#949494" }, // 3.05:1 — accessible outline stop
69
+ 500: { value: "#767676" }, // 4.54:1 — text-safe secondary
70
+ 600: { value: "#575757" },
71
+ 700: { value: "#404040" },
72
+ 800: { value: "#262626" },
73
+ 900: { value: "#1a1a1a" },
74
+ };
75
+
47
76
  /**
48
77
  * The base preset: the complete, working micro:bit design system. The base
49
78
  * token scales (base-tokens.ts), the micro:bit house style
@@ -58,9 +87,11 @@ import { toast } from "./Toast.recipe";
58
87
  * A private brand preset (a sibling repo, e.g. CreateAI) is merged AFTER this
59
88
  * one to restyle everything by overriding just these token *values* (never
60
89
  * their names — see the CSS-var contract in the README):
61
- * - colours: the `brand` and `brand2` ramps (OSS defaults: Chakra
62
- * blue / Chakra's unmodified gray). Other ramps a brand tweaks
63
- * (teal/purple/pink/…) already exist in the Chakra scales below.
90
+ * - colours: the `brand` ramp (OSS default: Chakra blue). Other ramps a
91
+ * brand tweaks (teal/purple/pink/…) already exist in the Chakra scales
92
+ * below. (There is deliberately no `brand2`: a second accent is an app
93
+ * decision, not a library slot — ml-trainer, its only user, defines it
94
+ * in its app preset.)
64
95
  * - font: `display` (OSS default: Helvetica; e.g. GT Walsheim privately).
65
96
  * The recipes and semantic tokens here reference those, so a brand swap needs
66
97
  * no recipe changes. With no private preset, these OSS defaults stand.
@@ -91,43 +122,11 @@ export const basePreset = definePreset({
91
122
  tokens: {
92
123
  colors: {
93
124
  ...colors,
94
- // The family gray ramp: pure neutrals (r=g=b throughout) as the OSS
95
- // default of the per-app tinted ramps (see docs/gray-ramp.md).
96
- // Stops keep Chakra's names, and 50–300 match Chakra's *luminance*
97
- // exactly big panel surfaces are made of these, and even a few
98
- // bits of darkening at the near-white end is visible across a
99
- // viewport (classroom's teacher page caught an earlier draft that
100
- // darkened 50 for tidier numbering). Departures from Chakra:
101
- // - 400 is re-graded darker to 3.05:1 on white, the accessible
102
- // form-outline stop; Chakra's 400 (2.26:1) sat uselessly between
103
- // roles and the ramp had nothing in the 3:1–4.5:1 band.
104
- // - 500 is #767676 (4.54:1) rather than Chakra's 4.0:1, so
105
- // placeholders and secondary text pass AA.
106
- // Quirk, kept deliberately: 10/25 are the family's *panel tints*,
107
- // which by luminance sit between 50 and 100 — the sub-50 names are
108
- // historical. Do not "fix" the ordering; the rendered surfaces are
109
- // what matter. (Decided: rename by position — 25 -> 75 — as its own
110
- // step before the role-tokens work; see docs/gray-ramp.md.)
111
- gray: {
112
- 10: { value: "#fcfcfc" },
113
- 25: { value: "#f5f5f5" },
114
- 50: { value: "#f9f9f9" },
115
- 100: { value: "#f1f1f1" },
116
- 200: { value: "#e7e7e7" },
117
- 300: { value: "#d4d4d4" },
118
- 400: { value: "#949494" }, // 3.05:1 — accessible outline stop
119
- 500: { value: "#767676" }, // 4.54:1 — text-safe secondary
120
- 600: { value: "#575757" },
121
- 700: { value: "#404040" },
122
- 800: { value: "#262626" },
123
- 900: { value: "#1a1a1a" },
124
- },
125
- // OSS default brand ramps (see the brand contract above). `brand`
126
- // aliases Chakra blue; `brand2` Chakra's *unmodified* gray (not the
127
- // `gray` above, whose 10/25 additions are lighter — getting this
128
- // wrong once made card text near-invisible).
125
+ gray,
126
+ // OSS default brand ramp (see the brand contract above): Chakra
127
+ // blue. (Chakra's slate gray is gone from base-tokens.ts — the
128
+ // family gray above is the one grey system.)
129
129
  brand: colors.blue,
130
- brand2: colors.gray,
131
130
  },
132
131
  spacing,
133
132
  sizes,
@@ -221,15 +220,24 @@ export const basePreset = definePreset({
221
220
  secondaryActiveBorder: { value: "{colors.brand.700}" },
222
221
  secondaryActiveBg: { value: "{colors.brand.50}" },
223
222
  },
224
- // Toast status colours: the Chakra-era toast Alert restyle (teal for
225
- // every status except error) shared across the app family.
226
- toastInfoBg: { value: "{colors.teal.800}" },
227
- toastSuccessBg: { value: "{colors.teal.800}" },
228
- toastWarningBg: { value: "{colors.teal.800}" },
223
+ // Toast status colours. Unconventional, and worth a design review:
224
+ // the family's Chakra-era restyle painted every status except error
225
+ // the same teal, so success/warning/info are indistinguishable by
226
+ // colour. Kept as-is for now; each brand re-points these values
227
+ // (blimp teals privately). The OSS literal is Chakra's teal.700
228
+ // the ramp value nearest the brands' teals (white text 7.3:1); the
229
+ // teal ramp itself is gone from base-tokens.ts (this was its only
230
+ // consumer).
231
+ toastInfoBg: { value: "#285E61" },
232
+ toastSuccessBg: { value: "#285E61" },
233
+ toastWarningBg: { value: "#285E61" },
229
234
  toastErrorBg: { value: "{colors.danger.600}" },
230
- // The native app's status-bar area colour, shared by the ActionBar
231
- // and the full-size dialog's safe-area gradient.
232
- statusBarBg: { value: "{colors.brand2.500}" },
235
+ // The native app's status-bar area colour, shared by the apps'
236
+ // ActionBars and the full-size dialog's safe-area gradient. The
237
+ // default is a deliberately bland grey — it only ever renders on a
238
+ // notched device, and every native app re-points it (classroom to
239
+ // black, ml-trainer to its brand2).
240
+ statusBarBg: { value: "{colors.gray.500}" },
233
241
  },
234
242
  },
235
243
  recipes: {
@@ -8,8 +8,10 @@
8
8
  */
9
9
  // The base token scales, in Panda token format. Began as a mechanical
10
10
  // snapshot of Chakra UI v2's @chakra-ui/theme defaults; hand-maintained.
11
- // Deliberately absent: `fonts` (foundation-owned, defined in base-preset.ts)
12
- // and `transition.property` (no Panda token category; inlined at use sites).
11
+ // Deliberately absent: `fonts` (foundation-owned, defined in base-preset.ts),
12
+ // `transition.property` (no Panda token category; inlined at use sites), and
13
+ // the colour ramps nothing in the family uses — `yellow`, `cyan`, `purple`
14
+ // and `teal` (the toast tokens that referenced it hold literals now).
13
15
 
14
16
  export const colors = {
15
17
  transparent: {
@@ -88,38 +90,9 @@ export const colors = {
88
90
  value: "rgba(0, 0, 0, 0.92)",
89
91
  },
90
92
  },
91
- gray: {
92
- "50": {
93
- value: "#F7FAFC",
94
- },
95
- "100": {
96
- value: "#EDF2F7",
97
- },
98
- "200": {
99
- value: "#E2E8F0",
100
- },
101
- "300": {
102
- value: "#CBD5E0",
103
- },
104
- "400": {
105
- value: "#A0AEC0",
106
- },
107
- "500": {
108
- value: "#718096",
109
- },
110
- "600": {
111
- value: "#4A5568",
112
- },
113
- "700": {
114
- value: "#2D3748",
115
- },
116
- "800": {
117
- value: "#1A202C",
118
- },
119
- "900": {
120
- value: "#171923",
121
- },
122
- },
93
+ // Chakra's blue-slate `gray` is deliberately not here: the family gray is
94
+ // the neutral ramp in base-preset.ts, and keeping the slate around invited
95
+ // mixing the two. See docs/gray-ramp.md.
123
96
  red: {
124
97
  "50": {
125
98
  value: "#FFF5F5",
@@ -184,38 +157,6 @@ export const colors = {
184
157
  value: "#652B19",
185
158
  },
186
159
  },
187
- yellow: {
188
- "50": {
189
- value: "#FFFFF0",
190
- },
191
- "100": {
192
- value: "#FEFCBF",
193
- },
194
- "200": {
195
- value: "#FAF089",
196
- },
197
- "300": {
198
- value: "#F6E05E",
199
- },
200
- "400": {
201
- value: "#ECC94B",
202
- },
203
- "500": {
204
- value: "#D69E2E",
205
- },
206
- "600": {
207
- value: "#B7791F",
208
- },
209
- "700": {
210
- value: "#975A16",
211
- },
212
- "800": {
213
- value: "#744210",
214
- },
215
- "900": {
216
- value: "#5F370E",
217
- },
218
- },
219
160
  green: {
220
161
  "50": {
221
162
  value: "#F0FFF4",
@@ -248,38 +189,6 @@ export const colors = {
248
189
  value: "#1C4532",
249
190
  },
250
191
  },
251
- teal: {
252
- "50": {
253
- value: "#E6FFFA",
254
- },
255
- "100": {
256
- value: "#B2F5EA",
257
- },
258
- "200": {
259
- value: "#81E6D9",
260
- },
261
- "300": {
262
- value: "#4FD1C5",
263
- },
264
- "400": {
265
- value: "#38B2AC",
266
- },
267
- "500": {
268
- value: "#319795",
269
- },
270
- "600": {
271
- value: "#2C7A7B",
272
- },
273
- "700": {
274
- value: "#285E61",
275
- },
276
- "800": {
277
- value: "#234E52",
278
- },
279
- "900": {
280
- value: "#1D4044",
281
- },
282
- },
283
192
  blue: {
284
193
  "50": {
285
194
  value: "#ebf8ff",
@@ -312,70 +221,6 @@ export const colors = {
312
221
  value: "#1A365D",
313
222
  },
314
223
  },
315
- cyan: {
316
- "50": {
317
- value: "#EDFDFD",
318
- },
319
- "100": {
320
- value: "#C4F1F9",
321
- },
322
- "200": {
323
- value: "#9DECF9",
324
- },
325
- "300": {
326
- value: "#76E4F7",
327
- },
328
- "400": {
329
- value: "#0BC5EA",
330
- },
331
- "500": {
332
- value: "#00B5D8",
333
- },
334
- "600": {
335
- value: "#00A3C4",
336
- },
337
- "700": {
338
- value: "#0987A0",
339
- },
340
- "800": {
341
- value: "#086F83",
342
- },
343
- "900": {
344
- value: "#065666",
345
- },
346
- },
347
- purple: {
348
- "50": {
349
- value: "#FAF5FF",
350
- },
351
- "100": {
352
- value: "#E9D8FD",
353
- },
354
- "200": {
355
- value: "#D6BCFA",
356
- },
357
- "300": {
358
- value: "#B794F4",
359
- },
360
- "400": {
361
- value: "#9F7AEA",
362
- },
363
- "500": {
364
- value: "#805AD5",
365
- },
366
- "600": {
367
- value: "#6B46C1",
368
- },
369
- "700": {
370
- value: "#553C9A",
371
- },
372
- "800": {
373
- value: "#44337A",
374
- },
375
- "900": {
376
- value: "#322659",
377
- },
378
- },
379
224
  pink: {
380
225
  "50": {
381
226
  value: "#FFF5F7",