@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 +13 -0
- package/package.json +3 -1
- package/reset.css +23 -0
- package/src/Button.tsx +2 -17
- package/src/LinkButton.tsx +80 -0
- package/src/base-preset.ts +16 -11
- package/src/button-icon.ts +23 -0
- package/src/index.ts +1 -0
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.
|
|
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,
|
|
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
|
+
);
|
package/src/base-preset.ts
CHANGED
|
@@ -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
|
|
150
|
-
//
|
|
151
|
-
// CreateAI
|
|
152
|
-
//
|
|
153
|
-
// overrides only
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
+
});
|