@microbit/ui 0.1.0-alpha.3

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 (67) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +118 -0
  3. package/lang/ui.ca.json +22 -0
  4. package/lang/ui.en-us.json +22 -0
  5. package/lang/ui.en.json +22 -0
  6. package/lang/ui.es-es.json +22 -0
  7. package/lang/ui.fr.json +22 -0
  8. package/lang/ui.ja.json +22 -0
  9. package/lang/ui.ko.json +22 -0
  10. package/lang/ui.lol.json +22 -0
  11. package/lang/ui.nl.json +22 -0
  12. package/lang/ui.pl.json +22 -0
  13. package/lang/ui.pt-br.json +22 -0
  14. package/lang/ui.zh-tw.json +22 -0
  15. package/package.json +54 -0
  16. package/src/Button.recipe.ts +162 -0
  17. package/src/Button.tsx +84 -0
  18. package/src/ButtonGroup.tsx +63 -0
  19. package/src/Card.recipe.ts +49 -0
  20. package/src/Card.tsx +56 -0
  21. package/src/Checkbox.recipe.ts +71 -0
  22. package/src/Checkbox.tsx +66 -0
  23. package/src/CloseButton.tsx +87 -0
  24. package/src/CloseIcon.tsx +40 -0
  25. package/src/Divider.tsx +21 -0
  26. package/src/Drawer.recipe.ts +98 -0
  27. package/src/Drawer.tsx +138 -0
  28. package/src/Heading.recipe.ts +52 -0
  29. package/src/Heading.tsx +14 -0
  30. package/src/Icon.tsx +55 -0
  31. package/src/IconButton.tsx +39 -0
  32. package/src/Image.tsx +11 -0
  33. package/src/Input.recipe.ts +63 -0
  34. package/src/Input.tsx +33 -0
  35. package/src/InputGroup.tsx +48 -0
  36. package/src/Link.tsx +23 -0
  37. package/src/LinkBox.tsx +88 -0
  38. package/src/List.tsx +27 -0
  39. package/src/Menu.recipe.ts +88 -0
  40. package/src/Menu.tsx +172 -0
  41. package/src/Modal.recipe.ts +163 -0
  42. package/src/Modal.tsx +265 -0
  43. package/src/NativeSelect.tsx +76 -0
  44. package/src/PopoverArrow.tsx +52 -0
  45. package/src/ProgressBar.tsx +61 -0
  46. package/src/SharedUIProvider.tsx +55 -0
  47. package/src/Slide.tsx +54 -0
  48. package/src/Slider.recipe.ts +91 -0
  49. package/src/Slider.tsx +99 -0
  50. package/src/Spinner.tsx +69 -0
  51. package/src/Svg.tsx +23 -0
  52. package/src/Switch.recipe.ts +80 -0
  53. package/src/Switch.tsx +60 -0
  54. package/src/Text.tsx +12 -0
  55. package/src/TextField.recipe.ts +54 -0
  56. package/src/TextField.tsx +72 -0
  57. package/src/Toast.recipe.ts +98 -0
  58. package/src/Toast.tsx +165 -0
  59. package/src/Tooltip.tsx +91 -0
  60. package/src/UnmountCallback.tsx +18 -0
  61. package/src/VisuallyHidden.tsx +14 -0
  62. package/src/base-preset.ts +278 -0
  63. package/src/chakra-tokens.ts +1014 -0
  64. package/src/hooks/useBreakpointValue.ts +63 -0
  65. package/src/index.ts +45 -0
  66. package/src/messages.ts +17 -0
  67. package/src/system.ts +36 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Micro:bit Educational Foundation and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @microbit/ui
2
+
3
+ react-aria-components + Panda CSS primitives with a design language ported from
4
+ the Micro:bit Educational Foundation apps' original Chakra UI v2 themes.
5
+
6
+ The package **ships as source**: components import `styled-system/*`, which
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.
10
+
11
+ ## App-side installation
12
+
13
+ This package's own Storybook (`.storybook/` + `panda.config.ts`) is a
14
+ worked example of the setup below, minus the app/brand presets. Everything
15
+ an app must do:
16
+
17
+ 1. **Panda preset stack** (`panda.config.ts`): `@pandacss/preset-base`, then
18
+ 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).
22
+
23
+ Later presets override earlier ones token-by-token — the base recipes and
24
+ semantic tokens reference the brand tokens, which is how a brand swap
25
+ restyles everything without touching recipes. Set `eject: true` (the stack
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.
30
+
31
+ 2. **Include this package's source** in `panda.config.ts` so Panda extracts
32
+ the styles the components use:
33
+ ```ts
34
+ include: [
35
+ "./src/**/*.{ts,tsx}",
36
+ "./node_modules/@microbit/ui/src/**/*.{ts,tsx}",
37
+ ],
38
+ ```
39
+ 3. **Resolve `styled-system/*` onto the generated output** for all
40
+ importers, this package's source included — a `styled-system` alias in
41
+ both `tsconfig.json` `paths` and the bundler config (see the
42
+ `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.
47
+ 5. **react-intl**: an `IntlProvider` above any shared-ui usage. English
48
+ works with no setup (components carry inline `defaultMessage`); for
49
+ other locales compile this package's `lang/ui.<locale>.json` into the
50
+ app's per-locale catalogs as part of the app's formatjs compile step,
51
+ e.g. `formatjs compile lang/ui.fr.json
52
+ node_modules/@microbit/ui/lang/ui.fr.json --ast --out-file ...` (multiple
53
+ input files merge; ids are `ui.`-namespaced so they can't collide). This
54
+ keeps the strings in the app's lazily loaded locale chunks rather than
55
+ an eagerly bundled catalog-of-all-locales.
56
+ 6. **`ToastProvider`** once near the root, inside the `IntlProvider`.
57
+ 7. Optionally **`SharedUIProvider`** with an overlay-close registrar so the
58
+ app can dismiss open menus from outside the tree (e.g. the Android
59
+ hardware back button). Apps without one can omit the provider.
60
+
61
+ ## The CSS-variable contract
62
+
63
+ Panda emits every token as a CSS custom property with its default naming —
64
+ `{category}-{path}` with dots become dashes, camelCase becomes kebab-case:
65
+
66
+ - `colors.brand.500` → `var(--colors-brand-500)`
67
+ - `colors.statusBarBg` → `var(--colors-status-bar-bg)`
68
+ - `fonts.display` → `var(--fonts-display)`
69
+
70
+ These names are **API** for styling that lives outside React/Panda — e.g.
71
+ CodeMirror highlight styles or xterm themes written as raw CSS. Two rules
72
+ keep them stable:
73
+
74
+ - Never set `hashing` or `prefix` in `panda.config.ts`.
75
+ - Brand/app presets may change token _values_, never token _names_.
76
+
77
+ Semantic tokens (`languageText`, `statusBarBg`, `danger.*`, `toast*Bg`,
78
+ `controlCheckedBg`, `focusBorder`, …) are the extension points brand presets
79
+ override; they resolve through var indirection, so overrides apply wherever
80
+ the token is consumed.
81
+
82
+ ## Runtime token lookups
83
+
84
+ For values that feed _computation_ rather than stylesheets (canvas painting,
85
+ colour math, inline `style` for data-driven values — see gotcha #9 in
86
+ RAC-MIGRATION.md), import the runtime lookup:
87
+
88
+ ```ts
89
+ import { token } from "@microbit/ui"; // re-exports styled-system/tokens
90
+
91
+ token("colors.brand.500"); // "#007dbc" — raw value, safe for colour math
92
+ token("colors.statusBarBg"); // "var(--colors-brand2-500)" — CSS contexts only
93
+ ```
94
+
95
+ Base tokens resolve to raw values; **semantic tokens resolve to `var()`
96
+ references**, which are only meaningful where the browser interprets CSS.
97
+ For computation, look up the base token the semantic one points at, or read
98
+ the computed style. `token.var("colors.x.y")` returns the variable reference
99
+ form explicitly.
100
+
101
+ ## Strings
102
+
103
+ Components never hardcode user-facing text. The few strings this package
104
+ needs internally (close-button labels, toast status announcements) are
105
+ react-intl messages with ids in the `ui.` namespace; everything else is
106
+ passed in by the caller as already-localized content.
107
+
108
+ The `lang/ui.<locale>.json` files (formatjs extracted format) are the source
109
+ of truth and are shipped as-is (exported at `./lang/*`); consuming apps
110
+ compile them into their own per-locale catalogs (see the consumption setup
111
+ above). Components also carry the English text inline as `defaultMessage`,
112
+ so an app that compiles no catalogs still renders English.
113
+
114
+ `en` is hand-edited; `en-US` is maintained manually. Other locales come from
115
+ Crowdin via the repo-root `npm run update-translations -- <path to extracted
116
+ Crowdin ZIP>` (config-driven over packages in
117
+ `bin/update-translations.cjs`), after which you run `npm run i18n:tidy`
118
+ from the root.
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Tanca ",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Close",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Close",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Cerrar",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Fermer",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "閉じる",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "닫기",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "crwdns362700:0crwdne362700:0",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Afsluiten",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Zamknij",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Fechar",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "關閉",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@microbit/ui",
3
+ "version": "0.1.0-alpha.3",
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
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./base-preset": "./src/base-preset.ts",
10
+ "./chakra-tokens": "./src/chakra-tokens.ts",
11
+ "./messages": "./src/messages.ts",
12
+ "./lang/*": "./lang/*"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "lang",
17
+ "README.md",
18
+ "LICENSE.md"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "panda": "panda codegen && panda cssgen --outfile styled-system.css",
23
+ "typecheck": "npm run panda && tsc --noEmit",
24
+ "prestorybook": "npm run panda",
25
+ "storybook": "storybook dev -p 6006 --no-open",
26
+ "prebuild-storybook": "npm run panda",
27
+ "build-storybook": "storybook build"
28
+ },
29
+ "peerDependencies": {
30
+ "@pandacss/dev": "^1.11.4",
31
+ "react": "^18.3.1",
32
+ "react-aria-components": "^1.19.0",
33
+ "react-icons": "^4.12.0 || ^5.0.0",
34
+ "react-intl": "^6.6.8 || ^7.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@pandacss/dev": "^1.11.4",
38
+ "@storybook/react-vite": "^10.5.2",
39
+ "@types/react": "^18.3.3",
40
+ "@types/react-dom": "^18.3.0",
41
+ "@vitejs/plugin-react": "^4.5.2",
42
+ "react": "^18.3.1",
43
+ "react-aria-components": "^1.19.0",
44
+ "react-dom": "^18.3.1",
45
+ "react-icons": "^4.12.0",
46
+ "react-intl": "^6.6.8",
47
+ "storybook": "^10.5.2",
48
+ "typescript": "^5.4.2",
49
+ "vite": "^6.3.5"
50
+ },
51
+ "publishConfig": {
52
+ "tag": "alpha"
53
+ }
54
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { defineRecipe } from "@pandacss/dev";
7
+
8
+ // Chakra's transition.property.common, inlined (Panda has no transitionProperty
9
+ // token category). Matches Chakra's Button base transition.
10
+ const transitionCommon =
11
+ "background-color, border-color, color, fill, stroke, opacity, box-shadow, transform";
12
+
13
+ /**
14
+ * Button recipe — Chakra's default Button base + sizes, with this app's
15
+ * borderRadius (`button` = 2rem) and variant set ported from
16
+ * `components/button.ts`. Interaction conditions (`_hover`/`_active`/
17
+ * `_disabled`/`_focusVisible`) are widened in the preset to also match
18
+ * react-aria-components' data attributes, so these Chakra-shaped variant
19
+ * objects work unchanged on RAC's <Button>.
20
+ *
21
+ * A config recipe (not a component cva): styles land in the `recipes` layer so
22
+ * call sites can override with plain style props, and presets extend the
23
+ * variants. This file holds the brand-independent variant set — the core
24
+ * Chakra variants plus the family-wide `language`/`toolbar` variants; a
25
+ * consuming app's preset extends it with app vocabulary (e.g. ml-trainer's
26
+ * `led`/`record*`/`secondary-disabled`). Brand divergence within a variant is
27
+ * token-driven (see the `languageText` semantic tokens).
28
+ *
29
+ * Registered in the base preset (base-preset.ts).
30
+ */
31
+ export const button = defineRecipe({
32
+ className: "btn",
33
+ jsx: ["Button", "IconButton"],
34
+ base: {
35
+ lineHeight: "1.2",
36
+ borderRadius: "button",
37
+ fontWeight: "semibold",
38
+ transitionProperty: transitionCommon,
39
+ transitionDuration: "normal",
40
+ display: "inline-flex",
41
+ appearance: "none",
42
+ alignItems: "center",
43
+ justifyContent: "center",
44
+ userSelect: "none",
45
+ position: "relative",
46
+ whiteSpace: "nowrap",
47
+ verticalAlign: "middle",
48
+ outline: "none",
49
+ _focusVisible: {
50
+ focusShadow: "outline",
51
+ },
52
+ _disabled: {
53
+ opacity: 0.4,
54
+ cursor: "not-allowed",
55
+ boxShadow: "none",
56
+ },
57
+ _hover: {
58
+ _disabled: {
59
+ bg: "initial",
60
+ },
61
+ },
62
+ },
63
+ variants: {
64
+ size: {
65
+ lg: { h: "12", minW: "12", fontSize: "lg", px: "6" },
66
+ md: { h: "10", minW: "10", fontSize: "md", px: "4" },
67
+ sm: { h: "8", minW: "8", fontSize: "sm", px: "3" },
68
+ xs: { h: "6", minW: "6", fontSize: "xs", px: "2" },
69
+ },
70
+ variant: {
71
+ // Chakra's unstyled reset + this app's border-radius removal.
72
+ unstyled: {
73
+ bg: "none",
74
+ color: "inherit",
75
+ display: "inline",
76
+ lineHeight: "inherit",
77
+ m: "0",
78
+ p: "0",
79
+ borderRadius: "unset",
80
+ },
81
+ // Chakra's link layout (no padding/height) + this app's colours.
82
+ link: {
83
+ padding: 0,
84
+ height: "auto",
85
+ lineHeight: "normal",
86
+ verticalAlign: "baseline",
87
+ borderWidth: "0",
88
+ color: "brand.600",
89
+ fontWeight: "normal",
90
+ bg: "transparent",
91
+ _hover: {
92
+ textDecoration: "underline",
93
+ },
94
+ },
95
+ secondary: {
96
+ borderWidth: "2px",
97
+ borderColor: "brand.500",
98
+ color: "brand.700",
99
+ bg: "transparent",
100
+ _hover: { borderColor: "brand.600" },
101
+ _active: { bg: "brand.50", borderColor: "brand.700" },
102
+ },
103
+ ghost: {
104
+ color: "black",
105
+ bg: "transparent",
106
+ _hover: { bg: "blackAlpha.50" },
107
+ _active: { bg: "blackAlpha.100" },
108
+ },
109
+ // Chakra had no `plain` variant, so `variant="plain"` fell through to
110
+ // base-only styling: a transparent, colour-inheriting button. Used for the
111
+ // action-bar icon-button menu triggers (settings/help), which supply their
112
+ // own colour and shape via instance styles.
113
+ plain: {
114
+ bg: "transparent",
115
+ color: "inherit",
116
+ },
117
+ primary: {
118
+ color: "white",
119
+ bg: "brand.500",
120
+ _hover: { bg: "brand.600", _disabled: { bg: "brand.500" } },
121
+ _active: { bg: "brand.700" },
122
+ },
123
+ warning: {
124
+ borderWidth: "2px",
125
+ borderColor: "danger.500",
126
+ color: "danger.500",
127
+ bg: "transparent",
128
+ _hover: { borderColor: "danger.600" },
129
+ _active: { bg: "danger.50", borderColor: "danger.500" },
130
+ },
131
+ // Chakra's built-in solid + red colorScheme (destructive confirm
132
+ // buttons). Same values as `record` today, but a separate variant so
133
+ // recording UI and destructive actions can diverge - hence danger
134
+ // tokens here, red.* literals there.
135
+ warningSolid: {
136
+ color: "white",
137
+ bg: "danger.500",
138
+ _hover: { bg: "danger.600", _disabled: { bg: "danger.500" } },
139
+ _active: { bg: "danger.700" },
140
+ },
141
+ // Family-wide variants (every censused app has language- and
142
+ // toolbar-class buttons).
143
+ toolbar: {
144
+ color: "black",
145
+ bg: "white",
146
+ _hover: { bg: "whiteAlpha.900", _disabled: { bg: "white" } },
147
+ _active: { bg: "whiteAlpha.800" },
148
+ _focusVisible: { focusShadow: "outlineDark" },
149
+ },
150
+ language: {
151
+ borderWidth: "2px",
152
+ borderColor: "gray.200",
153
+ color: "languageText",
154
+ _hover: { color: "languageTextHover", bg: "gray.100" },
155
+ },
156
+ },
157
+ },
158
+ defaultVariants: {
159
+ variant: "secondary",
160
+ size: "md",
161
+ },
162
+ });