@microbit/ui 0.1.0-alpha.3 → 0.1.0-alpha.31

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.
Files changed (115) hide show
  1. package/LICENSE.md +40 -0
  2. package/README.md +279 -26
  3. package/lang/ui.ar.json +62 -0
  4. package/lang/ui.ca.json +43 -3
  5. package/lang/ui.cy.json +62 -0
  6. package/lang/ui.de.json +62 -0
  7. package/lang/ui.en-us.json +40 -0
  8. package/lang/ui.en.json +40 -0
  9. package/lang/ui.es-es.json +43 -3
  10. package/lang/ui.fr.json +44 -4
  11. package/lang/ui.ga-ie.json +62 -0
  12. package/lang/ui.it.json +62 -0
  13. package/lang/ui.ja.json +44 -4
  14. package/lang/ui.ko.json +44 -4
  15. package/lang/ui.lo.json +62 -0
  16. package/lang/ui.lol.json +40 -0
  17. package/lang/ui.nl.json +44 -4
  18. package/lang/ui.pl.json +44 -4
  19. package/lang/ui.pt-br.json +44 -4
  20. package/lang/ui.vi.json +62 -0
  21. package/lang/ui.zh-cn.json +62 -0
  22. package/lang/ui.zh-tw.json +44 -4
  23. package/package.json +19 -10
  24. package/postcss-legacy-safari.cjs +96 -0
  25. package/reset.css +35 -0
  26. package/src/Avatar.recipe.ts +191 -0
  27. package/src/Avatar.tsx +278 -0
  28. package/src/Breadcrumb.recipe.ts +45 -0
  29. package/src/Breadcrumb.tsx +114 -0
  30. package/src/Button.recipe.ts +88 -50
  31. package/src/Button.tsx +68 -30
  32. package/src/ButtonGroup.tsx +37 -15
  33. package/src/Card.recipe.ts +1 -2
  34. package/src/Card.tsx +2 -1
  35. package/src/Checkbox.recipe.ts +49 -14
  36. package/src/Checkbox.tsx +111 -32
  37. package/src/CheckboxGroup.tsx +70 -0
  38. package/src/CloseButton.tsx +3 -3
  39. package/src/CloseIcon.tsx +6 -4
  40. package/src/Code.tsx +20 -0
  41. package/src/Collapse.tsx +179 -0
  42. package/src/ComboBox.tsx +246 -0
  43. package/src/Divider.tsx +72 -8
  44. package/src/Drawer.recipe.ts +21 -10
  45. package/src/Drawer.tsx +3 -4
  46. package/src/ExternalLink.tsx +43 -0
  47. package/src/Fade.tsx +62 -0
  48. package/src/Field.recipe.ts +114 -0
  49. package/src/Field.tsx +187 -0
  50. package/src/GridList.recipe.ts +57 -0
  51. package/src/GridList.tsx +81 -0
  52. package/src/Heading.recipe.ts +20 -1
  53. package/src/Heading.tsx +3 -3
  54. package/src/Icon.tsx +23 -7
  55. package/src/IconButton.tsx +8 -13
  56. package/src/Image.tsx +1 -1
  57. package/src/Input.recipe.ts +41 -28
  58. package/src/Input.tsx +23 -7
  59. package/src/InputGroup.tsx +26 -12
  60. package/src/Kbd.tsx +26 -0
  61. package/src/Link.tsx +3 -3
  62. package/src/LinkBox.tsx +2 -3
  63. package/src/LinkButton.tsx +81 -0
  64. package/src/List.tsx +8 -6
  65. package/src/ListBox.recipe.ts +43 -0
  66. package/src/ListBox.tsx +88 -0
  67. package/src/Menu.recipe.ts +51 -17
  68. package/src/Menu.tsx +117 -2
  69. package/src/Modal.recipe.ts +17 -9
  70. package/src/Modal.tsx +137 -24
  71. package/src/MoreMenuButton.tsx +63 -0
  72. package/src/NativeSelect.tsx +41 -14
  73. package/src/NativeSelectField.tsx +84 -0
  74. package/src/NumberField.recipe.ts +108 -0
  75. package/src/NumberField.tsx +138 -0
  76. package/src/PopoverArrow.tsx +19 -5
  77. package/src/ProgressBar.tsx +3 -5
  78. package/src/Radio.recipe.ts +108 -0
  79. package/src/Radio.tsx +62 -0
  80. package/src/RadioGroup.tsx +68 -0
  81. package/src/Select.recipe.ts +211 -0
  82. package/src/Select.tsx +194 -0
  83. package/src/SharedUIProvider.tsx +63 -7
  84. package/src/Skeleton.tsx +146 -0
  85. package/src/Slide.tsx +2 -2
  86. package/src/Slider.recipe.ts +27 -16
  87. package/src/Slider.tsx +78 -6
  88. package/src/Spinner.tsx +11 -9
  89. package/src/Svg.tsx +2 -3
  90. package/src/Switch.recipe.ts +57 -11
  91. package/src/Switch.tsx +39 -7
  92. package/src/Text.recipe.ts +26 -0
  93. package/src/Text.tsx +6 -2
  94. package/src/TextField.tsx +54 -40
  95. package/src/Toast.recipe.ts +52 -15
  96. package/src/Toast.tsx +151 -46
  97. package/src/Tooltip.recipe.ts +32 -0
  98. package/src/Tooltip.tsx +68 -28
  99. package/src/TooltipButton.tsx +229 -0
  100. package/src/UnmountCallback.tsx +2 -2
  101. package/src/VisuallyHidden.tsx +1 -1
  102. package/src/base-preset.ts +336 -84
  103. package/src/{chakra-tokens.ts → base-tokens.ts} +7 -7
  104. package/src/button-icon.ts +22 -0
  105. package/src/data-attrs.ts +16 -0
  106. package/src/dense-preset.ts +107 -0
  107. package/src/hooks/useBreakpointValue.ts +4 -4
  108. package/src/hooks/useClipboard.ts +63 -0
  109. package/src/hooks/useDisclosure.ts +31 -0
  110. package/src/hooks/useMediaQuery.ts +27 -0
  111. package/src/hooks/usePrevious.ts +15 -0
  112. package/src/index.ts +29 -3
  113. package/src/rac-locale.ts +33 -0
  114. package/src/system.ts +22 -1
  115. package/src/TextField.recipe.ts +0 -54
package/src/Checkbox.tsx CHANGED
@@ -3,64 +3,143 @@
3
3
  *
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
- import { ReactNode } from "react";
6
+ import { ReactNode, useId } from "react";
7
7
  import {
8
8
  Checkbox as RACCheckbox,
9
9
  CheckboxProps as RACCheckboxProps,
10
10
  } from "react-aria-components";
11
+ import { useIntl } from "react-intl";
11
12
  import { css, cx } from "styled-system/css";
12
- import { checkbox } from "styled-system/recipes";
13
+ import { checkbox, CheckboxVariantProps } from "styled-system/recipes";
13
14
  import { SystemStyleObject } from "styled-system/types";
15
+ import { FieldHelperText } from "./Field";
16
+ import { uiMessage } from "./messages";
17
+
18
+ /** What a render-prop child is told about the checkbox. */
19
+ export interface CheckboxState {
20
+ isSelected: boolean;
21
+ isFocusVisible: boolean;
22
+ isDisabled: boolean;
23
+ }
14
24
 
15
25
  export interface CheckboxProps
16
- extends Omit<RACCheckboxProps, "className" | "children" | "style"> {
26
+ extends Omit<RACCheckboxProps, "className" | "children" | "style">,
27
+ CheckboxVariantProps {
17
28
  /** Per-instance style overrides for the root, merged after the recipe. */
18
29
  css?: SystemStyleObject;
19
30
  className?: string;
20
- children?: ReactNode;
31
+ /**
32
+ * The label. A function receives the checkbox's state, for a label that
33
+ * changes with it.
34
+ */
35
+ children?: ReactNode | ((state: CheckboxState) => ReactNode);
36
+ /**
37
+ * Whether to draw the box. `false` is for a checkbox whose children draw
38
+ * the selected state themselves — a selectable tile, or an avatar that
39
+ * grows a tick. The label wrapper goes with it, so the children own the
40
+ * whole row, including the focus ring the box would otherwise carry.
41
+ *
42
+ * @default true
43
+ */
44
+ control?: boolean;
45
+ /**
46
+ * Help text below the checkbox, wired to its `aria-describedby` — the same
47
+ * chrome the labelled fields' `helperText` renders. With it the component
48
+ * gains a wrapping `<div>`, so the checkbox-plus-text moves as one block.
49
+ */
50
+ helperText?: ReactNode;
51
+ /** Per-instance style overrides for the helper text. */
52
+ helperTextCss?: SystemStyleObject;
21
53
  }
22
54
 
23
55
  /**
24
- * Checkbox — react-aria-components <Checkbox> styled like Chakra's md
25
- * checkbox. Children render as the label; wrap them in a visually-hidden
26
- * span for icon-less checkboxes.
56
+ * Checkbox — a styled react-aria-components <Checkbox>. Children render as
57
+ * the label; wrap them in a visually-hidden span for icon-less checkboxes.
27
58
  */
28
59
  export const Checkbox = ({
60
+ size,
29
61
  css: cssProp,
30
62
  className,
31
63
  children,
64
+ control,
65
+ helperText,
66
+ helperTextCss,
32
67
  ...rest
33
68
  }: CheckboxProps) => {
34
- const slots = checkbox();
35
- return (
69
+ const slots = checkbox({ size });
70
+ const helperId = useId();
71
+ const intl = useIntl();
72
+ const describedBy =
73
+ [rest["aria-describedby"], helperText != null ? helperId : undefined]
74
+ .filter(Boolean)
75
+ .join(" ") || undefined;
76
+ // Default name for a row-selection checkbox (a GridList row's). A future
77
+ // Table's select-all header checkbox shares the slot name and will need a
78
+ // different label.
79
+ const ariaLabel =
80
+ rest["aria-label"] ??
81
+ (rest.slot === "selection"
82
+ ? intl.formatMessage(uiMessage("ui.select-row-action"))
83
+ : undefined);
84
+ const checkboxElement = (
36
85
  <RACCheckbox
37
86
  className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
38
87
  {...rest}
88
+ aria-label={ariaLabel}
89
+ aria-describedby={describedBy}
39
90
  >
40
- {({ isSelected, isFocusVisible }) => (
41
- <>
42
- <span
43
- className={slots.control}
44
- data-selected={isSelected || undefined}
45
- data-focus-visible={isFocusVisible || undefined}
46
- aria-hidden
47
- >
48
- {isSelected && (
49
- <svg viewBox="0 0 12 10" className={slots.icon} aria-hidden>
50
- <polyline
51
- points="1.5 6 4.5 9 10.5 1"
52
- fill="none"
53
- stroke="currentColor"
54
- strokeWidth="2"
55
- strokeLinecap="round"
56
- strokeLinejoin="round"
57
- />
58
- </svg>
91
+ {({ isSelected, isFocusVisible, isDisabled }) => {
92
+ const content =
93
+ typeof children === "function"
94
+ ? children({ isSelected, isFocusVisible, isDisabled })
95
+ : children;
96
+ if (control === false) {
97
+ return content;
98
+ }
99
+ return (
100
+ <>
101
+ <span
102
+ className={slots.control}
103
+ data-selected={isSelected || undefined}
104
+ data-focus-visible={isFocusVisible || undefined}
105
+ data-disabled={isDisabled || undefined}
106
+ aria-hidden
107
+ >
108
+ {isSelected && (
109
+ <svg viewBox="0 0 12 10" className={slots.icon} aria-hidden>
110
+ <polyline
111
+ points="1.5 6 4.5 9 10.5 1"
112
+ fill="none"
113
+ stroke="currentColor"
114
+ strokeWidth="2"
115
+ strokeLinecap="round"
116
+ strokeLinejoin="round"
117
+ />
118
+ </svg>
119
+ )}
120
+ </span>
121
+ {content != null && (
122
+ <span
123
+ className={slots.label}
124
+ data-disabled={isDisabled || undefined}
125
+ >
126
+ {content}
127
+ </span>
59
128
  )}
60
- </span>
61
- {children != null && <span className={slots.label}>{children}</span>}
62
- </>
63
- )}
129
+ </>
130
+ );
131
+ }}
64
132
  </RACCheckbox>
65
133
  );
134
+ if (helperText == null) {
135
+ return checkboxElement;
136
+ }
137
+ return (
138
+ <div>
139
+ {checkboxElement}
140
+ <FieldHelperText id={helperId} css={helperTextCss}>
141
+ {helperText}
142
+ </FieldHelperText>
143
+ </div>
144
+ );
66
145
  };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * (c) 2026, Micro:bit Educational Foundation and contributors
3
+ *
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { ReactNode } from "react";
7
+ import {
8
+ CheckboxGroup as RACCheckboxGroup,
9
+ CheckboxGroupProps as RACCheckboxGroupProps,
10
+ } from "react-aria-components";
11
+ import { css, cx } from "styled-system/css";
12
+ import { SystemStyleObject } from "styled-system/types";
13
+ import { FieldLabel, FieldSupport, FieldSupportProps } from "./Field";
14
+
15
+ export interface CheckboxGroupProps
16
+ extends Omit<RACCheckboxGroupProps, "className" | "style">,
17
+ FieldSupportProps {
18
+ /**
19
+ * Visible label for the group. Use `aria-label` instead where the design
20
+ * has none.
21
+ */
22
+ label?: ReactNode;
23
+ /** Label style overrides. */
24
+ labelCss?: SystemStyleObject;
25
+ /** Per-instance style overrides. */
26
+ css?: SystemStyleObject;
27
+ className?: string;
28
+ }
29
+
30
+ /**
31
+ * CheckboxGroup — react-aria-components <CheckboxGroup> for a set of
32
+ * Checkboxes sharing one value array (each Checkbox's `value` marks its
33
+ * entry). Beyond the optional field chrome (label/helperText/errorMessage)
34
+ * it carries no styling of its own: compose with Stack for layout, as
35
+ * RadioGroup does.
36
+ */
37
+ export const CheckboxGroup = ({
38
+ label,
39
+ labelCss,
40
+ helperText,
41
+ errorMessage,
42
+ helperTextCss,
43
+ css: cssProp,
44
+ className,
45
+ children,
46
+ ...rest
47
+ }: CheckboxGroupProps) => {
48
+ return (
49
+ <RACCheckboxGroup
50
+ className={cx(cssProp ? css(cssProp) : undefined, className)}
51
+ {...rest}
52
+ >
53
+ {(renderProps) => (
54
+ <>
55
+ {label != null && (
56
+ <FieldLabel isRequired={rest.isRequired} css={labelCss}>
57
+ {label}
58
+ </FieldLabel>
59
+ )}
60
+ {typeof children === "function" ? children(renderProps) : children}
61
+ <FieldSupport
62
+ helperText={helperText}
63
+ errorMessage={errorMessage}
64
+ helperTextCss={helperTextCss}
65
+ />
66
+ </>
67
+ )}
68
+ </RACCheckboxGroup>
69
+ );
70
+ };
@@ -10,7 +10,7 @@ import { CloseIcon } from "./CloseIcon";
10
10
 
11
11
  export interface CloseButtonProps
12
12
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> {
13
- /** Chakra CloseButton sizes: sm 24px box, md 32px (default). */
13
+ /** sm is a 24px box, md 32px (default). */
14
14
  size?: "sm" | "md";
15
15
  /**
16
16
  * Grow the touch target 8px beyond the visible button on every side via an
@@ -24,7 +24,7 @@ export interface CloseButtonProps
24
24
  }
25
25
 
26
26
  /**
27
- * CloseButton — Chakra's standalone X button. A plain button (not react-aria)
27
+ * CloseButton — a standalone X button. A plain button (not react-aria)
28
28
  * so call sites can extend the hit area with pseudo-elements, which
29
29
  * react-aria's press bounding-rect check would defeat.
30
30
  */
@@ -57,7 +57,7 @@ export const CloseButton = forwardRef<HTMLButtonElement, CloseButtonProps>(
57
57
  transitionDuration: "normal",
58
58
  _hover: { bg: "blackAlpha.100" },
59
59
  _active: { bg: "blackAlpha.200" },
60
- _focusVisible: { focusShadow: "outline" },
60
+ _focusVisible: { focusRing: "outline" },
61
61
  },
62
62
  size === "sm"
63
63
  ? { width: "6", height: "6", fontSize: "2xs" }
package/src/CloseIcon.tsx CHANGED
@@ -12,10 +12,12 @@ export interface CloseIconProps {
12
12
  }
13
13
 
14
14
  /**
15
- * CloseIcon — the "✕" glyph used on close buttons (dialogs, toasts). This is
16
- * Chakra's exact CloseButton path so the visual matches; sized to `1em` and
17
- * `fill: currentColor` like the other icons. Reused wherever a close control
18
- * is needed.
15
+ * CloseIcon — the "✕" glyph used on close buttons (dialogs, toasts). Sized to
16
+ * `1em` and `fill: currentColor` like the other icons. Reused wherever a
17
+ * close control is needed.
18
+ *
19
+ * The path is Chakra UI's CloseButton icon, inlined for visual parity with
20
+ * the apps' original look (see the notice in LICENSE.md).
19
21
  */
20
22
  export const CloseIcon = ({ css: cssProp, className }: CloseIconProps) => (
21
23
  <svg
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 — an inline code chip (subtle grey, 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,179 @@
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. */
19
+ isOpen: boolean;
20
+ /** Height when collapsed. */
21
+ startingHeight?: number | string;
22
+ /**
23
+ * Height when expanded (defaults to the measured content height, tracked
24
+ * 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 — an expand/collapse transition: the content is measured and the
40
+ * wrapper's height (and opacity, when collapsing to zero) transitions between
41
+ * the collapsed and expanded sizes. Content stays mounted unless
42
+ * `unmountOnExit` is set.
43
+ *
44
+ * Only `isOpen` changes animate: initial mounts render at rest, and height
45
+ * corrections (late measurement, content growth) snap — otherwise a collapse
46
+ * mounted inside an entering view morphs during the outer animation.
47
+ */
48
+ export const Collapse = ({
49
+ isOpen,
50
+ startingHeight = 0,
51
+ endingHeight,
52
+ unmountOnExit = false,
53
+ css: cssProp,
54
+ className,
55
+ style,
56
+ children,
57
+ }: CollapseProps) => {
58
+ const contentRef = useRef<HTMLDivElement>(null);
59
+ const [measuredHeight, setMeasuredHeight] = useState<number>();
60
+ // For unmountOnExit: stay in the DOM until the exit transition ends.
61
+ const [present, setPresent] = useState(isOpen);
62
+ // The state the DOM shows. It follows isOpen from a layout effect so the
63
+ // transition-enable and the height flip land in the same commit (a
64
+ // render-time flip detector is unreliable: React can discard renders,
65
+ // losing the state update while keeping ref mutations).
66
+ const [displayOpen, setDisplayOpen] = useState(isOpen);
67
+ const [animating, setAnimating] = useState(false);
68
+
69
+ useLayoutEffect(() => {
70
+ if (isOpen === displayOpen) {
71
+ return;
72
+ }
73
+ if (isOpen) {
74
+ // Opening: mount (collapsed) first, then flip on the next frame so
75
+ // the browser has a computed collapsed state to transition from.
76
+ setPresent(true);
77
+ const raf = requestAnimationFrame(() => {
78
+ setAnimating(true);
79
+ setDisplayOpen(true);
80
+ });
81
+ return () => cancelAnimationFrame(raf);
82
+ }
83
+ // Closing: enable the transition and flip together, pre-paint.
84
+ setAnimating(true);
85
+ setDisplayOpen(false);
86
+ }, [isOpen, displayOpen]);
87
+
88
+ useLayoutEffect(() => {
89
+ const el = contentRef.current;
90
+ if (!el || endingHeight !== undefined) {
91
+ return;
92
+ }
93
+ setMeasuredHeight(el.scrollHeight);
94
+ const observer = new ResizeObserver(() =>
95
+ setMeasuredHeight(el.scrollHeight),
96
+ );
97
+ observer.observe(el);
98
+ return () => observer.disconnect();
99
+ }, [endingHeight, present]);
100
+
101
+ // Fallback for browsers/edge cases where transitionend doesn't fire.
102
+ useEffect(() => {
103
+ if (animating) {
104
+ const timeout = setTimeout(() => {
105
+ setAnimating(false);
106
+ if (!isOpen && unmountOnExit) {
107
+ setPresent(false);
108
+ }
109
+ }, 400);
110
+ return () => clearTimeout(timeout);
111
+ }
112
+ }, [animating, isOpen, unmountOnExit]);
113
+
114
+ if (unmountOnExit && !present) {
115
+ return null;
116
+ }
117
+
118
+ const expanded =
119
+ endingHeight !== undefined
120
+ ? toCssSize(endingHeight)
121
+ : measuredHeight !== undefined
122
+ ? `${measuredHeight}px`
123
+ : "auto";
124
+ const collapsed = toCssSize(startingHeight);
125
+ const hideWhenCollapsed = collapsed === "0px" || collapsed === "0";
126
+
127
+ return (
128
+ <div
129
+ onTransitionEnd={(e) => {
130
+ if (e.propertyName === "height" && e.target === e.currentTarget) {
131
+ setAnimating(false);
132
+ if (!isOpen && unmountOnExit) {
133
+ setPresent(false);
134
+ }
135
+ }
136
+ }}
137
+ className={cx(
138
+ css(
139
+ {
140
+ overflow: "hidden",
141
+ display: "block",
142
+ transitionDuration: "0.25s",
143
+ transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
144
+ _motionReduce: { transition: "none" },
145
+ },
146
+ cssProp,
147
+ ),
148
+ className,
149
+ )}
150
+ // Runtime-measured/caller-supplied heights; transitions enabled only
151
+ // while an isOpen change is in flight.
152
+ style={{
153
+ transitionProperty: animating ? "height, opacity" : "none",
154
+ height: displayOpen ? expanded : collapsed,
155
+ opacity: displayOpen || !hideWhenCollapsed ? 1 : 0,
156
+ // At rest fully-collapsed, remove the content from the a11y tree
157
+ // and tab order (kept visible while animating so the exit
158
+ // transition shows).
159
+ visibility:
160
+ displayOpen || animating || !hideWhenCollapsed ? undefined : "hidden",
161
+ ...style,
162
+ }}
163
+ >
164
+ <div
165
+ ref={contentRef}
166
+ // flow-root: the measured element must be a block formatting
167
+ // context, or children's margins collapse through it and
168
+ // scrollHeight under-measures — the outer (overflow: hidden)
169
+ // wrapper IS a BFC and needs the full height, so the mismatch
170
+ // clips the last line of margined content (python-editor's API
171
+ // docs). Rendering is unchanged: the margins always displayed
172
+ // inside the outer BFC.
173
+ className={css({ display: "flow-root" })}
174
+ >
175
+ {children}
176
+ </div>
177
+ </div>
178
+ );
179
+ };