@microbit/ui 0.1.0-alpha.5 → 0.1.0-alpha.7

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
@@ -44,22 +44,35 @@ an app must do:
44
44
  4. **Generate and load the CSS** with Panda's PostCSS plugin. Keep Vite's
45
45
  default transformer — do **not** set `css.transformer: "lightningcss"`,
46
46
  which disables PostCSS. Add a `postcss.config.cjs`:
47
+
47
48
  ```js
48
49
  module.exports = { plugins: { "@pandacss/dev/postcss": {} } };
49
50
  ```
51
+
50
52
  Run `panda codegen` as a `prepare`/`predev` step so the `styled-system/*`
51
53
  helpers exist before `tsc`; the plugin generates the CSS during the bundle.
52
54
  Import **one** entry stylesheet — first, before app styles — that declares
53
55
  the cascade-layer order; the plugin injects the generated CSS into it (the
54
56
  declaration must list all of Panda's layers, hence ≥5 names):
57
+
55
58
  ```css
56
59
  /* e.g. src/layers.css, imported once at the app root */
57
60
  @layer reset, vendor, base, tokens, recipes, utilities;
61
+
62
+ @import "@microbit/ui/reset.css" layer(reset);
58
63
  ```
64
+
65
+ The `reset.css` import is **required**: it carries the Chakra-parity
66
+ `* { border-color; word-wrap }` defaults, which must sit in the bottom
67
+ layer (the legacy-Safari cascade-layer flattening specificity-boosts
68
+ higher layers above CSS it can't see — runtime-injected styles, other
69
+ files; playbook gotcha #28). Without it, elements that set a border
70
+ width but no colour render `currentColor` borders.
59
71
  The `vendor` layer is for third-party stylesheets: import any vendor CSS
60
72
  with `@import "..." layer(vendor)` so it beats the preflight reset but
61
73
  loses to app styling. See `.storybook/{layers.css,preview.tsx,main.ts}` +
62
74
  `postcss.config.cjs` for the worked example.
75
+
63
76
  5. **react-intl**: an `IntlProvider` above any shared-ui usage. English
64
77
  works with no setup (components carry inline `defaultMessage`); for
65
78
  other locales compile this package's `lang/ui.<locale>.json` into the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microbit/ui",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
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",
@@ -10,12 +10,14 @@
10
10
  "./base-tokens": "./src/base-tokens.ts",
11
11
  "./messages": "./src/messages.ts",
12
12
  "./postcss-legacy-safari": "./postcss-legacy-safari.cjs",
13
+ "./reset.css": "./reset.css",
13
14
  "./lang/*": "./lang/*"
14
15
  },
15
16
  "files": [
16
17
  "src",
17
18
  "lang",
18
19
  "postcss-legacy-safari.cjs",
20
+ "reset.css",
19
21
  "README.md",
20
22
  "LICENSE.md"
21
23
  ],
package/reset.css ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Chakra-reset parity that must live in the `reset` cascade layer: import
7
+ * from the app's layers.css with
8
+ *
9
+ * @import "@microbit/ui/reset.css" layer(reset);
10
+ *
11
+ * Deliberately not in the preset's globalCss: globalCss emits into the
12
+ * `base` layer, and the legacy-Safari cascade-layer flattening
13
+ * specificity-boosts base+ rules above CSS it can't see (runtime-injected
14
+ * styles such as CodeMirror themes, and other per-file-processed app CSS).
15
+ * A reset must stay at the bottom of the cascade in production exactly as
16
+ * it is in dev — see playbook gotcha #28.
17
+ */
18
+ *,
19
+ *::before,
20
+ *::after {
21
+ border-color: var(--colors-gray-200);
22
+ word-wrap: break-word;
23
+ }
package/src/Button.tsx CHANGED
@@ -8,25 +8,10 @@ import {
8
8
  Button as RACButton,
9
9
  ButtonProps as RACButtonProps,
10
10
  } from "react-aria-components";
11
- import { css, cva, cx } from "styled-system/css";
11
+ import { css, cx } from "styled-system/css";
12
12
  import { button, ButtonVariantProps } from "styled-system/recipes";
13
13
  import { SystemStyleObject } from "styled-system/types";
14
-
15
- // Chakra's ButtonIcon: keeps the glyph centred and spaced from the label
16
- // (iconSpacing 0.5rem).
17
- const buttonIcon = cva({
18
- base: {
19
- display: "inline-flex",
20
- alignSelf: "center",
21
- flexShrink: 0,
22
- },
23
- variants: {
24
- side: {
25
- left: { marginEnd: "2" },
26
- right: { marginStart: "2" },
27
- },
28
- },
29
- });
14
+ import { buttonIcon } from "./button-icon";
30
15
 
31
16
  export interface ButtonProps
32
17
  extends Omit<RACButtonProps, "className" | "children">,
@@ -0,0 +1,80 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { forwardRef, ReactNode } from "react";
7
+ import {
8
+ Link as RACLink,
9
+ LinkProps as RACLinkProps,
10
+ } from "react-aria-components";
11
+ import { css, cx } from "styled-system/css";
12
+ import { button, ButtonVariantProps } from "styled-system/recipes";
13
+ import { SystemStyleObject } from "styled-system/types";
14
+ import { buttonIcon } from "./button-icon";
15
+
16
+ export interface LinkButtonProps
17
+ extends Omit<RACLinkProps, "className" | "children">,
18
+ ButtonVariantProps {
19
+ /** Per-instance style overrides, merged after the recipe. */
20
+ css?: SystemStyleObject;
21
+ className?: string;
22
+ /** Icon rendered before the label, matching Chakra's `leftIcon`. */
23
+ leftIcon?: ReactNode;
24
+ /** Icon rendered after the label, matching Chakra's `rightIcon`. */
25
+ rightIcon?: ReactNode;
26
+ children?: ReactNode;
27
+ }
28
+
29
+ // Anchors pick up underline styling that buttons never have.
30
+ const linkReset = css.raw({
31
+ textDecoration: "none",
32
+ _hover: { textDecoration: "none" },
33
+ });
34
+
35
+ /**
36
+ * LinkButton — a navigation link that looks like a Button (Chakra's
37
+ * `Button as="a"`). react-aria-components <Link> renders a real anchor
38
+ * (`href`, `target`, new-tab/middle-click semantics preserved) with the same
39
+ * interaction data attributes as Button, so the `button` recipe's
40
+ * hover/press/focus/disabled states apply unchanged.
41
+ *
42
+ * Use for navigation that should read as a call to action (e.g. an external
43
+ * help page presented as a dialog's primary action); use Button for
44
+ * in-page actions.
45
+ */
46
+ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
47
+ function LinkButton(
48
+ {
49
+ variant,
50
+ size,
51
+ css: cssProp,
52
+ className,
53
+ leftIcon,
54
+ rightIcon,
55
+ children,
56
+ ...rest
57
+ },
58
+ ref,
59
+ ) {
60
+ return (
61
+ <RACLink
62
+ ref={ref}
63
+ className={cx(
64
+ button({ variant, size }),
65
+ css(linkReset, cssProp),
66
+ className,
67
+ )}
68
+ {...rest}
69
+ >
70
+ {leftIcon ? (
71
+ <span className={buttonIcon({ side: "left" })}>{leftIcon}</span>
72
+ ) : null}
73
+ {children}
74
+ {rightIcon ? (
75
+ <span className={buttonIcon({ side: "right" })}>{rightIcon}</span>
76
+ ) : null}
77
+ </RACLink>
78
+ );
79
+ },
80
+ );
@@ -146,13 +146,16 @@ export const basePreset = definePreset({
146
146
  600: { value: "{colors.red.600}" },
147
147
  700: { value: "{colors.red.700}" },
148
148
  },
149
- // The `language` button variant's text colour is the one place the
150
- // brands diverge structurally (OSS uses the grey brand2 ramp, the
151
- // CreateAI brand its blue brand ramp with no hover change). Driven by
152
- // these semantic tokens so the recipe stays shared and a brand preset
153
- // overrides only the values.
154
- languageText: { value: "{colors.brand2.500}" },
155
- languageTextHover: { value: "{colors.brand2.600}" },
149
+ // The `language` button variant's text colour follows the primary
150
+ // interactive brand: every consumer resolves it to its `brand` ramp
151
+ // (CreateAI privately to brand.600 with no hover change,
152
+ // python-editor to brand.500/600 the default). Semantic tokens so
153
+ // the recipe stays shared and a brand preset overrides only values.
154
+ // (Was brand2.* — the grey ml-trainer OSS Chakra look — but both
155
+ // apps' final values sit on their primary brand, so the default
156
+ // follows; OSS language buttons are brand blue.)
157
+ languageText: { value: "{colors.brand.500}" },
158
+ languageTextHover: { value: "{colors.brand.600}" },
156
159
  // Toast status colours: the Chakra-era toast Alert restyle (teal for
157
160
  // every status except error) shared across the app family.
158
161
  toastInfoBg: { value: "{colors.teal.800}" },
@@ -208,10 +211,12 @@ export const basePreset = definePreset({
208
211
  "*::placeholder": {
209
212
  color: "gray.500",
210
213
  },
211
- "*, *::before, *::after": {
212
- borderColor: "gray.200",
213
- wordWrap: "break-word",
214
- },
214
+ // The `* { border-color; word-wrap }` Chakra-reset parity lives in
215
+ // ../reset.css, imported into the `reset` layer by consumers'
216
+ // layers.css — NOT here: globalCss emits into the `base` layer, which
217
+ // the legacy-Safari cascade-layer flattening specificity-boosts above
218
+ // runtime-injected CSS (CodeMirror themes) and other app CSS files.
219
+ // Resets must stay at the bottom (playbook gotcha #28).
215
220
  // Panda's preflight, unlike Chakra's reset, doesn't set the pointer
216
221
  // cursor on buttons. Recipes' disabled states (cursor: not-allowed)
217
222
  // override this from the higher recipes layer.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { cva } from "styled-system/css";
7
+
8
+ // Chakra's ButtonIcon: keeps the glyph centred and spaced from the label
9
+ // (iconSpacing 0.5rem). Shared by Button and LinkButton; deliberately not
10
+ // exported from the package index.
11
+ export const buttonIcon = cva({
12
+ base: {
13
+ display: "inline-flex",
14
+ alignSelf: "center",
15
+ flexShrink: 0,
16
+ },
17
+ variants: {
18
+ side: {
19
+ left: { marginEnd: "2" },
20
+ right: { marginStart: "2" },
21
+ },
22
+ },
23
+ });
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  export * from "./system";
12
12
  export * from "./Button";
13
+ export * from "./LinkButton";
13
14
  export * from "./ButtonGroup";
14
15
  export * from "./Card";
15
16
  export * from "./Checkbox";