@microbit/ui 0.1.0-alpha.18 → 0.1.0-alpha.20

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/src/Toast.tsx CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
- import { ReactNode, useMemo } from "react";
6
+ import { ReactNode, useMemo, useSyncExternalStore } from "react";
7
7
  import {
8
8
  Button as RACButton,
9
9
  UNSTABLE_Toast as RACToast,
@@ -55,6 +55,10 @@ const statusIcon: Record<ToastStatus, IconType> = {
55
55
  error: RiErrorWarningFill,
56
56
  };
57
57
 
58
+ // Module scope so useSyncExternalStore doesn't resubscribe every render.
59
+ const subscribeToQueue = (fn: () => void) => toastQueue.subscribe(fn);
60
+ const getVisibleCount = () => toastQueue.visibleToasts.length;
61
+
58
62
  /**
59
63
  * Mount once near the app root, inside the IntlProvider (the close button's
60
64
  * label and the status announcements are react-intl messages).
@@ -63,8 +67,19 @@ const statusIcon: Record<ToastStatus, IconType> = {
63
67
  export const ToastProvider = () => {
64
68
  const intl = useIntl();
65
69
  const slots = toastRecipe();
70
+ // The region's landmark label counts the visible toasts, so it has to
71
+ // track the queue.
72
+ const count = useSyncExternalStore(
73
+ subscribeToQueue,
74
+ getVisibleCount,
75
+ getVisibleCount,
76
+ );
66
77
  return (
67
- <RACToastRegion queue={toastQueue} className={slots.region}>
78
+ <RACToastRegion
79
+ queue={toastQueue}
80
+ aria-label={intl.formatMessage(uiMessage("ui.toast-region"), { count })}
81
+ className={slots.region}
82
+ >
68
83
  {({ toast }) => {
69
84
  const status = toast.content.status ?? "info";
70
85
  return (
@@ -45,6 +45,43 @@ import { tooltip } from "./Tooltip.recipe";
45
45
  import { field } from "./Field.recipe";
46
46
  import { toast } from "./Toast.recipe";
47
47
 
48
+ // The family gray ramp: pure neutrals as the library default. The two
49
+ // halves have different jobs and different override rules:
50
+ //
51
+ // - 10–300 are surface stops (panels, page backdrops, hairlines, subtle
52
+ // fills). Presets may freely override these values — pinning an app's
53
+ // existing surfaces or applying a brand tint — because a few bits of
54
+ // lightness here reads across a whole viewport.
55
+ // - 350 is the decorative/state fill stop (~2.1:1): avatar discs, skeleton
56
+ // pulse, pressed fills. Never text or boundaries.
57
+ // - 400–900 are ink stops (outlines, placeholders, text) with a contrast
58
+ // contract on white: 400 ≥ 3:1, the accessible form-outline stop
59
+ // (WCAG 1.4.11); 500 ≥ 4.5:1, text-safe secondary (placeholders, muted
60
+ // icons). Presets may re-tint these only luminance-matched — the
61
+ // contrast figures are the contract, hue is free.
62
+ //
63
+ // Override values, never names: raw var(--colors-gray-*) references and
64
+ // paired private presets depend on the names, so a rename is a breaking
65
+ // change to both and needs every app and paired preset moved in lockstep
66
+ // (as was done when Chakra's misnamed darker-than-50 stop `25` became
67
+ // `75`). And never override partially in a way that lets a stop fall
68
+ // through to a different grey system.
69
+ const gray = {
70
+ 10: { value: "#fcfcfc" },
71
+ 50: { value: "#f9f9f9" },
72
+ 75: { value: "#f5f5f5" },
73
+ 100: { value: "#f1f1f1" },
74
+ 200: { value: "#e7e7e7" },
75
+ 300: { value: "#d4d4d4" },
76
+ 350: { value: "#b4b4b4" }, // ~2.1:1 — decorative fills only
77
+ 400: { value: "#949494" }, // 3.05:1 — accessible outline stop
78
+ 500: { value: "#767676" }, // 4.54:1 — text-safe secondary
79
+ 600: { value: "#575757" },
80
+ 700: { value: "#404040" },
81
+ 800: { value: "#262626" },
82
+ 900: { value: "#1a1a1a" },
83
+ };
84
+
48
85
  /**
49
86
  * The base preset: the complete, working micro:bit design system. The base
50
87
  * token scales (base-tokens.ts), the micro:bit house style
@@ -92,16 +129,12 @@ export const basePreset = definePreset({
92
129
  tokens: {
93
130
  colors: {
94
131
  ...colors,
95
- gray: {
96
- ...colors.gray,
97
- // Very light grays the family's designs use below Chakra's 50.
98
- 10: { value: "#fcfcfc" },
99
- 25: { value: "#f5f5f5" },
100
- },
132
+ gray,
101
133
  // OSS default brand ramps (see the brand contract above). `brand`
102
- // aliases Chakra blue; `brand2` Chakra's *unmodified* gray (not the
103
- // `gray` above, whose 10/25 additions are lighter getting this
104
- // wrong once made card text near-invisible).
134
+ // aliases Chakra blue; `brand2` stays Chakra's slate gray a frozen
135
+ // legacy alias, deliberately decoupled from the neutral `gray` above
136
+ // so ml-trainer's OSS look and `statusBarBg`'s default don't move.
137
+ // Removing the slot is a follow-up needing an ml-trainer lockstep.
105
138
  brand: colors.blue,
106
139
  brand2: colors.gray,
107
140
  },
@@ -146,15 +179,13 @@ export const basePreset = definePreset({
146
179
  },
147
180
  semanticTokens: {
148
181
  colors: {
149
- // Checked/focus states of form controls (Chakra's default blue
150
- // colorScheme). Checkbox/Switch checked backgrounds, Input focus
151
- // border, and the Slider/ProgressBar fills (the last two are
152
- // near-dead defaults - current call sites override them).
153
- controlCheckedBg: { value: "{colors.blue.500}" },
154
- controlCheckedHoverBg: { value: "{colors.blue.600}" },
155
- focusBorder: { value: "{colors.blue.500}" },
156
- sliderFilledTrack: { value: "{colors.blue.500}" },
157
- progressFilledTrack: { value: "{colors.blue.500}" },
182
+ // Checked/focus states of form controls: Checkbox/Switch/Radio
183
+ // checked backgrounds and the Input/Select focus border. Semantic
184
+ // so a brand can diverge them from its ramp — e.g. a light
185
+ // brand.500 needing a darker 3:1 focus border.
186
+ controlCheckedBg: { value: "{colors.brand.500}" },
187
+ controlCheckedHoverBg: { value: "{colors.brand.600}" },
188
+ focusBorder: { value: "{colors.brand.500}" },
158
189
  // Error/destructive ramp (Chakra red). Destructive button variants,
159
190
  // field error states and the error toast; the record* button
160
191
  // variants deliberately stay on red.* (recording vocabulary, not