@microbit/ui 0.1.0-alpha.24 → 0.1.0-alpha.25
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 +10 -7
- package/lang/ui.ca.json +4 -0
- package/lang/ui.cy.json +4 -0
- package/lang/ui.de.json +4 -0
- package/lang/ui.en-us.json +4 -0
- package/lang/ui.en.json +4 -0
- package/lang/ui.es-es.json +4 -0
- package/lang/ui.fr.json +4 -0
- package/lang/ui.ga-ie.json +4 -0
- package/lang/ui.it.json +4 -0
- package/lang/ui.ja.json +4 -0
- package/lang/ui.ko.json +4 -0
- package/lang/ui.lo.json +4 -0
- package/lang/ui.lol.json +4 -0
- package/lang/ui.nl.json +4 -0
- package/lang/ui.pl.json +4 -0
- package/lang/ui.pt-br.json +4 -0
- package/lang/ui.vi.json +4 -0
- package/lang/ui.zh-cn.json +4 -0
- package/lang/ui.zh-tw.json +4 -0
- package/package.json +2 -7
- package/src/Button.recipe.ts +1 -8
- package/src/ExternalLink.tsx +47 -0
- package/src/SharedUIProvider.tsx +23 -4
- package/src/Toast.tsx +11 -2
- package/src/base-preset.ts +7 -6
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -13,9 +13,8 @@ PostCSS plugin for the CSS).
|
|
|
13
13
|
|
|
14
14
|
## App-side installation
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
an app must do:
|
|
16
|
+
The repo's Storybook harness (`apps/storybook`) is a worked example of the
|
|
17
|
+
setup below, minus the app/brand presets. Everything an app must do:
|
|
19
18
|
|
|
20
19
|
1. **Panda preset stack** (`panda.config.ts`): `@pandacss/preset-base`, then
|
|
21
20
|
the **base preset** (`@microbit/ui/base-preset` — the complete micro:bit
|
|
@@ -42,7 +41,7 @@ an app must do:
|
|
|
42
41
|
3. **Resolve `styled-system/*` onto the generated output** for all
|
|
43
42
|
importers, this package's source included — a `styled-system` alias in
|
|
44
43
|
both `tsconfig.json` `paths` and the bundler config (see the
|
|
45
|
-
`viteFinal` in `.storybook/main.ts`).
|
|
44
|
+
`viteFinal` in `apps/storybook`'s `.storybook/main.ts`).
|
|
46
45
|
4. **Generate and load the CSS** with Panda's PostCSS plugin. Keep Vite's
|
|
47
46
|
default transformer — do **not** set `css.transformer: "lightningcss"`,
|
|
48
47
|
which disables PostCSS. Add a `postcss.config.cjs`:
|
|
@@ -72,8 +71,9 @@ an app must do:
|
|
|
72
71
|
width but no colour render `currentColor` borders.
|
|
73
72
|
The `vendor` layer is for third-party stylesheets: import any vendor CSS
|
|
74
73
|
with `@import "..." layer(vendor)` so it beats the preflight reset but
|
|
75
|
-
loses to app styling. See
|
|
76
|
-
`postcss.config.cjs` for
|
|
74
|
+
loses to app styling. See `apps/storybook`'s
|
|
75
|
+
`.storybook/{layers.css,preview.tsx,main.ts}` + `postcss.config.cjs` for
|
|
76
|
+
the worked example.
|
|
77
77
|
|
|
78
78
|
5. **react-intl**: an `IntlProvider` above any shared-ui usage. English
|
|
79
79
|
works with no setup (components carry inline `defaultMessage`); for
|
|
@@ -87,7 +87,10 @@ node_modules/@microbit/ui/lang/ui.fr.json --ast --out-file ...` (multiple
|
|
|
87
87
|
6. **`SharedUIProvider`** inside the `IntlProvider`, wrapping the app. It
|
|
88
88
|
passes the locale on to react-aria, which translates its own built-in
|
|
89
89
|
strings (see [Strings](#strings)); without it those follow the browser
|
|
90
|
-
rather than the app's language setting.
|
|
90
|
+
rather than the app's language setting. It also keeps `<html lang>` in
|
|
91
|
+
step with the locale so assistive tech announces the page in the app's
|
|
92
|
+
language — pass `setDocumentLang={false}` where the app doesn't own the
|
|
93
|
+
document it's mounted in (an embedded widget). Also takes an optional
|
|
91
94
|
overlay-close registrar, so the app can dismiss open menus from outside
|
|
92
95
|
the tree (e.g. the Android hardware back button).
|
|
93
96
|
7. **`ToastProvider`** once near the root, inside the two providers above.
|
package/lang/ui.ca.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "S'està carregant",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrementa {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.cy.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Wrthi'n Llwytho",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.de.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Laden",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "{fieldLabel} verringern",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.en-us.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Loading",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.en.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Loading",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.es-es.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Cargando",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Reducir {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.fr.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Chargement",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Diminuer {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.ga-ie.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Ag lódáil",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.it.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Caricamento",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Riduci {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.ja.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "読み込み中",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "{fieldLabel}を縮小",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.ko.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "불러오는 중...",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "{fieldLabel} 감소",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.lo.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "ກຳລັງໂຫຼດ",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.lol.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Loading",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Decrease {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.nl.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Laden",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Verlaag {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.pl.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Ładowanie",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Zmniejsz {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.pt-br.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Carregando",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Diminuir {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.vi.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "Đang tải",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "Giảm {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.zh-cn.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "正在加载",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "降低 {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
package/lang/ui.zh-tw.json
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"defaultMessage": "正在載入",
|
|
20
20
|
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
21
|
},
|
|
22
|
+
"ui.new-tab-notice": {
|
|
23
|
+
"defaultMessage": "opens in a new tab",
|
|
24
|
+
"description": "Visually hidden suffix on links that open a new tab, announced by screen readers"
|
|
25
|
+
},
|
|
22
26
|
"ui.numberfield-decrease": {
|
|
23
27
|
"defaultMessage": "縮小 {fieldLabel}",
|
|
24
28
|
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
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.25",
|
|
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",
|
|
@@ -27,11 +27,7 @@
|
|
|
27
27
|
"panda": "panda codegen",
|
|
28
28
|
"typecheck": "npm run panda && tsc --noEmit",
|
|
29
29
|
"pretest": "npm run panda",
|
|
30
|
-
"test": "vitest run"
|
|
31
|
-
"prestorybook": "npm run panda",
|
|
32
|
-
"storybook": "storybook dev -p 6006 --no-open",
|
|
33
|
-
"prebuild-storybook": "npm run panda",
|
|
34
|
-
"build-storybook": "storybook build"
|
|
30
|
+
"test": "vitest run"
|
|
35
31
|
},
|
|
36
32
|
"peerDependencies": {
|
|
37
33
|
"@pandacss/dev": "^1.11.4",
|
|
@@ -55,7 +51,6 @@
|
|
|
55
51
|
"react-dom": "^18.3.1",
|
|
56
52
|
"react-icons": "^4.12.0",
|
|
57
53
|
"react-intl": "^6.6.8",
|
|
58
|
-
"storybook": "^10.5.2",
|
|
59
54
|
"typescript": "^5.4.2",
|
|
60
55
|
"vite": "^6.3.5",
|
|
61
56
|
"vitest": "^4.1.10"
|
package/src/Button.recipe.ts
CHANGED
|
@@ -154,8 +154,7 @@ export const button = defineRecipe({
|
|
|
154
154
|
_hover: { bg: "danger.600", _disabled: { bg: "danger.500" } },
|
|
155
155
|
_active: { bg: "danger.700" },
|
|
156
156
|
},
|
|
157
|
-
// Family-wide
|
|
158
|
-
// toolbar-class buttons).
|
|
157
|
+
// Family-wide variant (every censused app has toolbar-class buttons).
|
|
159
158
|
toolbar: {
|
|
160
159
|
color: "black",
|
|
161
160
|
bg: "white",
|
|
@@ -163,12 +162,6 @@ export const button = defineRecipe({
|
|
|
163
162
|
_active: { bg: "whiteAlpha.800" },
|
|
164
163
|
_focusVisible: { focusShadow: "outlineDark" },
|
|
165
164
|
},
|
|
166
|
-
language: {
|
|
167
|
-
borderWidth: "2px",
|
|
168
|
-
borderColor: "gray.200",
|
|
169
|
-
color: "languageText",
|
|
170
|
-
_hover: { color: "languageTextHover", bg: "gray.100" },
|
|
171
|
-
},
|
|
172
165
|
},
|
|
173
166
|
},
|
|
174
167
|
defaultVariants: {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { ComponentProps } from "react";
|
|
7
|
+
import { RiExternalLinkLine } from "react-icons/ri";
|
|
8
|
+
import { FormattedMessage } from "react-intl";
|
|
9
|
+
import { Icon } from "./Icon";
|
|
10
|
+
import { Link } from "./Link";
|
|
11
|
+
import { uiMessage } from "./messages";
|
|
12
|
+
import { VisuallyHidden } from "./VisuallyHidden";
|
|
13
|
+
|
|
14
|
+
export type ExternalLinkProps = Omit<
|
|
15
|
+
ComponentProps<typeof Link>,
|
|
16
|
+
"target" | "rel"
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ExternalLink — a Link that opens in a new tab and says so both ways: a
|
|
21
|
+
* trailing external-link glyph for sighted users, and a visually hidden,
|
|
22
|
+
* translated "opens in a new tab" for assistive tech. Use this rather than
|
|
23
|
+
* restating `target="_blank"` + icon so the announcement can't be forgotten.
|
|
24
|
+
*
|
|
25
|
+
* For app chrome, where a new tab protects the user's session-only state.
|
|
26
|
+
* Content surfaces should prefer same-tab links, or a visible
|
|
27
|
+
* "(opens in new tab)" suffix and no icon.
|
|
28
|
+
*
|
|
29
|
+
* Inline-safe: the icon is 1em and follows the text colour, so the link can
|
|
30
|
+
* sit in prose as well as stand alone.
|
|
31
|
+
*/
|
|
32
|
+
export const ExternalLink = ({ children, ...props }: ExternalLinkProps) => (
|
|
33
|
+
<Link {...props} target="_blank" rel="noopener">
|
|
34
|
+
{children}
|
|
35
|
+
<VisuallyHidden>
|
|
36
|
+
{/* The comma separates the suffix in the accessible name: inline nodes
|
|
37
|
+
concatenate without a space, and leading whitespace is trimmed. */}
|
|
38
|
+
{", "}
|
|
39
|
+
<FormattedMessage {...uiMessage("ui.new-tab-notice")} />
|
|
40
|
+
</VisuallyHidden>
|
|
41
|
+
<Icon
|
|
42
|
+
as={RiExternalLinkLine}
|
|
43
|
+
aria-hidden
|
|
44
|
+
css={{ verticalAlign: "text-bottom", ml: 1 }}
|
|
45
|
+
/>
|
|
46
|
+
</Link>
|
|
47
|
+
);
|
package/src/SharedUIProvider.tsx
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
ReactNode,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
} from "react";
|
|
7
13
|
import { I18nProvider } from "react-aria-components";
|
|
8
14
|
import { IntlContext } from "react-intl";
|
|
9
15
|
import { racLocale } from "./rac-locale";
|
|
@@ -30,6 +36,14 @@ export interface SharedUIProviderProps {
|
|
|
30
36
|
* two must differ.
|
|
31
37
|
*/
|
|
32
38
|
locale?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Keeps `<html lang>` in step with the locale, so assistive tech announces
|
|
41
|
+
* the page in the app's language. On by default; set false where the app
|
|
42
|
+
* doesn't own the document it's mounted in (an embedded widget), or when
|
|
43
|
+
* mounting more than one provider per page. The static `lang="en"` in
|
|
44
|
+
* index.html remains the pre-hydration default.
|
|
45
|
+
*/
|
|
46
|
+
setDocumentLang?: boolean;
|
|
33
47
|
children: ReactNode;
|
|
34
48
|
}
|
|
35
49
|
|
|
@@ -48,20 +62,25 @@ export interface SharedUIProviderProps {
|
|
|
48
62
|
export const SharedUIProvider = ({
|
|
49
63
|
overlayCloseRegistrar,
|
|
50
64
|
locale,
|
|
65
|
+
setDocumentLang = true,
|
|
51
66
|
children,
|
|
52
67
|
}: SharedUIProviderProps) => {
|
|
53
68
|
// Read the context rather than calling useIntl(), which throws when there is
|
|
54
69
|
// no IntlProvider: react-aria falls back to the browser locale, as before.
|
|
55
70
|
const intlLocale = useContext(IntlContext)?.locale;
|
|
71
|
+
const appLocale = locale ?? intlLocale;
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (setDocumentLang && appLocale) {
|
|
74
|
+
document.documentElement.lang = appLocale;
|
|
75
|
+
}
|
|
76
|
+
}, [setDocumentLang, appLocale]);
|
|
56
77
|
const value = useMemo(
|
|
57
78
|
() => ({ overlayCloseRegistrar }),
|
|
58
79
|
[overlayCloseRegistrar],
|
|
59
80
|
);
|
|
60
81
|
return (
|
|
61
82
|
<SharedUIContext.Provider value={value}>
|
|
62
|
-
<I18nProvider locale={racLocale(
|
|
63
|
-
{children}
|
|
64
|
-
</I18nProvider>
|
|
83
|
+
<I18nProvider locale={racLocale(appLocale)}>{children}</I18nProvider>
|
|
65
84
|
</SharedUIContext.Provider>
|
|
66
85
|
);
|
|
67
86
|
};
|
package/src/Toast.tsx
CHANGED
|
@@ -56,11 +56,20 @@ const wrapUpdate = (fn: () => void) => {
|
|
|
56
56
|
activeTransitions++;
|
|
57
57
|
document.documentElement.classList.add(TRANSITION_CLASS);
|
|
58
58
|
const transition = document.startViewTransition(fn);
|
|
59
|
-
transition
|
|
59
|
+
// A transition is routinely skipped — another update starts one while
|
|
60
|
+
// this is still animating, or the tab is hidden — and the browser then
|
|
61
|
+
// rejects its promises with "AbortError: Transition was skipped". The
|
|
62
|
+
// DOM update has still run; only the animation is dropped. Settle on
|
|
63
|
+
// rejection too (`.finally()` would re-throw, surfacing routine skips
|
|
64
|
+
// as unhandled rejections in error reporting) and swallow `ready`,
|
|
65
|
+
// which some engines reject without marking handled.
|
|
66
|
+
transition.ready.catch(() => {});
|
|
67
|
+
const settle = () => {
|
|
60
68
|
if (--activeTransitions === 0) {
|
|
61
69
|
document.documentElement.classList.remove(TRANSITION_CLASS);
|
|
62
70
|
}
|
|
63
|
-
}
|
|
71
|
+
};
|
|
72
|
+
transition.finished.then(settle, settle);
|
|
64
73
|
} else {
|
|
65
74
|
fn();
|
|
66
75
|
}
|
package/src/base-preset.ts
CHANGED
|
@@ -86,7 +86,7 @@ const gray = {
|
|
|
86
86
|
* The base preset: the complete, working micro:bit design system. The base
|
|
87
87
|
* token scales (base-tokens.ts), the micro:bit house style
|
|
88
88
|
* (pill `radii.button`, `outline*` focus shadows, Helvetica fonts, the
|
|
89
|
-
* `
|
|
89
|
+
* `toolbar` button variant in Button.recipe.ts, the
|
|
90
90
|
* `languageText`/`toast*Bg`/`statusBarBg` semantic tokens), the shared-ui
|
|
91
91
|
* component recipes, the react-aria condition widening, the `globalCss`
|
|
92
92
|
* defaults, and the `staticCss` that keeps runtime-prop recipe
|
|
@@ -207,11 +207,12 @@ export const basePreset = definePreset({
|
|
|
207
207
|
600: { value: "{colors.red.600}" },
|
|
208
208
|
700: { value: "{colors.red.700}" },
|
|
209
209
|
},
|
|
210
|
-
// The
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// python-editor to brand.500/600 —
|
|
214
|
-
// the
|
|
210
|
+
// The language-dialog cards' text colour (@microbit/ui-patterns'
|
|
211
|
+
// LanguageDialog) follows the primary interactive brand: every
|
|
212
|
+
// consumer resolves it to its `brand` ramp (CreateAI privately to
|
|
213
|
+
// brand.600 with no hover change, python-editor to brand.500/600 —
|
|
214
|
+
// the default; data-microbit-org to black). Semantic tokens so the
|
|
215
|
+
// pattern stays shared and a brand preset overrides only values.
|
|
215
216
|
languageText: { value: "{colors.brand.500}" },
|
|
216
217
|
languageTextHover: { value: "{colors.brand.600}" },
|
|
217
218
|
// The `label`/`subtitle` heading variants' colour (page-title chrome).
|
package/src/index.ts
CHANGED