@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,62 @@
|
|
|
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
|
+
* InputGroup — relative wrapper for an Input with addon elements. Pad the
|
|
10
|
+
* input (`pl`/`pr`) to make room for the elements. Replaces Chakra's
|
|
11
|
+
* InputGroup.
|
|
12
|
+
*/
|
|
13
|
+
export const InputGroup = styled("div", {
|
|
14
|
+
base: { position: "relative", width: "100%", display: "flex" },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// The element styles are written out twice rather than shared via a spread:
|
|
18
|
+
// Panda's extractor only reliably evaluates inline literals.
|
|
19
|
+
// The size variants are square boxes matching the input recipe's height per
|
|
20
|
+
// size, as in Chakra; pass the same `size` as the grouped Input.
|
|
21
|
+
|
|
22
|
+
/** Element overlaying the start of an InputGroup (Chakra InputLeftElement). */
|
|
23
|
+
export const InputLeftElement = styled("div", {
|
|
24
|
+
base: {
|
|
25
|
+
position: "absolute",
|
|
26
|
+
top: 0,
|
|
27
|
+
left: 0,
|
|
28
|
+
display: "flex",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
justifyContent: "center",
|
|
31
|
+
zIndex: 2,
|
|
32
|
+
},
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
lg: { width: "12", height: "12" },
|
|
36
|
+
md: { width: "10", height: "10" },
|
|
37
|
+
sm: { width: "8", height: "8" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: { size: "md" },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** Element overlaying the end of an InputGroup (Chakra InputRightElement). */
|
|
44
|
+
export const InputRightElement = styled("div", {
|
|
45
|
+
base: {
|
|
46
|
+
position: "absolute",
|
|
47
|
+
top: 0,
|
|
48
|
+
right: 0,
|
|
49
|
+
display: "flex",
|
|
50
|
+
alignItems: "center",
|
|
51
|
+
justifyContent: "center",
|
|
52
|
+
zIndex: 2,
|
|
53
|
+
},
|
|
54
|
+
variants: {
|
|
55
|
+
size: {
|
|
56
|
+
lg: { width: "12", height: "12" },
|
|
57
|
+
md: { width: "10", height: "10" },
|
|
58
|
+
sm: { width: "8", height: "8" },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
defaultVariants: { size: "md" },
|
|
62
|
+
});
|
package/src/Kbd.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
* Kbd — keyboard-key chip matching Chakra's <Kbd> (light mode).
|
|
10
|
+
*/
|
|
11
|
+
export const Kbd = styled("kbd", {
|
|
12
|
+
base: {
|
|
13
|
+
bg: "gray.100",
|
|
14
|
+
borderRadius: "md",
|
|
15
|
+
borderWidth: "1px",
|
|
16
|
+
borderStyle: "solid",
|
|
17
|
+
borderColor: "gray.200",
|
|
18
|
+
borderBottomWidth: "3px",
|
|
19
|
+
fontFamily: "mono",
|
|
20
|
+
fontSize: "0.8em",
|
|
21
|
+
fontWeight: "bold",
|
|
22
|
+
lineHeight: "normal",
|
|
23
|
+
px: "0.4em",
|
|
24
|
+
whiteSpace: "nowrap",
|
|
25
|
+
},
|
|
26
|
+
});
|
package/src/Link.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* Link — anchor styled to match Chakra's <Link> base (no underline until hover,
|
|
10
|
+
* focus ring on keyboard focus). Accepts Panda style props for colour etc.
|
|
11
|
+
*/
|
|
12
|
+
export const Link = styled("a", {
|
|
13
|
+
base: {
|
|
14
|
+
cursor: "pointer",
|
|
15
|
+
textDecoration: "none",
|
|
16
|
+
outline: "none",
|
|
17
|
+
transitionProperty:
|
|
18
|
+
"background-color, border-color, color, fill, stroke, opacity, box-shadow, transform",
|
|
19
|
+
transitionDuration: "normal",
|
|
20
|
+
_hover: { textDecoration: "underline" },
|
|
21
|
+
_focusVisible: { focusShadow: "outline" },
|
|
22
|
+
},
|
|
23
|
+
});
|
package/src/LinkBox.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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 { styled } from "styled-system/jsx";
|
|
9
|
+
import { button } from "styled-system/recipes";
|
|
10
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* LinkBox — makes a whole box clickable via a nested LinkOverlay (or any
|
|
14
|
+
* element with an inset `_before` overlay). Other interactive children must
|
|
15
|
+
* be raised with `zIndex: 1`. Replaces Chakra's <LinkBox>.
|
|
16
|
+
*/
|
|
17
|
+
export const LinkBox = styled("div", {
|
|
18
|
+
base: { position: "relative" },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* LinkOverlay — an anchor whose `::before` stretches over the nearest
|
|
23
|
+
* LinkBox, making the whole box its hit area. For button-driven overlays,
|
|
24
|
+
* apply the same `_before` inset style to a Button instead.
|
|
25
|
+
*/
|
|
26
|
+
export const LinkOverlay = styled("a", {
|
|
27
|
+
base: {
|
|
28
|
+
position: "static",
|
|
29
|
+
_before: {
|
|
30
|
+
content: '""',
|
|
31
|
+
cursor: "inherit",
|
|
32
|
+
display: "block",
|
|
33
|
+
position: "absolute",
|
|
34
|
+
top: 0,
|
|
35
|
+
left: 0,
|
|
36
|
+
zIndex: 0,
|
|
37
|
+
width: "100%",
|
|
38
|
+
height: "100%",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export interface LinkOverlayButtonProps
|
|
44
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> {
|
|
45
|
+
/** Per-instance style overrides, merged after the base. */
|
|
46
|
+
css?: SystemStyleObject;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* LinkOverlayButton — the button flavour of LinkOverlay: its `::before`
|
|
52
|
+
* stretches over the nearest LinkBox so the whole box is clickable.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately a plain button, not a react-aria one: react-aria's usePress
|
|
55
|
+
* cancels presses that land outside the button's bounding rect, which defeats
|
|
56
|
+
* the overlay. `position: static` overrides the button recipe's base so the
|
|
57
|
+
* overlay anchors to the LinkBox (Chakra's LinkOverlay did the same over its
|
|
58
|
+
* Button).
|
|
59
|
+
*/
|
|
60
|
+
export const LinkOverlayButton = forwardRef<
|
|
61
|
+
HTMLButtonElement,
|
|
62
|
+
LinkOverlayButtonProps
|
|
63
|
+
>(function LinkOverlayButton({ css: cssProp, className, ...rest }, ref) {
|
|
64
|
+
return (
|
|
65
|
+
<button
|
|
66
|
+
ref={ref}
|
|
67
|
+
type="button"
|
|
68
|
+
className={cx(
|
|
69
|
+
button({ variant: "unstyled" }),
|
|
70
|
+
css({
|
|
71
|
+
cursor: "pointer",
|
|
72
|
+
position: "static",
|
|
73
|
+
_before: {
|
|
74
|
+
content: '""',
|
|
75
|
+
cursor: "pointer",
|
|
76
|
+
display: "block",
|
|
77
|
+
position: "absolute",
|
|
78
|
+
inset: 0,
|
|
79
|
+
zIndex: 0,
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
cssProp ? css(cssProp) : undefined,
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
{...rest}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
Link as RACLink,
|
|
9
|
+
LinkProps as RACLinkProps,
|
|
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 LinkButtonProps
|
|
17
|
+
extends Omit<RACLinkProps, "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
|
+
// Anchors pick up underline styling that buttons never have.
|
|
30
|
+
const linkReset = css.raw({
|
|
31
|
+
textDecoration: "none",
|
|
32
|
+
_hover: { textDecoration: "none" },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* LinkButton — a navigation link that looks like a Button (Chakra's
|
|
37
|
+
* `Button as="a"`). react-aria-components <Link> renders a real anchor
|
|
38
|
+
* (`href`, `target`, new-tab/middle-click semantics preserved) with the same
|
|
39
|
+
* interaction data attributes as Button, so the `button` recipe's
|
|
40
|
+
* hover/press/focus/disabled states apply unchanged.
|
|
41
|
+
*
|
|
42
|
+
* Use for navigation that should read as a call to action (e.g. an external
|
|
43
|
+
* help page presented as a dialog's primary action); use Button for
|
|
44
|
+
* in-page actions.
|
|
45
|
+
*/
|
|
46
|
+
export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
|
|
47
|
+
function LinkButton(
|
|
48
|
+
{
|
|
49
|
+
variant,
|
|
50
|
+
size,
|
|
51
|
+
css: cssProp,
|
|
52
|
+
className,
|
|
53
|
+
leftIcon,
|
|
54
|
+
rightIcon,
|
|
55
|
+
children,
|
|
56
|
+
...rest
|
|
57
|
+
},
|
|
58
|
+
ref,
|
|
59
|
+
) {
|
|
60
|
+
return (
|
|
61
|
+
<RACLink
|
|
62
|
+
ref={ref}
|
|
63
|
+
className={cx(
|
|
64
|
+
button({ variant, size }),
|
|
65
|
+
css(linkReset, cssProp),
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...rest}
|
|
69
|
+
>
|
|
70
|
+
{leftIcon ? (
|
|
71
|
+
<span className={buttonIcon({ side: "left" })}>{leftIcon}</span>
|
|
72
|
+
) : null}
|
|
73
|
+
{children}
|
|
74
|
+
{rightIcon ? (
|
|
75
|
+
<span className={buttonIcon({ side: "right" })}>{rightIcon}</span>
|
|
76
|
+
) : null}
|
|
77
|
+
</RACLink>
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
);
|
package/src/List.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
* Unstyled list, matching Chakra's <List>. The marker removal is explicit —
|
|
10
|
+
* Chakra's List set listStyleType itself, and coexisting apps run without
|
|
11
|
+
* Panda's preflight (margins/padding are zeroed by whichever reset is
|
|
12
|
+
* active).
|
|
13
|
+
*/
|
|
14
|
+
export const List = styled("ul", {
|
|
15
|
+
base: { listStyleType: "none" },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** List item, matching Chakra's <ListItem>. */
|
|
19
|
+
export const ListItem = styled("li", {
|
|
20
|
+
base: {},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** Bulleted list matching Chakra's <UnorderedList>. */
|
|
24
|
+
export const UnorderedList = styled("ul", {
|
|
25
|
+
base: { listStyleType: "disc", marginStart: "1em" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** Numbered list matching Chakra's <OrderedList>. */
|
|
29
|
+
export const OrderedList = styled("ol", {
|
|
30
|
+
base: { listStyleType: "decimal", marginStart: "1em" },
|
|
31
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
* ListBox slot recipe — a standalone list of choosable options, single or
|
|
10
|
+
* multiple. Distinct from the `select` recipe's `list`/`option` slots, which
|
|
11
|
+
* style the same react-aria primitive inside a dropdown card: this one sits
|
|
12
|
+
* inline on the page, so it carries no surface of its own.
|
|
13
|
+
*
|
|
14
|
+
* An option is a leaf — if the rows need their own buttons or menus, they
|
|
15
|
+
* want `GridList` instead.
|
|
16
|
+
*
|
|
17
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
18
|
+
* `staticCss` entry that keeps the runtime-prop variants generated.
|
|
19
|
+
*/
|
|
20
|
+
export const listBox = defineSlotRecipe({
|
|
21
|
+
className: "list-box",
|
|
22
|
+
slots: ["root", "option"],
|
|
23
|
+
base: {
|
|
24
|
+
root: {
|
|
25
|
+
// The listbox holds the roving tab index, so it is focusable itself and
|
|
26
|
+
// would otherwise draw the platform ring around the whole list.
|
|
27
|
+
outline: "none",
|
|
28
|
+
},
|
|
29
|
+
option: {
|
|
30
|
+
display: "flex",
|
|
31
|
+
alignItems: "center",
|
|
32
|
+
cursor: "pointer",
|
|
33
|
+
outline: "none",
|
|
34
|
+
transitionProperty: "background",
|
|
35
|
+
transitionDuration: "ultra-fast",
|
|
36
|
+
transitionTimingFunction: "ease-in",
|
|
37
|
+
_hover: { bg: "gray.50" },
|
|
38
|
+
"&[data-selected]": { bg: "gray.100", _hover: { bg: "gray.100" } },
|
|
39
|
+
"&[data-focus-visible]": { focusShadow: "outline" },
|
|
40
|
+
"&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
package/src/ListBox.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) 2026, Micro:bit Educational Foundation and contributors
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
ListBox as RACListBox,
|
|
8
|
+
ListBoxItem as RACListBoxItem,
|
|
9
|
+
ListBoxItemProps as RACListBoxItemProps,
|
|
10
|
+
ListBoxProps as RACListBoxProps,
|
|
11
|
+
} from "react-aria-components";
|
|
12
|
+
import { css, cx } from "styled-system/css";
|
|
13
|
+
import { listBox } from "styled-system/recipes";
|
|
14
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
15
|
+
|
|
16
|
+
export interface ListBoxProps<T extends object>
|
|
17
|
+
extends Omit<RACListBoxProps<T>, "className" | "style" | "children"> {
|
|
18
|
+
/** `ListBoxOption`s, or a render function when `items` is given. */
|
|
19
|
+
children: RACListBoxProps<T>["children"];
|
|
20
|
+
/** Per-instance style overrides for the list, merged after the recipe. */
|
|
21
|
+
css?: SystemStyleObject;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ListBox — react-aria-components' <ListBox>, standing on the page rather
|
|
27
|
+
* than inside a dropdown (that is `Select`/`ComboBox`, which share their own
|
|
28
|
+
* recipe).
|
|
29
|
+
*
|
|
30
|
+
* Options are leaves: a button inside one is unreachable by keyboard, so rows
|
|
31
|
+
* carrying their own controls want `GridList`.
|
|
32
|
+
*/
|
|
33
|
+
export const ListBox = <T extends object>({
|
|
34
|
+
css: cssProp,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...rest
|
|
38
|
+
}: ListBoxProps<T>) => {
|
|
39
|
+
const slots = listBox();
|
|
40
|
+
return (
|
|
41
|
+
<RACListBox
|
|
42
|
+
{...rest}
|
|
43
|
+
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</RACListBox>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export interface ListBoxOptionProps<T extends object = object>
|
|
51
|
+
extends Omit<RACListBoxItemProps<T>, "className" | "style" | "children"> {
|
|
52
|
+
/**
|
|
53
|
+
* The option's content. A function receives the option's state, for a row
|
|
54
|
+
* that draws its own selected marker rather than taking the recipe's
|
|
55
|
+
* background.
|
|
56
|
+
*/
|
|
57
|
+
children?: RACListBoxItemProps<T>["children"];
|
|
58
|
+
/** Per-instance style overrides for the option, merged after the recipe. */
|
|
59
|
+
css?: SystemStyleObject;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* An option in a `ListBox`.
|
|
65
|
+
*
|
|
66
|
+
* Give it a `textValue` where its children aren't a plain string: react-aria
|
|
67
|
+
* derives typeahead text from string children only.
|
|
68
|
+
*/
|
|
69
|
+
export const ListBoxOption = <T extends object = object>({
|
|
70
|
+
css: cssProp,
|
|
71
|
+
className,
|
|
72
|
+
children,
|
|
73
|
+
...rest
|
|
74
|
+
}: ListBoxOptionProps<T>) => {
|
|
75
|
+
const slots = listBox();
|
|
76
|
+
return (
|
|
77
|
+
<RACListBoxItem
|
|
78
|
+
{...rest}
|
|
79
|
+
className={cx(
|
|
80
|
+
slots.option,
|
|
81
|
+
cssProp ? css(cssProp) : undefined,
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</RACListBoxItem>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
* Menu slot recipe — Chakra's default Menu parts (light mode). `content` is the
|
|
10
|
+
* dropdown card (react-aria-components' Popover), `list` the RAC Menu, `item` a
|
|
11
|
+
* MenuItem, `icon` the leading-icon wrapper.
|
|
12
|
+
*
|
|
13
|
+
* A config slot recipe (rather than an atomic `sva`) for consistency with
|
|
14
|
+
* `dialog` and so presets can override it later if brands diverge.
|
|
15
|
+
* No variants, so it needs no `staticCss` entry.
|
|
16
|
+
*
|
|
17
|
+
* Registered in the base preset (base-preset.ts).
|
|
18
|
+
*/
|
|
19
|
+
export const menu = defineSlotRecipe({
|
|
20
|
+
className: "menu",
|
|
21
|
+
slots: [
|
|
22
|
+
"content",
|
|
23
|
+
"list",
|
|
24
|
+
"item",
|
|
25
|
+
"icon",
|
|
26
|
+
"label",
|
|
27
|
+
"divider",
|
|
28
|
+
"group",
|
|
29
|
+
"groupTitle",
|
|
30
|
+
"itemIndicator",
|
|
31
|
+
],
|
|
32
|
+
base: {
|
|
33
|
+
content: {
|
|
34
|
+
bg: "white",
|
|
35
|
+
color: "inherit",
|
|
36
|
+
minWidth: "3xs",
|
|
37
|
+
py: "2",
|
|
38
|
+
// `popover` (1500), not `dropdown` (1000): a RAC Popover always portals
|
|
39
|
+
// to the body, so a menu opened from inside a Modal (zIndex `modal`,
|
|
40
|
+
// 1400) escapes the modal's stacking context and would paint behind it.
|
|
41
|
+
// Chakra never hit this — its MenuList rendered inline unless explicitly
|
|
42
|
+
// portalled. Nothing else lives between 1400 and the toast/tooltip layer.
|
|
43
|
+
zIndex: "popover",
|
|
44
|
+
borderRadius: "md",
|
|
45
|
+
borderWidth: "1px",
|
|
46
|
+
borderColor: "gray.200",
|
|
47
|
+
boxShadow: "sm",
|
|
48
|
+
// Approximate Chakra's menu fade/scale. RAC toggles data-entering/
|
|
49
|
+
// data-exiting on the Popover and waits for the transition before unmount.
|
|
50
|
+
transformOrigin: "top",
|
|
51
|
+
opacity: 1,
|
|
52
|
+
transform: "scale(1)",
|
|
53
|
+
transition: "opacity 0.1s ease-out, transform 0.1s ease-out",
|
|
54
|
+
"&[data-entering]": { opacity: 0, transform: "scale(0.95)" },
|
|
55
|
+
"&[data-exiting]": { opacity: 0, transform: "scale(0.95)" },
|
|
56
|
+
_motionReduce: { transition: "none" },
|
|
57
|
+
},
|
|
58
|
+
list: {
|
|
59
|
+
outline: "none",
|
|
60
|
+
},
|
|
61
|
+
item: {
|
|
62
|
+
display: "flex",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
py: "1.5",
|
|
65
|
+
px: "3",
|
|
66
|
+
cursor: "pointer",
|
|
67
|
+
color: "inherit",
|
|
68
|
+
textDecoration: "none",
|
|
69
|
+
outline: "none",
|
|
70
|
+
transitionProperty: "background",
|
|
71
|
+
transitionDuration: "ultra-fast",
|
|
72
|
+
transitionTimingFunction: "ease-in",
|
|
73
|
+
// RAC highlights the active item (keyboard or pointer) with data-focused;
|
|
74
|
+
// data-pressed is the pressed state — mirrors Chakra's _focus/_active.
|
|
75
|
+
"&[data-focused]": { bg: "gray.100" },
|
|
76
|
+
"&[data-pressed]": { bg: "gray.200" },
|
|
77
|
+
"&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
|
|
78
|
+
},
|
|
79
|
+
label: {
|
|
80
|
+
// Chakra wraps an icon-item's children in a flex:1 span, so block
|
|
81
|
+
// children (e.g. two stacked <Text>s) lay out vertically rather than as
|
|
82
|
+
// flex-row siblings of the icon.
|
|
83
|
+
flex: "1",
|
|
84
|
+
},
|
|
85
|
+
icon: {
|
|
86
|
+
display: "inline-flex",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
justifyContent: "center",
|
|
89
|
+
flexShrink: 0,
|
|
90
|
+
marginEnd: "0.75rem",
|
|
91
|
+
// Chakra's MenuIcon shrinks glyphs to 0.8em; items passing an explicitly
|
|
92
|
+
// sized icon (e.g. h/w) override this.
|
|
93
|
+
fontSize: "0.8em",
|
|
94
|
+
},
|
|
95
|
+
divider: {
|
|
96
|
+
border: 0,
|
|
97
|
+
borderBottom: "1px solid",
|
|
98
|
+
borderColor: "gray.200",
|
|
99
|
+
my: "2",
|
|
100
|
+
opacity: 0.6,
|
|
101
|
+
},
|
|
102
|
+
group: {},
|
|
103
|
+
// Chakra's MenuGroup/MenuOptionGroup title.
|
|
104
|
+
groupTitle: {
|
|
105
|
+
display: "block",
|
|
106
|
+
mx: "4",
|
|
107
|
+
my: "2",
|
|
108
|
+
fontWeight: "semibold",
|
|
109
|
+
fontSize: "sm",
|
|
110
|
+
},
|
|
111
|
+
// Check glyph slot for MenuItemOption: space always reserved (as Chakra
|
|
112
|
+
// does), visible only on the selected item (RAC sets data-selected on it).
|
|
113
|
+
itemIndicator: {
|
|
114
|
+
display: "inline-flex",
|
|
115
|
+
alignItems: "center",
|
|
116
|
+
justifyContent: "center",
|
|
117
|
+
flexShrink: 0,
|
|
118
|
+
marginEnd: "0.75rem",
|
|
119
|
+
fontSize: "0.8em",
|
|
120
|
+
opacity: 0,
|
|
121
|
+
"[data-selected] &": { opacity: 1 },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
});
|