@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.
- package/LICENSE.md +21 -0
- package/README.md +118 -0
- package/lang/ui.ca.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.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-tw.json +22 -0
- package/package.json +54 -0
- package/src/Button.recipe.ts +162 -0
- package/src/Button.tsx +84 -0
- package/src/ButtonGroup.tsx +63 -0
- package/src/Card.recipe.ts +49 -0
- package/src/Card.tsx +56 -0
- package/src/Checkbox.recipe.ts +71 -0
- package/src/Checkbox.tsx +66 -0
- package/src/CloseButton.tsx +87 -0
- package/src/CloseIcon.tsx +40 -0
- package/src/Divider.tsx +21 -0
- package/src/Drawer.recipe.ts +98 -0
- package/src/Drawer.tsx +138 -0
- package/src/Heading.recipe.ts +52 -0
- package/src/Heading.tsx +14 -0
- package/src/Icon.tsx +55 -0
- package/src/IconButton.tsx +39 -0
- package/src/Image.tsx +11 -0
- package/src/Input.recipe.ts +63 -0
- package/src/Input.tsx +33 -0
- package/src/InputGroup.tsx +48 -0
- package/src/Link.tsx +23 -0
- package/src/LinkBox.tsx +88 -0
- package/src/List.tsx +27 -0
- package/src/Menu.recipe.ts +88 -0
- package/src/Menu.tsx +172 -0
- package/src/Modal.recipe.ts +163 -0
- package/src/Modal.tsx +265 -0
- package/src/NativeSelect.tsx +76 -0
- package/src/PopoverArrow.tsx +52 -0
- package/src/ProgressBar.tsx +61 -0
- package/src/SharedUIProvider.tsx +55 -0
- package/src/Slide.tsx +54 -0
- package/src/Slider.recipe.ts +91 -0
- package/src/Slider.tsx +99 -0
- package/src/Spinner.tsx +69 -0
- package/src/Svg.tsx +23 -0
- package/src/Switch.recipe.ts +80 -0
- package/src/Switch.tsx +60 -0
- package/src/Text.tsx +12 -0
- package/src/TextField.recipe.ts +54 -0
- package/src/TextField.tsx +72 -0
- package/src/Toast.recipe.ts +98 -0
- package/src/Toast.tsx +165 -0
- package/src/Tooltip.tsx +91 -0
- package/src/UnmountCallback.tsx +18 -0
- package/src/VisuallyHidden.tsx +14 -0
- package/src/base-preset.ts +278 -0
- package/src/chakra-tokens.ts +1014 -0
- package/src/hooks/useBreakpointValue.ts +63 -0
- package/src/index.ts +45 -0
- package/src/messages.ts +17 -0
- package/src/system.ts +36 -0
package/src/Button.tsx
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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, cva, cx } from "styled-system/css";
|
|
12
|
+
import { button, ButtonVariantProps } from "styled-system/recipes";
|
|
13
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
14
|
+
|
|
15
|
+
// Chakra's ButtonIcon: keeps the glyph centred and spaced from the label
|
|
16
|
+
// (iconSpacing 0.5rem).
|
|
17
|
+
const buttonIcon = cva({
|
|
18
|
+
base: {
|
|
19
|
+
display: "inline-flex",
|
|
20
|
+
alignSelf: "center",
|
|
21
|
+
flexShrink: 0,
|
|
22
|
+
},
|
|
23
|
+
variants: {
|
|
24
|
+
side: {
|
|
25
|
+
left: { marginEnd: "2" },
|
|
26
|
+
right: { marginStart: "2" },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export interface ButtonProps
|
|
32
|
+
extends Omit<RACButtonProps, "className" | "children">,
|
|
33
|
+
ButtonVariantProps {
|
|
34
|
+
/** Per-instance style overrides, merged after the recipe. */
|
|
35
|
+
css?: SystemStyleObject;
|
|
36
|
+
className?: string;
|
|
37
|
+
/** Icon rendered before the label, matching Chakra's `leftIcon`. */
|
|
38
|
+
leftIcon?: ReactNode;
|
|
39
|
+
/** Icon rendered after the label, matching Chakra's `rightIcon`. */
|
|
40
|
+
rightIcon?: ReactNode;
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Button — react-aria-components <Button> styled with the `button` config
|
|
46
|
+
* recipe. The recipe's interaction states are driven by RAC's data attributes
|
|
47
|
+
* (see the preset's widened conditions), so disabled/hover/press/focus all work
|
|
48
|
+
* without extra wiring.
|
|
49
|
+
*/
|
|
50
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
51
|
+
function Button(
|
|
52
|
+
{
|
|
53
|
+
variant,
|
|
54
|
+
size,
|
|
55
|
+
css: cssProp,
|
|
56
|
+
className,
|
|
57
|
+
leftIcon,
|
|
58
|
+
rightIcon,
|
|
59
|
+
children,
|
|
60
|
+
...rest
|
|
61
|
+
},
|
|
62
|
+
ref,
|
|
63
|
+
) {
|
|
64
|
+
return (
|
|
65
|
+
<RACButton
|
|
66
|
+
ref={ref}
|
|
67
|
+
className={cx(
|
|
68
|
+
button({ variant, size }),
|
|
69
|
+
cssProp ? css(cssProp) : undefined,
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
{...rest}
|
|
73
|
+
>
|
|
74
|
+
{leftIcon ? (
|
|
75
|
+
<span className={buttonIcon({ side: "left" })}>{leftIcon}</span>
|
|
76
|
+
) : null}
|
|
77
|
+
{children}
|
|
78
|
+
{rightIcon ? (
|
|
79
|
+
<span className={buttonIcon({ side: "right" })}>{rightIcon}</span>
|
|
80
|
+
) : null}
|
|
81
|
+
</RACButton>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
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's Button applied zIndex 1 on focus when grouped).
|
|
37
|
+
"& > *": { _focus: { zIndex: 1 } },
|
|
38
|
+
}),
|
|
39
|
+
isAttached
|
|
40
|
+
? css({
|
|
41
|
+
gap: 0,
|
|
42
|
+
// :first-child/:last-child, not :first-of-type: attached
|
|
43
|
+
// groups can mix element types (e.g. select + button), and
|
|
44
|
+
// -of-type matches per element type.
|
|
45
|
+
"& > *:first-child:not(:last-child)": {
|
|
46
|
+
borderEndRadius: 0,
|
|
47
|
+
},
|
|
48
|
+
"& > *:not(:first-child):not(:last-child)": {
|
|
49
|
+
borderRadius: 0,
|
|
50
|
+
},
|
|
51
|
+
"& > *:not(:first-child):last-child": {
|
|
52
|
+
borderStartRadius: 0,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
: css({ gap: 2 }),
|
|
56
|
+
cssProp ? css(cssProp) : undefined,
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
{...rest}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
@@ -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,71 @@
|
|
|
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 md checkbox with the default blue
|
|
10
|
+
* colorScheme (light mode). The control's `borderColor: inherit` picks up a
|
|
11
|
+
* `borderColor` set on the root, matching Chakra's convention for tinting the
|
|
12
|
+
* 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). No variants, so it
|
|
18
|
+
* needs no `staticCss` entry.
|
|
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
|
+
"&[data-disabled]": { cursor: "not-allowed" },
|
|
31
|
+
},
|
|
32
|
+
control: {
|
|
33
|
+
display: "inline-flex",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
flexShrink: 0,
|
|
37
|
+
width: "4",
|
|
38
|
+
height: "4",
|
|
39
|
+
transitionProperty: "box-shadow",
|
|
40
|
+
transitionDuration: "normal",
|
|
41
|
+
border: "2px solid",
|
|
42
|
+
borderRadius: "sm",
|
|
43
|
+
borderColor: "inherit",
|
|
44
|
+
color: "white",
|
|
45
|
+
bg: "white",
|
|
46
|
+
"&[data-selected]": {
|
|
47
|
+
bg: "controlCheckedBg",
|
|
48
|
+
borderColor: "controlCheckedBg",
|
|
49
|
+
color: "white",
|
|
50
|
+
_hover: {
|
|
51
|
+
bg: "controlCheckedHoverBg",
|
|
52
|
+
borderColor: "controlCheckedHoverBg",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
"&[data-focus-visible]": {
|
|
56
|
+
focusShadow: "outline",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
icon: {
|
|
60
|
+
width: "0.75rem",
|
|
61
|
+
height: "0.75rem",
|
|
62
|
+
transitionProperty: "transform",
|
|
63
|
+
transitionDuration: "normal",
|
|
64
|
+
},
|
|
65
|
+
label: {
|
|
66
|
+
userSelect: "none",
|
|
67
|
+
marginStart: "2",
|
|
68
|
+
"&[data-disabled]": { opacity: 0.4 },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
package/src/Checkbox.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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 } from "styled-system/recipes";
|
|
13
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
14
|
+
|
|
15
|
+
export interface CheckboxProps
|
|
16
|
+
extends Omit<RACCheckboxProps, "className" | "children" | "style"> {
|
|
17
|
+
/** Per-instance style overrides for the root, merged after the recipe. */
|
|
18
|
+
css?: SystemStyleObject;
|
|
19
|
+
className?: string;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Checkbox — react-aria-components <Checkbox> styled like Chakra's md
|
|
25
|
+
* checkbox. Children render as the label; wrap them in a visually-hidden
|
|
26
|
+
* span for icon-less checkboxes.
|
|
27
|
+
*/
|
|
28
|
+
export const Checkbox = ({
|
|
29
|
+
css: cssProp,
|
|
30
|
+
className,
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}: CheckboxProps) => {
|
|
34
|
+
const slots = checkbox();
|
|
35
|
+
return (
|
|
36
|
+
<RACCheckbox
|
|
37
|
+
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
{({ isSelected, isFocusVisible }) => (
|
|
41
|
+
<>
|
|
42
|
+
<span
|
|
43
|
+
className={slots.control}
|
|
44
|
+
data-selected={isSelected || undefined}
|
|
45
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
46
|
+
aria-hidden
|
|
47
|
+
>
|
|
48
|
+
{isSelected && (
|
|
49
|
+
<svg viewBox="0 0 12 10" className={slots.icon} aria-hidden>
|
|
50
|
+
<polyline
|
|
51
|
+
points="1.5 6 4.5 9 10.5 1"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth="2"
|
|
55
|
+
strokeLinecap="round"
|
|
56
|
+
strokeLinejoin="round"
|
|
57
|
+
/>
|
|
58
|
+
</svg>
|
|
59
|
+
)}
|
|
60
|
+
</span>
|
|
61
|
+
{children != null && <span className={slots.label}>{children}</span>}
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
</RACCheckbox>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { ButtonHTMLAttributes, forwardRef } from "react";
|
|
7
|
+
import { css, cx } from "styled-system/css";
|
|
8
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
9
|
+
import { CloseIcon } from "./CloseIcon";
|
|
10
|
+
|
|
11
|
+
export interface CloseButtonProps
|
|
12
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> {
|
|
13
|
+
/** Chakra CloseButton sizes: sm 24px box, md 32px (default). */
|
|
14
|
+
size?: "sm" | "md";
|
|
15
|
+
/**
|
|
16
|
+
* Grow the touch target 8px beyond the visible button on every side via an
|
|
17
|
+
* ::after overlay.
|
|
18
|
+
*/
|
|
19
|
+
expandHitArea?: boolean;
|
|
20
|
+
"aria-label": string;
|
|
21
|
+
/** Per-instance style overrides, merged after the base. */
|
|
22
|
+
css?: SystemStyleObject;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CloseButton — Chakra's standalone X button. A plain button (not react-aria)
|
|
28
|
+
* so call sites can extend the hit area with pseudo-elements, which
|
|
29
|
+
* react-aria's press bounding-rect check would defeat.
|
|
30
|
+
*/
|
|
31
|
+
export const CloseButton = forwardRef<HTMLButtonElement, CloseButtonProps>(
|
|
32
|
+
function CloseButton(
|
|
33
|
+
{ size = "md", expandHitArea = false, css: cssProp, className, ...rest },
|
|
34
|
+
ref,
|
|
35
|
+
) {
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
ref={ref}
|
|
39
|
+
type="button"
|
|
40
|
+
className={cx(
|
|
41
|
+
// A single css() call so per-instance overrides of base properties
|
|
42
|
+
// (e.g. borderRadius) merge rather than racing on stylesheet order.
|
|
43
|
+
css(
|
|
44
|
+
{
|
|
45
|
+
display: "inline-flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
flexShrink: 0,
|
|
49
|
+
position: "relative",
|
|
50
|
+
cursor: "pointer",
|
|
51
|
+
bg: "transparent",
|
|
52
|
+
border: "none",
|
|
53
|
+
color: "inherit",
|
|
54
|
+
outline: "none",
|
|
55
|
+
borderRadius: "md",
|
|
56
|
+
transitionProperty: "background-color, box-shadow",
|
|
57
|
+
transitionDuration: "normal",
|
|
58
|
+
_hover: { bg: "blackAlpha.100" },
|
|
59
|
+
_active: { bg: "blackAlpha.200" },
|
|
60
|
+
_focusVisible: { focusShadow: "outline" },
|
|
61
|
+
},
|
|
62
|
+
size === "sm"
|
|
63
|
+
? { width: "6", height: "6", fontSize: "2xs" }
|
|
64
|
+
: { width: "8", height: "8", fontSize: "xs" },
|
|
65
|
+
expandHitArea
|
|
66
|
+
? {
|
|
67
|
+
_after: {
|
|
68
|
+
position: "absolute",
|
|
69
|
+
top: -2,
|
|
70
|
+
right: -2,
|
|
71
|
+
bottom: -2,
|
|
72
|
+
left: -2,
|
|
73
|
+
content: '""',
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
: undefined,
|
|
77
|
+
cssProp,
|
|
78
|
+
),
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
{...rest}
|
|
82
|
+
>
|
|
83
|
+
<CloseIcon />
|
|
84
|
+
</button>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { css, cx } from "styled-system/css";
|
|
7
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
8
|
+
|
|
9
|
+
export interface CloseIconProps {
|
|
10
|
+
css?: SystemStyleObject;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CloseIcon — the "✕" glyph used on close buttons (dialogs, toasts). This is
|
|
16
|
+
* Chakra's exact CloseButton path so the visual matches; sized to `1em` and
|
|
17
|
+
* `fill: currentColor` like the other icons. Reused wherever a close control
|
|
18
|
+
* is needed.
|
|
19
|
+
*/
|
|
20
|
+
export const CloseIcon = ({ css: cssProp, className }: CloseIconProps) => (
|
|
21
|
+
<svg
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
focusable="false"
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
className={cx(
|
|
26
|
+
css({
|
|
27
|
+
width: "1em",
|
|
28
|
+
height: "1em",
|
|
29
|
+
display: "inline-block",
|
|
30
|
+
lineHeight: "1em",
|
|
31
|
+
flexShrink: 0,
|
|
32
|
+
fill: "currentColor",
|
|
33
|
+
...cssProp,
|
|
34
|
+
}),
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<path d="M.439,21.44a1.5,1.5,0,0,0,2.122,2.121L11.823,14.3a.25.25,0,0,1,.354,0l9.262,9.263a1.5,1.5,0,1,0,2.122-2.121L14.3,12.177a.25.25,0,0,1,0-.354l9.263-9.262A1.5,1.5,0,0,0,21.439.44L12.177,9.7a.25.25,0,0,1-.354,0L2.561.44A1.5,1.5,0,0,0,.439,2.561L9.7,11.823a.25.25,0,0,1,0,.354Z" />
|
|
39
|
+
</svg>
|
|
40
|
+
);
|
package/src/Divider.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { styled } from "styled-system/jsx";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Divider — a horizontal rule matching Chakra's <Divider> (hairline at 60%
|
|
10
|
+
* opacity; set `borderColor` to tint). Horizontal only.
|
|
11
|
+
*/
|
|
12
|
+
export const Divider = styled("hr", {
|
|
13
|
+
base: {
|
|
14
|
+
border: 0,
|
|
15
|
+
borderBottomWidth: "1px",
|
|
16
|
+
borderBottomStyle: "solid",
|
|
17
|
+
borderColor: "gray.200",
|
|
18
|
+
opacity: 0.6,
|
|
19
|
+
width: "100%",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
* Drawer slot recipe — Chakra's default Drawer parts (light mode) at its
|
|
10
|
+
* default `xs` size (20rem panel). Consumed by the shared-ui Drawer, which maps
|
|
11
|
+
* the slots onto react-aria-components' ModalOverlay / Modal / Dialog.
|
|
12
|
+
*
|
|
13
|
+
* The enter transition approximates Chakra's slide spring (damping 25,
|
|
14
|
+
* stiffness 180) with a decelerating tween; exit matches its 0.15s ease-in-out
|
|
15
|
+
* tween.
|
|
16
|
+
*
|
|
17
|
+
* Registered in the base preset (base-preset.ts); `placement` is
|
|
18
|
+
* forwarded as a runtime prop so the variants are generated via `staticCss`.
|
|
19
|
+
*/
|
|
20
|
+
export const drawer = defineSlotRecipe({
|
|
21
|
+
className: "drawer",
|
|
22
|
+
slots: ["overlay", "content", "inner", "header", "body"],
|
|
23
|
+
base: {
|
|
24
|
+
overlay: {
|
|
25
|
+
position: "fixed",
|
|
26
|
+
inset: 0,
|
|
27
|
+
w: "100%",
|
|
28
|
+
h: "100%",
|
|
29
|
+
bg: "blackAlpha.600",
|
|
30
|
+
zIndex: "modal",
|
|
31
|
+
opacity: 1,
|
|
32
|
+
transition: "opacity 0.2s ease-out",
|
|
33
|
+
"&[data-entering]": { opacity: 0 },
|
|
34
|
+
"&[data-exiting]": { opacity: 0 },
|
|
35
|
+
_motionReduce: { transition: "none" },
|
|
36
|
+
},
|
|
37
|
+
content: {
|
|
38
|
+
position: "fixed",
|
|
39
|
+
top: 0,
|
|
40
|
+
bottom: 0,
|
|
41
|
+
width: "100%",
|
|
42
|
+
maxWidth: "xs",
|
|
43
|
+
maxH: "100dvh",
|
|
44
|
+
color: "inherit",
|
|
45
|
+
bg: "white",
|
|
46
|
+
boxShadow: "lg",
|
|
47
|
+
display: "flex",
|
|
48
|
+
flexDirection: "column",
|
|
49
|
+
outline: "none",
|
|
50
|
+
zIndex: "modal",
|
|
51
|
+
transform: "translateX(0)",
|
|
52
|
+
transition: "transform 0.3s cubic-bezier(0, 0, 0.2, 1)",
|
|
53
|
+
"&[data-exiting]": {
|
|
54
|
+
transition: "transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
55
|
+
},
|
|
56
|
+
_motionReduce: { transition: "none" },
|
|
57
|
+
},
|
|
58
|
+
inner: {
|
|
59
|
+
outline: "none",
|
|
60
|
+
display: "flex",
|
|
61
|
+
flexDirection: "column",
|
|
62
|
+
width: "100%",
|
|
63
|
+
height: "100%",
|
|
64
|
+
},
|
|
65
|
+
header: {
|
|
66
|
+
px: "6",
|
|
67
|
+
py: "4",
|
|
68
|
+
fontSize: "xl",
|
|
69
|
+
fontWeight: "semibold",
|
|
70
|
+
flexShrink: 0,
|
|
71
|
+
},
|
|
72
|
+
body: {
|
|
73
|
+
px: "6",
|
|
74
|
+
py: "2",
|
|
75
|
+
flex: "1",
|
|
76
|
+
overflow: "auto",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
variants: {
|
|
80
|
+
placement: {
|
|
81
|
+
left: {
|
|
82
|
+
content: {
|
|
83
|
+
left: 0,
|
|
84
|
+
"&[data-entering]": { transform: "translateX(-100%)" },
|
|
85
|
+
"&[data-exiting]": { transform: "translateX(-100%)" },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
right: {
|
|
89
|
+
content: {
|
|
90
|
+
right: 0,
|
|
91
|
+
"&[data-entering]": { transform: "translateX(100%)" },
|
|
92
|
+
"&[data-exiting]": { transform: "translateX(100%)" },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
defaultVariants: { placement: "left" },
|
|
98
|
+
});
|