@microbit/ui 0.1.0-alpha.4 → 0.1.0-alpha.6

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 CHANGED
@@ -19,3 +19,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+ ---
24
+
25
+ ## Third-party notices
26
+
27
+ This package includes design tokens and component styling derived from
28
+ [Chakra UI](https://github.com/chakra-ui/chakra-ui) v2: the token scales in
29
+ `src/base-tokens.ts` were snapshotted from `@chakra-ui/theme`'s default
30
+ values, and the component recipes were ported from Chakra's component styles.
31
+ Chakra UI is used under the MIT License:
32
+
33
+ > MIT License
34
+ >
35
+ > Copyright (c) 2019 Segun Adebayo
36
+ >
37
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ > of this software and associated documentation files (the "Software"), to deal
39
+ > in the Software without restriction, including without limitation the rights
40
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ > copies of the Software, and to permit persons to whom the Software is
42
+ > furnished to do so, subject to the following conditions:
43
+ >
44
+ > The above copyright notice and this permission notice shall be included in all
45
+ > copies or substantial portions of the Software.
46
+ >
47
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
53
+ > SOFTWARE.
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # @microbit/ui
2
2
 
3
3
  react-aria-components + Panda CSS primitives with a design language ported from
4
- the Micro:bit Educational Foundation apps' original Chakra UI v2 themes.
4
+ the Micro:bit Educational Foundation apps' original
5
+ [Chakra UI](https://chakra-ui.com/) v2 themes (see
6
+ [Chakra UI heritage](#chakra-ui-heritage-and-license)).
5
7
 
6
8
  The package **ships as source**: components import `styled-system/*`, which
7
9
  each consumer generates with its own Panda preset stack. There is no build
@@ -154,8 +156,9 @@ the token is consumed.
154
156
  ## Runtime token lookups
155
157
 
156
158
  For values that feed _computation_ rather than stylesheets (canvas painting,
157
- colour math, inline `style` for data-driven values — see gotcha #9 in
158
- RAC-MIGRATION.md), import the runtime lookup:
159
+ colour math, inline `style` for data-driven values — see gotcha #9 in the
160
+ repo's [migration playbook](../../docs/migration-playbook.md)), import the
161
+ runtime lookup:
159
162
 
160
163
  ```ts
161
164
  import { token } from "@microbit/ui"; // re-exports styled-system/tokens
@@ -188,3 +191,17 @@ Crowdin via the repo-root `npm run update-translations -- <path to extracted
188
191
  Crowdin ZIP>` (config-driven over packages in
189
192
  `bin/update-translations.cjs`), after which you run `npm run i18n:tidy`
190
193
  from the root.
194
+
195
+ ## Chakra UI heritage and license
196
+
197
+ This package's design language began as a faithful port of
198
+ [Chakra UI](https://chakra-ui.com/) v2's, done so the apps it serves could
199
+ leave Chakra without a redesign: `src/base-tokens.ts` was snapshotted from
200
+ `@chakra-ui/theme`'s default token scales, and the `*.recipe.ts` files were
201
+ ported from Chakra's component styles onto Panda config recipes.
202
+ However far it evolves from that starting point, it owes its foundations to
203
+ Chakra. Thanks to Segun Adebayo and the Chakra UI contributors.
204
+
205
+ The package is [MIT](LICENSE.md) © Micro:bit Educational Foundation and
206
+ contributors; Chakra UI is MIT © Segun Adebayo, and its notice is carried
207
+ in [LICENSE.md](LICENSE.md).
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Schließen",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "Dún",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ui.close-action": {
3
+ "defaultMessage": "关闭",
4
+ "description": "Close button text or label"
5
+ },
6
+ "ui.toast-status-error": {
7
+ "defaultMessage": "Error",
8
+ "description": "Announced by screen readers before an error notification"
9
+ },
10
+ "ui.toast-status-info": {
11
+ "defaultMessage": "Information",
12
+ "description": "Announced by screen readers before an informational notification"
13
+ },
14
+ "ui.toast-status-success": {
15
+ "defaultMessage": "Success",
16
+ "description": "Announced by screen readers before a success notification"
17
+ },
18
+ "ui.toast-status-warning": {
19
+ "defaultMessage": "Warning",
20
+ "description": "Announced by screen readers before a warning notification"
21
+ }
22
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@microbit/ui",
3
- "version": "0.1.0-alpha.4",
4
- "description": "micro:bit design-system primitives: react-aria-components + Panda CSS with a Chakra v2 design language. Ships as source; see README for the consumption setup.",
3
+ "version": "0.1.0-alpha.6",
4
+ "description": "micro:bit design-system primitives: react-aria-components + Panda CSS with a design language ported from Chakra UI v2. Ships as source; see README for the consumption setup.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
9
  "./base-preset": "./src/base-preset.ts",
10
- "./chakra-tokens": "./src/chakra-tokens.ts",
10
+ "./base-tokens": "./src/base-tokens.ts",
11
11
  "./messages": "./src/messages.ts",
12
12
  "./postcss-legacy-safari": "./postcss-legacy-safari.cjs",
13
13
  "./lang/*": "./lang/*"
package/src/Button.tsx CHANGED
@@ -8,25 +8,10 @@ import {
8
8
  Button as RACButton,
9
9
  ButtonProps as RACButtonProps,
10
10
  } from "react-aria-components";
11
- import { css, cva, cx } from "styled-system/css";
11
+ import { css, cx } from "styled-system/css";
12
12
  import { button, ButtonVariantProps } from "styled-system/recipes";
13
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
- });
14
+ import { buttonIcon } from "./button-icon";
30
15
 
31
16
  export interface ButtonProps
32
17
  extends Omit<RACButtonProps, "className" | "children">,
package/src/Code.tsx ADDED
@@ -0,0 +1,20 @@
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
+ * Code — inline code chip matching Chakra's <Code> (gray subtle, light mode).
10
+ */
11
+ export const Code = styled("code", {
12
+ base: {
13
+ fontFamily: "mono",
14
+ fontSize: "sm",
15
+ px: "0.2em",
16
+ borderRadius: "sm",
17
+ bg: "gray.100",
18
+ color: "gray.800",
19
+ },
20
+ });
@@ -0,0 +1,168 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import {
7
+ CSSProperties,
8
+ ReactNode,
9
+ useEffect,
10
+ useLayoutEffect,
11
+ useRef,
12
+ useState,
13
+ } from "react";
14
+ import { css, cx } from "styled-system/css";
15
+ import { SystemStyleObject } from "styled-system/types";
16
+
17
+ export interface CollapseProps {
18
+ /** Expanded when true (Chakra's `in`). */
19
+ isOpen: boolean;
20
+ /** Height when collapsed (Chakra's `startingHeight`). */
21
+ startingHeight?: number | string;
22
+ /**
23
+ * Height when expanded (Chakra's `endingHeight`; defaults to the measured
24
+ * content height, tracked with a ResizeObserver so nested expansion works).
25
+ */
26
+ endingHeight?: number | string;
27
+ /** Remove the content from the DOM once the exit transition finishes. */
28
+ unmountOnExit?: boolean;
29
+ css?: SystemStyleObject;
30
+ className?: string;
31
+ style?: CSSProperties;
32
+ children: ReactNode;
33
+ }
34
+
35
+ const toCssSize = (v: number | string) =>
36
+ typeof v === "number" ? `${v}px` : v;
37
+
38
+ /**
39
+ * Collapse — Chakra's Collapse transition without framer-motion: the content
40
+ * is measured and the wrapper's height (and opacity, when collapsing to zero)
41
+ * transitions between the collapsed and expanded sizes. Content stays mounted
42
+ * unless `unmountOnExit` is set.
43
+ *
44
+ * As with framer-motion, only `isOpen` changes animate: initial mounts render
45
+ * at rest, and height corrections (late measurement, content growth) snap —
46
+ * otherwise a collapse mounted inside an entering view morphs during the
47
+ * outer animation.
48
+ */
49
+ export const Collapse = ({
50
+ isOpen,
51
+ startingHeight = 0,
52
+ endingHeight,
53
+ unmountOnExit = false,
54
+ css: cssProp,
55
+ className,
56
+ style,
57
+ children,
58
+ }: CollapseProps) => {
59
+ const contentRef = useRef<HTMLDivElement>(null);
60
+ const [measuredHeight, setMeasuredHeight] = useState<number>();
61
+ // For unmountOnExit: stay in the DOM until the exit transition ends.
62
+ const [present, setPresent] = useState(isOpen);
63
+ // The state the DOM shows. It follows isOpen from a layout effect so the
64
+ // transition-enable and the height flip land in the same commit (a
65
+ // render-time flip detector is unreliable: React can discard renders,
66
+ // losing the state update while keeping ref mutations).
67
+ const [displayOpen, setDisplayOpen] = useState(isOpen);
68
+ const [animating, setAnimating] = useState(false);
69
+
70
+ useLayoutEffect(() => {
71
+ if (isOpen === displayOpen) {
72
+ return;
73
+ }
74
+ if (isOpen) {
75
+ // Opening: mount (collapsed) first, then flip on the next frame so
76
+ // the browser has a computed collapsed state to transition from.
77
+ setPresent(true);
78
+ const raf = requestAnimationFrame(() => {
79
+ setAnimating(true);
80
+ setDisplayOpen(true);
81
+ });
82
+ return () => cancelAnimationFrame(raf);
83
+ }
84
+ // Closing: enable the transition and flip together, pre-paint.
85
+ setAnimating(true);
86
+ setDisplayOpen(false);
87
+ }, [isOpen, displayOpen]);
88
+
89
+ useLayoutEffect(() => {
90
+ const el = contentRef.current;
91
+ if (!el || endingHeight !== undefined) {
92
+ return;
93
+ }
94
+ setMeasuredHeight(el.scrollHeight);
95
+ const observer = new ResizeObserver(() =>
96
+ setMeasuredHeight(el.scrollHeight),
97
+ );
98
+ observer.observe(el);
99
+ return () => observer.disconnect();
100
+ }, [endingHeight, present]);
101
+
102
+ // Fallback for browsers/edge cases where transitionend doesn't fire.
103
+ useEffect(() => {
104
+ if (animating) {
105
+ const timeout = setTimeout(() => {
106
+ setAnimating(false);
107
+ if (!isOpen && unmountOnExit) {
108
+ setPresent(false);
109
+ }
110
+ }, 400);
111
+ return () => clearTimeout(timeout);
112
+ }
113
+ }, [animating, isOpen, unmountOnExit]);
114
+
115
+ if (unmountOnExit && !present) {
116
+ return null;
117
+ }
118
+
119
+ const expanded =
120
+ endingHeight !== undefined
121
+ ? toCssSize(endingHeight)
122
+ : measuredHeight !== undefined
123
+ ? `${measuredHeight}px`
124
+ : "auto";
125
+ const collapsed = toCssSize(startingHeight);
126
+ const hideWhenCollapsed = collapsed === "0px" || collapsed === "0";
127
+
128
+ return (
129
+ <div
130
+ onTransitionEnd={(e) => {
131
+ if (e.propertyName === "height" && e.target === e.currentTarget) {
132
+ setAnimating(false);
133
+ if (!isOpen && unmountOnExit) {
134
+ setPresent(false);
135
+ }
136
+ }
137
+ }}
138
+ className={cx(
139
+ css(
140
+ {
141
+ overflow: "hidden",
142
+ display: "block",
143
+ transitionDuration: "0.25s",
144
+ transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
145
+ _motionReduce: { transition: "none" },
146
+ },
147
+ cssProp,
148
+ ),
149
+ className,
150
+ )}
151
+ // Runtime-measured/caller-supplied heights; transitions enabled only
152
+ // while an isOpen change is in flight.
153
+ style={{
154
+ transitionProperty: animating ? "height, opacity" : "none",
155
+ height: displayOpen ? expanded : collapsed,
156
+ opacity: displayOpen || !hideWhenCollapsed ? 1 : 0,
157
+ // At rest fully-collapsed, remove the content from the a11y tree
158
+ // and tab order, as Chakra did (kept visible while animating so
159
+ // the exit transition shows).
160
+ visibility:
161
+ displayOpen || animating || !hideWhenCollapsed ? undefined : "hidden",
162
+ ...style,
163
+ }}
164
+ >
165
+ <div ref={contentRef}>{children}</div>
166
+ </div>
167
+ );
168
+ };
package/src/Divider.tsx CHANGED
@@ -3,19 +3,50 @@
3
3
  *
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
+ import { ComponentProps, forwardRef } from "react";
6
7
  import { styled } from "styled-system/jsx";
7
8
 
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", {
9
+ const StyledDivider = styled("hr", {
13
10
  base: {
14
11
  border: 0,
15
- borderBottomWidth: "1px",
16
- borderBottomStyle: "solid",
17
12
  borderColor: "gray.200",
18
13
  opacity: 0.6,
19
- width: "100%",
20
14
  },
15
+ variants: {
16
+ orientation: {
17
+ horizontal: {
18
+ borderBottomWidth: "1px",
19
+ borderBottomStyle: "solid",
20
+ width: "100%",
21
+ },
22
+ vertical: {
23
+ borderLeftWidth: "1px",
24
+ borderLeftStyle: "solid",
25
+ height: "100%",
26
+ },
27
+ },
28
+ },
29
+ defaultVariants: { orientation: "horizontal" },
21
30
  });
31
+
32
+ export interface DividerProps extends ComponentProps<typeof StyledDivider> {}
33
+
34
+ /**
35
+ * Divider — a hairline rule matching Chakra's <Divider> (60% opacity; set
36
+ * `borderColor` to tint). `orientation="vertical"` needs a height from the
37
+ * layout, e.g. a stretched flex row (Chakra's vertical divider likewise
38
+ * relied on `height: 100%`).
39
+ */
40
+ export const Divider = forwardRef<HTMLHRElement, DividerProps>(
41
+ function Divider(props, ref) {
42
+ return (
43
+ <StyledDivider
44
+ ref={ref}
45
+ aria-orientation={
46
+ props.orientation === "vertical" ? "vertical" : "horizontal"
47
+ }
48
+ {...props}
49
+ />
50
+ );
51
+ },
52
+ );
package/src/Fade.tsx ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { ReactNode } from "react";
7
+ import { css, cx } from "styled-system/css";
8
+ import { SystemStyleObject } from "styled-system/types";
9
+
10
+ export interface FadeProps {
11
+ /** Visible when true; faded out (but mounted) when false. */
12
+ isOpen: boolean;
13
+ css?: SystemStyleObject;
14
+ className?: string;
15
+ children: ReactNode;
16
+ }
17
+
18
+ /**
19
+ * Fade — Chakra's Fade transition as a CSS opacity transition (see Slide for
20
+ * the pattern). Content stays mounted throughout.
21
+ */
22
+ export const Fade = ({
23
+ isOpen,
24
+ css: cssProp,
25
+ className,
26
+ children,
27
+ }: FadeProps) => (
28
+ <div
29
+ data-open={isOpen ? "" : undefined}
30
+ className={cx(
31
+ css(
32
+ {
33
+ opacity: 0,
34
+ pointerEvents: "none",
35
+ "&[data-open]": { opacity: 1, pointerEvents: "auto" },
36
+ transitionProperty: "opacity",
37
+ transitionDuration: "0.2s",
38
+ transitionTimingFunction: "ease-out",
39
+ _motionReduce: { transition: "none" },
40
+ },
41
+ cssProp,
42
+ ),
43
+ className,
44
+ )}
45
+ >
46
+ {children}
47
+ </div>
48
+ );
@@ -10,7 +10,12 @@ export interface IconButtonProps
10
10
  extends Omit<ButtonProps, "leftIcon" | "rightIcon"> {
11
11
  /** Icon-only buttons have no visible label, so this is required. */
12
12
  "aria-label": string;
13
- /** Circular rather than the recipe's border-radius (Chakra's `isRound`). */
13
+ /**
14
+ * Circular rather than the recipe's border-radius (Chakra's `isRound`).
15
+ * Usually a visual no-op here: the 2rem `button` radius already renders
16
+ * square icon buttons as circles. Kept for ported call sites and for
17
+ * variants/overrides with a smaller radius (e.g. `unstyled`).
18
+ */
14
19
  isRound?: boolean;
15
20
  }
16
21
 
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
+ });
@@ -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 CHANGED
@@ -6,10 +6,14 @@
6
6
  import { styled } from "styled-system/jsx";
7
7
 
8
8
  /**
9
- * Unstyled list, matching Chakra's <List> (Panda's preflight already zeroes
10
- * margins/padding and removes list markers).
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).
11
13
  */
12
- export const List = styled("ul");
14
+ export const List = styled("ul", {
15
+ base: { listStyleType: "none" },
16
+ });
13
17
 
14
18
  /** List item, matching Chakra's <ListItem>. */
15
19
  export const ListItem = styled("li", {
@@ -18,7 +18,17 @@ import { defineSlotRecipe } from "@pandacss/dev";
18
18
  */
19
19
  export const menu = defineSlotRecipe({
20
20
  className: "menu",
21
- slots: ["content", "list", "item", "icon", "label", "divider"],
21
+ slots: [
22
+ "content",
23
+ "list",
24
+ "item",
25
+ "icon",
26
+ "label",
27
+ "divider",
28
+ "group",
29
+ "groupTitle",
30
+ "itemIndicator",
31
+ ],
22
32
  base: {
23
33
  content: {
24
34
  bg: "white",
@@ -84,5 +94,26 @@ export const menu = defineSlotRecipe({
84
94
  my: "2",
85
95
  opacity: 0.6,
86
96
  },
97
+ group: {},
98
+ // Chakra's MenuGroup/MenuOptionGroup title.
99
+ groupTitle: {
100
+ display: "block",
101
+ mx: "4",
102
+ my: "2",
103
+ fontWeight: "semibold",
104
+ fontSize: "sm",
105
+ },
106
+ // Check glyph slot for MenuItemOption: space always reserved (as Chakra
107
+ // does), visible only on the selected item (RAC sets data-selected on it).
108
+ itemIndicator: {
109
+ display: "inline-flex",
110
+ alignItems: "center",
111
+ justifyContent: "center",
112
+ flexShrink: 0,
113
+ marginEnd: "0.75rem",
114
+ fontSize: "0.8em",
115
+ opacity: 0,
116
+ "[data-selected] &": { opacity: 1 },
117
+ },
87
118
  },
88
119
  });