@microbit/ui 0.1.0-alpha.14 → 0.1.0-alpha.16

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.
@@ -0,0 +1,146 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { HTMLAttributes, ReactNode } from "react";
7
+ import { css, cx } from "styled-system/css";
8
+ import { SystemStyleObject } from "styled-system/types";
9
+
10
+ /**
11
+ * The placeholder block, as an object rather than a precomputed class so a
12
+ * caller's `css` is merged into one `css()` call and its overrides win
13
+ * (playbook gotcha #8).
14
+ *
15
+ * The colours are Chakra's, through the same pair of custom properties, so a
16
+ * call site can retint one skeleton without knowing how the animation works.
17
+ */
18
+ const skeletonBase: SystemStyleObject = {
19
+ "--skeleton-start-color": "token(colors.gray.100)",
20
+ "--skeleton-end-color": "token(colors.gray.400)",
21
+ background: "var(--skeleton-start-color)",
22
+ borderColor: "var(--skeleton-end-color)",
23
+ opacity: 0.7,
24
+ borderRadius: "sm",
25
+ boxShadow: "none",
26
+ backgroundClip: "padding-box",
27
+ cursor: "default",
28
+ color: "transparent",
29
+ pointerEvents: "none",
30
+ userSelect: "none",
31
+ // Chakra hid the content rather than unmounting it, so a skeleton sized
32
+ // from real children keeps their dimensions.
33
+ "&::before, &::after, *": { visibility: "hidden" },
34
+ animation:
35
+ "skeletonFade var(--skeleton-speed, 0.8s) linear infinite alternate",
36
+ _motionReduce: { animation: "none" },
37
+ };
38
+
39
+ export interface SkeletonProps
40
+ extends Omit<HTMLAttributes<HTMLDivElement>, "color"> {
41
+ /** Show the children instead of the placeholder. */
42
+ isLoaded?: boolean;
43
+ /** Seconds per pulse (Chakra's `speed`, default 0.8). */
44
+ speed?: number;
45
+ children?: ReactNode;
46
+ /** Per-instance style overrides, merged after the base. */
47
+ css?: SystemStyleObject;
48
+ className?: string;
49
+ }
50
+
51
+ /**
52
+ * Skeleton — Chakra's loading placeholder: a block pulsing between two greys
53
+ * until its content is ready.
54
+ *
55
+ * Chakra faded the real content in over 0.4s when `isLoaded` turned true;
56
+ * here it simply appears. Wrap in `Fade` where that transition matters.
57
+ */
58
+ export const Skeleton = ({
59
+ isLoaded,
60
+ speed,
61
+ children,
62
+ css: cssProp,
63
+ className,
64
+ style,
65
+ ...rest
66
+ }: SkeletonProps) => {
67
+ if (isLoaded) {
68
+ return (
69
+ <div
70
+ {...rest}
71
+ style={style}
72
+ className={cx(cssProp ? css(cssProp) : undefined, className)}
73
+ >
74
+ {children}
75
+ </div>
76
+ );
77
+ }
78
+ return (
79
+ <div
80
+ {...rest}
81
+ style={
82
+ speed === undefined
83
+ ? style
84
+ : ({ ...style, "--skeleton-speed": `${speed}s` } as typeof style)
85
+ }
86
+ className={cx(css({ ...skeletonBase, ...cssProp }), className)}
87
+ >
88
+ {children}
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export interface SkeletonTextProps extends SkeletonProps {
94
+ /** How many lines to draw (Chakra's default is 3). */
95
+ noOfLines?: number;
96
+ /** Gap between the lines. Any CSS length. */
97
+ spacing?: string;
98
+ /** Height of each line. Any CSS length. */
99
+ skeletonHeight?: string;
100
+ }
101
+
102
+ /**
103
+ * SkeletonText — a paragraph-shaped `Skeleton`: evenly spaced lines, the last
104
+ * one short, as Chakra drew them.
105
+ */
106
+ export const SkeletonText = ({
107
+ noOfLines = 3,
108
+ spacing = "0.5rem",
109
+ skeletonHeight = "0.5rem",
110
+ isLoaded,
111
+ speed,
112
+ children,
113
+ css: cssProp,
114
+ className,
115
+ ...rest
116
+ }: SkeletonTextProps) => {
117
+ if (isLoaded) {
118
+ return (
119
+ <div
120
+ {...rest}
121
+ className={cx(cssProp ? css(cssProp) : undefined, className)}
122
+ >
123
+ {children}
124
+ </div>
125
+ );
126
+ }
127
+ return (
128
+ <div
129
+ {...rest}
130
+ className={cx(cssProp ? css(cssProp) : undefined, className)}
131
+ >
132
+ {Array.from({ length: noOfLines }, (_, index) => (
133
+ <Skeleton
134
+ key={index}
135
+ speed={speed}
136
+ style={{
137
+ height: skeletonHeight,
138
+ // Chakra's shape: a last line at 80%, and no gap after it.
139
+ width: noOfLines > 1 && index === noOfLines - 1 ? "80%" : "100%",
140
+ marginBottom: index === noOfLines - 1 ? "0" : spacing,
141
+ }}
142
+ />
143
+ ))}
144
+ </div>
145
+ );
146
+ };
package/src/Spinner.tsx CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { CSSProperties } from "react";
7
7
  import { css, cx } from "styled-system/css";
8
+ import { dataAttrs } from "./data-attrs";
8
9
  import { SystemStyleObject } from "styled-system/types";
9
10
 
10
11
  export interface SpinnerProps {
@@ -23,6 +24,8 @@ export interface SpinnerProps {
23
24
  * "Loading..." by default, so a nameless spinner would regress on it.
24
25
  */
25
26
  "aria-label": string;
27
+ /** `data-*` attributes land on the spinner, for tests that wait on it. */
28
+ [key: `data-${string}`]: unknown;
26
29
  }
27
30
 
28
31
  /**
@@ -35,8 +38,10 @@ export const Spinner = ({
35
38
  css: cssProp,
36
39
  className,
37
40
  "aria-label": ariaLabel,
41
+ ...rest
38
42
  }: SpinnerProps) => (
39
43
  <span
44
+ {...dataAttrs(rest)}
40
45
  role="status"
41
46
  aria-label={ariaLabel}
42
47
  style={speed ? ({ "--spinner-speed": speed } as CSSProperties) : undefined}
package/src/TextField.tsx CHANGED
@@ -49,11 +49,13 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
49
49
  helperTextCss,
50
50
  onFocus,
51
51
  autoCapitalize,
52
- size,
53
- ...rest
52
+ ...props
54
53
  },
55
54
  ref,
56
55
  ) {
56
+ // As Input: forward every recipe variant group, not just `size`, so a
57
+ // preset that adds one keeps working.
58
+ const [variantProps, rest] = input.splitVariantProps(props);
57
59
  const slots = field();
58
60
  return (
59
61
  <RACTextField {...rest} className={slots.root}>
@@ -67,7 +69,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
67
69
  </RACLabel>
68
70
  <RACInput
69
71
  ref={ref}
70
- className={input({ size })}
72
+ className={input(variantProps)}
71
73
  onFocus={onFocus}
72
74
  autoCapitalize={autoCapitalize}
73
75
  />
@@ -0,0 +1,37 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { defineRecipe } from "@pandacss/dev";
7
+
8
+ /**
9
+ * Tooltip recipe — Chakra's dark tooltip.
10
+ *
11
+ * A recipe rather than styles inside the component because tooltip typography
12
+ * is the kind of thing an app sets once for all of them: classroom's Chakra
13
+ * theme did exactly that (`fontSize: md`), and a `css` override at today's
14
+ * call sites would quietly not apply to tomorrow's.
15
+ *
16
+ * The colour, vertical padding and radius are Chakra's exactly. They had
17
+ * drifted (white, `py: 1`, `borderRadius: md`) while this lived inside the
18
+ * component, which classroom's port measured: a 6px radius where Chakra drew
19
+ * 2px. ml-trainer and python-editor pick the correction up too.
20
+ *
21
+ * Registered in the base preset (base-preset.ts).
22
+ */
23
+ export const tooltip = defineRecipe({
24
+ className: "tooltip",
25
+ base: {
26
+ bg: "gray.700",
27
+ color: "whiteAlpha.900",
28
+ px: "2",
29
+ py: "0.5",
30
+ borderRadius: "sm",
31
+ fontSize: "sm",
32
+ fontWeight: "medium",
33
+ boxShadow: "md",
34
+ maxW: "xs",
35
+ zIndex: "tooltip",
36
+ },
37
+ });
package/src/Tooltip.tsx CHANGED
@@ -5,26 +5,11 @@
5
5
  */
6
6
  import { ReactElement, ReactNode, RefObject } from "react";
7
7
  import { Tooltip as RACTooltip, TooltipTrigger } from "react-aria-components";
8
- import { css } from "styled-system/css";
8
+ import { css, cx } from "styled-system/css";
9
+ import { tooltip } from "styled-system/recipes";
9
10
  import { SystemStyleObject } from "styled-system/types";
10
11
  import { PopoverArrow } from "./PopoverArrow";
11
12
 
12
- // Base as an object (not a precomputed class) so a caller's `css` override is
13
- // merged into a single css() call — Panda then dedupes conflicting utilities
14
- // (e.g. px/py) so overrides actually win.
15
- const tooltipBase: SystemStyleObject = {
16
- bg: "gray.700",
17
- color: "white",
18
- px: "2",
19
- py: "1",
20
- borderRadius: "md",
21
- fontSize: "sm",
22
- fontWeight: "medium",
23
- boxShadow: "md",
24
- maxW: "xs",
25
- zIndex: "tooltip",
26
- };
27
-
28
13
  export interface TooltipProps {
29
14
  /**
30
15
  * Tooltip body (Chakra's `label`). Not named `content`: Panda extracts
@@ -82,7 +67,7 @@ export const Tooltip = ({
82
67
  triggerRef={triggerRef}
83
68
  placement={placement}
84
69
  offset={hasArrow ? 8 : 4}
85
- className={css({ ...tooltipBase, ...cssProp })}
70
+ className={cx(tooltip(), cssProp ? css(cssProp) : undefined)}
86
71
  >
87
72
  {hasArrow && <PopoverArrow css={{ "& svg": { fill: "gray.700" } }} />}
88
73
  {label}
@@ -23,19 +23,24 @@ import {
23
23
  } from "./base-tokens";
24
24
  // Config recipes are colocated with the shared-ui components they style; this
25
25
  // preset registers them so Panda merges them at codegen time.
26
+ import { avatar } from "./Avatar.recipe";
26
27
  import { button } from "./Button.recipe";
27
28
  import { card } from "./Card.recipe";
28
29
  import { checkbox } from "./Checkbox.recipe";
29
30
  import { radio } from "./Radio.recipe";
30
31
  import { drawer } from "./Drawer.recipe";
32
+ import { gridList } from "./GridList.recipe";
31
33
  import { heading } from "./Heading.recipe";
32
34
  import { input } from "./Input.recipe";
35
+ import { listBox } from "./ListBox.recipe";
33
36
  import { numberField } from "./NumberField.recipe";
34
37
  import { menu } from "./Menu.recipe";
38
+ import { select } from "./Select.recipe";
35
39
  import { slider } from "./Slider.recipe";
36
40
  import { switchRecipe } from "./Switch.recipe";
37
41
  import { dialog } from "./Modal.recipe";
38
42
  import { text } from "./Text.recipe";
43
+ import { tooltip } from "./Tooltip.recipe";
39
44
  import { field } from "./TextField.recipe";
40
45
  import { toast } from "./Toast.recipe";
41
46
 
@@ -65,11 +70,23 @@ export const basePreset = definePreset({
65
70
  theme: {
66
71
  breakpoints,
67
72
  keyframes: {
68
- // Spinner's revolution (the only keyframe a shared-ui component uses).
73
+ // Spinner's revolution.
69
74
  spin: {
70
75
  "0%": { transform: "rotate(0deg)" },
71
76
  "100%": { transform: "rotate(360deg)" },
72
77
  },
78
+ // Skeleton's pulse, over the pair of custom properties the component
79
+ // sets, so a retinted skeleton animates between its own colours.
80
+ skeletonFade: {
81
+ from: {
82
+ borderColor: "var(--skeleton-start-color)",
83
+ background: "var(--skeleton-start-color)",
84
+ },
85
+ to: {
86
+ borderColor: "var(--skeleton-end-color)",
87
+ background: "var(--skeleton-end-color)",
88
+ },
89
+ },
73
90
  },
74
91
  tokens: {
75
92
  colors: {
@@ -158,6 +175,27 @@ export const basePreset = definePreset({
158
175
  // follows; OSS language buttons are brand blue.)
159
176
  languageText: { value: "{colors.brand.500}" },
160
177
  languageTextHover: { value: "{colors.brand.600}" },
178
+ // The `primary`/`secondary` button variants' colours. Two brand
179
+ // idioms exist in the family: brand-coloured buttons (ml-trainer,
180
+ // python-editor — the defaults below) and a black-on-white system
181
+ // (classroom, data-microbit-org: black solid, black outline, no
182
+ // border colour change on hover but a blackAlpha wash instead).
183
+ // Tokens rather than per-app recipe overrides so both idioms share
184
+ // one recipe — a `variant` fork would be duplicated by every app on
185
+ // the far side of it. `primary`'s text colour stays a literal
186
+ // `white`: every app in the family puts white on a dark solid.
187
+ // `ghost` needs no tokens (black + blackAlpha in all four apps).
188
+ button: {
189
+ primaryBg: { value: "{colors.brand.500}" },
190
+ primaryHoverBg: { value: "{colors.brand.600}" },
191
+ primaryActiveBg: { value: "{colors.brand.700}" },
192
+ secondaryText: { value: "{colors.brand.700}" },
193
+ secondaryBorder: { value: "{colors.brand.500}" },
194
+ secondaryHoverBorder: { value: "{colors.brand.600}" },
195
+ secondaryHoverBg: { value: "transparent" },
196
+ secondaryActiveBorder: { value: "{colors.brand.700}" },
197
+ secondaryActiveBg: { value: "{colors.brand.50}" },
198
+ },
161
199
  // Toast status colours: the Chakra-era toast Alert restyle (teal for
162
200
  // every status except error) shared across the app family.
163
201
  toastInfoBg: { value: "{colors.teal.800}" },
@@ -174,16 +212,21 @@ export const basePreset = definePreset({
174
212
  heading,
175
213
  input,
176
214
  text,
215
+ tooltip,
177
216
  },
178
217
  slotRecipes: {
218
+ avatar,
179
219
  card,
180
220
  checkbox,
181
221
  dialog,
182
222
  drawer,
183
223
  field,
224
+ gridList,
225
+ listBox,
184
226
  menu,
185
227
  numberField,
186
228
  radio,
229
+ select,
187
230
  slider,
188
231
  switchRecipe,
189
232
  toast,
@@ -240,7 +283,12 @@ export const basePreset = definePreset({
240
283
  // can silently lose runtime-prop variants.
241
284
  staticCss: {
242
285
  recipes: {
243
- button: ["*"],
286
+ // Size is passed responsively at call sites ported from Chakra's
287
+ // `size={["md", "lg"]}`, so generate the breakpoint-prefixed variants
288
+ // too — otherwise the class lands on the element with no rule behind it
289
+ // and the button silently falls back to the base size.
290
+ avatar: ["*"],
291
+ button: [{ size: ["*"], responsive: true }, { variant: ["*"] }],
244
292
  checkbox: ["*"],
245
293
  heading: ["*"],
246
294
  card: ["*"],
@@ -248,10 +296,14 @@ export const basePreset = definePreset({
248
296
  // as a runtime prop, so generate the breakpoint-prefixed variants too.
249
297
  dialog: [{ size: ["*"], responsive: true }, { centered: ["*"] }],
250
298
  drawer: ["*"],
299
+ gridList: ["*"],
300
+ listBox: ["*"],
251
301
  input: ["*"],
252
302
  radio: ["*"],
303
+ select: ["*"],
253
304
  switchRecipe: ["*"],
254
305
  text: ["*"],
306
+ tooltip: ["*"],
255
307
  // Toast status is chosen at runtime from the toast content.
256
308
  toast: ["*"],
257
309
  },
@@ -0,0 +1,16 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ /**
8
+ * The `data-*` entries of a props object, for components that let a caller
9
+ * put test hooks on an inner element rather than the one their props land on.
10
+ *
11
+ * Internal: not exported from the package.
12
+ */
13
+ export const dataAttrs = (props: object): Record<string, unknown> =>
14
+ Object.fromEntries(
15
+ Object.entries(props).filter(([key]) => key.startsWith("data-")),
16
+ );
@@ -0,0 +1,108 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { definePreset } from "@pandacss/dev";
7
+
8
+ const toTokens = (values: Record<string, string>) =>
9
+ Object.fromEntries(
10
+ Object.entries(values).map(([k, value]) => [k, { value }]),
11
+ ) as Record<string, { value: string }>;
12
+
13
+ /**
14
+ * The numeric spacing/size grid, Chakra's 0.25rem step × 0.88.
15
+ *
16
+ * Only the numeric scale is touched: the named `sizes` (`xs`…`8xl`, `max`,
17
+ * `full`, `container.*`) stay at their base-preset values, as they did in
18
+ * both apps' Chakra themes.
19
+ */
20
+ const scale = toTokens({
21
+ px: "1px",
22
+ 0.5: "0.11rem",
23
+ 1: "0.22rem",
24
+ 1.5: "0.33rem",
25
+ 2: "0.44rem",
26
+ 2.5: "0.55rem",
27
+ 3: "0.66rem",
28
+ 3.5: "0.77rem",
29
+ 4: "0.88rem",
30
+ 5: "1.1rem",
31
+ 6: "1.32rem",
32
+ 7: "1.54rem",
33
+ 8: "1.76rem",
34
+ 9: "1.98rem",
35
+ 10: "2.2rem",
36
+ 12: "2.64rem",
37
+ 14: "3.08rem",
38
+ 16: "3.52rem",
39
+ 20: "4.4rem",
40
+ 24: "5.28rem",
41
+ 28: "6.16rem",
42
+ 32: "7.04rem",
43
+ 36: "7.92rem",
44
+ 40: "8.8rem",
45
+ 44: "9.68rem",
46
+ 48: "10.56rem",
47
+ 52: "11.44rem",
48
+ 56: "12.32rem",
49
+ 60: "13.2rem",
50
+ 64: "14.08rem",
51
+ 72: "15.84rem",
52
+ 80: "17.6rem",
53
+ 96: "21.12rem",
54
+ });
55
+
56
+ /**
57
+ * Font sizes from `md` up, × 0.9. `xs`/`sm` keep their full size so small
58
+ * text never gets too small, and `3xs`/`2xs` (which neither app's theme
59
+ * listed) stay at their base-preset values.
60
+ */
61
+ const denseFontSizes = toTokens({
62
+ md: "0.9rem",
63
+ lg: "1.012rem",
64
+ xl: "1.125rem",
65
+ "2xl": "1.35rem",
66
+ "3xl": "1.687rem",
67
+ "4xl": "2.025rem",
68
+ "5xl": "2.7rem",
69
+ "6xl": "3.375rem",
70
+ "7xl": "4.05rem",
71
+ "8xl": "5.4rem",
72
+ "9xl": "7.2rem",
73
+ });
74
+
75
+ /**
76
+ * The dense preset — an optional density override for the information-dense
77
+ * apps in the family. Stacks between the base preset and the app preset:
78
+ *
79
+ * ```ts
80
+ * presets: ["@pandacss/preset-base", basePreset, densePreset, appPreset]
81
+ * ```
82
+ *
83
+ * Both python-editor and classroom shipped the same "make everything
84
+ * smaller" Chakra theme change (2022): the numeric spacing/sizes grid at
85
+ * × 0.88 and `fontSizes` from `md` up at × 0.9. The two themes' values were
86
+ * byte-identical, so the scale lives here rather than being replicated in
87
+ * each app preset (migration-playbook gotcha #25 — a global scale override
88
+ * hides from every safeguard, so it needs to be explicit and shared).
89
+ *
90
+ * Whether this density stays or the family aligns on one scale is an open
91
+ * design question; when it is answered, this preset is the single place the
92
+ * answer lands (deleting it from an app's stack un-shrinks that app).
93
+ * ml-trainer and data-microbit-org do not use it.
94
+ */
95
+ export const densePreset = definePreset({
96
+ name: "microbit-ui-dense",
97
+ theme: {
98
+ extend: {
99
+ tokens: {
100
+ spacing: scale,
101
+ sizes: scale,
102
+ fontSizes: denseFontSizes,
103
+ },
104
+ },
105
+ },
106
+ });
107
+
108
+ export default densePreset;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { useCallback, useMemo, useState } from "react";
7
+
8
+ export interface Disclosure {
9
+ isOpen: boolean;
10
+ onOpen: () => void;
11
+ onClose: () => void;
12
+ onToggle: () => void;
13
+ }
14
+
15
+ /**
16
+ * useDisclosure — Chakra's hook of the same name: the open/closed state of a
17
+ * dialog, menu or drawer, and the three functions that change it.
18
+ *
19
+ * A thin `useState` wrapper, kept because it is the shape a Chakra app's
20
+ * dialog call sites are written in, and because a stable object means a
21
+ * disclosure can be passed to a memoised child without re-rendering it.
22
+ */
23
+ export const useDisclosure = (defaultIsOpen = false): Disclosure => {
24
+ const [isOpen, setIsOpen] = useState(defaultIsOpen);
25
+ const onOpen = useCallback(() => setIsOpen(true), []);
26
+ const onClose = useCallback(() => setIsOpen(false), []);
27
+ const onToggle = useCallback(() => setIsOpen((open) => !open), []);
28
+ return useMemo(
29
+ () => ({ isOpen, onOpen, onClose, onToggle }),
30
+ [isOpen, onOpen, onClose, onToggle],
31
+ );
32
+ };
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * match the Chakra theme; behaviour follows react-aria patterns.
10
10
  */
11
11
  export * from "./system";
12
+ export * from "./Avatar";
12
13
  export * from "./Button";
13
14
  export * from "./LinkButton";
14
15
  export * from "./ButtonGroup";
@@ -23,8 +24,10 @@ export * from "./NativeSelect";
23
24
  export * from "./NumberField";
24
25
  export * from "./ProgressBar";
25
26
  export * from "./Radio";
27
+ export * from "./Skeleton";
26
28
  export * from "./Slide";
27
29
  export * from "./Slider";
30
+ export * from "./Select";
28
31
  export * from "./Spinner";
29
32
  export * from "./Svg";
30
33
  export * from "./Switch";
@@ -40,8 +43,11 @@ export * from "./Collapse";
40
43
  export * from "./Fade";
41
44
  export * from "./Kbd";
42
45
  export * from "./Divider";
46
+ export * from "./GridList";
43
47
  export * from "./Drawer";
44
48
  export * from "./List";
49
+ export * from "./ListBox";
50
+ export * from "./ComboBox";
45
51
  export * from "./Menu";
46
52
  export * from "./Modal";
47
53
  export * from "./PopoverArrow";
@@ -51,5 +57,7 @@ export * from "./Toast";
51
57
  export * from "./VisuallyHidden";
52
58
  export { useBreakpointValue } from "./hooks/useBreakpointValue";
53
59
  export { useClipboard } from "./hooks/useClipboard";
60
+ export { useDisclosure } from "./hooks/useDisclosure";
61
+ export type { Disclosure } from "./hooks/useDisclosure";
54
62
  export { useMediaQuery } from "./hooks/useMediaQuery";
55
63
  export { usePrevious } from "./hooks/usePrevious";
package/src/system.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  export { css, cva, sva, cx } from "styled-system/css";
12
12
  export { token } from "styled-system/tokens";
13
13
  export type { SystemStyleObject } from "styled-system/types";
14
+ // react-aria collection types call sites need for selection handlers.
15
+ export type { Key, Selection } from "react-aria-components";
14
16
  export type {
15
17
  BoxProps,
16
18
  FlexProps,
@@ -21,9 +23,16 @@ export type {
21
23
  } from "styled-system/jsx";
22
24
 
23
25
  // Layout patterns — the Panda-native equivalents of Chakra's Box/Flex/Stack/etc.
26
+ //
27
+ // `styled` is re-exported for the `styled(Component)` form, which works from
28
+ // anywhere. The `styled.tag` JSX form does NOT: Panda recognises the factory by
29
+ // the module it was imported from, so `<styled.table css={…}>` on a `styled`
30
+ // imported from here silently produces no CSS. Import it from
31
+ // "styled-system/jsx" for that (playbook gotcha #41).
24
32
  export {
25
33
  AspectRatio,
26
34
  Box,
35
+ Container,
27
36
  Flex,
28
37
  Stack,
29
38
  HStack,