@microbit/ui 0.1.0-alpha.3 → 0.1.0-alpha.4

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
@@ -5,8 +5,9 @@ the Micro:bit Educational Foundation apps' original Chakra UI v2 themes.
5
5
 
6
6
  The package **ships as source**: components import `styled-system/*`, which
7
7
  each consumer generates with its own Panda preset stack. There is no build
8
- step and no CSS shipped — the consumer's codegen produces exactly the styles
9
- its tree uses.
8
+ step and no CSS shipped — the consumer's Panda run produces exactly the styles
9
+ its tree uses (`panda codegen` for the `styled-system/*` helpers, the Panda
10
+ PostCSS plugin for the CSS).
10
11
 
11
12
  ## App-side installation
12
13
 
@@ -16,17 +17,15 @@ an app must do:
16
17
 
17
18
  1. **Panda preset stack** (`panda.config.ts`): `@pandacss/preset-base`, then
18
19
  the **base preset** (`@microbit/ui/base-preset` — the complete micro:bit
19
- design system , then optionally the app's own preset, then optionally a
20
- **private brand preset** (these are used for Foundation colours, licensed
21
- fonts).
20
+ design system), then optionally the app's own preset, then optionally a
21
+ **private brand preset** (Foundation colours, licensed fonts).
22
22
 
23
23
  Later presets override earlier ones token-by-token — the base recipes and
24
24
  semantic tokens reference the brand tokens, which is how a brand swap
25
25
  restyles everything without touching recipes. Set `eject: true` (the stack
26
26
  supplies the full token system). After changing an _external_ preset
27
- dependency, regenerate clean: `rm -rf styled-system styled-system.css &&
28
- npm run panda` — incremental codegen does not detect external preset
29
- changes.
27
+ dependency, regenerate clean: `rm -rf styled-system && npm run panda` —
28
+ incremental codegen does not detect external preset changes.
30
29
 
31
30
  2. **Include this package's source** in `panda.config.ts` so Panda extracts
32
31
  the styles the components use:
@@ -40,10 +39,25 @@ npm run panda` — incremental codegen does not detect external preset
40
39
  importers, this package's source included — a `styled-system` alias in
41
40
  both `tsconfig.json` `paths` and the bundler config (see the
42
41
  `viteFinal` in `.storybook/main.ts`).
43
- 4. **Cascade layers** (`src/layers.css`, imported first): declares the
44
- document-wide layer order including the `vendor` layer for third-party
45
- stylesheets import any vendor CSS with `@import "..." layer(vendor)`
46
- so it beats the preflight but loses to app styling.
42
+ 4. **Generate and load the CSS** with Panda's PostCSS plugin. Keep Vite's
43
+ default transformer do **not** set `css.transformer: "lightningcss"`,
44
+ which disables PostCSS. Add a `postcss.config.cjs`:
45
+ ```js
46
+ module.exports = { plugins: { "@pandacss/dev/postcss": {} } };
47
+ ```
48
+ Run `panda codegen` as a `prepare`/`predev` step so the `styled-system/*`
49
+ helpers exist before `tsc`; the plugin generates the CSS during the bundle.
50
+ Import **one** entry stylesheet — first, before app styles — that declares
51
+ the cascade-layer order; the plugin injects the generated CSS into it (the
52
+ declaration must list all of Panda's layers, hence ≥5 names):
53
+ ```css
54
+ /* e.g. src/layers.css, imported once at the app root */
55
+ @layer reset, vendor, base, tokens, recipes, utilities;
56
+ ```
57
+ The `vendor` layer is for third-party stylesheets: import any vendor CSS
58
+ with `@import "..." layer(vendor)` so it beats the preflight reset but
59
+ loses to app styling. See `.storybook/{layers.css,preview.tsx,main.ts}` +
60
+ `postcss.config.cjs` for the worked example.
47
61
  5. **react-intl**: an `IntlProvider` above any shared-ui usage. English
48
62
  works with no setup (components carry inline `defaultMessage`); for
49
63
  other locales compile this package's `lang/ui.<locale>.json` into the
@@ -58,6 +72,64 @@ node_modules/@microbit/ui/lang/ui.fr.json --ast --out-file ...` (multiple
58
72
  app can dismiss open menus from outside the tree (e.g. the Android
59
73
  hardware back button). Apps without one can omit the provider.
60
74
 
75
+ ## Legacy browser support (Safari < 15) — temporary
76
+
77
+ Panda's output uses two things Safari below 15 mishandles. If an app must
78
+ support that far back (e.g. Safari 14.1 web views), add the app-side wiring
79
+ below. **All of it is meant to be deleted once the app's support floor rises
80
+ past these browsers** — it lives entirely in the consuming app's build config,
81
+ never in shipped component source. This package's own Storybook does _not_ use
82
+ any of it (it targets modern browsers, where these work natively).
83
+
84
+ Two concerns:
85
+
86
+ 1. **`@layer`** — Safari < 15.4 drops `@layer` blocks wholesale, leaving the
87
+ app unstyled. Flatten them with `@csstools/postcss-cascade-layers` (which
88
+ rewrites layers into `:not(#\#)` specificity fallbacks; ~+8% gzipped CSS,
89
+ mostly compressible).
90
+ 2. **Logical shorthands + `var()`** — Safari 14.x silently drops
91
+ `padding-inline: var(--…)` and friends (a literal value, or the -start/-end
92
+ longhands, both work). Panda emits these shorthands for its px/py/mx/my
93
+ utilities, so most token spacing collapses. Expand them to longhands with
94
+ this package's `postcss-legacy-safari` plugin (kept logical, so RTL flips).
95
+
96
+ ```bash
97
+ npm i -D @csstools/postcss-cascade-layers
98
+ ```
99
+
100
+ ```js
101
+ // postcss.config.cjs — the two legacy plugins run AFTER Panda's (step 4), so
102
+ // switch that config to array form:
103
+ const {
104
+ expandLogicalShorthands,
105
+ } = require("@microbit/ui/postcss-legacy-safari");
106
+
107
+ module.exports = {
108
+ plugins: [
109
+ require("@pandacss/dev/postcss")(),
110
+ expandLogicalShorthands(),
111
+ require("@csstools/postcss-cascade-layers"),
112
+ ],
113
+ };
114
+ ```
115
+
116
+ ```ts
117
+ // vite.config.ts — pin the CSS/JS floor. Otherwise the lightningcss minifier
118
+ // inherits build.target and downlevels logical longhands into fragile
119
+ // :lang()-based physical rules. Keep in sync with package.json "browserslist".
120
+ const BUILD_TARGETS = ["safari14.1", "ios14.5", "chrome90", "edge90", "firefox88"];
121
+ // ...
122
+ build: {
123
+ target: BUILD_TARGETS,
124
+ cssTarget: BUILD_TARGETS,
125
+ cssMinify: "lightningcss", // lightningcss as minifier only, not the transformer
126
+ },
127
+ ```
128
+
129
+ To drop it all: raise `BUILD_TARGETS`/`browserslist` past the affected
130
+ browsers, then remove the two PostCSS plugins (and this package's
131
+ `postcss-legacy-safari` export).
132
+
61
133
  ## The CSS-variable contract
62
134
 
63
135
  Panda emits every token as a CSS custom property with its default naming —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microbit/ui",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "micro:bit design-system primitives: react-aria-components + Panda CSS with a Chakra v2 design language. Ships as source; see README for the consumption setup.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,17 +9,19 @@
9
9
  "./base-preset": "./src/base-preset.ts",
10
10
  "./chakra-tokens": "./src/chakra-tokens.ts",
11
11
  "./messages": "./src/messages.ts",
12
+ "./postcss-legacy-safari": "./postcss-legacy-safari.cjs",
12
13
  "./lang/*": "./lang/*"
13
14
  },
14
15
  "files": [
15
16
  "src",
16
17
  "lang",
18
+ "postcss-legacy-safari.cjs",
17
19
  "README.md",
18
20
  "LICENSE.md"
19
21
  ],
20
22
  "sideEffects": false,
21
23
  "scripts": {
22
- "panda": "panda codegen && panda cssgen --outfile styled-system.css",
24
+ "panda": "panda codegen",
23
25
  "typecheck": "npm run panda && tsc --noEmit",
24
26
  "prestorybook": "npm run panda",
25
27
  "storybook": "storybook dev -p 6006 --no-open",
@@ -0,0 +1,96 @@
1
+ /*
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * TEMPORARY compatibility shim for consuming apps that still support Safari
7
+ * below 15. Delete this file (and the app-side wiring — see the README) once
8
+ * every consuming app has raised its support floor past the affected browsers.
9
+ *
10
+ * The bug: Safari 14.x silently drops logical *shorthands* whose value
11
+ * contains var() — `padding-inline: var(--spacing-2)` applies nothing, even
12
+ * though `padding-inline: 10px` (literal) and `padding-inline-start:
13
+ * var(--spacing-2)` (longhand) both work. Panda emits these shorthands for its
14
+ * px/py/mx/my utilities, so the bug removes most token-based spacing.
15
+ *
16
+ * The fix: rewrite the inline/block logical shorthands into their -start/-end
17
+ * longhands. Kept logical (not physical left/right) so RTL still flips.
18
+ *
19
+ * This is one of TWO legacy concerns for the same era of browsers; the other
20
+ * is @layer, which those browsers drop wholesale. Apps also run
21
+ * @csstools/postcss-cascade-layers (de-layering) and pin build.cssTarget — see
22
+ * the "Legacy browser support" section of the README. All of it is expected to
23
+ * be removed together when the floor rises.
24
+ *
25
+ * Exposed as a PostCSS plugin factory:
26
+ * const { expandLogicalShorthands } = require("@microbit/ui/postcss-legacy-safari");
27
+ * module.exports = { plugins: [expandLogicalShorthands(), ...] };
28
+ */
29
+
30
+ // Logical shorthands whose value is `<start> <end>` (one value applies to
31
+ // both). NOT included: border-inline / border-block — those are compound
32
+ // (`width style color`) and duplicate the whole value to each side rather than
33
+ // splitting, so they would need different handling. Add them explicitly if a
34
+ // consuming app ever emits them.
35
+ const LOGICAL_SHORTHANDS = {
36
+ "padding-inline": ["padding-inline-start", "padding-inline-end"],
37
+ "padding-block": ["padding-block-start", "padding-block-end"],
38
+ "margin-inline": ["margin-inline-start", "margin-inline-end"],
39
+ "margin-block": ["margin-block-start", "margin-block-end"],
40
+ "inset-inline": ["inset-inline-start", "inset-inline-end"],
41
+ "inset-block": ["inset-block-start", "inset-block-end"],
42
+ "scroll-margin-inline": [
43
+ "scroll-margin-inline-start",
44
+ "scroll-margin-inline-end",
45
+ ],
46
+ "scroll-margin-block": [
47
+ "scroll-margin-block-start",
48
+ "scroll-margin-block-end",
49
+ ],
50
+ "scroll-padding-inline": [
51
+ "scroll-padding-inline-start",
52
+ "scroll-padding-inline-end",
53
+ ],
54
+ "scroll-padding-block": [
55
+ "scroll-padding-block-start",
56
+ "scroll-padding-block-end",
57
+ ],
58
+ };
59
+
60
+ // Split a value on top-level whitespace, ignoring spaces inside parens so
61
+ // var() fallbacks stay intact. One value applies to both sides; two map to
62
+ // start then end (per the CSS shorthand rules).
63
+ const splitTopLevel = (value) => {
64
+ const parts = [];
65
+ let depth = 0;
66
+ let current = "";
67
+ for (const ch of value) {
68
+ if (ch === "(") depth++;
69
+ else if (ch === ")") depth--;
70
+ if (depth === 0 && /\s/.test(ch)) {
71
+ if (current.trim()) parts.push(current.trim());
72
+ current = "";
73
+ } else {
74
+ current += ch;
75
+ }
76
+ }
77
+ if (current.trim()) parts.push(current.trim());
78
+ return parts;
79
+ };
80
+
81
+ const expandLogicalShorthands = () => ({
82
+ postcssPlugin: "microbit-ui-expand-logical-shorthands",
83
+ Declaration(decl) {
84
+ const longhands = LOGICAL_SHORTHANDS[decl.prop.toLowerCase()];
85
+ if (!longhands) return;
86
+ const parts = splitTopLevel(decl.value);
87
+ if (parts.length === 0) return;
88
+ const [start, end = start] = parts;
89
+ decl.cloneBefore({ prop: longhands[0], value: start });
90
+ decl.cloneBefore({ prop: longhands[1], value: end });
91
+ decl.remove();
92
+ },
93
+ });
94
+ expandLogicalShorthands.postcss = true;
95
+
96
+ module.exports = { expandLogicalShorthands };