@microbit/ui 0.1.0-alpha.9 → 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/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 +13 -12
- package/src/Checkbox.tsx +103 -34
- 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 +30 -26
- package/src/Input.tsx +16 -7
- package/src/InputGroup.tsx +9 -9
- 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 +37 -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 +13 -12
- package/src/Radio.tsx +2 -29
- 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 +40 -21
- package/src/Switch.tsx +35 -5
- package/src/Text.recipe.ts +26 -0
- package/src/Text.tsx +6 -2
- package/src/TextField.tsx +35 -38
- 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 +323 -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 +5 -6
- 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 +19 -3
- package/src/rac-locale.ts +33 -0
- package/src/system.ts +22 -1
- package/src/TextField.recipe.ts +0 -54
package/src/Kbd.tsx
CHANGED
package/src/Link.tsx
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { styled } from "styled-system/jsx";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Link —
|
|
10
|
-
* focus
|
|
9
|
+
* Link — a styled anchor (no underline until hover, focus ring on keyboard
|
|
10
|
+
* focus). Accepts Panda style props for colour etc.
|
|
11
11
|
*/
|
|
12
12
|
export const Link = styled("a", {
|
|
13
13
|
base: {
|
|
@@ -18,6 +18,6 @@ export const Link = styled("a", {
|
|
|
18
18
|
"background-color, border-color, color, fill, stroke, opacity, box-shadow, transform",
|
|
19
19
|
transitionDuration: "normal",
|
|
20
20
|
_hover: { textDecoration: "underline" },
|
|
21
|
-
_focusVisible: {
|
|
21
|
+
_focusVisible: { focusRing: "outline" },
|
|
22
22
|
},
|
|
23
23
|
});
|
package/src/LinkBox.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import { SystemStyleObject } from "styled-system/types";
|
|
|
12
12
|
/**
|
|
13
13
|
* LinkBox — makes a whole box clickable via a nested LinkOverlay (or any
|
|
14
14
|
* element with an inset `_before` overlay). Other interactive children must
|
|
15
|
-
* be raised with `zIndex: 1`.
|
|
15
|
+
* be raised with `zIndex: 1`.
|
|
16
16
|
*/
|
|
17
17
|
export const LinkBox = styled("div", {
|
|
18
18
|
base: { position: "relative" },
|
|
@@ -54,8 +54,7 @@ export interface LinkOverlayButtonProps
|
|
|
54
54
|
* Deliberately a plain button, not a react-aria one: react-aria's usePress
|
|
55
55
|
* cancels presses that land outside the button's bounding rect, which defeats
|
|
56
56
|
* the overlay. `position: static` overrides the button recipe's base so the
|
|
57
|
-
* overlay anchors to the LinkBox
|
|
58
|
-
* Button).
|
|
57
|
+
* overlay anchors to the LinkBox.
|
|
59
58
|
*/
|
|
60
59
|
export const LinkOverlayButton = forwardRef<
|
|
61
60
|
HTMLButtonElement,
|
package/src/LinkButton.tsx
CHANGED
|
@@ -19,10 +19,10 @@ export interface LinkButtonProps
|
|
|
19
19
|
/** Per-instance style overrides, merged after the recipe. */
|
|
20
20
|
css?: SystemStyleObject;
|
|
21
21
|
className?: string;
|
|
22
|
-
/** Icon rendered before the label
|
|
23
|
-
|
|
24
|
-
/** Icon rendered after the label
|
|
25
|
-
|
|
22
|
+
/** Icon rendered before the label. */
|
|
23
|
+
startIcon?: ReactNode;
|
|
24
|
+
/** Icon rendered after the label. */
|
|
25
|
+
endIcon?: ReactNode;
|
|
26
26
|
children?: ReactNode;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -33,8 +33,8 @@ const linkReset = css.raw({
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* LinkButton — a navigation link that looks like a Button
|
|
37
|
-
*
|
|
36
|
+
* LinkButton — a navigation link that looks like a Button.
|
|
37
|
+
* react-aria-components <Link> renders a real anchor
|
|
38
38
|
* (`href`, `target`, new-tab/middle-click semantics preserved) with the same
|
|
39
39
|
* interaction data attributes as Button, so the `button` recipe's
|
|
40
40
|
* hover/press/focus/disabled states apply unchanged.
|
|
@@ -48,10 +48,11 @@ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
|
|
|
48
48
|
{
|
|
49
49
|
variant,
|
|
50
50
|
size,
|
|
51
|
+
tone,
|
|
51
52
|
css: cssProp,
|
|
52
53
|
className,
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
startIcon,
|
|
55
|
+
endIcon,
|
|
55
56
|
children,
|
|
56
57
|
...rest
|
|
57
58
|
},
|
|
@@ -61,18 +62,18 @@ export const LinkButton = forwardRef<HTMLAnchorElement, LinkButtonProps>(
|
|
|
61
62
|
<RACLink
|
|
62
63
|
ref={ref}
|
|
63
64
|
className={cx(
|
|
64
|
-
button({ variant, size }),
|
|
65
|
+
button({ variant, size, tone }),
|
|
65
66
|
css(linkReset, cssProp),
|
|
66
67
|
className,
|
|
67
68
|
)}
|
|
68
69
|
{...rest}
|
|
69
70
|
>
|
|
70
|
-
{
|
|
71
|
-
<span className={buttonIcon({ side: "
|
|
71
|
+
{startIcon ? (
|
|
72
|
+
<span className={buttonIcon({ side: "start" })}>{startIcon}</span>
|
|
72
73
|
) : null}
|
|
73
74
|
{children}
|
|
74
|
-
{
|
|
75
|
-
<span className={buttonIcon({ side: "
|
|
75
|
+
{endIcon ? (
|
|
76
|
+
<span className={buttonIcon({ side: "end" })}>{endIcon}</span>
|
|
76
77
|
) : null}
|
|
77
78
|
</RACLink>
|
|
78
79
|
);
|
package/src/List.tsx
CHANGED
|
@@ -6,26 +6,24 @@
|
|
|
6
6
|
import { styled } from "styled-system/jsx";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Unstyled list
|
|
10
|
-
*
|
|
11
|
-
* Panda's preflight (margins/padding are zeroed by whichever reset is
|
|
12
|
-
* active).
|
|
9
|
+
* Unstyled list. The marker removal is explicit rather than relying on a
|
|
10
|
+
* reset.
|
|
13
11
|
*/
|
|
14
12
|
export const List = styled("ul", {
|
|
15
13
|
base: { listStyleType: "none" },
|
|
16
14
|
});
|
|
17
15
|
|
|
18
|
-
/** List item
|
|
16
|
+
/** List item. */
|
|
19
17
|
export const ListItem = styled("li", {
|
|
20
18
|
base: {},
|
|
21
19
|
});
|
|
22
20
|
|
|
23
|
-
/** Bulleted list
|
|
21
|
+
/** Bulleted list. */
|
|
24
22
|
export const UnorderedList = styled("ul", {
|
|
25
23
|
base: { listStyleType: "disc", marginStart: "1em" },
|
|
26
24
|
});
|
|
27
25
|
|
|
28
|
-
/** Numbered list
|
|
26
|
+
/** Numbered list. */
|
|
29
27
|
export const OrderedList = styled("ol", {
|
|
30
28
|
base: { listStyleType: "decimal", marginStart: "1em" },
|
|
31
29
|
});
|
|
@@ -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]": { focusRing: "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
|
+
};
|
package/src/Menu.recipe.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Menu slot recipe
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Menu slot recipe. `content` is the dropdown card (react-aria-components'
|
|
10
|
+
* Popover), `list` the RAC Menu, `item` a MenuItem, `icon` the leading-icon
|
|
11
|
+
* wrapper.
|
|
12
12
|
*
|
|
13
13
|
* A config slot recipe (rather than an atomic `sva`) for consistency with
|
|
14
14
|
* `dialog` and so presets can override it later if brands diverge.
|
|
@@ -35,13 +35,17 @@ export const menu = defineSlotRecipe({
|
|
|
35
35
|
color: "inherit",
|
|
36
36
|
minWidth: "3xs",
|
|
37
37
|
py: "2",
|
|
38
|
-
|
|
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
|
+
// Nothing else lives between 1400 and the toast/tooltip layer.
|
|
42
|
+
zIndex: "popover",
|
|
39
43
|
borderRadius: "md",
|
|
40
44
|
borderWidth: "1px",
|
|
41
45
|
borderColor: "gray.200",
|
|
42
46
|
boxShadow: "sm",
|
|
43
|
-
//
|
|
44
|
-
//
|
|
47
|
+
// Fade/scale enter/exit. RAC toggles data-entering/data-exiting on the
|
|
48
|
+
// Popover and waits for the transition before unmount.
|
|
45
49
|
transformOrigin: "top",
|
|
46
50
|
opacity: 1,
|
|
47
51
|
transform: "scale(1)",
|
|
@@ -62,19 +66,18 @@ export const menu = defineSlotRecipe({
|
|
|
62
66
|
color: "inherit",
|
|
63
67
|
textDecoration: "none",
|
|
64
68
|
outline: "none",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// RAC highlights the active item (keyboard or pointer) with data-focused;
|
|
69
|
-
// data-pressed is the pressed state — mirrors Chakra's _focus/_active.
|
|
69
|
+
// data-focused is RAC's active item, either modality. The highlight
|
|
70
|
+
// is focus indication: no transition (it snaps with the ring, which
|
|
71
|
+
// keyboard nav adds; inset, the rows being full-bleed).
|
|
70
72
|
"&[data-focused]": { bg: "gray.100" },
|
|
73
|
+
"&[data-focus-visible]": { focusRing: "outlineInset" },
|
|
71
74
|
"&[data-pressed]": { bg: "gray.200" },
|
|
72
75
|
"&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
|
|
73
76
|
},
|
|
74
77
|
label: {
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
+
// A flex:1 wrapper around an icon-item's children, so block children
|
|
79
|
+
// (e.g. two stacked <Text>s) lay out vertically rather than as flex-row
|
|
80
|
+
// siblings of the icon.
|
|
78
81
|
flex: "1",
|
|
79
82
|
},
|
|
80
83
|
icon: {
|
|
@@ -83,8 +86,8 @@ export const menu = defineSlotRecipe({
|
|
|
83
86
|
justifyContent: "center",
|
|
84
87
|
flexShrink: 0,
|
|
85
88
|
marginEnd: "0.75rem",
|
|
86
|
-
//
|
|
87
|
-
//
|
|
89
|
+
// Glyphs shrink to 0.8em; items passing an explicitly sized icon
|
|
90
|
+
// (e.g. h/w) override this.
|
|
88
91
|
fontSize: "0.8em",
|
|
89
92
|
},
|
|
90
93
|
divider: {
|
|
@@ -95,7 +98,7 @@ export const menu = defineSlotRecipe({
|
|
|
95
98
|
opacity: 0.6,
|
|
96
99
|
},
|
|
97
100
|
group: {},
|
|
98
|
-
//
|
|
101
|
+
// Title for a menu group or option group.
|
|
99
102
|
groupTitle: {
|
|
100
103
|
display: "block",
|
|
101
104
|
mx: "4",
|
|
@@ -103,8 +106,8 @@ export const menu = defineSlotRecipe({
|
|
|
103
106
|
fontWeight: "semibold",
|
|
104
107
|
fontSize: "sm",
|
|
105
108
|
},
|
|
106
|
-
// Check glyph slot for MenuItemOption: space always reserved
|
|
107
|
-
//
|
|
109
|
+
// Check glyph slot for MenuItemOption: space always reserved, visible
|
|
110
|
+
// only on the selected item (RAC sets data-selected on it).
|
|
108
111
|
itemIndicator: {
|
|
109
112
|
display: "inline-flex",
|
|
110
113
|
alignItems: "center",
|
package/src/Menu.tsx
CHANGED
|
@@ -97,7 +97,6 @@ export interface MenuListProps {
|
|
|
97
97
|
*/
|
|
98
98
|
export const MenuList = ({
|
|
99
99
|
children,
|
|
100
|
-
// Matches Chakra Menu's default "bottom-start".
|
|
101
100
|
placement = "bottom start",
|
|
102
101
|
css: cssProp,
|
|
103
102
|
className,
|
|
@@ -119,7 +118,7 @@ export const MenuList = ({
|
|
|
119
118
|
|
|
120
119
|
export interface MenuItemProps
|
|
121
120
|
extends Omit<RACMenuItemProps, "className" | "children"> {
|
|
122
|
-
/** Icon rendered before the label
|
|
121
|
+
/** Icon rendered before the label. */
|
|
123
122
|
icon?: ReactNode;
|
|
124
123
|
/** Per-instance style overrides, merged after the recipe. */
|
|
125
124
|
css?: SystemStyleObject;
|
|
@@ -156,45 +155,72 @@ export const MenuItem = ({
|
|
|
156
155
|
);
|
|
157
156
|
};
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
/** Group heading shown above the options
|
|
158
|
+
interface MenuOptionGroupBaseProps {
|
|
159
|
+
/** Group heading shown above the options. */
|
|
161
160
|
title?: ReactNode;
|
|
162
|
-
/** The selected `MenuItemOption`'s value (radio semantics). */
|
|
163
|
-
value?: string;
|
|
164
|
-
/** Called with the newly selected option's value. */
|
|
165
|
-
onChange?: (value: string) => void;
|
|
166
161
|
/** `MenuItemOption` children. */
|
|
167
162
|
children: ReactNode;
|
|
168
163
|
css?: SystemStyleObject;
|
|
169
164
|
className?: string;
|
|
170
165
|
}
|
|
171
166
|
|
|
167
|
+
export interface MenuOptionGroupRadioProps extends MenuOptionGroupBaseProps {
|
|
168
|
+
type?: "radio";
|
|
169
|
+
/** The selected `MenuItemOption`'s value. */
|
|
170
|
+
value?: string;
|
|
171
|
+
/** Called with the newly selected option's value. */
|
|
172
|
+
onChange?: (value: string) => void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface MenuOptionGroupCheckboxProps extends MenuOptionGroupBaseProps {
|
|
176
|
+
type: "checkbox";
|
|
177
|
+
/** The checked `MenuItemOption`s' values. */
|
|
178
|
+
value?: string[];
|
|
179
|
+
/** Called with the full set of checked values after a toggle. */
|
|
180
|
+
onChange?: (value: string[]) => void;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type MenuOptionGroupProps =
|
|
184
|
+
| MenuOptionGroupRadioProps
|
|
185
|
+
| MenuOptionGroupCheckboxProps;
|
|
186
|
+
|
|
172
187
|
/**
|
|
173
|
-
* MenuOptionGroup — a
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
188
|
+
* MenuOptionGroup — a group of checkable `MenuItemOption`s within a menu.
|
|
189
|
+
* `type="radio"` (the default) is single-select and renders `menuitemradio`;
|
|
190
|
+
* `type="checkbox"` is multi-select and renders `menuitemcheckbox`, each
|
|
191
|
+
* option toggling independently.
|
|
192
|
+
*
|
|
193
|
+
* Selection is section-scoped (RAC MenuSection), so a menu can mix action items
|
|
194
|
+
* and option groups. An option's own `onAction` still fires on every press,
|
|
195
|
+
* including the press that deselects it — so a lone toggle can be driven either
|
|
196
|
+
* by the group's `onChange` or by the item's `onAction`.
|
|
197
|
+
*
|
|
198
|
+
* Choosing an option leaves the menu open (a plain `MenuItem` closes it).
|
|
177
199
|
*/
|
|
178
|
-
export const MenuOptionGroup = ({
|
|
179
|
-
title,
|
|
180
|
-
value,
|
|
181
|
-
onChange,
|
|
182
|
-
children,
|
|
183
|
-
css: cssProp,
|
|
184
|
-
className,
|
|
185
|
-
}: MenuOptionGroupProps) => {
|
|
200
|
+
export const MenuOptionGroup = (props: MenuOptionGroupProps) => {
|
|
201
|
+
const { title, children, css: cssProp, className } = props;
|
|
186
202
|
const slots = menu();
|
|
203
|
+
const selectedKeys =
|
|
204
|
+
props.type === "checkbox"
|
|
205
|
+
? props.value ?? []
|
|
206
|
+
: props.value != null
|
|
207
|
+
? [props.value]
|
|
208
|
+
: [];
|
|
187
209
|
return (
|
|
188
210
|
<RACMenuSection
|
|
189
211
|
className={cx(slots.group, cssProp ? css(cssProp) : undefined, className)}
|
|
190
|
-
selectionMode="single"
|
|
191
|
-
selectedKeys={
|
|
212
|
+
selectionMode={props.type === "checkbox" ? "multiple" : "single"}
|
|
213
|
+
selectedKeys={selectedKeys}
|
|
192
214
|
onSelectionChange={(keys) => {
|
|
193
|
-
if (keys
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
if (keys === "all") {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const values = [...keys].map(String);
|
|
219
|
+
if (props.type === "checkbox") {
|
|
220
|
+
props.onChange?.(values);
|
|
221
|
+
} else if (values.length > 0) {
|
|
222
|
+
// Radio: a press that clears the selection reports nothing.
|
|
223
|
+
props.onChange?.(values[0]);
|
|
198
224
|
}
|
|
199
225
|
}}
|
|
200
226
|
>
|
|
@@ -217,7 +243,7 @@ export interface MenuItemOptionProps
|
|
|
217
243
|
|
|
218
244
|
/**
|
|
219
245
|
* MenuItemOption — a selectable option inside a `MenuOptionGroup`, with a
|
|
220
|
-
* check indicator on the selected item
|
|
246
|
+
* check indicator on the selected item.
|
|
221
247
|
*/
|
|
222
248
|
export const MenuItemOption = ({
|
|
223
249
|
value,
|
package/src/Modal.recipe.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { defineSlotRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Dialog slot recipe
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Dialog slot recipe. The overlay spans the full viewport (the iOS WKWebView
|
|
10
|
+
* 100% fix) and the `full` size adds safe-area insets and the brand
|
|
11
|
+
* status-bar gradient.
|
|
12
12
|
*
|
|
13
13
|
* A config recipe (rather than an atomic `sva`) so the `size` variant accepts
|
|
14
14
|
* responsive values, e.g. `{ base: "full", md: "4xl" }`. Consumed by the
|
|
@@ -33,6 +33,9 @@ const dialogBox = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
// The `full` variant also styles these slots; same symmetry requirement.
|
|
36
|
+
// The header's padding stays physical to pair with that variant's
|
|
37
|
+
// window-controls inset, which is a screen position rather than a
|
|
38
|
+
// reading-order one; the base `px` covers the start side either way.
|
|
36
39
|
const dialogSlots = {
|
|
37
40
|
header: { pl: "6" },
|
|
38
41
|
body: { overflowY: "visible" },
|
|
@@ -54,7 +57,13 @@ export const dialog = defineSlotRecipe({
|
|
|
54
57
|
overlay: {
|
|
55
58
|
position: "fixed",
|
|
56
59
|
inset: 0,
|
|
57
|
-
|
|
60
|
+
// 100vw, not 100%: react-aria's scroll lock reserves the root
|
|
61
|
+
// scrollbar gutter (scrollbar-gutter: stable), which narrows the
|
|
62
|
+
// containing block for fixed elements — 100% leaves an uncovered
|
|
63
|
+
// strip where the page scrollbar was. Viewport units span the
|
|
64
|
+
// reserved gutter, so the backdrop (and a full-size dialog) reach
|
|
65
|
+
// the real viewport edge.
|
|
66
|
+
w: "100vw",
|
|
58
67
|
h: "100%",
|
|
59
68
|
bg: "blackAlpha.600",
|
|
60
69
|
zIndex: "modal",
|
|
@@ -79,7 +88,7 @@ export const dialog = defineSlotRecipe({
|
|
|
79
88
|
display: "flex",
|
|
80
89
|
flexDirection: "column",
|
|
81
90
|
outline: "none",
|
|
82
|
-
//
|
|
91
|
+
// Fade + scale enter/exit.
|
|
83
92
|
opacity: 1,
|
|
84
93
|
transform: "scale(1)",
|
|
85
94
|
transition: "opacity 0.2s ease-out, transform 0.2s ease-out",
|
|
@@ -108,16 +117,15 @@ export const dialog = defineSlotRecipe({
|
|
|
108
117
|
display: "flex",
|
|
109
118
|
alignItems: "center",
|
|
110
119
|
justifyContent: "flex-end",
|
|
111
|
-
//
|
|
112
|
-
// baked in as the house style. Override via css for tighter layouts.
|
|
120
|
+
// The gap is house style; override via css for tighter layouts.
|
|
113
121
|
gap: "5",
|
|
114
122
|
flexShrink: 0,
|
|
115
123
|
},
|
|
116
124
|
closeTrigger: { position: "absolute", top: "2", insetEnd: "3" },
|
|
117
125
|
},
|
|
118
126
|
variants: {
|
|
119
|
-
//
|
|
120
|
-
//
|
|
127
|
+
// Vertically centre the dialog in the viewport rather than the default
|
|
128
|
+
// top alignment.
|
|
121
129
|
centered: {
|
|
122
130
|
true: {
|
|
123
131
|
overlay: { alignItems: "center" },
|