@microbit/ui 0.0.0-gray.ramp.90
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/LICENSE.md +53 -0
- package/README.md +222 -0
- package/lang/ui.ca.json +22 -0
- package/lang/ui.cy.json +22 -0
- package/lang/ui.de.json +22 -0
- package/lang/ui.en-us.json +22 -0
- package/lang/ui.en.json +22 -0
- package/lang/ui.es-es.json +22 -0
- package/lang/ui.fr.json +22 -0
- package/lang/ui.ga-ie.json +22 -0
- package/lang/ui.it.json +22 -0
- package/lang/ui.ja.json +22 -0
- package/lang/ui.ko.json +22 -0
- package/lang/ui.lol.json +22 -0
- package/lang/ui.nl.json +22 -0
- package/lang/ui.pl.json +22 -0
- package/lang/ui.pt-br.json +22 -0
- package/lang/ui.zh-cn.json +22 -0
- package/lang/ui.zh-tw.json +22 -0
- package/package.json +66 -0
- package/postcss-legacy-safari.cjs +96 -0
- package/reset.css +35 -0
- package/src/Avatar.recipe.ts +168 -0
- package/src/Avatar.tsx +276 -0
- package/src/Button.recipe.ts +192 -0
- package/src/Button.tsx +69 -0
- package/src/ButtonGroup.tsx +70 -0
- package/src/Card.recipe.ts +49 -0
- package/src/Card.tsx +56 -0
- package/src/Checkbox.recipe.ts +111 -0
- package/src/Checkbox.tsx +106 -0
- package/src/CloseButton.tsx +87 -0
- package/src/CloseIcon.tsx +40 -0
- package/src/Code.tsx +20 -0
- package/src/Collapse.tsx +180 -0
- package/src/ComboBox.tsx +192 -0
- package/src/Divider.tsx +85 -0
- package/src/Drawer.recipe.ts +98 -0
- package/src/Drawer.tsx +138 -0
- package/src/Fade.tsx +48 -0
- package/src/GridList.recipe.ts +58 -0
- package/src/GridList.tsx +81 -0
- package/src/Heading.recipe.ts +52 -0
- package/src/Heading.tsx +14 -0
- package/src/Icon.tsx +75 -0
- package/src/IconButton.tsx +44 -0
- package/src/Image.tsx +11 -0
- package/src/Input.recipe.ts +75 -0
- package/src/Input.tsx +44 -0
- package/src/InputGroup.tsx +62 -0
- package/src/Kbd.tsx +26 -0
- package/src/Link.tsx +23 -0
- package/src/LinkBox.tsx +88 -0
- package/src/LinkButton.tsx +80 -0
- package/src/List.tsx +31 -0
- package/src/ListBox.recipe.ts +43 -0
- package/src/ListBox.tsx +88 -0
- package/src/Menu.recipe.ts +124 -0
- package/src/Menu.tsx +291 -0
- package/src/Modal.recipe.ts +163 -0
- package/src/Modal.tsx +377 -0
- package/src/NativeSelect.tsx +94 -0
- package/src/NumberField.recipe.ts +67 -0
- package/src/NumberField.tsx +86 -0
- package/src/PopoverArrow.tsx +67 -0
- package/src/ProgressBar.tsx +61 -0
- package/src/Radio.recipe.ts +112 -0
- package/src/Radio.tsx +89 -0
- package/src/Select.recipe.ts +210 -0
- package/src/Select.tsx +153 -0
- package/src/SharedUIProvider.tsx +55 -0
- package/src/Skeleton.tsx +146 -0
- package/src/Slide.tsx +54 -0
- package/src/Slider.recipe.ts +102 -0
- package/src/Slider.tsx +163 -0
- package/src/Spinner.tsx +74 -0
- package/src/Svg.tsx +23 -0
- package/src/Switch.recipe.ts +107 -0
- package/src/Switch.tsx +62 -0
- package/src/Text.recipe.ts +29 -0
- package/src/Text.tsx +16 -0
- package/src/TextField.recipe.ts +54 -0
- package/src/TextField.tsx +91 -0
- package/src/Toast.recipe.ts +98 -0
- package/src/Toast.tsx +173 -0
- package/src/Tooltip.recipe.ts +37 -0
- package/src/Tooltip.tsx +76 -0
- package/src/UnmountCallback.tsx +18 -0
- package/src/VisuallyHidden.tsx +14 -0
- package/src/base-preset.ts +371 -0
- package/src/base-tokens.ts +1017 -0
- package/src/button-icon.ts +23 -0
- package/src/data-attrs.ts +16 -0
- package/src/dense-preset.ts +108 -0
- package/src/hooks/useBreakpointValue.ts +63 -0
- package/src/hooks/useClipboard.ts +64 -0
- package/src/hooks/useDisclosure.ts +32 -0
- package/src/hooks/useMediaQuery.ts +28 -0
- package/src/hooks/usePrevious.ts +18 -0
- package/src/index.ts +63 -0
- package/src/messages.ts +17 -0
- package/src/system.ts +45 -0
|
@@ -0,0 +1,192 @@
|
|
|
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 `button.*` and `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
|
+
// Colours come from the `button.*` semantic tokens so the family's two
|
|
96
|
+
// button idioms (brand-coloured vs black-on-white) share this recipe —
|
|
97
|
+
// see the token block in base-preset.ts.
|
|
98
|
+
secondary: {
|
|
99
|
+
borderWidth: "2px",
|
|
100
|
+
borderColor: "button.secondaryBorder",
|
|
101
|
+
color: "button.secondaryText",
|
|
102
|
+
bg: "transparent",
|
|
103
|
+
_hover: {
|
|
104
|
+
borderColor: "button.secondaryHoverBorder",
|
|
105
|
+
bg: "button.secondaryHoverBg",
|
|
106
|
+
},
|
|
107
|
+
_active: {
|
|
108
|
+
bg: "button.secondaryActiveBg",
|
|
109
|
+
borderColor: "button.secondaryActiveBorder",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
ghost: {
|
|
113
|
+
color: "black",
|
|
114
|
+
bg: "transparent",
|
|
115
|
+
_hover: { bg: "blackAlpha.50" },
|
|
116
|
+
_active: { bg: "blackAlpha.100" },
|
|
117
|
+
},
|
|
118
|
+
// Chakra had no `plain` variant, so `variant="plain"` fell through to
|
|
119
|
+
// base-only styling: a transparent, colour-inheriting button. Used for the
|
|
120
|
+
// action-bar icon-button menu triggers (settings/help), which supply their
|
|
121
|
+
// own colour and shape via instance styles.
|
|
122
|
+
plain: {
|
|
123
|
+
bg: "transparent",
|
|
124
|
+
color: "inherit",
|
|
125
|
+
},
|
|
126
|
+
primary: {
|
|
127
|
+
color: "white",
|
|
128
|
+
bg: "button.primaryBg",
|
|
129
|
+
_hover: {
|
|
130
|
+
bg: "button.primaryHoverBg",
|
|
131
|
+
_disabled: { bg: "button.primaryBg" },
|
|
132
|
+
},
|
|
133
|
+
_active: { bg: "button.primaryActiveBg" },
|
|
134
|
+
},
|
|
135
|
+
// 600/700, matching what python-editor's Chakra outline + red
|
|
136
|
+
// colorScheme resolved to. (Extracted from ml-trainer at 500/600, but
|
|
137
|
+
// its one warning button tolerates the darkening; python-editor's
|
|
138
|
+
// "Reset project" was visibly lighter than its Chakra self.)
|
|
139
|
+
// NOTE (2026-08-02, from classroom): two Chakra `outline` shapes have
|
|
140
|
+
// no home here and are currently restated per call site in that app —
|
|
141
|
+
// worth considering as variants once a second consumer wants them.
|
|
142
|
+
// - a neutral outline (transparent, 1px gray.200, inherited text,
|
|
143
|
+
// gray.50/gray.100 hover/press): Chakra's default-colorScheme
|
|
144
|
+
// `outline`, and python-editor's *default* variant per the
|
|
145
|
+
// playbook's cross-app vocabulary, so likely already a 2-app shape.
|
|
146
|
+
// - an on-colour outline (white 2px + white text over a coloured bar,
|
|
147
|
+
// whiteAlpha hover/press): Chakra's `outline` + `whiteAlpha`.
|
|
148
|
+
// `warning` below is the *destructive* outline and is not a substitute
|
|
149
|
+
// for either; `warningSolid` did map exactly onto Chakra solid+red.
|
|
150
|
+
warning: {
|
|
151
|
+
borderWidth: "2px",
|
|
152
|
+
borderColor: "danger.600",
|
|
153
|
+
color: "danger.600",
|
|
154
|
+
bg: "transparent",
|
|
155
|
+
_hover: { borderColor: "danger.700", color: "danger.700" },
|
|
156
|
+
_active: { bg: "danger.50" },
|
|
157
|
+
},
|
|
158
|
+
// Chakra's built-in solid + red colorScheme (destructive confirm
|
|
159
|
+
// buttons). Same values as `record` today, but a separate variant so
|
|
160
|
+
// recording UI and destructive actions can diverge - hence danger
|
|
161
|
+
// tokens here, red.* literals there.
|
|
162
|
+
warningSolid: {
|
|
163
|
+
color: "white",
|
|
164
|
+
bg: "danger.500",
|
|
165
|
+
_hover: { bg: "danger.600", _disabled: { bg: "danger.500" } },
|
|
166
|
+
_active: { bg: "danger.700" },
|
|
167
|
+
},
|
|
168
|
+
// Family-wide variants (every censused app has language- and
|
|
169
|
+
// toolbar-class buttons).
|
|
170
|
+
toolbar: {
|
|
171
|
+
color: "black",
|
|
172
|
+
bg: "white",
|
|
173
|
+
_hover: { bg: "whiteAlpha.900", _disabled: { bg: "white" } },
|
|
174
|
+
_active: { bg: "whiteAlpha.800" },
|
|
175
|
+
_focusVisible: { focusShadow: "outlineDark" },
|
|
176
|
+
},
|
|
177
|
+
language: {
|
|
178
|
+
borderWidth: "2px",
|
|
179
|
+
borderColor: "gray.200",
|
|
180
|
+
color: "languageText",
|
|
181
|
+
// The hover wash reads as a highlight because the app's gray ramp
|
|
182
|
+
// carries its brand undertone (per-app tinted ramps) — an
|
|
183
|
+
// achromatic gray.100 here goes drab, a tinted one doesn't.
|
|
184
|
+
_hover: { color: "languageTextHover", bg: "gray.100" },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
defaultVariants: {
|
|
189
|
+
variant: "secondary",
|
|
190
|
+
size: "md",
|
|
191
|
+
},
|
|
192
|
+
});
|
package/src/Button.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
Button as RACButton,
|
|
9
|
+
ButtonProps as RACButtonProps,
|
|
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 ButtonProps
|
|
17
|
+
extends Omit<RACButtonProps, "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
|
+
/**
|
|
30
|
+
* Button — react-aria-components <Button> styled with the `button` config
|
|
31
|
+
* recipe. The recipe's interaction states are driven by RAC's data attributes
|
|
32
|
+
* (see the preset's widened conditions), so disabled/hover/press/focus all work
|
|
33
|
+
* without extra wiring.
|
|
34
|
+
*/
|
|
35
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
36
|
+
function Button(
|
|
37
|
+
{
|
|
38
|
+
variant,
|
|
39
|
+
size,
|
|
40
|
+
css: cssProp,
|
|
41
|
+
className,
|
|
42
|
+
leftIcon,
|
|
43
|
+
rightIcon,
|
|
44
|
+
children,
|
|
45
|
+
...rest
|
|
46
|
+
},
|
|
47
|
+
ref,
|
|
48
|
+
) {
|
|
49
|
+
return (
|
|
50
|
+
<RACButton
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cx(
|
|
53
|
+
button({ variant, size }),
|
|
54
|
+
cssProp ? css(cssProp) : undefined,
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...rest}
|
|
58
|
+
>
|
|
59
|
+
{leftIcon ? (
|
|
60
|
+
<span className={buttonIcon({ side: "left" })}>{leftIcon}</span>
|
|
61
|
+
) : null}
|
|
62
|
+
{children}
|
|
63
|
+
{rightIcon ? (
|
|
64
|
+
<span className={buttonIcon({ side: "right" })}>{rightIcon}</span>
|
|
65
|
+
) : null}
|
|
66
|
+
</RACButton>
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { forwardRef, HTMLAttributes } from "react";
|
|
7
|
+
import { css, cx } from "styled-system/css";
|
|
8
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
9
|
+
|
|
10
|
+
export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
/**
|
|
12
|
+
* Square the inner radii so adjacent buttons form one control (Chakra's
|
|
13
|
+
* `isAttached`). Otherwise buttons get a small gap.
|
|
14
|
+
*/
|
|
15
|
+
isAttached?: boolean;
|
|
16
|
+
/** Per-instance style overrides. */
|
|
17
|
+
css?: SystemStyleObject;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ButtonGroup — lays out related buttons in a row. Works with any button
|
|
23
|
+
* elements (shared-ui or native). Replaces Chakra's ButtonGroup.
|
|
24
|
+
*/
|
|
25
|
+
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
26
|
+
function ButtonGroup({ isAttached, css: cssProp, className, ...rest }, ref) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cx(
|
|
31
|
+
css({
|
|
32
|
+
display: "inline-flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
// Buttons are position: relative, so without this an attached
|
|
35
|
+
// sibling paints over the focused button's focus-ring shadow.
|
|
36
|
+
// Chakra raised on :focus, but with the attached -1px overlap a
|
|
37
|
+
// raised button also paints over a seam its neighbour draws
|
|
38
|
+
// (e.g. a solid split button's white borderLeft), so raise only
|
|
39
|
+
// when the ring is actually shown.
|
|
40
|
+
"& > *": { _focusVisible: { zIndex: 1 } },
|
|
41
|
+
}),
|
|
42
|
+
isAttached
|
|
43
|
+
? css({
|
|
44
|
+
gap: 0,
|
|
45
|
+
// :first-child/:last-child, not :first-of-type: attached
|
|
46
|
+
// groups can mix element types (e.g. select + button), and
|
|
47
|
+
// -of-type matches per element type.
|
|
48
|
+
// marginEnd -1px (Chakra parity): overlaps adjacent borders
|
|
49
|
+
// so two 1px inner edges read as a single 1px seam.
|
|
50
|
+
"& > *:first-child:not(:last-child)": {
|
|
51
|
+
borderEndRadius: 0,
|
|
52
|
+
marginEnd: "-1px",
|
|
53
|
+
},
|
|
54
|
+
"& > *:not(:first-child):not(:last-child)": {
|
|
55
|
+
borderRadius: 0,
|
|
56
|
+
marginEnd: "-1px",
|
|
57
|
+
},
|
|
58
|
+
"& > *:not(:first-child):last-child": {
|
|
59
|
+
borderStartRadius: 0,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
: css({ gap: 2 }),
|
|
63
|
+
cssProp ? css(cssProp) : undefined,
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...rest}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Card slot recipe — Chakra's Card at its default md size (light mode), with
|
|
10
|
+
* the `elevated` (default) and `outline` variants this app uses.
|
|
11
|
+
*
|
|
12
|
+
* Registered in the base preset (base-preset.ts); `variant` is
|
|
13
|
+
* forwarded as a runtime prop so the variants are generated via `staticCss`.
|
|
14
|
+
*/
|
|
15
|
+
export const card = defineSlotRecipe({
|
|
16
|
+
className: "card",
|
|
17
|
+
slots: ["container", "body"],
|
|
18
|
+
base: {
|
|
19
|
+
container: {
|
|
20
|
+
display: "flex",
|
|
21
|
+
flexDirection: "column",
|
|
22
|
+
position: "relative",
|
|
23
|
+
minWidth: 0,
|
|
24
|
+
wordWrap: "break-word",
|
|
25
|
+
bg: "white",
|
|
26
|
+
borderRadius: "md",
|
|
27
|
+
color: "inherit",
|
|
28
|
+
},
|
|
29
|
+
body: {
|
|
30
|
+
padding: "5",
|
|
31
|
+
flex: "1 1 0%",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
variants: {
|
|
35
|
+
variant: {
|
|
36
|
+
elevated: {
|
|
37
|
+
container: { boxShadow: "base" },
|
|
38
|
+
},
|
|
39
|
+
outline: {
|
|
40
|
+
container: {
|
|
41
|
+
borderWidth: "1px",
|
|
42
|
+
borderStyle: "solid",
|
|
43
|
+
borderColor: "gray.200",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: { variant: "elevated" },
|
|
49
|
+
});
|
package/src/Card.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { forwardRef, HTMLAttributes } from "react";
|
|
7
|
+
import { css, cx } from "styled-system/css";
|
|
8
|
+
import { card, CardVariantProps } from "styled-system/recipes";
|
|
9
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
10
|
+
|
|
11
|
+
export interface CardProps
|
|
12
|
+
extends HTMLAttributes<HTMLDivElement>,
|
|
13
|
+
CardVariantProps {
|
|
14
|
+
/** Per-instance style overrides, merged after the recipe. */
|
|
15
|
+
css?: SystemStyleObject;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Card — Chakra's <Card> equivalent (md size). Place a CardBody inside.
|
|
20
|
+
*/
|
|
21
|
+
export const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
|
|
22
|
+
{ variant, css: cssProp, className, ...rest },
|
|
23
|
+
ref,
|
|
24
|
+
) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
ref={ref}
|
|
28
|
+
className={cx(
|
|
29
|
+
card({ variant }).container,
|
|
30
|
+
cssProp ? css(cssProp) : undefined,
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
{...rest}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export interface CardBodyProps extends HTMLAttributes<HTMLDivElement> {
|
|
39
|
+
css?: SystemStyleObject;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
|
43
|
+
function CardBody({ css: cssProp, className, ...rest }, ref) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cx(
|
|
48
|
+
card({}).body,
|
|
49
|
+
cssProp ? css(cssProp) : undefined,
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
{...rest}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checkbox slot recipe — Chakra's checkbox with the default blue colorScheme
|
|
10
|
+
* (light mode) and its sm/md/lg size scale. The control's
|
|
11
|
+
* `borderColor: inherit` picks up a `borderColor` set on the root, matching
|
|
12
|
+
* Chakra's convention for tinting the box from the call site.
|
|
13
|
+
*
|
|
14
|
+
* State styling keys off data attributes stamped on the control by the
|
|
15
|
+
* shared-ui Checkbox (react-aria provides the state via render props).
|
|
16
|
+
*
|
|
17
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
18
|
+
* `staticCss` entry that keeps the runtime-prop size variants generated.
|
|
19
|
+
*/
|
|
20
|
+
export const checkbox = defineSlotRecipe({
|
|
21
|
+
className: "checkbox",
|
|
22
|
+
slots: ["root", "control", "icon", "label"],
|
|
23
|
+
base: {
|
|
24
|
+
root: {
|
|
25
|
+
display: "inline-flex",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
verticalAlign: "top",
|
|
28
|
+
cursor: "pointer",
|
|
29
|
+
position: "relative",
|
|
30
|
+
// The control's `borderColor: inherit` reads this. Stated here rather
|
|
31
|
+
// than on the control so a call site can still tint the whole control
|
|
32
|
+
// by setting borderColor on the root. Without it, inherit fell through
|
|
33
|
+
// to the reset's gray.200 (~1.3:1) — the accessible outline stop, as
|
|
34
|
+
// the input recipe (docs/gray-ramp.md).
|
|
35
|
+
borderColor: "gray.400",
|
|
36
|
+
"&[data-disabled]": { cursor: "not-allowed" },
|
|
37
|
+
},
|
|
38
|
+
control: {
|
|
39
|
+
display: "inline-flex",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
justifyContent: "center",
|
|
42
|
+
flexShrink: 0,
|
|
43
|
+
transitionProperty: "box-shadow",
|
|
44
|
+
transitionDuration: "normal",
|
|
45
|
+
borderWidth: "2px",
|
|
46
|
+
borderStyle: "solid",
|
|
47
|
+
borderRadius: "sm",
|
|
48
|
+
borderColor: "inherit",
|
|
49
|
+
color: "white",
|
|
50
|
+
bg: "white",
|
|
51
|
+
"&[data-selected]": {
|
|
52
|
+
bg: "controlCheckedBg",
|
|
53
|
+
borderColor: "controlCheckedBg",
|
|
54
|
+
color: "white",
|
|
55
|
+
_hover: {
|
|
56
|
+
bg: "controlCheckedHoverBg",
|
|
57
|
+
borderColor: "controlCheckedHoverBg",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
// Chakra's disabled greys; the selected block restates _hover so the
|
|
61
|
+
// widened native-:hover condition can't re-tint a disabled control.
|
|
62
|
+
"&[data-disabled]": {
|
|
63
|
+
bg: "gray.100",
|
|
64
|
+
borderColor: "gray.100",
|
|
65
|
+
},
|
|
66
|
+
"&[data-selected][data-disabled]": {
|
|
67
|
+
bg: "gray.200",
|
|
68
|
+
borderColor: "gray.200",
|
|
69
|
+
color: "gray.500",
|
|
70
|
+
_hover: { bg: "gray.200", borderColor: "gray.200" },
|
|
71
|
+
},
|
|
72
|
+
"&[data-focus-visible]": {
|
|
73
|
+
focusShadow: "outline",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
icon: {
|
|
77
|
+
transitionProperty: "transform",
|
|
78
|
+
transitionDuration: "normal",
|
|
79
|
+
},
|
|
80
|
+
label: {
|
|
81
|
+
userSelect: "none",
|
|
82
|
+
marginStart: "2",
|
|
83
|
+
"&[data-disabled]": { opacity: 0.4 },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
variants: {
|
|
87
|
+
// Chakra's Checkbox size scale. The icon dimensions are Chakra's
|
|
88
|
+
// 1.2em-wide check glyph at each size's icon fontSize (3xs/2xs/2xs),
|
|
89
|
+
// resolved to rem.
|
|
90
|
+
size: {
|
|
91
|
+
sm: {
|
|
92
|
+
control: { width: "3", height: "3" },
|
|
93
|
+
icon: { width: "0.54rem", height: "0.54rem" },
|
|
94
|
+
label: { fontSize: "sm" },
|
|
95
|
+
},
|
|
96
|
+
md: {
|
|
97
|
+
control: { width: "4", height: "4" },
|
|
98
|
+
icon: { width: "0.75rem", height: "0.75rem" },
|
|
99
|
+
label: { fontSize: "md" },
|
|
100
|
+
},
|
|
101
|
+
lg: {
|
|
102
|
+
control: { width: "5", height: "5" },
|
|
103
|
+
icon: { width: "0.75rem", height: "0.75rem" },
|
|
104
|
+
label: { fontSize: "lg" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
defaultVariants: {
|
|
109
|
+
size: "md",
|
|
110
|
+
},
|
|
111
|
+
});
|
package/src/Checkbox.tsx
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { ReactNode } from "react";
|
|
7
|
+
import {
|
|
8
|
+
Checkbox as RACCheckbox,
|
|
9
|
+
CheckboxProps as RACCheckboxProps,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import { css, cx } from "styled-system/css";
|
|
12
|
+
import { checkbox, CheckboxVariantProps } from "styled-system/recipes";
|
|
13
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
14
|
+
|
|
15
|
+
/** What a render-prop child is told about the checkbox. */
|
|
16
|
+
export interface CheckboxState {
|
|
17
|
+
isSelected: boolean;
|
|
18
|
+
isFocusVisible: boolean;
|
|
19
|
+
isDisabled: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CheckboxProps
|
|
23
|
+
extends Omit<RACCheckboxProps, "className" | "children" | "style">,
|
|
24
|
+
CheckboxVariantProps {
|
|
25
|
+
/** Per-instance style overrides for the root, merged after the recipe. */
|
|
26
|
+
css?: SystemStyleObject;
|
|
27
|
+
className?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The label. A function receives the checkbox's state, for a label that
|
|
30
|
+
* changes with it.
|
|
31
|
+
*/
|
|
32
|
+
children?: ReactNode | ((state: CheckboxState) => ReactNode);
|
|
33
|
+
/**
|
|
34
|
+
* Whether to draw the box. `false` is for a checkbox whose children draw
|
|
35
|
+
* the selected state themselves — a selectable tile, or an avatar that
|
|
36
|
+
* grows a tick. The label wrapper goes with it, so the children own the
|
|
37
|
+
* whole row, including the focus ring the box would otherwise carry.
|
|
38
|
+
*
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
control?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checkbox — react-aria-components <Checkbox> styled like Chakra's checkbox.
|
|
46
|
+
* Children render as the label; wrap them in a visually-hidden span for
|
|
47
|
+
* icon-less checkboxes.
|
|
48
|
+
*/
|
|
49
|
+
export const Checkbox = ({
|
|
50
|
+
size,
|
|
51
|
+
css: cssProp,
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
control,
|
|
55
|
+
...rest
|
|
56
|
+
}: CheckboxProps) => {
|
|
57
|
+
const slots = checkbox({ size });
|
|
58
|
+
return (
|
|
59
|
+
<RACCheckbox
|
|
60
|
+
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
61
|
+
{...rest}
|
|
62
|
+
>
|
|
63
|
+
{({ isSelected, isFocusVisible, isDisabled }) => {
|
|
64
|
+
const content =
|
|
65
|
+
typeof children === "function"
|
|
66
|
+
? children({ isSelected, isFocusVisible, isDisabled })
|
|
67
|
+
: children;
|
|
68
|
+
if (control === false) {
|
|
69
|
+
return content;
|
|
70
|
+
}
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
73
|
+
<span
|
|
74
|
+
className={slots.control}
|
|
75
|
+
data-selected={isSelected || undefined}
|
|
76
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
77
|
+
data-disabled={isDisabled || undefined}
|
|
78
|
+
aria-hidden
|
|
79
|
+
>
|
|
80
|
+
{isSelected && (
|
|
81
|
+
<svg viewBox="0 0 12 10" className={slots.icon} aria-hidden>
|
|
82
|
+
<polyline
|
|
83
|
+
points="1.5 6 4.5 9 10.5 1"
|
|
84
|
+
fill="none"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
strokeWidth="2"
|
|
87
|
+
strokeLinecap="round"
|
|
88
|
+
strokeLinejoin="round"
|
|
89
|
+
/>
|
|
90
|
+
</svg>
|
|
91
|
+
)}
|
|
92
|
+
</span>
|
|
93
|
+
{content != null && (
|
|
94
|
+
<span
|
|
95
|
+
className={slots.label}
|
|
96
|
+
data-disabled={isDisabled || undefined}
|
|
97
|
+
>
|
|
98
|
+
{content}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
}}
|
|
104
|
+
</RACCheckbox>
|
|
105
|
+
);
|
|
106
|
+
};
|