@microbit/ui 0.0.0-gray.ramp.92 → 0.0.0-gray.ramp.v2.106

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.
@@ -24,6 +24,7 @@ import {
24
24
  // Config recipes are colocated with the shared-ui components they style; this
25
25
  // preset registers them so Panda merges them at codegen time.
26
26
  import { avatar } from "./Avatar.recipe";
27
+ import { breadcrumb } from "./Breadcrumb.recipe";
27
28
  import { button } from "./Button.recipe";
28
29
  import { card } from "./Card.recipe";
29
30
  import { checkbox } from "./Checkbox.recipe";
@@ -41,30 +42,36 @@ import { switchRecipe } from "./Switch.recipe";
41
42
  import { dialog } from "./Modal.recipe";
42
43
  import { text } from "./Text.recipe";
43
44
  import { tooltip } from "./Tooltip.recipe";
44
- import { field } from "./TextField.recipe";
45
+ import { field } from "./Field.recipe";
45
46
  import { toast } from "./Toast.recipe";
46
47
 
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:
48
+ // The family gray ramp: pure neutrals (r=g=b throughout) as the library
49
+ // default (docs/gray-ramp.md). Stops keep Chakra's names — including the
50
+ // historical `25`, which sits darker than 50 exactly as it did against
51
+ // Chakra's 50 (renaming it was tried in the first spike and orphaned raw
52
+ // var() references and a stale paired-preset build; not worth it. See the
53
+ // doc's no-renames rule). 50–300 match Chakra's *luminance* exactly — big
54
+ // panel surfaces are made of these, and even a few bits of darkening at the
55
+ // near-white end shows across a viewport. Apps may override the light-stop
56
+ // VALUES in their presets (byte-pinning today's surfaces, or a coherent
57
+ // per-brand tint later); the dark half is family-shared so the re-grades
58
+ // below hold everywhere:
53
59
  // - 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).
60
+ // form-outline stop (WCAG 1.4.11); Chakra's 400 (2.26:1) sat uselessly
61
+ // between roles.
62
+ // - 500 is #767676 (4.54:1) rather than Chakra's 4.0:1, so placeholders
63
+ // and secondary text pass AA.
61
64
  const gray = {
62
65
  10: { value: "#fcfcfc" },
66
+ 25: { value: "#f5f5f5" },
63
67
  50: { value: "#f9f9f9" },
64
- 75: { value: "#f5f5f5" },
65
68
  100: { value: "#f1f1f1" },
66
69
  200: { value: "#e7e7e7" },
67
70
  300: { value: "#d4d4d4" },
71
+ // The press-weight grey: fills the deliberate 300→400 gap for transient
72
+ // state fills (pressed pills, heavy hovers) that want more than 300 but
73
+ // must not read as an outline. ~2.1:1 — never for text or boundaries.
74
+ 350: { value: "#b4b4b4" },
68
75
  400: { value: "#949494" }, // 3.05:1 — accessible outline stop
69
76
  500: { value: "#767676" }, // 4.54:1 — text-safe secondary
70
77
  600: { value: "#575757" },
@@ -87,11 +94,9 @@ const gray = {
87
94
  * A private brand preset (a sibling repo, e.g. CreateAI) is merged AFTER this
88
95
  * one to restyle everything by overriding just these token *values* (never
89
96
  * their names — see the CSS-var contract in the README):
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.)
97
+ * - colours: the `brand` and `brand2` ramps (OSS defaults: Chakra
98
+ * blue / Chakra's unmodified gray). Other ramps a brand tweaks
99
+ * (teal/purple/pink/…) already exist in the Chakra scales below.
95
100
  * - font: `display` (OSS default: Helvetica; e.g. GT Walsheim privately).
96
101
  * The recipes and semantic tokens here reference those, so a brand swap needs
97
102
  * no recipe changes. With no private preset, these OSS defaults stand.
@@ -123,10 +128,15 @@ export const basePreset = definePreset({
123
128
  colors: {
124
129
  ...colors,
125
130
  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.)
131
+ // OSS default brand ramps (see the brand contract above). `brand`
132
+ // aliases Chakra blue; `brand2` stays Chakra's slate gray — a frozen
133
+ // legacy alias, now deliberately decoupled from the neutral `gray`
134
+ // above (it exists so ml-trainer's OSS look and `statusBarBg`'s
135
+ // default don't move; a second accent is really an app decision and
136
+ // removing the slot entirely is recorded in docs/gray-ramp.md as a
137
+ // follow-up needing an ml-trainer lockstep).
129
138
  brand: colors.blue,
139
+ brand2: colors.gray,
130
140
  },
131
141
  spacing,
132
142
  sizes,
@@ -199,6 +209,12 @@ export const basePreset = definePreset({
199
209
  // follows; OSS language buttons are brand blue.)
200
210
  languageText: { value: "{colors.brand.500}" },
201
211
  languageTextHover: { value: "{colors.brand.600}" },
212
+ // The `label`/`subtitle` heading variants' colour (page-title chrome).
213
+ // classroom and data-microbit-org carried byte-identical variants with
214
+ // a hardcoded #cd0365 — the brand deep pink, which is data's
215
+ // `pink.500`; both override this to it. The OSS default follows the
216
+ // languageText precedent: the primary interactive brand.
217
+ headingAccent: { value: "{colors.brand.500}" },
202
218
  // The `primary`/`secondary` button variants' colours. Two brand
203
219
  // idioms exist in the family: brand-coloured buttons (ml-trainer,
204
220
  // python-editor — the defaults below) and a black-on-white system
@@ -220,24 +236,15 @@ export const basePreset = definePreset({
220
236
  secondaryActiveBorder: { value: "{colors.brand.700}" },
221
237
  secondaryActiveBg: { value: "{colors.brand.50}" },
222
238
  },
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" },
239
+ // Toast status colours: the Chakra-era toast Alert restyle (teal for
240
+ // every status except error) shared across the app family.
241
+ toastInfoBg: { value: "{colors.teal.800}" },
242
+ toastSuccessBg: { value: "{colors.teal.800}" },
243
+ toastWarningBg: { value: "{colors.teal.800}" },
234
244
  toastErrorBg: { value: "{colors.danger.600}" },
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}" },
245
+ // The native app's status-bar area colour, shared by the ActionBar
246
+ // and the full-size dialog's safe-area gradient.
247
+ statusBarBg: { value: "{colors.brand2.500}" },
241
248
  },
242
249
  },
243
250
  recipes: {
@@ -249,6 +256,7 @@ export const basePreset = definePreset({
249
256
  },
250
257
  slotRecipes: {
251
258
  avatar,
259
+ breadcrumb,
252
260
  card,
253
261
  checkbox,
254
262
  dialog,
@@ -329,9 +337,11 @@ export const basePreset = definePreset({
329
337
  // as a runtime prop, so generate the breakpoint-prefixed variants too.
330
338
  dialog: [{ size: ["*"], responsive: true }, { centered: ["*"] }],
331
339
  drawer: ["*"],
340
+ field: ["*"],
332
341
  gridList: ["*"],
333
342
  listBox: ["*"],
334
343
  input: ["*"],
344
+ numberField: ["*"],
335
345
  radio: ["*"],
336
346
  select: ["*"],
337
347
  switchRecipe: ["*"],
@@ -8,10 +8,8 @@
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
- // `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).
11
+ // Deliberately absent: `fonts` (foundation-owned, defined in base-preset.ts)
12
+ // and `transition.property` (no Panda token category; inlined at use sites).
15
13
 
16
14
  export const colors = {
17
15
  transparent: {
@@ -90,9 +88,38 @@ export const colors = {
90
88
  value: "rgba(0, 0, 0, 0.92)",
91
89
  },
92
90
  },
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.
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
+ },
96
123
  red: {
97
124
  "50": {
98
125
  value: "#FFF5F5",
@@ -157,6 +184,38 @@ export const colors = {
157
184
  value: "#652B19",
158
185
  },
159
186
  },
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
+ },
160
219
  green: {
161
220
  "50": {
162
221
  value: "#F0FFF4",
@@ -189,6 +248,38 @@ export const colors = {
189
248
  value: "#1C4532",
190
249
  },
191
250
  },
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
+ },
192
283
  blue: {
193
284
  "50": {
194
285
  value: "#ebf8ff",
@@ -221,6 +312,70 @@ export const colors = {
221
312
  value: "#1A365D",
222
313
  },
223
314
  },
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
+ },
224
379
  pink: {
225
380
  "50": {
226
381
  value: "#FFF5F7",
package/src/index.ts CHANGED
@@ -10,20 +10,25 @@
10
10
  */
11
11
  export * from "./system";
12
12
  export * from "./Avatar";
13
+ export * from "./Breadcrumb";
13
14
  export * from "./Button";
14
15
  export * from "./LinkButton";
15
16
  export * from "./ButtonGroup";
16
17
  export * from "./Card";
17
18
  export * from "./Checkbox";
19
+ export * from "./CheckboxGroup";
20
+ export * from "./Field";
18
21
  export * from "./IconButton";
19
22
  export * from "./Image";
20
23
  export * from "./Input";
21
24
  export * from "./InputGroup";
22
25
  export * from "./LinkBox";
23
26
  export * from "./NativeSelect";
27
+ export * from "./NativeSelectField";
24
28
  export * from "./NumberField";
25
29
  export * from "./ProgressBar";
26
30
  export * from "./Radio";
31
+ export * from "./RadioGroup";
27
32
  export * from "./Skeleton";
28
33
  export * from "./Slide";
29
34
  export * from "./Slider";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ /**
8
+ * Sanitizes one of our locale ids for react-aria's I18nProvider.
9
+ *
10
+ * No mapping is needed to reach react-aria's own strings: its fallback chain
11
+ * runs exact tag, then the bare language, then any variant of that language,
12
+ * then en-US — so `fr` finds fr-FR, `ja` finds ja-JP, and the locales it has no
13
+ * strings for at all (ca, cy, ga-IE, and our `lol` pseudo-locale) land on
14
+ * English.
15
+ *
16
+ * What does need handling is a malformed tag: I18nProvider's explicit-locale
17
+ * path feeds it straight to `new Intl.Locale` with no guard of its own (unlike
18
+ * its browser-locale path), so a bad tag throws during render and takes out the
19
+ * tree. Only reachable through SharedUIProvider's `locale` prop in practice —
20
+ * react-intl rejects a malformed locale before we could read it from context.
21
+ */
22
+ export const racLocale = (locale: string | undefined): string | undefined => {
23
+ if (locale === undefined) {
24
+ // Leave react-aria on its browser-locale default.
25
+ return undefined;
26
+ }
27
+ try {
28
+ Intl.DateTimeFormat.supportedLocalesOf([locale]);
29
+ } catch {
30
+ return "en-GB";
31
+ }
32
+ return locale;
33
+ };
@@ -1,54 +0,0 @@
1
- /**
2
- * (c) 2026, Micro:bit Educational Foundation and contributors
3
- *
4
- * SPDX-License-Identifier: MIT
5
- */
6
- import { defineSlotRecipe } from "@pandacss/dev";
7
-
8
- /**
9
- * Field slot recipe — Chakra's FormControl parts (FormLabel/FormHelperText/
10
- * FormErrorMessage, light mode). Consumed by the shared-ui TextField, which
11
- * maps the slots onto react-aria-components' TextField/Label/Text/FieldError;
12
- * the input itself is styled by the `input` recipe (Input.recipe.ts).
13
- *
14
- * Registered in the base preset (base-preset.ts). No variants, so it
15
- * needs no `staticCss` entry.
16
- */
17
- export const field = defineSlotRecipe({
18
- className: "field",
19
- slots: ["root", "label", "requiredIndicator", "helperText", "errorMessage"],
20
- base: {
21
- root: {
22
- display: "flex",
23
- flexDirection: "column",
24
- alignItems: "stretch",
25
- width: "100%",
26
- },
27
- label: {
28
- display: "block",
29
- fontSize: "md",
30
- fontWeight: "medium",
31
- marginEnd: "3",
32
- mb: "2",
33
- "&[data-disabled]": { opacity: 0.4 },
34
- },
35
- requiredIndicator: {
36
- marginStart: "1",
37
- color: "danger.500",
38
- },
39
- helperText: {
40
- mt: "2",
41
- fontSize: "sm",
42
- lineHeight: "normal",
43
- color: "gray.600",
44
- },
45
- errorMessage: {
46
- display: "flex",
47
- alignItems: "center",
48
- mt: "2",
49
- fontSize: "sm",
50
- lineHeight: "normal",
51
- color: "danger.500",
52
- },
53
- },
54
- });