@microbit/ui 0.0.0-gray.ramp.90

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.
Files changed (102) hide show
  1. package/LICENSE.md +53 -0
  2. package/README.md +222 -0
  3. package/lang/ui.ca.json +22 -0
  4. package/lang/ui.cy.json +22 -0
  5. package/lang/ui.de.json +22 -0
  6. package/lang/ui.en-us.json +22 -0
  7. package/lang/ui.en.json +22 -0
  8. package/lang/ui.es-es.json +22 -0
  9. package/lang/ui.fr.json +22 -0
  10. package/lang/ui.ga-ie.json +22 -0
  11. package/lang/ui.it.json +22 -0
  12. package/lang/ui.ja.json +22 -0
  13. package/lang/ui.ko.json +22 -0
  14. package/lang/ui.lol.json +22 -0
  15. package/lang/ui.nl.json +22 -0
  16. package/lang/ui.pl.json +22 -0
  17. package/lang/ui.pt-br.json +22 -0
  18. package/lang/ui.zh-cn.json +22 -0
  19. package/lang/ui.zh-tw.json +22 -0
  20. package/package.json +66 -0
  21. package/postcss-legacy-safari.cjs +96 -0
  22. package/reset.css +35 -0
  23. package/src/Avatar.recipe.ts +168 -0
  24. package/src/Avatar.tsx +276 -0
  25. package/src/Button.recipe.ts +192 -0
  26. package/src/Button.tsx +69 -0
  27. package/src/ButtonGroup.tsx +70 -0
  28. package/src/Card.recipe.ts +49 -0
  29. package/src/Card.tsx +56 -0
  30. package/src/Checkbox.recipe.ts +111 -0
  31. package/src/Checkbox.tsx +106 -0
  32. package/src/CloseButton.tsx +87 -0
  33. package/src/CloseIcon.tsx +40 -0
  34. package/src/Code.tsx +20 -0
  35. package/src/Collapse.tsx +180 -0
  36. package/src/ComboBox.tsx +192 -0
  37. package/src/Divider.tsx +85 -0
  38. package/src/Drawer.recipe.ts +98 -0
  39. package/src/Drawer.tsx +138 -0
  40. package/src/Fade.tsx +48 -0
  41. package/src/GridList.recipe.ts +58 -0
  42. package/src/GridList.tsx +81 -0
  43. package/src/Heading.recipe.ts +52 -0
  44. package/src/Heading.tsx +14 -0
  45. package/src/Icon.tsx +75 -0
  46. package/src/IconButton.tsx +44 -0
  47. package/src/Image.tsx +11 -0
  48. package/src/Input.recipe.ts +75 -0
  49. package/src/Input.tsx +44 -0
  50. package/src/InputGroup.tsx +62 -0
  51. package/src/Kbd.tsx +26 -0
  52. package/src/Link.tsx +23 -0
  53. package/src/LinkBox.tsx +88 -0
  54. package/src/LinkButton.tsx +80 -0
  55. package/src/List.tsx +31 -0
  56. package/src/ListBox.recipe.ts +43 -0
  57. package/src/ListBox.tsx +88 -0
  58. package/src/Menu.recipe.ts +124 -0
  59. package/src/Menu.tsx +291 -0
  60. package/src/Modal.recipe.ts +163 -0
  61. package/src/Modal.tsx +377 -0
  62. package/src/NativeSelect.tsx +94 -0
  63. package/src/NumberField.recipe.ts +67 -0
  64. package/src/NumberField.tsx +86 -0
  65. package/src/PopoverArrow.tsx +67 -0
  66. package/src/ProgressBar.tsx +61 -0
  67. package/src/Radio.recipe.ts +112 -0
  68. package/src/Radio.tsx +89 -0
  69. package/src/Select.recipe.ts +210 -0
  70. package/src/Select.tsx +153 -0
  71. package/src/SharedUIProvider.tsx +55 -0
  72. package/src/Skeleton.tsx +146 -0
  73. package/src/Slide.tsx +54 -0
  74. package/src/Slider.recipe.ts +102 -0
  75. package/src/Slider.tsx +163 -0
  76. package/src/Spinner.tsx +74 -0
  77. package/src/Svg.tsx +23 -0
  78. package/src/Switch.recipe.ts +107 -0
  79. package/src/Switch.tsx +62 -0
  80. package/src/Text.recipe.ts +29 -0
  81. package/src/Text.tsx +16 -0
  82. package/src/TextField.recipe.ts +54 -0
  83. package/src/TextField.tsx +91 -0
  84. package/src/Toast.recipe.ts +98 -0
  85. package/src/Toast.tsx +173 -0
  86. package/src/Tooltip.recipe.ts +37 -0
  87. package/src/Tooltip.tsx +76 -0
  88. package/src/UnmountCallback.tsx +18 -0
  89. package/src/VisuallyHidden.tsx +14 -0
  90. package/src/base-preset.ts +371 -0
  91. package/src/base-tokens.ts +1017 -0
  92. package/src/button-icon.ts +23 -0
  93. package/src/data-attrs.ts +16 -0
  94. package/src/dense-preset.ts +108 -0
  95. package/src/hooks/useBreakpointValue.ts +63 -0
  96. package/src/hooks/useClipboard.ts +64 -0
  97. package/src/hooks/useDisclosure.ts +32 -0
  98. package/src/hooks/useMediaQuery.ts +28 -0
  99. package/src/hooks/usePrevious.ts +18 -0
  100. package/src/index.ts +63 -0
  101. package/src/messages.ts +17 -0
  102. package/src/system.ts +45 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { definePreset } from "@pandacss/dev";
7
+ import {
8
+ blurs,
9
+ borders,
10
+ breakpoints,
11
+ colors,
12
+ durations,
13
+ easings,
14
+ fontSizes,
15
+ fontWeights,
16
+ letterSpacings,
17
+ lineHeights,
18
+ radii,
19
+ shadows,
20
+ sizes,
21
+ spacing,
22
+ zIndex,
23
+ } from "./base-tokens";
24
+ // Config recipes are colocated with the shared-ui components they style; this
25
+ // preset registers them so Panda merges them at codegen time.
26
+ import { avatar } from "./Avatar.recipe";
27
+ import { button } from "./Button.recipe";
28
+ import { card } from "./Card.recipe";
29
+ import { checkbox } from "./Checkbox.recipe";
30
+ import { radio } from "./Radio.recipe";
31
+ import { drawer } from "./Drawer.recipe";
32
+ import { gridList } from "./GridList.recipe";
33
+ import { heading } from "./Heading.recipe";
34
+ import { input } from "./Input.recipe";
35
+ import { listBox } from "./ListBox.recipe";
36
+ import { numberField } from "./NumberField.recipe";
37
+ import { menu } from "./Menu.recipe";
38
+ import { select } from "./Select.recipe";
39
+ import { slider } from "./Slider.recipe";
40
+ import { switchRecipe } from "./Switch.recipe";
41
+ import { dialog } from "./Modal.recipe";
42
+ import { text } from "./Text.recipe";
43
+ import { tooltip } from "./Tooltip.recipe";
44
+ import { field } from "./TextField.recipe";
45
+ import { toast } from "./Toast.recipe";
46
+
47
+ /**
48
+ * The base preset: the complete, working micro:bit design system. The base
49
+ * token scales (base-tokens.ts), the micro:bit house style
50
+ * (pill `radii.button`, `outline*` focus shadows, Helvetica fonts, the
51
+ * `language`/`toolbar` button variants in Button.recipe.ts, the
52
+ * `languageText`/`toast*Bg`/`statusBarBg` semantic tokens), the shared-ui
53
+ * component recipes, the react-aria condition widening, the Chakra-reset
54
+ * parity `globalCss`, and the `staticCss` that keeps runtime-prop recipe
55
+ * variants generated. Used alone it renders in the OSS default look.
56
+ *
57
+ * ── Brand contract ──────────────────────────────────────────────────────
58
+ * A private brand preset (a sibling repo, e.g. CreateAI) is merged AFTER this
59
+ * one to restyle everything by overriding just these token *values* (never
60
+ * 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.
64
+ * - font: `display` (OSS default: Helvetica; e.g. GT Walsheim privately).
65
+ * The recipes and semantic tokens here reference those, so a brand swap needs
66
+ * no recipe changes. With no private preset, these OSS defaults stand.
67
+ */
68
+ export const basePreset = definePreset({
69
+ name: "microbit-ui-base",
70
+ theme: {
71
+ breakpoints,
72
+ keyframes: {
73
+ // Spinner's revolution.
74
+ spin: {
75
+ "0%": { transform: "rotate(0deg)" },
76
+ "100%": { transform: "rotate(360deg)" },
77
+ },
78
+ // Skeleton's pulse, over the pair of custom properties the component
79
+ // sets, so a retinted skeleton animates between its own colours.
80
+ skeletonFade: {
81
+ from: {
82
+ borderColor: "var(--skeleton-start-color)",
83
+ background: "var(--skeleton-start-color)",
84
+ },
85
+ to: {
86
+ borderColor: "var(--skeleton-end-color)",
87
+ background: "var(--skeleton-end-color)",
88
+ },
89
+ },
90
+ },
91
+ tokens: {
92
+ colors: {
93
+ ...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).
129
+ brand: colors.blue,
130
+ brand2: colors.gray,
131
+ },
132
+ spacing,
133
+ sizes,
134
+ fontSizes,
135
+ fontWeights,
136
+ lineHeights,
137
+ letterSpacings,
138
+ zIndex,
139
+ blurs,
140
+ borders,
141
+ durations,
142
+ easings,
143
+ radii: {
144
+ ...radii,
145
+ // Pill button radius (4/4 apps in the family census).
146
+ button: { value: "2rem" },
147
+ },
148
+ shadows: {
149
+ ...shadows,
150
+ // Chakra's outline shadow widened to 4px, plus dark/light-surface
151
+ // companions. Consumed via the `focusShadow` utility.
152
+ outline: { value: "0 0 0 4px rgba(66, 153, 225, 0.6)" },
153
+ outlineDark: { value: "0 0 0 4px rgba(0, 0, 0, 0.5)" },
154
+ outlineLight: { value: "0 0 0 4px rgba(255, 255, 255, 0.8)" },
155
+ },
156
+ fonts: {
157
+ // Helvetica heading/body (4/4 apps); a brand preset leaves these and
158
+ // overrides only `display` (the marketing font — see the brand
159
+ // contract above). `mono` is Chakra's default stack.
160
+ heading: { value: "Helvetica, Arial, sans-serif" },
161
+ body: { value: "Helvetica, Arial, sans-serif" },
162
+ mono: {
163
+ value:
164
+ 'SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace',
165
+ },
166
+ // OSS default marketing font (the `marketing` heading variant). No OSS
167
+ // brand display face, so it falls back to the body stack.
168
+ display: { value: "Helvetica, Arial, sans-serif" },
169
+ },
170
+ },
171
+ semanticTokens: {
172
+ colors: {
173
+ // Checked/focus states of form controls (Chakra's default blue
174
+ // colorScheme). Checkbox/Switch checked backgrounds, Input focus
175
+ // border, and the Slider/ProgressBar fills (the last two are
176
+ // near-dead defaults - current call sites override them).
177
+ controlCheckedBg: { value: "{colors.blue.500}" },
178
+ controlCheckedHoverBg: { value: "{colors.blue.600}" },
179
+ focusBorder: { value: "{colors.blue.500}" },
180
+ sliderFilledTrack: { value: "{colors.blue.500}" },
181
+ progressFilledTrack: { value: "{colors.blue.500}" },
182
+ // Error/destructive ramp (Chakra red). Destructive button variants,
183
+ // field error states and the error toast; the record* button
184
+ // variants deliberately stay on red.* (recording vocabulary, not
185
+ // danger).
186
+ danger: {
187
+ 50: { value: "{colors.red.50}" },
188
+ 100: { value: "{colors.red.100}" },
189
+ 500: { value: "{colors.red.500}" },
190
+ 600: { value: "{colors.red.600}" },
191
+ 700: { value: "{colors.red.700}" },
192
+ },
193
+ // The `language` button variant's text colour follows the primary
194
+ // interactive brand: every consumer resolves it to its `brand` ramp
195
+ // (CreateAI privately to brand.600 with no hover change,
196
+ // python-editor to brand.500/600 — the default). Semantic tokens so
197
+ // the recipe stays shared and a brand preset overrides only values.
198
+ // (Was brand2.* — the grey ml-trainer OSS Chakra look — but both
199
+ // apps' final values sit on their primary brand, so the default
200
+ // follows; OSS language buttons are brand blue.)
201
+ languageText: { value: "{colors.brand.500}" },
202
+ languageTextHover: { value: "{colors.brand.600}" },
203
+ // The `primary`/`secondary` button variants' colours. Two brand
204
+ // idioms exist in the family: brand-coloured buttons (ml-trainer,
205
+ // python-editor — the defaults below) and a black-on-white system
206
+ // (classroom, data-microbit-org: black solid, black outline, no
207
+ // border colour change on hover but a blackAlpha wash instead).
208
+ // Tokens rather than per-app recipe overrides so both idioms share
209
+ // one recipe — a `variant` fork would be duplicated by every app on
210
+ // the far side of it. `primary`'s text colour stays a literal
211
+ // `white`: every app in the family puts white on a dark solid.
212
+ // `ghost` needs no tokens (black + blackAlpha in all four apps).
213
+ button: {
214
+ primaryBg: { value: "{colors.brand.500}" },
215
+ primaryHoverBg: { value: "{colors.brand.600}" },
216
+ primaryActiveBg: { value: "{colors.brand.700}" },
217
+ secondaryText: { value: "{colors.brand.700}" },
218
+ secondaryBorder: { value: "{colors.brand.500}" },
219
+ secondaryHoverBorder: { value: "{colors.brand.600}" },
220
+ secondaryHoverBg: { value: "transparent" },
221
+ secondaryActiveBorder: { value: "{colors.brand.700}" },
222
+ secondaryActiveBg: { value: "{colors.brand.50}" },
223
+ },
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}" },
229
+ 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}" },
233
+ },
234
+ },
235
+ recipes: {
236
+ button,
237
+ heading,
238
+ input,
239
+ text,
240
+ tooltip,
241
+ },
242
+ slotRecipes: {
243
+ avatar,
244
+ card,
245
+ checkbox,
246
+ dialog,
247
+ drawer,
248
+ field,
249
+ gridList,
250
+ listBox,
251
+ menu,
252
+ numberField,
253
+ radio,
254
+ select,
255
+ slider,
256
+ switchRecipe,
257
+ toast,
258
+ },
259
+ },
260
+ // What ChakraProvider used to inject and Panda's preflight doesn't cover:
261
+ // the theme's styles.global (body text/background defaults, global
262
+ // border/placeholder colours) plus the parts of Chakra's CSS reset that
263
+ // Panda's has no equivalent for — kerning/text-rendering (their absence
264
+ // shifts glyphs page-wide), word-wrap and touch-action. Token references
265
+ // resolve against the merged preset stack, so the values track any brand
266
+ // overrides exactly as they did under Chakra's runtime theme.
267
+ globalCss: {
268
+ html: {
269
+ textRendering: "optimizeLegibility",
270
+ touchAction: "manipulation",
271
+ },
272
+ body: {
273
+ position: "relative",
274
+ minHeight: "100%",
275
+ fontFeatureSettings: '"kern"',
276
+ fontFamily: "body",
277
+ color: "gray.800",
278
+ bg: "white",
279
+ transitionProperty: "background-color",
280
+ transitionDuration: "normal",
281
+ lineHeight: "base",
282
+ },
283
+ "*::placeholder": {
284
+ color: "gray.500",
285
+ },
286
+ // The `* { border-color; word-wrap }` Chakra-reset parity lives in
287
+ // ../reset.css, imported into the `reset` layer by consumers'
288
+ // layers.css — NOT here: globalCss emits into the `base` layer, which
289
+ // the legacy-Safari cascade-layer flattening specificity-boosts above
290
+ // runtime-injected CSS (CodeMirror themes) and other app CSS files.
291
+ // Resets must stay at the bottom (playbook gotcha #28).
292
+ // Panda's preflight, unlike Chakra's reset, doesn't set the pointer
293
+ // cursor on buttons. Recipes' disabled states (cursor: not-allowed)
294
+ // override this from the higher recipes layer.
295
+ "button, [role='button']": {
296
+ cursor: "pointer",
297
+ },
298
+ // Panda's preflight balance-wraps headings; Chakra didn't, and balanced
299
+ // multi-line headings break at different points (mobile/translations).
300
+ "h1, h2, h3, h4, h5, h6": {
301
+ textWrap: "wrap",
302
+ },
303
+ },
304
+ // shared-ui components forward `variant`/`size`/etc. as runtime props to
305
+ // the recipe functions, so Panda's static analysis can't see which variants
306
+ // are used and would emit no CSS for them. Generate every recipe variant.
307
+ // Lives in the preset (not the consumer's panda.config.ts) so no consumer
308
+ // can silently lose runtime-prop variants.
309
+ staticCss: {
310
+ recipes: {
311
+ // Size is passed responsively at call sites ported from Chakra's
312
+ // `size={["md", "lg"]}`, so generate the breakpoint-prefixed variants
313
+ // too — otherwise the class lands on the element with no rule behind it
314
+ // and the button silently falls back to the base size.
315
+ avatar: ["*"],
316
+ button: [{ size: ["*"], responsive: true }, { variant: ["*"] }],
317
+ checkbox: ["*"],
318
+ heading: ["*"],
319
+ card: ["*"],
320
+ // Dialog size is chosen with responsive objects ({ base, md }) passed
321
+ // as a runtime prop, so generate the breakpoint-prefixed variants too.
322
+ dialog: [{ size: ["*"], responsive: true }, { centered: ["*"] }],
323
+ drawer: ["*"],
324
+ gridList: ["*"],
325
+ listBox: ["*"],
326
+ input: ["*"],
327
+ radio: ["*"],
328
+ select: ["*"],
329
+ switchRecipe: ["*"],
330
+ text: ["*"],
331
+ tooltip: ["*"],
332
+ // Toast status is chosen at runtime from the toast content.
333
+ toast: ["*"],
334
+ },
335
+ },
336
+ utilities: {
337
+ extend: {
338
+ // The app-wide focus indicator, usually inside `_focusVisible`. Values
339
+ // are the outline* shadow token names. The transparent outline is for
340
+ // forced-colors modes, which strip box-shadows but recolour outlines to
341
+ // a visible system colour. (Named to avoid preset-base's outline-based
342
+ // `focusRing` utility, whose values would break this transform.)
343
+ focusShadow: {
344
+ className: "focus-shadow",
345
+ values: ["outline", "outlineDark", "outlineLight"],
346
+ transform: (value: string, { token }) => ({
347
+ outline: "2px solid transparent",
348
+ outlineOffset: "2px",
349
+ boxShadow: token(`shadows.${value}`),
350
+ }),
351
+ },
352
+ },
353
+ },
354
+ // Widen the interaction conditions so the Chakra-shaped recipe/style objects
355
+ // (`_hover`/`_active`/`_focusVisible`/`_disabled`) also respond to
356
+ // react-aria-components' data attributes, not just native pseudo-classes.
357
+ conditions: {
358
+ extend: {
359
+ hover: "&:is(:hover, [data-hovered])",
360
+ active: "&:is(:active, [data-pressed])",
361
+ focusVisible: "&:is(:focus-visible, [data-focus-visible])",
362
+ disabled:
363
+ "&:is(:disabled, [disabled], [data-disabled], [aria-disabled=true])",
364
+ // High-contrast/forced-palette modes (e.g. Windows High Contrast), which
365
+ // strip author backgrounds and box-shadows.
366
+ forcedColors: "@media (forced-colors: active)",
367
+ },
368
+ },
369
+ });
370
+
371
+ export default basePreset;