@microbit/ui 0.1.0-alpha.8 → 0.1.0
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 +8 -0
- package/README.md +171 -20
- package/lang/ui.ar.json +62 -0
- package/lang/ui.ca.json +43 -3
- package/lang/ui.cy.json +62 -0
- package/lang/ui.de.json +44 -4
- package/lang/ui.en-us.json +40 -0
- package/lang/ui.en.json +40 -0
- package/lang/ui.es-es.json +43 -3
- package/lang/ui.fr.json +44 -4
- package/lang/ui.ga-ie.json +43 -3
- package/lang/ui.it.json +62 -0
- package/lang/ui.ja.json +44 -4
- package/lang/ui.ko.json +44 -4
- package/lang/ui.lo.json +62 -0
- package/lang/ui.lol.json +40 -0
- package/lang/ui.nl.json +44 -4
- package/lang/ui.pl.json +44 -4
- package/lang/ui.pt-br.json +44 -4
- package/lang/ui.vi.json +62 -0
- package/lang/ui.zh-cn.json +44 -4
- package/lang/ui.zh-tw.json +44 -4
- package/package.json +12 -10
- package/reset.css +12 -0
- package/src/Avatar.recipe.ts +191 -0
- package/src/Avatar.tsx +278 -0
- package/src/Breadcrumb.recipe.ts +45 -0
- package/src/Breadcrumb.tsx +114 -0
- package/src/Button.recipe.ts +88 -50
- package/src/Button.tsx +67 -14
- package/src/ButtonGroup.tsx +37 -15
- package/src/Card.recipe.ts +1 -2
- package/src/Card.tsx +2 -1
- package/src/Checkbox.recipe.ts +49 -14
- package/src/Checkbox.tsx +111 -32
- package/src/CheckboxGroup.tsx +70 -0
- package/src/CloseButton.tsx +3 -3
- package/src/CloseIcon.tsx +6 -4
- package/src/Code.tsx +1 -1
- package/src/Collapse.tsx +13 -14
- package/src/ComboBox.tsx +246 -0
- package/src/Divider.tsx +40 -7
- package/src/Drawer.recipe.ts +21 -10
- package/src/Drawer.tsx +3 -4
- package/src/ExternalLink.tsx +43 -0
- package/src/Fade.tsx +18 -4
- package/src/Field.recipe.ts +114 -0
- package/src/Field.tsx +187 -0
- package/src/GridList.recipe.ts +57 -0
- package/src/GridList.tsx +81 -0
- package/src/Heading.recipe.ts +20 -1
- package/src/Heading.tsx +3 -3
- package/src/Icon.tsx +23 -7
- package/src/IconButton.tsx +8 -18
- package/src/Image.tsx +1 -1
- package/src/Input.recipe.ts +41 -28
- package/src/Input.tsx +23 -7
- package/src/InputGroup.tsx +26 -12
- package/src/Kbd.tsx +1 -1
- package/src/Link.tsx +3 -3
- package/src/LinkBox.tsx +2 -3
- package/src/LinkButton.tsx +14 -13
- package/src/List.tsx +5 -7
- package/src/ListBox.recipe.ts +43 -0
- package/src/ListBox.tsx +88 -0
- package/src/Menu.recipe.ts +22 -19
- package/src/Menu.tsx +54 -28
- package/src/Modal.recipe.ts +17 -9
- package/src/Modal.tsx +123 -24
- package/src/MoreMenuButton.tsx +63 -0
- package/src/NativeSelect.tsx +41 -14
- package/src/NativeSelectField.tsx +84 -0
- package/src/NumberField.recipe.ts +56 -15
- package/src/NumberField.tsx +74 -22
- package/src/PopoverArrow.tsx +1 -2
- package/src/ProgressBar.tsx +3 -5
- package/src/Radio.recipe.ts +108 -0
- package/src/Radio.tsx +62 -0
- package/src/RadioGroup.tsx +68 -0
- package/src/Select.recipe.ts +211 -0
- package/src/Select.tsx +194 -0
- package/src/SharedUIProvider.tsx +63 -7
- package/src/Skeleton.tsx +146 -0
- package/src/Slide.tsx +2 -2
- package/src/Slider.recipe.ts +27 -16
- package/src/Slider.tsx +20 -12
- package/src/Spinner.tsx +11 -9
- package/src/Svg.tsx +2 -3
- package/src/Switch.recipe.ts +57 -11
- package/src/Switch.tsx +39 -7
- package/src/Text.recipe.ts +26 -0
- package/src/Text.tsx +6 -2
- package/src/TextField.tsx +39 -40
- package/src/Toast.recipe.ts +52 -15
- package/src/Toast.tsx +147 -48
- package/src/Tooltip.recipe.ts +32 -0
- package/src/Tooltip.tsx +68 -28
- package/src/TooltipButton.tsx +229 -0
- package/src/UnmountCallback.tsx +2 -2
- package/src/VisuallyHidden.tsx +1 -1
- package/src/base-preset.ts +329 -84
- package/src/base-tokens.ts +0 -3
- package/src/button-icon.ts +4 -5
- package/src/data-attrs.ts +16 -0
- package/src/dense-preset.ts +107 -0
- package/src/hooks/useBreakpointValue.ts +4 -4
- package/src/hooks/useClipboard.ts +43 -7
- package/src/hooks/useDisclosure.ts +31 -0
- package/src/hooks/useMediaQuery.ts +3 -4
- package/src/hooks/usePrevious.ts +1 -4
- package/src/index.ts +20 -3
- package/src/rac-locale.ts +33 -0
- package/src/system.ts +22 -1
- package/src/TextField.recipe.ts +0 -54
package/src/Button.tsx
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
import { forwardRef, ReactNode } from "react";
|
|
7
|
+
import { useIntl } from "react-intl";
|
|
7
8
|
import {
|
|
8
9
|
Button as RACButton,
|
|
9
10
|
ButtonProps as RACButtonProps,
|
|
@@ -12,6 +13,33 @@ import { css, cx } from "styled-system/css";
|
|
|
12
13
|
import { button, ButtonVariantProps } from "styled-system/recipes";
|
|
13
14
|
import { SystemStyleObject } from "styled-system/types";
|
|
14
15
|
import { buttonIcon } from "./button-icon";
|
|
16
|
+
import { uiMessage } from "./messages";
|
|
17
|
+
import { Spinner } from "./Spinner";
|
|
18
|
+
|
|
19
|
+
// A 1em spinner centred over the hidden label. Out of flow, so the label alone
|
|
20
|
+
// sets the button's size; the recipe's base `position: relative` is what it
|
|
21
|
+
// anchors to. Its own component so useIntl runs only while a button is
|
|
22
|
+
// actually loading — a bare Button must keep working without an IntlProvider
|
|
23
|
+
// (test renders commonly lack one).
|
|
24
|
+
const ButtonSpinner = () => {
|
|
25
|
+
const intl = useIntl();
|
|
26
|
+
return (
|
|
27
|
+
<span
|
|
28
|
+
className={css({
|
|
29
|
+
position: "absolute",
|
|
30
|
+
inset: 0,
|
|
31
|
+
display: "flex",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
justifyContent: "center",
|
|
34
|
+
})}
|
|
35
|
+
>
|
|
36
|
+
<Spinner
|
|
37
|
+
aria-label={intl.formatMessage(uiMessage("ui.loading"))}
|
|
38
|
+
css={{ width: "1em", height: "1em" }}
|
|
39
|
+
/>
|
|
40
|
+
</span>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
15
43
|
|
|
16
44
|
export interface ButtonProps
|
|
17
45
|
extends Omit<RACButtonProps, "className" | "children">,
|
|
@@ -19,10 +47,16 @@ export interface ButtonProps
|
|
|
19
47
|
/** Per-instance style overrides, merged after the recipe. */
|
|
20
48
|
css?: SystemStyleObject;
|
|
21
49
|
className?: string;
|
|
22
|
-
/** Icon rendered before the label
|
|
23
|
-
|
|
24
|
-
/** Icon rendered after the label
|
|
25
|
-
|
|
50
|
+
/** Icon rendered before the label. */
|
|
51
|
+
startIcon?: ReactNode;
|
|
52
|
+
/** Icon rendered after the label. */
|
|
53
|
+
endIcon?: ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Show a spinner in place of the label and disable interaction: the label
|
|
56
|
+
* stays in the layout but invisible, so the button keeps its size, and the
|
|
57
|
+
* dimmed disabled look applies.
|
|
58
|
+
*/
|
|
59
|
+
isLoading?: boolean;
|
|
26
60
|
children?: ReactNode;
|
|
27
61
|
}
|
|
28
62
|
|
|
@@ -37,32 +71,51 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
37
71
|
{
|
|
38
72
|
variant,
|
|
39
73
|
size,
|
|
74
|
+
tone,
|
|
40
75
|
css: cssProp,
|
|
41
76
|
className,
|
|
42
|
-
|
|
43
|
-
|
|
77
|
+
startIcon,
|
|
78
|
+
endIcon,
|
|
79
|
+
isLoading,
|
|
44
80
|
children,
|
|
45
81
|
...rest
|
|
46
82
|
},
|
|
47
83
|
ref,
|
|
48
84
|
) {
|
|
85
|
+
const label = (
|
|
86
|
+
<>
|
|
87
|
+
{startIcon ? (
|
|
88
|
+
<span className={buttonIcon({ side: "start" })}>{startIcon}</span>
|
|
89
|
+
) : null}
|
|
90
|
+
{children}
|
|
91
|
+
{endIcon ? (
|
|
92
|
+
<span className={buttonIcon({ side: "end" })}>{endIcon}</span>
|
|
93
|
+
) : null}
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
49
96
|
return (
|
|
50
97
|
<RACButton
|
|
51
98
|
ref={ref}
|
|
52
99
|
className={cx(
|
|
53
|
-
button({ variant, size }),
|
|
100
|
+
button({ variant, size, tone }),
|
|
54
101
|
cssProp ? css(cssProp) : undefined,
|
|
55
102
|
className,
|
|
56
103
|
)}
|
|
104
|
+
data-loading={isLoading ? "" : undefined}
|
|
57
105
|
{...rest}
|
|
106
|
+
isDisabled={isLoading || rest.isDisabled}
|
|
58
107
|
>
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
108
|
+
{isLoading ? (
|
|
109
|
+
<>
|
|
110
|
+
<ButtonSpinner />
|
|
111
|
+
{/* Hidden with opacity, not removed: it keeps the button the size
|
|
112
|
+
it is when idle, so a row of buttons doesn't reflow. Still in
|
|
113
|
+
the accessibility tree, so the button keeps its name. */}
|
|
114
|
+
<span className={css({ opacity: 0 })}>{label}</span>
|
|
115
|
+
</>
|
|
116
|
+
) : (
|
|
117
|
+
label
|
|
118
|
+
)}
|
|
66
119
|
</RACButton>
|
|
67
120
|
);
|
|
68
121
|
},
|
package/src/ButtonGroup.tsx
CHANGED
|
@@ -9,8 +9,8 @@ import { SystemStyleObject } from "styled-system/types";
|
|
|
9
9
|
|
|
10
10
|
export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
11
11
|
/**
|
|
12
|
-
* Square the inner radii so adjacent buttons form one control
|
|
13
|
-
*
|
|
12
|
+
* Square the inner radii so adjacent buttons form one control. Otherwise
|
|
13
|
+
* buttons get a small gap.
|
|
14
14
|
*/
|
|
15
15
|
isAttached?: boolean;
|
|
16
16
|
/** Per-instance style overrides. */
|
|
@@ -20,36 +20,58 @@ export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* ButtonGroup — lays out related buttons in a row. Works with any button
|
|
23
|
-
* elements (shared-ui or native).
|
|
23
|
+
* elements (shared-ui or native).
|
|
24
|
+
*
|
|
25
|
+
* Attached, the buttons divide by a hairline and the group keeps whatever
|
|
26
|
+
* outline the variant draws round the outside, so a 2px `secondary` reads as
|
|
27
|
+
* one bordered control with cells inside it rather than three boxes. A
|
|
28
|
+
* variant with no border of its own (`primary`, `solid`, `neutral`) divides
|
|
29
|
+
* by a gap in the same place, the surface behind the group showing through —
|
|
30
|
+
* see the `[data-attached]` rule in base-preset.ts.
|
|
24
31
|
*/
|
|
25
32
|
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
26
33
|
function ButtonGroup({ isAttached, css: cssProp, className, ...rest }, ref) {
|
|
27
34
|
return (
|
|
28
35
|
<div
|
|
29
36
|
ref={ref}
|
|
37
|
+
data-attached={isAttached ? "" : undefined}
|
|
30
38
|
className={cx(
|
|
31
39
|
css({
|
|
32
40
|
display: "inline-flex",
|
|
33
41
|
alignItems: "center",
|
|
34
|
-
// Buttons are position: relative, so
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
42
|
+
// Buttons are position: relative, so a sibling would paint over
|
|
43
|
+
// the focused button's ring. Only while it shows: a permanently
|
|
44
|
+
// raised child would own the seam it shares, so a hover on its
|
|
45
|
+
// neighbour would stop short of it.
|
|
46
|
+
"& > *": { _focusVisible: { zIndex: 1 } },
|
|
38
47
|
}),
|
|
39
48
|
isAttached
|
|
40
49
|
? css({
|
|
41
50
|
gap: 0,
|
|
42
|
-
// :first-child/:last-child, not
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
51
|
+
// :first-child/:last-child, not -of-type: attached groups can
|
|
52
|
+
// mix element types (e.g. select + button).
|
|
53
|
+
//
|
|
54
|
+
// A hairline whatever the variant's border, which stays as the
|
|
55
|
+
// group's outline — matching it made a 2px seam read as a gap
|
|
56
|
+
// (microbit-foundation/ui#22). Both sides of a seam keep a
|
|
57
|
+
// border and overlap by its width so the two paint as one
|
|
58
|
+
// line; dropping one instead would move that button's border
|
|
59
|
+
// box off the cell you see, and `focusRing` is an outline, so
|
|
60
|
+
// the ring would land on the seam rather than outside it.
|
|
61
|
+
//
|
|
62
|
+
// padding-box so a fill doesn't paint under the transparent
|
|
63
|
+
// seam a borderless variant gets from the preset. Here rather
|
|
64
|
+
// than with that rule because `bg` is the `background`
|
|
65
|
+
// shorthand, which resets it from `recipes`.
|
|
66
|
+
"& > *": { backgroundClip: "padding-box" },
|
|
67
|
+
"& > *:not(:last-child)": {
|
|
46
68
|
borderEndRadius: 0,
|
|
69
|
+
borderInlineEndWidth: "1px",
|
|
47
70
|
},
|
|
48
|
-
"& > *:not(:first-child)
|
|
49
|
-
borderRadius: 0,
|
|
50
|
-
},
|
|
51
|
-
"& > *:not(:first-child):last-child": {
|
|
71
|
+
"& > *:not(:first-child)": {
|
|
52
72
|
borderStartRadius: 0,
|
|
73
|
+
borderInlineStartWidth: "1px",
|
|
74
|
+
marginInlineStart: "-1px",
|
|
53
75
|
},
|
|
54
76
|
})
|
|
55
77
|
: css({ gap: 2 }),
|
package/src/Card.recipe.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Card slot recipe
|
|
10
|
-
* the `elevated` (default) and `outline` variants this app uses.
|
|
9
|
+
* Card slot recipe, with `elevated` (default) and `outline` variants.
|
|
11
10
|
*
|
|
12
11
|
* Registered in the base preset (base-preset.ts); `variant` is
|
|
13
12
|
* forwarded as a runtime prop so the variants are generated via `staticCss`.
|
package/src/Card.tsx
CHANGED
|
@@ -16,7 +16,8 @@ export interface CardProps
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Card —
|
|
19
|
+
* Card — a container styled with the `card` config recipe (md size). Place a
|
|
20
|
+
* CardBody inside.
|
|
20
21
|
*/
|
|
21
22
|
export const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
|
|
22
23
|
{ variant, css: cssProp, className, ...rest },
|
package/src/Checkbox.recipe.ts
CHANGED
|
@@ -6,16 +6,15 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Checkbox slot recipe
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* box from the call site.
|
|
9
|
+
* Checkbox slot recipe with an sm/md/lg size scale. The control's
|
|
10
|
+
* `borderColor: inherit` picks up a `borderColor` set on the root, so call
|
|
11
|
+
* sites can tint the box from there.
|
|
13
12
|
*
|
|
14
13
|
* State styling keys off data attributes stamped on the control by the
|
|
15
14
|
* shared-ui Checkbox (react-aria provides the state via render props).
|
|
16
15
|
*
|
|
17
|
-
* Registered in the base preset (base-preset.ts)
|
|
18
|
-
*
|
|
16
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
17
|
+
* `staticCss` entry that keeps the runtime-prop size variants generated.
|
|
19
18
|
*/
|
|
20
19
|
export const checkbox = defineSlotRecipe({
|
|
21
20
|
className: "checkbox",
|
|
@@ -27,6 +26,11 @@ export const checkbox = defineSlotRecipe({
|
|
|
27
26
|
verticalAlign: "top",
|
|
28
27
|
cursor: "pointer",
|
|
29
28
|
position: "relative",
|
|
29
|
+
// The accessible outline stop (WCAG 1.4.11), as the input recipe.
|
|
30
|
+
// Stated here rather than on the control — whose `borderColor:
|
|
31
|
+
// inherit` reads it — so a call site can still tint the whole control
|
|
32
|
+
// by setting borderColor on the root.
|
|
33
|
+
borderColor: "gray.400",
|
|
30
34
|
"&[data-disabled]": { cursor: "not-allowed" },
|
|
31
35
|
},
|
|
32
36
|
control: {
|
|
@@ -34,11 +38,8 @@ export const checkbox = defineSlotRecipe({
|
|
|
34
38
|
alignItems: "center",
|
|
35
39
|
justifyContent: "center",
|
|
36
40
|
flexShrink: 0,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
transitionProperty: "box-shadow",
|
|
40
|
-
transitionDuration: "normal",
|
|
41
|
-
border: "2px solid",
|
|
41
|
+
borderWidth: "2px",
|
|
42
|
+
borderStyle: "solid",
|
|
42
43
|
borderRadius: "sm",
|
|
43
44
|
borderColor: "inherit",
|
|
44
45
|
color: "white",
|
|
@@ -52,13 +53,23 @@ export const checkbox = defineSlotRecipe({
|
|
|
52
53
|
borderColor: "controlCheckedHoverBg",
|
|
53
54
|
},
|
|
54
55
|
},
|
|
56
|
+
// Disabled greys; the selected block restates _hover so the widened
|
|
57
|
+
// native-:hover condition can't re-tint a disabled control.
|
|
58
|
+
"&[data-disabled]": {
|
|
59
|
+
bg: "gray.100",
|
|
60
|
+
borderColor: "gray.100",
|
|
61
|
+
},
|
|
62
|
+
"&[data-selected][data-disabled]": {
|
|
63
|
+
bg: "gray.200",
|
|
64
|
+
borderColor: "gray.200",
|
|
65
|
+
color: "gray.500",
|
|
66
|
+
_hover: { bg: "gray.200", borderColor: "gray.200" },
|
|
67
|
+
},
|
|
55
68
|
"&[data-focus-visible]": {
|
|
56
|
-
|
|
69
|
+
focusRing: "outline",
|
|
57
70
|
},
|
|
58
71
|
},
|
|
59
72
|
icon: {
|
|
60
|
-
width: "0.75rem",
|
|
61
|
-
height: "0.75rem",
|
|
62
73
|
transitionProperty: "transform",
|
|
63
74
|
transitionDuration: "normal",
|
|
64
75
|
},
|
|
@@ -68,4 +79,28 @@ export const checkbox = defineSlotRecipe({
|
|
|
68
79
|
"&[data-disabled]": { opacity: 0.4 },
|
|
69
80
|
},
|
|
70
81
|
},
|
|
82
|
+
variants: {
|
|
83
|
+
// The icon dimensions are the 1.2em-wide check glyph at each size's icon
|
|
84
|
+
// fontSize (3xs/2xs/2xs), resolved to rem.
|
|
85
|
+
size: {
|
|
86
|
+
sm: {
|
|
87
|
+
control: { width: "3", height: "3" },
|
|
88
|
+
icon: { width: "0.54rem", height: "0.54rem" },
|
|
89
|
+
label: { fontSize: "sm" },
|
|
90
|
+
},
|
|
91
|
+
md: {
|
|
92
|
+
control: { width: "4", height: "4" },
|
|
93
|
+
icon: { width: "0.75rem", height: "0.75rem" },
|
|
94
|
+
label: { fontSize: "md" },
|
|
95
|
+
},
|
|
96
|
+
lg: {
|
|
97
|
+
control: { width: "5", height: "5" },
|
|
98
|
+
icon: { width: "0.75rem", height: "0.75rem" },
|
|
99
|
+
label: { fontSize: "lg" },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
defaultVariants: {
|
|
104
|
+
size: "md",
|
|
105
|
+
},
|
|
71
106
|
});
|
package/src/Checkbox.tsx
CHANGED
|
@@ -3,64 +3,143 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { ReactNode } from "react";
|
|
6
|
+
import { ReactNode, useId } from "react";
|
|
7
7
|
import {
|
|
8
8
|
Checkbox as RACCheckbox,
|
|
9
9
|
CheckboxProps as RACCheckboxProps,
|
|
10
10
|
} from "react-aria-components";
|
|
11
|
+
import { useIntl } from "react-intl";
|
|
11
12
|
import { css, cx } from "styled-system/css";
|
|
12
|
-
import { checkbox } from "styled-system/recipes";
|
|
13
|
+
import { checkbox, CheckboxVariantProps } from "styled-system/recipes";
|
|
13
14
|
import { SystemStyleObject } from "styled-system/types";
|
|
15
|
+
import { FieldHelperText } from "./Field";
|
|
16
|
+
import { uiMessage } from "./messages";
|
|
17
|
+
|
|
18
|
+
/** What a render-prop child is told about the checkbox. */
|
|
19
|
+
export interface CheckboxState {
|
|
20
|
+
isSelected: boolean;
|
|
21
|
+
isFocusVisible: boolean;
|
|
22
|
+
isDisabled: boolean;
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
export interface CheckboxProps
|
|
16
|
-
extends Omit<RACCheckboxProps, "className" | "children" | "style"
|
|
26
|
+
extends Omit<RACCheckboxProps, "className" | "children" | "style">,
|
|
27
|
+
CheckboxVariantProps {
|
|
17
28
|
/** Per-instance style overrides for the root, merged after the recipe. */
|
|
18
29
|
css?: SystemStyleObject;
|
|
19
30
|
className?: string;
|
|
20
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The label. A function receives the checkbox's state, for a label that
|
|
33
|
+
* changes with it.
|
|
34
|
+
*/
|
|
35
|
+
children?: ReactNode | ((state: CheckboxState) => ReactNode);
|
|
36
|
+
/**
|
|
37
|
+
* Whether to draw the box. `false` is for a checkbox whose children draw
|
|
38
|
+
* the selected state themselves — a selectable tile, or an avatar that
|
|
39
|
+
* grows a tick. The label wrapper goes with it, so the children own the
|
|
40
|
+
* whole row, including the focus ring the box would otherwise carry.
|
|
41
|
+
*
|
|
42
|
+
* @default true
|
|
43
|
+
*/
|
|
44
|
+
control?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Help text below the checkbox, wired to its `aria-describedby` — the same
|
|
47
|
+
* chrome the labelled fields' `helperText` renders. With it the component
|
|
48
|
+
* gains a wrapping `<div>`, so the checkbox-plus-text moves as one block.
|
|
49
|
+
*/
|
|
50
|
+
helperText?: ReactNode;
|
|
51
|
+
/** Per-instance style overrides for the helper text. */
|
|
52
|
+
helperTextCss?: SystemStyleObject;
|
|
21
53
|
}
|
|
22
54
|
|
|
23
55
|
/**
|
|
24
|
-
* Checkbox — react-aria-components <Checkbox
|
|
25
|
-
*
|
|
26
|
-
* span for icon-less checkboxes.
|
|
56
|
+
* Checkbox — a styled react-aria-components <Checkbox>. Children render as
|
|
57
|
+
* the label; wrap them in a visually-hidden span for icon-less checkboxes.
|
|
27
58
|
*/
|
|
28
59
|
export const Checkbox = ({
|
|
60
|
+
size,
|
|
29
61
|
css: cssProp,
|
|
30
62
|
className,
|
|
31
63
|
children,
|
|
64
|
+
control,
|
|
65
|
+
helperText,
|
|
66
|
+
helperTextCss,
|
|
32
67
|
...rest
|
|
33
68
|
}: CheckboxProps) => {
|
|
34
|
-
const slots = checkbox();
|
|
35
|
-
|
|
69
|
+
const slots = checkbox({ size });
|
|
70
|
+
const helperId = useId();
|
|
71
|
+
const intl = useIntl();
|
|
72
|
+
const describedBy =
|
|
73
|
+
[rest["aria-describedby"], helperText != null ? helperId : undefined]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join(" ") || undefined;
|
|
76
|
+
// Default name for a row-selection checkbox (a GridList row's). A future
|
|
77
|
+
// Table's select-all header checkbox shares the slot name and will need a
|
|
78
|
+
// different label.
|
|
79
|
+
const ariaLabel =
|
|
80
|
+
rest["aria-label"] ??
|
|
81
|
+
(rest.slot === "selection"
|
|
82
|
+
? intl.formatMessage(uiMessage("ui.select-row-action"))
|
|
83
|
+
: undefined);
|
|
84
|
+
const checkboxElement = (
|
|
36
85
|
<RACCheckbox
|
|
37
86
|
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
38
87
|
{...rest}
|
|
88
|
+
aria-label={ariaLabel}
|
|
89
|
+
aria-describedby={describedBy}
|
|
39
90
|
>
|
|
40
|
-
{({ isSelected, isFocusVisible }) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
91
|
+
{({ isSelected, isFocusVisible, isDisabled }) => {
|
|
92
|
+
const content =
|
|
93
|
+
typeof children === "function"
|
|
94
|
+
? children({ isSelected, isFocusVisible, isDisabled })
|
|
95
|
+
: children;
|
|
96
|
+
if (control === false) {
|
|
97
|
+
return content;
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
<>
|
|
101
|
+
<span
|
|
102
|
+
className={slots.control}
|
|
103
|
+
data-selected={isSelected || undefined}
|
|
104
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
105
|
+
data-disabled={isDisabled || undefined}
|
|
106
|
+
aria-hidden
|
|
107
|
+
>
|
|
108
|
+
{isSelected && (
|
|
109
|
+
<svg viewBox="0 0 12 10" className={slots.icon} aria-hidden>
|
|
110
|
+
<polyline
|
|
111
|
+
points="1.5 6 4.5 9 10.5 1"
|
|
112
|
+
fill="none"
|
|
113
|
+
stroke="currentColor"
|
|
114
|
+
strokeWidth="2"
|
|
115
|
+
strokeLinecap="round"
|
|
116
|
+
strokeLinejoin="round"
|
|
117
|
+
/>
|
|
118
|
+
</svg>
|
|
119
|
+
)}
|
|
120
|
+
</span>
|
|
121
|
+
{content != null && (
|
|
122
|
+
<span
|
|
123
|
+
className={slots.label}
|
|
124
|
+
data-disabled={isDisabled || undefined}
|
|
125
|
+
>
|
|
126
|
+
{content}
|
|
127
|
+
</span>
|
|
59
128
|
)}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)}
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}}
|
|
64
132
|
</RACCheckbox>
|
|
65
133
|
);
|
|
134
|
+
if (helperText == null) {
|
|
135
|
+
return checkboxElement;
|
|
136
|
+
}
|
|
137
|
+
return (
|
|
138
|
+
<div>
|
|
139
|
+
{checkboxElement}
|
|
140
|
+
<FieldHelperText id={helperId} css={helperTextCss}>
|
|
141
|
+
{helperText}
|
|
142
|
+
</FieldHelperText>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
66
145
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
CheckboxGroup as RACCheckboxGroup,
|
|
9
|
+
CheckboxGroupProps as RACCheckboxGroupProps,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import { css, cx } from "styled-system/css";
|
|
12
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
13
|
+
import { FieldLabel, FieldSupport, FieldSupportProps } from "./Field";
|
|
14
|
+
|
|
15
|
+
export interface CheckboxGroupProps
|
|
16
|
+
extends Omit<RACCheckboxGroupProps, "className" | "style">,
|
|
17
|
+
FieldSupportProps {
|
|
18
|
+
/**
|
|
19
|
+
* Visible label for the group. Use `aria-label` instead where the design
|
|
20
|
+
* has none.
|
|
21
|
+
*/
|
|
22
|
+
label?: ReactNode;
|
|
23
|
+
/** Label style overrides. */
|
|
24
|
+
labelCss?: SystemStyleObject;
|
|
25
|
+
/** Per-instance style overrides. */
|
|
26
|
+
css?: SystemStyleObject;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* CheckboxGroup — react-aria-components <CheckboxGroup> for a set of
|
|
32
|
+
* Checkboxes sharing one value array (each Checkbox's `value` marks its
|
|
33
|
+
* entry). Beyond the optional field chrome (label/helperText/errorMessage)
|
|
34
|
+
* it carries no styling of its own: compose with Stack for layout, as
|
|
35
|
+
* RadioGroup does.
|
|
36
|
+
*/
|
|
37
|
+
export const CheckboxGroup = ({
|
|
38
|
+
label,
|
|
39
|
+
labelCss,
|
|
40
|
+
helperText,
|
|
41
|
+
errorMessage,
|
|
42
|
+
helperTextCss,
|
|
43
|
+
css: cssProp,
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
...rest
|
|
47
|
+
}: CheckboxGroupProps) => {
|
|
48
|
+
return (
|
|
49
|
+
<RACCheckboxGroup
|
|
50
|
+
className={cx(cssProp ? css(cssProp) : undefined, className)}
|
|
51
|
+
{...rest}
|
|
52
|
+
>
|
|
53
|
+
{(renderProps) => (
|
|
54
|
+
<>
|
|
55
|
+
{label != null && (
|
|
56
|
+
<FieldLabel isRequired={rest.isRequired} css={labelCss}>
|
|
57
|
+
{label}
|
|
58
|
+
</FieldLabel>
|
|
59
|
+
)}
|
|
60
|
+
{typeof children === "function" ? children(renderProps) : children}
|
|
61
|
+
<FieldSupport
|
|
62
|
+
helperText={helperText}
|
|
63
|
+
errorMessage={errorMessage}
|
|
64
|
+
helperTextCss={helperTextCss}
|
|
65
|
+
/>
|
|
66
|
+
</>
|
|
67
|
+
)}
|
|
68
|
+
</RACCheckboxGroup>
|
|
69
|
+
);
|
|
70
|
+
};
|
package/src/CloseButton.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { CloseIcon } from "./CloseIcon";
|
|
|
10
10
|
|
|
11
11
|
export interface CloseButtonProps
|
|
12
12
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> {
|
|
13
|
-
/**
|
|
13
|
+
/** sm is a 24px box, md 32px (default). */
|
|
14
14
|
size?: "sm" | "md";
|
|
15
15
|
/**
|
|
16
16
|
* Grow the touch target 8px beyond the visible button on every side via an
|
|
@@ -24,7 +24,7 @@ export interface CloseButtonProps
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* CloseButton —
|
|
27
|
+
* CloseButton — a standalone X button. A plain button (not react-aria)
|
|
28
28
|
* so call sites can extend the hit area with pseudo-elements, which
|
|
29
29
|
* react-aria's press bounding-rect check would defeat.
|
|
30
30
|
*/
|
|
@@ -57,7 +57,7 @@ export const CloseButton = forwardRef<HTMLButtonElement, CloseButtonProps>(
|
|
|
57
57
|
transitionDuration: "normal",
|
|
58
58
|
_hover: { bg: "blackAlpha.100" },
|
|
59
59
|
_active: { bg: "blackAlpha.200" },
|
|
60
|
-
_focusVisible: {
|
|
60
|
+
_focusVisible: { focusRing: "outline" },
|
|
61
61
|
},
|
|
62
62
|
size === "sm"
|
|
63
63
|
? { width: "6", height: "6", fontSize: "2xs" }
|
package/src/CloseIcon.tsx
CHANGED
|
@@ -12,10 +12,12 @@ export interface CloseIconProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* CloseIcon — the "✕" glyph used on close buttons (dialogs, toasts).
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* CloseIcon — the "✕" glyph used on close buttons (dialogs, toasts). Sized to
|
|
16
|
+
* `1em` and `fill: currentColor` like the other icons. Reused wherever a
|
|
17
|
+
* close control is needed.
|
|
18
|
+
*
|
|
19
|
+
* The path is Chakra UI's CloseButton icon, inlined for visual parity with
|
|
20
|
+
* the apps' original look (see the notice in LICENSE.md).
|
|
19
21
|
*/
|
|
20
22
|
export const CloseIcon = ({ css: cssProp, className }: CloseIconProps) => (
|
|
21
23
|
<svg
|
package/src/Code.tsx
CHANGED