@microbit/ui 0.1.0-alpha.28 → 0.1.0-alpha.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microbit/ui",
3
- "version": "0.1.0-alpha.28",
3
+ "version": "0.1.0-alpha.29",
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",
@@ -20,11 +20,9 @@ const transitionCommon =
20
20
  *
21
21
  * A config recipe (not a component cva): styles land in the `recipes` layer so
22
22
  * call sites can override with plain style props, and presets extend the
23
- * variants. This file holds the brand-independent variant set the core
24
- * variants plus the family-wide `language`/`toolbar` variants; a
25
- * consuming app's preset extends it with app vocabulary (e.g. ml-trainer's
26
- * `led`/`record*`/`secondary-disabled`). Brand divergence within a variant is
27
- * token-driven (see the `button.*` and `languageText` semantic tokens).
23
+ * variants. Colour reaches a variant either through the `button.*` semantic
24
+ * tokens (the app's button idiom) or through `tone`, never through a per-app
25
+ * fork of a shape.
28
26
  *
29
27
  * Registered in the base preset (base-preset.ts).
30
28
  */
@@ -131,29 +129,40 @@ export const button = defineRecipe({
131
129
  },
132
130
  _active: { bg: "button.primaryActiveBg" },
133
131
  },
134
- // The *destructive* outline (text darker than 500 for contrast).
135
- // Two other outline shapes are currently restated per call site in
136
- // classroom a neutral outline (1px gray.200, inherited text,
137
- // gray.50/gray.100 hover/press) and an on-colour outline (white 2px +
138
- // white text over a coloured bar, whiteAlpha hover/press) — worth
139
- // considering as variants if a second consumer wants them.
140
- warning: {
132
+ // `solid`/`outline` are coloured by `tone`, and reference only the
133
+ // stops it guarantees (50/500/600/700). They are not `primary`/
134
+ // `secondary` in another colour: those follow the app's button idiom,
135
+ // which is black-on-white in half the family and so can't be a palette.
136
+ solid: {
137
+ color: "white",
138
+ bg: "colorPalette.500",
139
+ _hover: {
140
+ bg: "colorPalette.600",
141
+ _disabled: { bg: "colorPalette.500" },
142
+ },
143
+ _active: { bg: "colorPalette.700" },
144
+ },
145
+ // Border at 500 (a boundary needs only 3:1) so a `solid`/`outline`
146
+ // toggle pair shows the same edge either way; the label needs 600.
147
+ //
148
+ // Classroom restates two other outlines per call site — a neutral 1px
149
+ // grey one and a white-on-colour one. Promote either if a second
150
+ // consumer appears.
151
+ outline: {
141
152
  borderWidth: "2px",
142
- borderColor: "danger.600",
143
- color: "danger.600",
153
+ borderColor: "colorPalette.500",
154
+ color: "colorPalette.600",
144
155
  bg: "transparent",
145
- _hover: { borderColor: "danger.700", color: "danger.700" },
146
- _active: { bg: "danger.50" },
156
+ _hover: { borderColor: "colorPalette.600", color: "colorPalette.700" },
157
+ _active: { bg: "colorPalette.50" },
147
158
  },
148
- // The destructive solid (confirm buttons). Same values as ml-trainer's
149
- // `record` today, but a separate variant so recording UI and
150
- // destructive actions can diverge - hence danger tokens here, red.*
151
- // literals there.
152
- warningSolid: {
153
- color: "white",
154
- bg: "danger.500",
155
- _hover: { bg: "danger.600", _disabled: { bg: "danger.500" } },
156
- _active: { bg: "danger.700" },
159
+ // Not a palette shape: a light fill under dark text is a different
160
+ // button from `solid`, and 350 exists in no other ramp.
161
+ neutral: {
162
+ color: "gray.800",
163
+ bg: "gray.100",
164
+ _hover: { bg: "gray.300", _disabled: { bg: "gray.100" } },
165
+ _active: { bg: "gray.350" },
157
166
  },
158
167
  // Family-wide variant (every censused app has toolbar-class buttons).
159
168
  // No ring override: the bar decides, and a dark one must spread
@@ -165,9 +174,27 @@ export const button = defineRecipe({
165
174
  _active: { bg: "whiteAlpha.800" },
166
175
  },
167
176
  },
177
+ /**
178
+ * The palette behind `solid`/`outline`. An allowlist rather than Panda's
179
+ * open `colorPalette` prop, because a palette missing a stop a shape
180
+ * reads renders it as nothing at all, with no error anywhere
181
+ * (docs/hints.md): this is where a palette is vetted. A tone should
182
+ * alias a whole ramp, so it has nowhere to fall through. Apps may add
183
+ * their own.
184
+ */
185
+ tone: {
186
+ brand: { colorPalette: "brand" },
187
+ // The destructive role, which a brand preset can re-point.
188
+ danger: { colorPalette: "danger" },
189
+ // Conventional red that isn't destructive (record buttons), so
190
+ // deliberately not following a brand's error colour.
191
+ red: { colorPalette: "red" },
192
+ },
168
193
  },
169
194
  defaultVariants: {
170
195
  variant: "secondary",
171
196
  size: "md",
197
+ // Never absent: a shape with no palette renders nothing.
198
+ tone: "brand",
172
199
  },
173
200
  });
package/src/Button.tsx CHANGED
@@ -71,6 +71,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
71
71
  {
72
72
  variant,
73
73
  size,
74
+ tone,
74
75
  css: cssProp,
75
76
  className,
76
77
  leftIcon,
@@ -96,7 +97,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
96
97
  <RACButton
97
98
  ref={ref}
98
99
  className={cx(
99
- button({ variant, size }),
100
+ button({ variant, size, tone }),
100
101
  cssProp ? css(cssProp) : undefined,
101
102
  className,
102
103
  )}
@@ -52,8 +52,7 @@ export const field = defineSlotRecipe({
52
52
  },
53
53
  requiredIndicator: {
54
54
  marginStart: "1",
55
- // 600: red-as-text needs 4.5:1; danger.500 is border-grade (~4.1:1).
56
- color: "danger.600",
55
+ color: "danger.500",
57
56
  },
58
57
  helperText: {
59
58
  // RAC's Text renders a span, and RadioGroup/CheckboxGroup roots are not
@@ -71,8 +70,7 @@ export const field = defineSlotRecipe({
71
70
  mt: "2",
72
71
  fontSize: "sm",
73
72
  lineHeight: "normal",
74
- // 600: red-as-text needs 4.5:1 (the invalid border stays 500).
75
- color: "danger.600",
73
+ color: "danger.500",
76
74
  },
77
75
  },
78
76
  variants: {
@@ -48,6 +48,7 @@ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
48
48
  {
49
49
  variant,
50
50
  size,
51
+ tone,
51
52
  css: cssProp,
52
53
  className,
53
54
  leftIcon,
@@ -61,7 +62,7 @@ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
61
62
  <RACLink
62
63
  ref={ref}
63
64
  className={cx(
64
- button({ variant, size }),
65
+ button({ variant, size, tone }),
65
66
  css(linkReset, cssProp),
66
67
  className,
67
68
  )}
@@ -83,6 +83,25 @@ const gray = {
83
83
  900: { value: "#1a1a1a" },
84
84
  };
85
85
 
86
+ // The family red, on the gray ramp's ladder — it is the conventional colour
87
+ // for errors and recording, not a brand colour, so it can be graded rather
88
+ // than negotiated. 400 was already exactly gray's 3:1 and stays verbatim,
89
+ // anchoring the hue and saturation the darker stops hold while their
90
+ // lightness solves for gray's ratio. The washes have no contract and keep
91
+ // their values.
92
+ const red = {
93
+ 50: { value: "#FFF5F5" },
94
+ 100: { value: "#FED7D7" },
95
+ 200: { value: "#FEB2B2" },
96
+ 300: { value: "#FC8181" },
97
+ 400: { value: "#F56565" }, // 3.03:1
98
+ 500: { value: "#e22b2b" }, // 4.55:1 — the white-text fill stop
99
+ 600: { value: "#ac1818" },
100
+ 700: { value: "#811212" },
101
+ 800: { value: "#4f0b0b" },
102
+ 900: { value: "#380808" },
103
+ };
104
+
86
105
  /**
87
106
  * The base preset: the complete, working micro:bit design system. The base
88
107
  * token scales (base-tokens.ts), the micro:bit house style
@@ -141,6 +160,7 @@ export const basePreset = definePreset({
141
160
  colors: {
142
161
  ...colors,
143
162
  gray,
163
+ red,
144
164
  // OSS default brand ramps (see the brand contract above). `brand`
145
165
  // aliases the blue ramp; `brand2` is a frozen legacy alias of the
146
166
  // slate gray in base-tokens, deliberately decoupled from the neutral
@@ -208,16 +228,20 @@ export const basePreset = definePreset({
208
228
  focusRing: {
209
229
  value: { base: "{colors.gray.900}", _onDark: "{colors.white}" },
210
230
  },
211
- // Error/destructive ramp. Destructive button variants,
212
- // field error states and the error toast; the record* button
213
- // variants deliberately stay on red.* (recording vocabulary, not
214
- // danger).
231
+ // Error/destructive ramp: field error states, the error toast, and
232
+ // the `danger` button tone. Aliased whole, not just at the stops in
233
+ // use, so a tone has nowhere to fall through (Button.recipe.ts).
215
234
  danger: {
216
235
  50: { value: "{colors.red.50}" },
217
236
  100: { value: "{colors.red.100}" },
237
+ 200: { value: "{colors.red.200}" },
238
+ 300: { value: "{colors.red.300}" },
239
+ 400: { value: "{colors.red.400}" },
218
240
  500: { value: "{colors.red.500}" },
219
241
  600: { value: "{colors.red.600}" },
220
242
  700: { value: "{colors.red.700}" },
243
+ 800: { value: "{colors.red.800}" },
244
+ 900: { value: "{colors.red.900}" },
221
245
  },
222
246
  // The language-dialog cards' text colour (@microbit/ui-patterns'
223
247
  // LanguageDialog) follows the primary interactive brand: every
@@ -259,7 +283,9 @@ export const basePreset = definePreset({
259
283
  toastInfoBg: { value: "{colors.teal.800}" },
260
284
  toastSuccessBg: { value: "{colors.teal.800}" },
261
285
  toastWarningBg: { value: "{colors.teal.800}" },
262
- toastErrorBg: { value: "{colors.danger.600}" },
286
+ // 500, the text-safe stop, rather than following the teal toasts'
287
+ // 800: white on it is 4.55:1 and an error toast should read as red.
288
+ toastErrorBg: { value: "{colors.danger.500}" },
263
289
  // The native app's status-bar area colour, shared by the ActionBar
264
290
  // and the full-size dialog's safe-area gradient.
265
291
  statusBarBg: { value: "{colors.brand2.500}" },
@@ -384,7 +410,13 @@ export const basePreset = definePreset({
384
410
  // too — otherwise the class lands on the element with no rule behind it
385
411
  // and the button silently falls back to the base size.
386
412
  avatar: ["*"],
387
- button: [{ size: ["*"], responsive: true }, { variant: ["*"] }],
413
+ // `tone` generates as its own rule, not crossed with `variant`: it
414
+ // only assigns the palette custom properties a shape reads.
415
+ button: [
416
+ { size: ["*"], responsive: true },
417
+ { variant: ["*"] },
418
+ { tone: ["*"] },
419
+ ],
388
420
  checkbox: ["*"],
389
421
  heading: ["*"],
390
422
  card: ["*"],