@microbit/ui 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +40 -0
- package/README.md +256 -26
- package/lang/ui.ca.json +43 -3
- package/lang/ui.cy.json +62 -0
- package/lang/ui.de.json +62 -0
- 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 +62 -0
- 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 +62 -0
- package/lang/ui.zh-tw.json +44 -4
- package/package.json +19 -10
- package/postcss-legacy-safari.cjs +96 -0
- package/reset.css +35 -0
- package/src/Avatar.recipe.ts +166 -0
- package/src/Avatar.tsx +278 -0
- package/src/Breadcrumb.recipe.ts +39 -0
- package/src/Breadcrumb.tsx +114 -0
- package/src/Button.recipe.ts +88 -50
- package/src/Button.tsx +64 -26
- package/src/ButtonGroup.tsx +37 -15
- package/src/Card.recipe.ts +1 -2
- package/src/Card.tsx +2 -1
- package/src/Checkbox.recipe.ts +49 -14
- package/src/Checkbox.tsx +111 -32
- package/src/CheckboxGroup.tsx +70 -0
- package/src/CloseButton.tsx +3 -3
- package/src/CloseIcon.tsx +6 -4
- package/src/Code.tsx +20 -0
- package/src/Collapse.tsx +179 -0
- package/src/ComboBox.tsx +246 -0
- package/src/Divider.tsx +72 -8
- package/src/Drawer.recipe.ts +4 -5
- package/src/Drawer.tsx +3 -4
- package/src/ExternalLink.tsx +43 -0
- package/src/Fade.tsx +62 -0
- 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 +7 -12
- package/src/Image.tsx +1 -1
- package/src/Input.recipe.ts +41 -28
- package/src/Input.tsx +23 -7
- package/src/InputGroup.tsx +21 -8
- package/src/Kbd.tsx +26 -0
- package/src/Link.tsx +3 -3
- package/src/LinkBox.tsx +2 -3
- package/src/LinkButton.tsx +81 -0
- package/src/List.tsx +8 -6
- package/src/ListBox.recipe.ts +43 -0
- package/src/ListBox.tsx +88 -0
- package/src/Menu.recipe.ts +51 -17
- package/src/Menu.tsx +117 -2
- package/src/Modal.recipe.ts +14 -9
- package/src/Modal.tsx +137 -24
- package/src/MoreMenuButton.tsx +63 -0
- package/src/NativeSelect.tsx +39 -12
- package/src/NativeSelectField.tsx +84 -0
- package/src/NumberField.recipe.ts +108 -0
- package/src/NumberField.tsx +138 -0
- package/src/PopoverArrow.tsx +19 -5
- package/src/ProgressBar.tsx +3 -5
- package/src/Radio.recipe.ts +108 -0
- package/src/Radio.tsx +62 -0
- package/src/RadioGroup.tsx +68 -0
- package/src/Select.recipe.ts +211 -0
- package/src/Select.tsx +194 -0
- package/src/SharedUIProvider.tsx +51 -7
- package/src/Skeleton.tsx +146 -0
- package/src/Slide.tsx +2 -2
- package/src/Slider.recipe.ts +24 -15
- package/src/Slider.tsx +65 -5
- package/src/Spinner.tsx +11 -9
- package/src/Svg.tsx +2 -3
- package/src/Switch.recipe.ts +58 -13
- package/src/Switch.tsx +39 -7
- package/src/Text.recipe.ts +26 -0
- package/src/Text.tsx +6 -2
- package/src/TextField.tsx +54 -40
- package/src/Toast.recipe.ts +49 -12
- package/src/Toast.tsx +151 -46
- package/src/Tooltip.recipe.ts +32 -0
- package/src/Tooltip.tsx +51 -28
- package/src/TooltipButton.tsx +229 -0
- package/src/UnmountCallback.tsx +2 -2
- package/src/VisuallyHidden.tsx +1 -1
- package/src/base-preset.ts +336 -84
- package/src/{chakra-tokens.ts → base-tokens.ts} +7 -7
- package/src/button-icon.ts +22 -0
- 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 +63 -0
- package/src/hooks/useDisclosure.ts +31 -0
- package/src/hooks/useMediaQuery.ts +27 -0
- package/src/hooks/usePrevious.ts +15 -0
- package/src/index.ts +29 -3
- package/src/rac-locale.ts +33 -0
- package/src/system.ts +22 -1
- package/src/TextField.recipe.ts +0 -54
package/src/Field.tsx
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
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 {
|
|
8
|
+
FieldError,
|
|
9
|
+
Label as RACLabel,
|
|
10
|
+
LabelProps as RACLabelProps,
|
|
11
|
+
Text as RACText,
|
|
12
|
+
} from "react-aria-components";
|
|
13
|
+
import { css, cx } from "styled-system/css";
|
|
14
|
+
import { field, FieldVariantProps } from "styled-system/recipes";
|
|
15
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The helper/error chrome every labelled form field shares — TextField,
|
|
19
|
+
* Select, ComboBox, NumberField, RadioGroup and CheckboxGroup carry the same
|
|
20
|
+
* props (data-microbit-org's forms attach helper and error text to all of
|
|
21
|
+
* these).
|
|
22
|
+
*/
|
|
23
|
+
export interface FieldSupportProps {
|
|
24
|
+
/** Help text below the field. */
|
|
25
|
+
helperText?: ReactNode;
|
|
26
|
+
/** Shown below the field when invalid. */
|
|
27
|
+
errorMessage?: ReactNode;
|
|
28
|
+
/** Per-instance style overrides for the helper text. */
|
|
29
|
+
helperTextCss?: SystemStyleObject;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `labelPosition` for the four single-control fields (TextField, NumberField,
|
|
34
|
+
* Select, ComboBox). Deliberately not on RadioGroup/CheckboxGroup: their roots
|
|
35
|
+
* carry no layout, and RAC's own `orientation` prop is a different axis there
|
|
36
|
+
* (it lays out the radios, not the label).
|
|
37
|
+
*/
|
|
38
|
+
export interface FieldLayoutProps {
|
|
39
|
+
/**
|
|
40
|
+
* `side` puts the label beside the control — the settings-row pattern. The
|
|
41
|
+
* label absorbs the free space; the control keeps its own width, so give it
|
|
42
|
+
* one (`groupCss`, `wrapperCss` or `triggerCss` depending on the field).
|
|
43
|
+
* Helper and error text drop to a full-width line below the pair.
|
|
44
|
+
*/
|
|
45
|
+
labelPosition?: FieldVariantProps["labelPosition"];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Helper text and error message for a react-aria field container. Render
|
|
50
|
+
* inside any RAC component with field validation context (TextField, Select,
|
|
51
|
+
* ComboBox, NumberField, RadioGroup, CheckboxGroup) — react-aria wires the
|
|
52
|
+
* description to the input's aria-describedby, and the error renders only
|
|
53
|
+
* while the field is invalid. Also exported for app-side composites built on
|
|
54
|
+
* RAC containers.
|
|
55
|
+
*/
|
|
56
|
+
export const FieldSupport = ({
|
|
57
|
+
helperText,
|
|
58
|
+
errorMessage,
|
|
59
|
+
helperTextCss,
|
|
60
|
+
labelPosition,
|
|
61
|
+
}: FieldSupportProps & FieldLayoutProps) => {
|
|
62
|
+
const slots = field({ labelPosition });
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{helperText != null && (
|
|
66
|
+
<RACText
|
|
67
|
+
slot="description"
|
|
68
|
+
className={cx(
|
|
69
|
+
slots.helperText,
|
|
70
|
+
helperTextCss ? css(helperTextCss) : undefined,
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{helperText}
|
|
74
|
+
</RACText>
|
|
75
|
+
)}
|
|
76
|
+
<FieldError className={slots.errorMessage}>{errorMessage}</FieldError>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
interface FieldTextProps extends HTMLAttributes<HTMLDivElement> {
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
/** The field's `labelPosition`, so the text lays out to match. */
|
|
84
|
+
labelPosition?: FieldVariantProps["labelPosition"];
|
|
85
|
+
/** Per-instance style overrides. */
|
|
86
|
+
css?: SystemStyleObject;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Context-free helper text for a control react-aria isn't wiring — a native
|
|
91
|
+
* select or a masked input. Inside a RAC field
|
|
92
|
+
* container use `FieldSupport`, which wires `aria-describedby` and validation
|
|
93
|
+
* for free; here the caller owns that wiring: give this an `id` and reference
|
|
94
|
+
* it from the control's `aria-describedby` (as `NativeSelectField` does).
|
|
95
|
+
*/
|
|
96
|
+
export const FieldHelperText = ({
|
|
97
|
+
children,
|
|
98
|
+
labelPosition,
|
|
99
|
+
css: cssProp,
|
|
100
|
+
...rest
|
|
101
|
+
}: FieldTextProps) => (
|
|
102
|
+
<div
|
|
103
|
+
{...rest}
|
|
104
|
+
className={cx(
|
|
105
|
+
field({ labelPosition }).helperText,
|
|
106
|
+
cssProp ? css(cssProp) : undefined,
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Context-free error message, the counterpart to `FieldHelperText` — see its
|
|
115
|
+
* note on the wiring the caller owns. RAC's
|
|
116
|
+
* `FieldError` renders only while its field is invalid; here that decision is
|
|
117
|
+
* the caller's too: render it conditionally.
|
|
118
|
+
*/
|
|
119
|
+
export const FieldErrorMessage = ({
|
|
120
|
+
children,
|
|
121
|
+
labelPosition,
|
|
122
|
+
css: cssProp,
|
|
123
|
+
...rest
|
|
124
|
+
}: FieldTextProps) => (
|
|
125
|
+
<div
|
|
126
|
+
{...rest}
|
|
127
|
+
className={cx(
|
|
128
|
+
field({ labelPosition }).errorMessage,
|
|
129
|
+
cssProp ? css(cssProp) : undefined,
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The required-field asterisk. Render inside the field's label when
|
|
138
|
+
* `isRequired`; aria-hidden because react-aria already announces requiredness
|
|
139
|
+
* from the input itself.
|
|
140
|
+
*/
|
|
141
|
+
export const FieldRequiredIndicator = () => (
|
|
142
|
+
<span aria-hidden className={field().requiredIndicator}>
|
|
143
|
+
*
|
|
144
|
+
</span>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
export interface FieldLabelProps
|
|
148
|
+
extends Omit<RACLabelProps, "className" | "children" | "style"> {
|
|
149
|
+
children: ReactNode;
|
|
150
|
+
/** Adds the required asterisk; pass the field's `isRequired`. */
|
|
151
|
+
isRequired?: boolean;
|
|
152
|
+
/** The field's `size`, so the label scales with its control. */
|
|
153
|
+
size?: FieldVariantProps["size"];
|
|
154
|
+
/** The field's `labelPosition`, so the label lays out to match. */
|
|
155
|
+
labelPosition?: FieldVariantProps["labelPosition"];
|
|
156
|
+
/** Per-instance style overrides. */
|
|
157
|
+
css?: SystemStyleObject;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A field's visible label, asterisk included. Every labelled field in the
|
|
162
|
+
* library renders its label through this, so the `field` recipe is the single
|
|
163
|
+
* answer to what a label looks like.
|
|
164
|
+
*
|
|
165
|
+
* Also exported for app-side composites: inside a RAC field container the
|
|
166
|
+
* association is automatic, and outside one (a masked or native input) pass
|
|
167
|
+
* `id` and `htmlFor` yourself.
|
|
168
|
+
*/
|
|
169
|
+
export const FieldLabel = ({
|
|
170
|
+
children,
|
|
171
|
+
isRequired,
|
|
172
|
+
size,
|
|
173
|
+
labelPosition,
|
|
174
|
+
css: cssProp,
|
|
175
|
+
...rest
|
|
176
|
+
}: FieldLabelProps) => (
|
|
177
|
+
<RACLabel
|
|
178
|
+
{...rest}
|
|
179
|
+
className={cx(
|
|
180
|
+
field({ size, labelPosition }).label,
|
|
181
|
+
cssProp ? css(cssProp) : undefined,
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
{children}
|
|
185
|
+
{isRequired ? <FieldRequiredIndicator /> : null}
|
|
186
|
+
</RACLabel>
|
|
187
|
+
);
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
* GridList slot recipe — a vertical list of selectable rows, each of which may
|
|
10
|
+
* hold its own interactive controls (which is what makes it a grid rather than
|
|
11
|
+
* a listbox: the roving tab index moves through rows, and the controls inside
|
|
12
|
+
* a row are reachable without leaving it).
|
|
13
|
+
*
|
|
14
|
+
* The greys here are the family's neutral list styling; an app with a strong
|
|
15
|
+
* selection colour restates them (classroom's roster does).
|
|
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 gridList = defineSlotRecipe({
|
|
21
|
+
className: "grid-list",
|
|
22
|
+
slots: ["root", "item"],
|
|
23
|
+
base: {
|
|
24
|
+
root: {
|
|
25
|
+
// The list takes 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
|
+
item: {
|
|
30
|
+
display: "flex",
|
|
31
|
+
alignItems: "center",
|
|
32
|
+
position: "relative",
|
|
33
|
+
// A row is interactive by definition — it selects, or it acts.
|
|
34
|
+
cursor: "pointer",
|
|
35
|
+
outline: "none",
|
|
36
|
+
transitionProperty: "background",
|
|
37
|
+
transitionDuration: "ultra-fast",
|
|
38
|
+
transitionTimingFunction: "ease-in",
|
|
39
|
+
_hover: { bg: "gray.50" },
|
|
40
|
+
// A row holding an open menu (or any other popover) keeps the hover
|
|
41
|
+
// grey, so the row an open menu belongs to stays visible. Hover state
|
|
42
|
+
// cannot do this on its own: a Popover lays a fixed full-viewport
|
|
43
|
+
// underlay over the page while open, which takes the pointer off the
|
|
44
|
+
// row — `:hover` and RAC's own `data-hovered` both drop the moment the
|
|
45
|
+
// menu appears. A trigger carries `aria-expanded` (useOverlayTrigger),
|
|
46
|
+
// so the row can see its own open overlay.
|
|
47
|
+
"&:has([aria-expanded=true])": { bg: "gray.50" },
|
|
48
|
+
"&[data-selected]": {
|
|
49
|
+
bg: "gray.100",
|
|
50
|
+
_hover: { bg: "gray.100" },
|
|
51
|
+
"&:has([aria-expanded=true])": { bg: "gray.100" },
|
|
52
|
+
},
|
|
53
|
+
"&[data-focus-visible]": { focusRing: "outline" },
|
|
54
|
+
"&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
package/src/GridList.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
GridList as RACGridList,
|
|
9
|
+
GridListItem as RACGridListItem,
|
|
10
|
+
GridListItemProps as RACGridListItemProps,
|
|
11
|
+
GridListProps as RACGridListProps,
|
|
12
|
+
} from "react-aria-components";
|
|
13
|
+
import { css, cx } from "styled-system/css";
|
|
14
|
+
import { gridList } from "styled-system/recipes";
|
|
15
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
16
|
+
|
|
17
|
+
export interface GridListProps<T extends object>
|
|
18
|
+
extends Omit<RACGridListProps<T>, "className" | "style" | "children"> {
|
|
19
|
+
/** `GridListItem`s, or a render function when `items` is given. */
|
|
20
|
+
children: RACGridListProps<T>["children"];
|
|
21
|
+
/** Per-instance style overrides for the list, merged after the recipe. */
|
|
22
|
+
css?: SystemStyleObject;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* GridList — react-aria-components' <GridList>: a list of selectable rows,
|
|
28
|
+
* each of which may contain its own buttons and menus.
|
|
29
|
+
*
|
|
30
|
+
* Reach for it over a `ListBox` when the rows carry controls: a listbox option
|
|
31
|
+
* is a leaf, so a button inside one is unreachable by keyboard, where a grid
|
|
32
|
+
* row's contents are part of the grid's navigation.
|
|
33
|
+
*/
|
|
34
|
+
export const GridList = <T extends object>({
|
|
35
|
+
css: cssProp,
|
|
36
|
+
className,
|
|
37
|
+
children,
|
|
38
|
+
...rest
|
|
39
|
+
}: GridListProps<T>) => {
|
|
40
|
+
const slots = gridList();
|
|
41
|
+
return (
|
|
42
|
+
<RACGridList
|
|
43
|
+
{...rest}
|
|
44
|
+
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</RACGridList>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface GridListItemProps<T extends object = object>
|
|
52
|
+
extends Omit<RACGridListItemProps<T>, "className" | "style" | "children"> {
|
|
53
|
+
children?: ReactNode;
|
|
54
|
+
/** Per-instance style overrides for the row, merged after the recipe. */
|
|
55
|
+
css?: SystemStyleObject;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A row in a `GridList`. Its children are laid out by the row itself — the
|
|
61
|
+
* gridcell react-aria puts between them is `display: contents`.
|
|
62
|
+
*
|
|
63
|
+
* Give every row a `textValue`: react-aria derives typeahead text from string
|
|
64
|
+
* children only, and a row is usually a composition rather than a string.
|
|
65
|
+
*/
|
|
66
|
+
export const GridListItem = <T extends object = object>({
|
|
67
|
+
css: cssProp,
|
|
68
|
+
className,
|
|
69
|
+
children,
|
|
70
|
+
...rest
|
|
71
|
+
}: GridListItemProps<T>) => {
|
|
72
|
+
const slots = gridList();
|
|
73
|
+
return (
|
|
74
|
+
<RACGridListItem
|
|
75
|
+
{...rest}
|
|
76
|
+
className={cx(slots.item, cssProp ? css(cssProp) : undefined, className)}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</RACGridListItem>
|
|
80
|
+
);
|
|
81
|
+
};
|
package/src/Heading.recipe.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { defineRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Heading recipe —
|
|
9
|
+
* Heading recipe — a bold heading-font base + responsive sizes.
|
|
10
10
|
* A config recipe for the same reasons as `button` (see Button.recipe.ts);
|
|
11
11
|
* the `marketing` variant is token-driven via the `display` font, so brands
|
|
12
12
|
* only override tokens.
|
|
@@ -44,6 +44,25 @@ export const heading = defineRecipe({
|
|
|
44
44
|
// GT Walsheim in the private preset.
|
|
45
45
|
variant: {
|
|
46
46
|
marketing: { fontFamily: "display" },
|
|
47
|
+
// Page-title chrome in the accent colour (`headingAccent` — see
|
|
48
|
+
// base-preset.ts). Converged from classroom and data-microbit-org,
|
|
49
|
+
// which carried these two byte-identically app-side.
|
|
50
|
+
//
|
|
51
|
+
// `fontSize` goes through a doubled selector because `size` sets it too,
|
|
52
|
+
// responsively. Source order can't settle that: Panda hoists media
|
|
53
|
+
// queries below the base rules and emits variant rules in the order it
|
|
54
|
+
// meets them, so which of the two wins would depend on the breakpoint
|
|
55
|
+
// and on what other call sites exist. Two classes beat one at every
|
|
56
|
+
// width instead.
|
|
57
|
+
label: {
|
|
58
|
+
"&&": { fontSize: "4xl" },
|
|
59
|
+
color: "headingAccent",
|
|
60
|
+
},
|
|
61
|
+
subtitle: {
|
|
62
|
+
"&&": { fontSize: "xl" },
|
|
63
|
+
fontWeight: "normal",
|
|
64
|
+
color: "headingAccent",
|
|
65
|
+
},
|
|
47
66
|
},
|
|
48
67
|
},
|
|
49
68
|
defaultVariants: {
|
package/src/Heading.tsx
CHANGED
|
@@ -7,8 +7,8 @@ import { styled } from "styled-system/jsx";
|
|
|
7
7
|
import { heading } from "styled-system/recipes";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Heading —
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Heading — a heading backed by the `heading` config recipe (so the private
|
|
11
|
+
* preset can add brand variants). Defaults to an <h2>; pass `as` to change
|
|
12
|
+
* the element and `size` to pick the type scale.
|
|
13
13
|
*/
|
|
14
14
|
export const Heading = styled("h2", heading);
|
package/src/Icon.tsx
CHANGED
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { ComponentType, SVGProps } from "react";
|
|
7
7
|
import { css, cx } from "styled-system/css";
|
|
8
8
|
import { SystemStyleObject } from "styled-system/types";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Any component that renders an `<svg>` from svg props. Deliberately no
|
|
12
|
+
* narrower than the props `Icon` actually passes, so it accepts both
|
|
13
|
+
* react-icons' `IconType` and svgr components (`import X from "./x.svg?react"`,
|
|
14
|
+
* which the apps use for their custom-path icons).
|
|
15
|
+
*/
|
|
16
|
+
export type IconComponent = ComponentType<
|
|
17
|
+
Pick<
|
|
18
|
+
SVGProps<SVGSVGElement>,
|
|
19
|
+
"className" | "focusable" | "role" | "aria-label" | "aria-hidden"
|
|
20
|
+
>
|
|
21
|
+
>;
|
|
22
|
+
|
|
10
23
|
export interface IconProps {
|
|
11
|
-
/** The react-icons
|
|
12
|
-
as:
|
|
24
|
+
/** The icon component to render: a react-icons glyph or an svgr import. */
|
|
25
|
+
as: IconComponent;
|
|
13
26
|
/** Panda style overrides (size via fontSize/boxSize, colour, etc.). */
|
|
14
27
|
css?: SystemStyleObject;
|
|
15
28
|
className?: string;
|
|
@@ -18,10 +31,9 @@ export interface IconProps {
|
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
/**
|
|
21
|
-
* Icon — renders a react-icons glyph inline at `1em
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* like Chakra's icons rather than defaulting to black.
|
|
34
|
+
* Icon — renders a react-icons glyph inline at `1em`. `fill: currentColor`
|
|
35
|
+
* means the glyph follows the surrounding text colour rather than defaulting
|
|
36
|
+
* to black (set `css={{ color: ... }}` to override).
|
|
25
37
|
*
|
|
26
38
|
* Unlabelled icons are treated as decorative and hidden from assistive tech;
|
|
27
39
|
* a labelled icon gets `role="img"` (a bare svg aria-label is unreliably
|
|
@@ -43,6 +55,10 @@ export const Icon = ({
|
|
|
43
55
|
lineHeight: "1em",
|
|
44
56
|
flexShrink: 0,
|
|
45
57
|
fill: "currentColor",
|
|
58
|
+
// Set on the element itself: an inline-block icon sits ~3px off
|
|
59
|
+
// without it. Don't rely on Panda's preflight happening to set it on
|
|
60
|
+
// every svg.
|
|
61
|
+
verticalAlign: "middle",
|
|
46
62
|
...cssProp,
|
|
47
63
|
}),
|
|
48
64
|
className,
|
package/src/IconButton.tsx
CHANGED
|
@@ -10,8 +10,6 @@ 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`). */
|
|
14
|
-
isRound?: boolean;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
/**
|
|
@@ -19,19 +17,16 @@ export interface IconButtonProps
|
|
|
19
17
|
* padding (its size variants add `px`, which would squeeze a single glyph in a
|
|
20
18
|
* fixed-width button) and keeps the `minW` from the size so the box stays
|
|
21
19
|
* square. Pass the icon as children.
|
|
20
|
+
*
|
|
21
|
+
* Circular at every size without asking: the 2rem `button` radius exceeds half
|
|
22
|
+
* the tallest size (3rem), so CSS clamps it to a full round. Override
|
|
23
|
+
* `borderRadius` per instance for anything else — an attached group or a
|
|
24
|
+
* button that sits in a card corner needs per-corner control, not a shape flag.
|
|
22
25
|
*/
|
|
23
26
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
24
|
-
function IconButton({
|
|
27
|
+
function IconButton({ css: cssProp, children, ...rest }, ref) {
|
|
25
28
|
return (
|
|
26
|
-
<Button
|
|
27
|
-
ref={ref}
|
|
28
|
-
css={{
|
|
29
|
-
px: 0,
|
|
30
|
-
...(isRound ? { borderRadius: "full" } : {}),
|
|
31
|
-
...cssProp,
|
|
32
|
-
}}
|
|
33
|
-
{...rest}
|
|
34
|
-
>
|
|
29
|
+
<Button ref={ref} css={{ px: 0, ...cssProp }} {...rest}>
|
|
35
30
|
{children}
|
|
36
31
|
</Button>
|
|
37
32
|
);
|
package/src/Image.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { styled } from "styled-system/jsx";
|
|
7
7
|
|
|
8
|
-
/** Image — an <img> accepting Panda style props.
|
|
8
|
+
/** Image — an <img> accepting Panda style props. */
|
|
9
9
|
export const Image = styled("img", {
|
|
10
10
|
base: { maxWidth: "100%" },
|
|
11
11
|
});
|
package/src/Input.recipe.ts
CHANGED
|
@@ -5,21 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { defineRecipe } from "@pandacss/dev";
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
// token category).
|
|
8
|
+
// The common transition-property list, inlined (Panda has no
|
|
9
|
+
// transitionProperty token category).
|
|
10
10
|
const transitionCommon =
|
|
11
|
-
"background-color, border-color, color, fill, stroke, opacity,
|
|
11
|
+
"background-color, border-color, color, fill, stroke, opacity, transform";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Input recipe —
|
|
14
|
+
* Input recipe — the outline text field with an sm/md/lg size scale. Used by
|
|
15
15
|
* the shared-ui Input and NativeSelect, and by TextField's input slot.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* hover < focused < invalid is a specificity ladder (`&&`, `&&&`): Panda
|
|
18
|
+
* sorts state rules against its own pseudo-class table, not declaration
|
|
19
|
+
* order, so equal-specificity borderColor ties would leave hover winning.
|
|
20
|
+
* Variants still override freely (later cascade layer).
|
|
21
21
|
*
|
|
22
|
-
* Registered in the base preset (base-preset.ts)
|
|
22
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
23
|
+
* `staticCss` entry that keeps the runtime-prop size variants generated.
|
|
23
24
|
*/
|
|
24
25
|
export const input = defineRecipe({
|
|
25
26
|
className: "input",
|
|
@@ -32,32 +33,44 @@ export const input = defineRecipe({
|
|
|
32
33
|
font: "inherit",
|
|
33
34
|
transitionProperty: transitionCommon,
|
|
34
35
|
transitionDuration: "normal",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
borderColor: "gray.
|
|
36
|
+
border: "2px solid",
|
|
37
|
+
// Deliberately below 3:1 — valid while something else visually
|
|
38
|
+
// identifies the field (label, or a ≥3:1 icon/placeholder).
|
|
39
|
+
// Checkbox/Radio keep gray.400: their box IS the identifier.
|
|
40
|
+
// Rationale: ui-private docs/a11y-positions.md.
|
|
41
|
+
borderColor: "gray.300",
|
|
41
42
|
bg: "inherit",
|
|
42
43
|
color: "inherit",
|
|
43
|
-
_hover: { borderColor: "gray.
|
|
44
|
-
|
|
44
|
+
_hover: { borderColor: "gray.500" },
|
|
45
|
+
// Any focus, pointer included; the keyboard ring composes on top.
|
|
46
|
+
// zIndex so the border — and the ring with it — paint over
|
|
47
|
+
// attached-group neighbours.
|
|
48
|
+
"&&:is(:focus, [data-focused])": { zIndex: 1, borderColor: "focusBorder" },
|
|
49
|
+
"&&&:is([data-invalid], :user-invalid)": {
|
|
45
50
|
borderColor: "danger.500",
|
|
46
|
-
boxShadow: "0 0 0 1px token(colors.danger.500)",
|
|
47
|
-
},
|
|
48
|
-
"&:is(:focus-visible, [data-focused])": {
|
|
49
|
-
zIndex: 1,
|
|
50
|
-
borderColor: "focusBorder",
|
|
51
|
-
boxShadow: "0 0 0 1px token(colors.focusBorder)",
|
|
52
|
-
// Focus indicator for forced-colors modes, which strip the box-shadow
|
|
53
|
-
// and force the border colour (the focusShadow utility's technique; the
|
|
54
|
-
// ring here is the 1px border tint, not an outline* shadow token).
|
|
55
|
-
outline: "2px solid transparent",
|
|
56
|
-
outlineOffset: "2px",
|
|
57
51
|
},
|
|
52
|
+
// Modality-tracked: RAC's attribute, or Input.tsx's. Native
|
|
53
|
+
// :focus-visible is the fallback for a bare element wearing the recipe,
|
|
54
|
+
// but not text-entry ones — browsers match it there on a pointer click,
|
|
55
|
+
// which is what the tracking exists to exclude.
|
|
56
|
+
"&&&:is([data-focus-visible], :focus-visible:not([data-rac], input, textarea))":
|
|
57
|
+
{
|
|
58
|
+
focusRing: "outline",
|
|
59
|
+
},
|
|
58
60
|
"&:is(:disabled, [data-disabled])": {
|
|
59
61
|
opacity: 0.4,
|
|
60
62
|
cursor: "not-allowed",
|
|
61
63
|
},
|
|
62
64
|
},
|
|
65
|
+
variants: {
|
|
66
|
+
// The sm/md/lg size scale.
|
|
67
|
+
size: {
|
|
68
|
+
lg: { fontSize: "lg", px: "4", h: "12", borderRadius: "md" },
|
|
69
|
+
md: { fontSize: "md", px: "4", h: "10", borderRadius: "md" },
|
|
70
|
+
sm: { fontSize: "sm", px: "3", h: "8", borderRadius: "sm" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
defaultVariants: {
|
|
74
|
+
size: "md",
|
|
75
|
+
},
|
|
63
76
|
});
|
package/src/Input.tsx
CHANGED
|
@@ -4,30 +4,46 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
import { forwardRef, InputHTMLAttributes } from "react";
|
|
7
|
+
import { mergeProps, useFocusRing } from "react-aria";
|
|
7
8
|
import { css, cx } from "styled-system/css";
|
|
8
|
-
import { input } from "styled-system/recipes";
|
|
9
|
+
import { input, InputVariantProps } from "styled-system/recipes";
|
|
9
10
|
import { SystemStyleObject } from "styled-system/types";
|
|
10
11
|
|
|
11
12
|
export interface InputProps
|
|
12
|
-
|
|
13
|
+
// `size` is the recipe's size scale; the native character-count attribute
|
|
14
|
+
// it shadows was unused.
|
|
15
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "className" | "size">,
|
|
16
|
+
InputVariantProps {
|
|
13
17
|
/** Per-instance style overrides, merged after the recipe. */
|
|
14
18
|
css?: SystemStyleObject;
|
|
15
19
|
className?: string;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
* Input — a native input
|
|
20
|
-
*
|
|
23
|
+
* Input — a native input in the outline field style. For a labelled field
|
|
24
|
+
* with help/error text use TextField instead.
|
|
21
25
|
*/
|
|
22
26
|
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
23
|
-
{ css: cssProp, className, ...
|
|
27
|
+
{ css: cssProp, className, ...props },
|
|
24
28
|
ref,
|
|
25
29
|
) {
|
|
30
|
+
// splitVariantProps, not a hand-picked `size`: an app preset can add variant
|
|
31
|
+
// groups to the recipe (classroom adds `variant`), and cherry-picking would
|
|
32
|
+
// silently drop them onto the DOM as unknown attributes instead.
|
|
33
|
+
const [variantProps, rest] = input.splitVariantProps(props);
|
|
34
|
+
// A native input never gets RAC's data-focus-visible, which the recipe's
|
|
35
|
+
// ring keys off — track modality ourselves, as TextField's input does.
|
|
36
|
+
const { isFocusVisible, focusProps } = useFocusRing({ isTextInput: true });
|
|
26
37
|
return (
|
|
27
38
|
<input
|
|
28
39
|
ref={ref}
|
|
29
|
-
|
|
30
|
-
{
|
|
40
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
41
|
+
className={cx(
|
|
42
|
+
input(variantProps),
|
|
43
|
+
cssProp ? css(cssProp) : undefined,
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...mergeProps(rest, focusProps)}
|
|
31
47
|
/>
|
|
32
48
|
);
|
|
33
49
|
});
|