@microbit/ui 0.1.0-alpha.17 → 0.1.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +8 -0
- package/README.md +34 -11
- package/lang/ui.ca.json +36 -0
- package/lang/ui.cy.json +36 -0
- package/lang/ui.de.json +36 -0
- package/lang/ui.en-us.json +36 -0
- package/lang/ui.en.json +28 -0
- package/lang/ui.es-es.json +36 -0
- package/lang/ui.fr.json +36 -0
- package/lang/ui.ga-ie.json +36 -0
- package/lang/ui.it.json +36 -0
- package/lang/ui.ja.json +36 -0
- package/lang/ui.ko.json +36 -0
- package/lang/ui.lol.json +36 -0
- package/lang/ui.nl.json +36 -0
- package/lang/ui.pl.json +36 -0
- package/lang/ui.pt-br.json +36 -0
- package/lang/ui.zh-cn.json +36 -0
- package/lang/ui.zh-tw.json +36 -0
- package/package.json +1 -1
- package/src/Avatar.recipe.ts +3 -1
- package/src/Checkbox.recipe.ts +5 -0
- package/src/Checkbox.tsx +42 -2
- package/src/CheckboxGroup.tsx +6 -10
- package/src/ComboBox.tsx +38 -16
- package/src/Field.recipe.ts +113 -0
- package/src/Field.tsx +189 -0
- package/src/Input.recipe.ts +4 -2
- package/src/NativeSelectField.tsx +84 -0
- package/src/NumberField.recipe.ts +25 -7
- package/src/NumberField.tsx +52 -22
- package/src/Radio.recipe.ts +4 -0
- package/src/Radio.tsx +1 -63
- package/src/RadioGroup.tsx +69 -0
- package/src/Select.recipe.ts +27 -17
- package/src/Select.tsx +38 -15
- package/src/Skeleton.tsx +5 -3
- package/src/Switch.recipe.ts +19 -0
- package/src/Switch.tsx +34 -3
- package/src/TextField.tsx +19 -8
- package/src/Toast.tsx +17 -2
- package/src/base-preset.ts +45 -10
- package/src/index.ts +3 -1
- package/src/FieldSupport.tsx +0 -68
- package/src/TextField.recipe.ts +0 -58
package/lang/ui.pl.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"ui.breadcrumb": {
|
|
3
|
+
"defaultMessage": "Breadcrumb",
|
|
4
|
+
"description": "Accessible label for the breadcrumb navigation trail (the WAI-ARIA APG's conventional name)"
|
|
5
|
+
},
|
|
2
6
|
"ui.close-action": {
|
|
3
7
|
"defaultMessage": "Zamknij",
|
|
4
8
|
"description": "Close button text or label"
|
|
5
9
|
},
|
|
10
|
+
"ui.combobox-listbox": {
|
|
11
|
+
"defaultMessage": "Sugestie",
|
|
12
|
+
"description": "Accessible label for a ComboBox's dropdown list of suggestions"
|
|
13
|
+
},
|
|
14
|
+
"ui.combobox-trigger": {
|
|
15
|
+
"defaultMessage": "Wyświetlaj sugestie",
|
|
16
|
+
"description": "Accessible label for the button that opens a ComboBox's suggestions dropdown"
|
|
17
|
+
},
|
|
18
|
+
"ui.loading": {
|
|
19
|
+
"defaultMessage": "Loading",
|
|
20
|
+
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
|
+
},
|
|
22
|
+
"ui.numberfield-decrease": {
|
|
23
|
+
"defaultMessage": "Zmniejsz {fieldLabel}",
|
|
24
|
+
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
|
25
|
+
},
|
|
26
|
+
"ui.numberfield-increase": {
|
|
27
|
+
"defaultMessage": "Zwiększ {fieldLabel}",
|
|
28
|
+
"description": "Accessible label for a number field's increment stepper; fieldLabel is the field's label and may be empty"
|
|
29
|
+
},
|
|
30
|
+
"ui.select-placeholder": {
|
|
31
|
+
"defaultMessage": "Wybierz element",
|
|
32
|
+
"description": "Shown in a Select's trigger while nothing is chosen and the app supplies no placeholder"
|
|
33
|
+
},
|
|
34
|
+
"ui.select-row-action": {
|
|
35
|
+
"defaultMessage": "Zaznacz",
|
|
36
|
+
"description": "Accessible label for a row-selection checkbox in a list or table; screen readers read it together with the row's content"
|
|
37
|
+
},
|
|
38
|
+
"ui.toast-region": {
|
|
39
|
+
"defaultMessage": "{count, plural, one {# powiadomienie} few {# powiadomienia} many {# powiadomień} other {# powiadomienia}}.",
|
|
40
|
+
"description": "Accessible label for the notifications area; count is the number of notifications showing"
|
|
41
|
+
},
|
|
6
42
|
"ui.toast-status-error": {
|
|
7
43
|
"defaultMessage": "Error",
|
|
8
44
|
"description": "Announced by screen readers before an error notification"
|
package/lang/ui.pt-br.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"ui.breadcrumb": {
|
|
3
|
+
"defaultMessage": "Breadcrumb",
|
|
4
|
+
"description": "Accessible label for the breadcrumb navigation trail (the WAI-ARIA APG's conventional name)"
|
|
5
|
+
},
|
|
2
6
|
"ui.close-action": {
|
|
3
7
|
"defaultMessage": "Fechar",
|
|
4
8
|
"description": "Close button text or label"
|
|
5
9
|
},
|
|
10
|
+
"ui.combobox-listbox": {
|
|
11
|
+
"defaultMessage": "Sugestões",
|
|
12
|
+
"description": "Accessible label for a ComboBox's dropdown list of suggestions"
|
|
13
|
+
},
|
|
14
|
+
"ui.combobox-trigger": {
|
|
15
|
+
"defaultMessage": "Mostrar sugestões",
|
|
16
|
+
"description": "Accessible label for the button that opens a ComboBox's suggestions dropdown"
|
|
17
|
+
},
|
|
18
|
+
"ui.loading": {
|
|
19
|
+
"defaultMessage": "Loading",
|
|
20
|
+
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
|
+
},
|
|
22
|
+
"ui.numberfield-decrease": {
|
|
23
|
+
"defaultMessage": "Diminuir {fieldLabel}",
|
|
24
|
+
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
|
25
|
+
},
|
|
26
|
+
"ui.numberfield-increase": {
|
|
27
|
+
"defaultMessage": "Aumentar {fieldLabel}",
|
|
28
|
+
"description": "Accessible label for a number field's increment stepper; fieldLabel is the field's label and may be empty"
|
|
29
|
+
},
|
|
30
|
+
"ui.select-placeholder": {
|
|
31
|
+
"defaultMessage": "Selecione um item",
|
|
32
|
+
"description": "Shown in a Select's trigger while nothing is chosen and the app supplies no placeholder"
|
|
33
|
+
},
|
|
34
|
+
"ui.select-row-action": {
|
|
35
|
+
"defaultMessage": "Selecionar",
|
|
36
|
+
"description": "Accessible label for a row-selection checkbox in a list or table; screen readers read it together with the row's content"
|
|
37
|
+
},
|
|
38
|
+
"ui.toast-region": {
|
|
39
|
+
"defaultMessage": "{count, plural, one {# notificação} other {# notificações}}.",
|
|
40
|
+
"description": "Accessible label for the notifications area; count is the number of notifications showing"
|
|
41
|
+
},
|
|
6
42
|
"ui.toast-status-error": {
|
|
7
43
|
"defaultMessage": "Error",
|
|
8
44
|
"description": "Announced by screen readers before an error notification"
|
package/lang/ui.zh-cn.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"ui.breadcrumb": {
|
|
3
|
+
"defaultMessage": "Breadcrumb",
|
|
4
|
+
"description": "Accessible label for the breadcrumb navigation trail (the WAI-ARIA APG's conventional name)"
|
|
5
|
+
},
|
|
2
6
|
"ui.close-action": {
|
|
3
7
|
"defaultMessage": "关闭",
|
|
4
8
|
"description": "Close button text or label"
|
|
5
9
|
},
|
|
10
|
+
"ui.combobox-listbox": {
|
|
11
|
+
"defaultMessage": "建议",
|
|
12
|
+
"description": "Accessible label for a ComboBox's dropdown list of suggestions"
|
|
13
|
+
},
|
|
14
|
+
"ui.combobox-trigger": {
|
|
15
|
+
"defaultMessage": "显示建议",
|
|
16
|
+
"description": "Accessible label for the button that opens a ComboBox's suggestions dropdown"
|
|
17
|
+
},
|
|
18
|
+
"ui.loading": {
|
|
19
|
+
"defaultMessage": "Loading",
|
|
20
|
+
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
|
+
},
|
|
22
|
+
"ui.numberfield-decrease": {
|
|
23
|
+
"defaultMessage": "降低 {fieldLabel}",
|
|
24
|
+
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
|
25
|
+
},
|
|
26
|
+
"ui.numberfield-increase": {
|
|
27
|
+
"defaultMessage": "提高 {fieldLabel}",
|
|
28
|
+
"description": "Accessible label for a number field's increment stepper; fieldLabel is the field's label and may be empty"
|
|
29
|
+
},
|
|
30
|
+
"ui.select-placeholder": {
|
|
31
|
+
"defaultMessage": "选择一个项目",
|
|
32
|
+
"description": "Shown in a Select's trigger while nothing is chosen and the app supplies no placeholder"
|
|
33
|
+
},
|
|
34
|
+
"ui.select-row-action": {
|
|
35
|
+
"defaultMessage": "选择",
|
|
36
|
+
"description": "Accessible label for a row-selection checkbox in a list or table; screen readers read it together with the row's content"
|
|
37
|
+
},
|
|
38
|
+
"ui.toast-region": {
|
|
39
|
+
"defaultMessage": "{count, plural, one {# 个通知} other {# 个通知}}。",
|
|
40
|
+
"description": "Accessible label for the notifications area; count is the number of notifications showing"
|
|
41
|
+
},
|
|
6
42
|
"ui.toast-status-error": {
|
|
7
43
|
"defaultMessage": "Error",
|
|
8
44
|
"description": "Announced by screen readers before an error notification"
|
package/lang/ui.zh-tw.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"ui.breadcrumb": {
|
|
3
|
+
"defaultMessage": "Breadcrumb",
|
|
4
|
+
"description": "Accessible label for the breadcrumb navigation trail (the WAI-ARIA APG's conventional name)"
|
|
5
|
+
},
|
|
2
6
|
"ui.close-action": {
|
|
3
7
|
"defaultMessage": "關閉",
|
|
4
8
|
"description": "Close button text or label"
|
|
5
9
|
},
|
|
10
|
+
"ui.combobox-listbox": {
|
|
11
|
+
"defaultMessage": "建議",
|
|
12
|
+
"description": "Accessible label for a ComboBox's dropdown list of suggestions"
|
|
13
|
+
},
|
|
14
|
+
"ui.combobox-trigger": {
|
|
15
|
+
"defaultMessage": "顯示建議",
|
|
16
|
+
"description": "Accessible label for the button that opens a ComboBox's suggestions dropdown"
|
|
17
|
+
},
|
|
18
|
+
"ui.loading": {
|
|
19
|
+
"defaultMessage": "Loading",
|
|
20
|
+
"description": "Announced by screen readers for a button showing a loading spinner"
|
|
21
|
+
},
|
|
22
|
+
"ui.numberfield-decrease": {
|
|
23
|
+
"defaultMessage": "縮小 {fieldLabel}",
|
|
24
|
+
"description": "Accessible label for a number field's decrement stepper; fieldLabel is the field's label and may be empty"
|
|
25
|
+
},
|
|
26
|
+
"ui.numberfield-increase": {
|
|
27
|
+
"defaultMessage": "放大 {fieldLabel}",
|
|
28
|
+
"description": "Accessible label for a number field's increment stepper; fieldLabel is the field's label and may be empty"
|
|
29
|
+
},
|
|
30
|
+
"ui.select-placeholder": {
|
|
31
|
+
"defaultMessage": "選取項目",
|
|
32
|
+
"description": "Shown in a Select's trigger while nothing is chosen and the app supplies no placeholder"
|
|
33
|
+
},
|
|
34
|
+
"ui.select-row-action": {
|
|
35
|
+
"defaultMessage": "選取",
|
|
36
|
+
"description": "Accessible label for a row-selection checkbox in a list or table; screen readers read it together with the row's content"
|
|
37
|
+
},
|
|
38
|
+
"ui.toast-region": {
|
|
39
|
+
"defaultMessage": "{count, plural, one {# 個通知} other {# 個通知}}。",
|
|
40
|
+
"description": "Accessible label for the notifications area; count is the number of notifications showing"
|
|
41
|
+
},
|
|
6
42
|
"ui.toast-status-error": {
|
|
7
43
|
"defaultMessage": "Error",
|
|
8
44
|
"description": "Announced by screen readers before an error notification"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microbit/ui",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.19",
|
|
4
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",
|
package/src/Avatar.recipe.ts
CHANGED
|
@@ -44,7 +44,9 @@ export const avatar = defineSlotRecipe({
|
|
|
44
44
|
fontWeight: "medium",
|
|
45
45
|
borderRadius: "full",
|
|
46
46
|
// Chakra's no-name defaults; the name-derived pair arrives inline.
|
|
47
|
-
|
|
47
|
+
// gray.350 is the decorative-fill stop — 400+ are reserved for
|
|
48
|
+
// accessible outlines and text (see the ramp in base-preset.ts).
|
|
49
|
+
background: "var(--avatar-bg, token(colors.gray.350))",
|
|
48
50
|
color: "var(--avatar-color, token(colors.white))",
|
|
49
51
|
borderColor: "white",
|
|
50
52
|
},
|
package/src/Checkbox.recipe.ts
CHANGED
|
@@ -27,6 +27,11 @@ export const checkbox = defineSlotRecipe({
|
|
|
27
27
|
verticalAlign: "top",
|
|
28
28
|
cursor: "pointer",
|
|
29
29
|
position: "relative",
|
|
30
|
+
// The accessible outline stop (WCAG 1.4.11), as the input recipe.
|
|
31
|
+
// Stated here rather than on the control — whose `borderColor:
|
|
32
|
+
// inherit` reads it — so a call site can still tint the whole control
|
|
33
|
+
// by setting borderColor on the root.
|
|
34
|
+
borderColor: "gray.400",
|
|
30
35
|
"&[data-disabled]": { cursor: "not-allowed" },
|
|
31
36
|
},
|
|
32
37
|
control: {
|
package/src/Checkbox.tsx
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
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
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";
|
|
14
17
|
|
|
15
18
|
/** What a render-prop child is told about the checkbox. */
|
|
16
19
|
export interface CheckboxState {
|
|
@@ -39,6 +42,14 @@ export interface CheckboxProps
|
|
|
39
42
|
* @default true
|
|
40
43
|
*/
|
|
41
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;
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
/**
|
|
@@ -52,13 +63,31 @@ export const Checkbox = ({
|
|
|
52
63
|
className,
|
|
53
64
|
children,
|
|
54
65
|
control,
|
|
66
|
+
helperText,
|
|
67
|
+
helperTextCss,
|
|
55
68
|
...rest
|
|
56
69
|
}: CheckboxProps) => {
|
|
57
70
|
const slots = checkbox({ size });
|
|
58
|
-
|
|
71
|
+
const helperId = useId();
|
|
72
|
+
const intl = useIntl();
|
|
73
|
+
const describedBy =
|
|
74
|
+
[rest["aria-describedby"], helperText != null ? helperId : undefined]
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.join(" ") || undefined;
|
|
77
|
+
// Default name for a row-selection checkbox (a GridList row's). A future
|
|
78
|
+
// Table's select-all header checkbox shares the slot name and will need a
|
|
79
|
+
// different label.
|
|
80
|
+
const ariaLabel =
|
|
81
|
+
rest["aria-label"] ??
|
|
82
|
+
(rest.slot === "selection"
|
|
83
|
+
? intl.formatMessage(uiMessage("ui.select-row-action"))
|
|
84
|
+
: undefined);
|
|
85
|
+
const checkboxElement = (
|
|
59
86
|
<RACCheckbox
|
|
60
87
|
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
61
88
|
{...rest}
|
|
89
|
+
aria-label={ariaLabel}
|
|
90
|
+
aria-describedby={describedBy}
|
|
62
91
|
>
|
|
63
92
|
{({ isSelected, isFocusVisible, isDisabled }) => {
|
|
64
93
|
const content =
|
|
@@ -103,4 +132,15 @@ export const Checkbox = ({
|
|
|
103
132
|
}}
|
|
104
133
|
</RACCheckbox>
|
|
105
134
|
);
|
|
135
|
+
if (helperText == null) {
|
|
136
|
+
return checkboxElement;
|
|
137
|
+
}
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
{checkboxElement}
|
|
141
|
+
<FieldHelperText id={helperId} css={helperTextCss}>
|
|
142
|
+
{helperText}
|
|
143
|
+
</FieldHelperText>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
106
146
|
};
|
package/src/CheckboxGroup.tsx
CHANGED
|
@@ -7,16 +7,10 @@ import { ReactNode } from "react";
|
|
|
7
7
|
import {
|
|
8
8
|
CheckboxGroup as RACCheckboxGroup,
|
|
9
9
|
CheckboxGroupProps as RACCheckboxGroupProps,
|
|
10
|
-
Label as RACLabel,
|
|
11
10
|
} from "react-aria-components";
|
|
12
11
|
import { css, cx } from "styled-system/css";
|
|
13
|
-
import { field } from "styled-system/recipes";
|
|
14
12
|
import { SystemStyleObject } from "styled-system/types";
|
|
15
|
-
import {
|
|
16
|
-
FieldRequiredIndicator,
|
|
17
|
-
FieldSupport,
|
|
18
|
-
FieldSupportProps,
|
|
19
|
-
} from "./FieldSupport";
|
|
13
|
+
import { FieldLabel, FieldSupport, FieldSupportProps } from "./Field";
|
|
20
14
|
|
|
21
15
|
export interface CheckboxGroupProps
|
|
22
16
|
extends Omit<RACCheckboxGroupProps, "className" | "style">,
|
|
@@ -26,6 +20,8 @@ export interface CheckboxGroupProps
|
|
|
26
20
|
* `aria-label` instead where the design has none.
|
|
27
21
|
*/
|
|
28
22
|
label?: ReactNode;
|
|
23
|
+
/** Label style overrides. */
|
|
24
|
+
labelCss?: SystemStyleObject;
|
|
29
25
|
/** Per-instance style overrides. */
|
|
30
26
|
css?: SystemStyleObject;
|
|
31
27
|
className?: string;
|
|
@@ -42,6 +38,7 @@ export interface CheckboxGroupProps
|
|
|
42
38
|
*/
|
|
43
39
|
export const CheckboxGroup = ({
|
|
44
40
|
label,
|
|
41
|
+
labelCss,
|
|
45
42
|
helperText,
|
|
46
43
|
errorMessage,
|
|
47
44
|
helperTextCss,
|
|
@@ -58,10 +55,9 @@ export const CheckboxGroup = ({
|
|
|
58
55
|
{(renderProps) => (
|
|
59
56
|
<>
|
|
60
57
|
{label != null && (
|
|
61
|
-
<
|
|
58
|
+
<FieldLabel isRequired={rest.isRequired} css={labelCss}>
|
|
62
59
|
{label}
|
|
63
|
-
|
|
64
|
-
</RACLabel>
|
|
60
|
+
</FieldLabel>
|
|
65
61
|
)}
|
|
66
62
|
{typeof children === "function" ? children(renderProps) : children}
|
|
67
63
|
<FieldSupport
|
package/src/ComboBox.tsx
CHANGED
|
@@ -16,29 +16,34 @@ import {
|
|
|
16
16
|
ComboBox as RACComboBox,
|
|
17
17
|
ComboBoxProps as RACComboBoxProps,
|
|
18
18
|
Input as RACInput,
|
|
19
|
-
Label as RACLabel,
|
|
20
19
|
ListBox as RACListBox,
|
|
21
20
|
Popover,
|
|
22
21
|
PopoverProps,
|
|
23
22
|
} from "react-aria-components";
|
|
23
|
+
import { useIntl } from "react-intl";
|
|
24
24
|
import { RiArrowDownSLine } from "react-icons/ri";
|
|
25
25
|
import { css, cx } from "styled-system/css";
|
|
26
|
-
import { select, SelectVariantProps } from "styled-system/recipes";
|
|
26
|
+
import { field, select, SelectVariantProps } from "styled-system/recipes";
|
|
27
27
|
import { SystemStyleObject } from "styled-system/types";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
FieldLabel,
|
|
30
|
+
FieldLayoutProps,
|
|
30
31
|
FieldSupport,
|
|
31
32
|
FieldSupportProps,
|
|
32
|
-
} from "./
|
|
33
|
+
} from "./Field";
|
|
33
34
|
import { Icon } from "./Icon";
|
|
35
|
+
import { uiMessage } from "./messages";
|
|
34
36
|
import { SelectSlotProvider } from "./Select";
|
|
35
37
|
|
|
36
38
|
export interface ComboBoxProps<T extends object>
|
|
37
39
|
extends Omit<RACComboBoxProps<T>, "className" | "children" | "style">,
|
|
38
40
|
SelectVariantProps,
|
|
39
|
-
FieldSupportProps
|
|
41
|
+
FieldSupportProps,
|
|
42
|
+
FieldLayoutProps {
|
|
40
43
|
/** Visible label. Use `aria-label` instead where the design has none. */
|
|
41
44
|
label?: ReactNode;
|
|
45
|
+
/** Label style overrides. */
|
|
46
|
+
labelCss?: SystemStyleObject;
|
|
42
47
|
placeholder?: string;
|
|
43
48
|
/**
|
|
44
49
|
* Rendered inside the control, before the input — an icon for the current
|
|
@@ -81,11 +86,12 @@ export interface ComboBoxProps<T extends object>
|
|
|
81
86
|
*/
|
|
82
87
|
maxHeight?: number;
|
|
83
88
|
/**
|
|
84
|
-
* Per-instance overrides for the
|
|
85
|
-
* `startContent` and its indicator,
|
|
86
|
-
*
|
|
89
|
+
* Per-instance overrides for the trigger — the box around the input, its
|
|
90
|
+
* `startContent` and its indicator, the same slot `Select`'s `triggerCss`
|
|
91
|
+
* styles. Reach the input itself through the `select` recipe's `value`
|
|
92
|
+
* slot.
|
|
87
93
|
*/
|
|
88
|
-
|
|
94
|
+
triggerCss?: SystemStyleObject;
|
|
89
95
|
/** Per-instance overrides for the dropdown card. */
|
|
90
96
|
contentCss?: SystemStyleObject;
|
|
91
97
|
className?: string;
|
|
@@ -103,6 +109,7 @@ export interface ComboBoxProps<T extends object>
|
|
|
103
109
|
const ComboBoxInner = <T extends object>(
|
|
104
110
|
{
|
|
105
111
|
label,
|
|
112
|
+
labelCss,
|
|
106
113
|
placeholder,
|
|
107
114
|
startContent,
|
|
108
115
|
children,
|
|
@@ -114,7 +121,8 @@ const ComboBoxInner = <T extends object>(
|
|
|
114
121
|
helperText,
|
|
115
122
|
errorMessage,
|
|
116
123
|
helperTextCss,
|
|
117
|
-
|
|
124
|
+
labelPosition,
|
|
125
|
+
triggerCss,
|
|
118
126
|
contentCss,
|
|
119
127
|
className,
|
|
120
128
|
...props
|
|
@@ -123,7 +131,9 @@ const ComboBoxInner = <T extends object>(
|
|
|
123
131
|
) => {
|
|
124
132
|
// As Select: forward whatever variant groups the merged recipe has.
|
|
125
133
|
const [variantProps, rest] = select.splitVariantProps(props);
|
|
134
|
+
const intl = useIntl();
|
|
126
135
|
const slots = select(variantProps);
|
|
136
|
+
const fieldSlots = field({ size: variantProps.size, labelPosition });
|
|
127
137
|
// Anchor the card to the whole control, not to the bare input inside it —
|
|
128
138
|
// otherwise it hangs off the text baseline and is as narrow as the input.
|
|
129
139
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
@@ -152,17 +162,24 @@ const ComboBoxInner = <T extends object>(
|
|
|
152
162
|
<RACComboBox
|
|
153
163
|
allowsEmptyCollection={emptyState != null}
|
|
154
164
|
{...(rest as RACComboBoxProps<T>)}
|
|
155
|
-
className={cx(slots.root, className)}
|
|
165
|
+
className={cx(fieldSlots.root, slots.root, className)}
|
|
156
166
|
>
|
|
157
167
|
{label != null && (
|
|
158
|
-
<
|
|
168
|
+
<FieldLabel
|
|
169
|
+
size={variantProps.size}
|
|
170
|
+
labelPosition={labelPosition}
|
|
171
|
+
isRequired={props.isRequired}
|
|
172
|
+
css={labelCss}
|
|
173
|
+
>
|
|
159
174
|
{label}
|
|
160
|
-
|
|
161
|
-
</RACLabel>
|
|
175
|
+
</FieldLabel>
|
|
162
176
|
)}
|
|
163
177
|
<div
|
|
164
178
|
ref={triggerRef}
|
|
165
|
-
className={cx(
|
|
179
|
+
className={cx(
|
|
180
|
+
slots.trigger,
|
|
181
|
+
triggerCss ? css(triggerCss) : undefined,
|
|
182
|
+
)}
|
|
166
183
|
>
|
|
167
184
|
{startContent}
|
|
168
185
|
<RACInput
|
|
@@ -171,7 +188,10 @@ const ComboBoxInner = <T extends object>(
|
|
|
171
188
|
className={slots.value}
|
|
172
189
|
/>
|
|
173
190
|
{indicator !== null && (
|
|
174
|
-
<RACButton
|
|
191
|
+
<RACButton
|
|
192
|
+
aria-label={intl.formatMessage(uiMessage("ui.combobox-trigger"))}
|
|
193
|
+
className={slots.indicator}
|
|
194
|
+
>
|
|
175
195
|
{indicator ?? <Icon as={RiArrowDownSLine} />}
|
|
176
196
|
</RACButton>
|
|
177
197
|
)}
|
|
@@ -188,6 +208,7 @@ const ComboBoxInner = <T extends object>(
|
|
|
188
208
|
)}
|
|
189
209
|
>
|
|
190
210
|
<RACListBox
|
|
211
|
+
aria-label={intl.formatMessage(uiMessage("ui.combobox-listbox"))}
|
|
191
212
|
className={slots.list}
|
|
192
213
|
renderEmptyState={
|
|
193
214
|
emptyState
|
|
@@ -203,6 +224,7 @@ const ComboBoxInner = <T extends object>(
|
|
|
203
224
|
helperText={helperText}
|
|
204
225
|
errorMessage={errorMessage}
|
|
205
226
|
helperTextCss={helperTextCss}
|
|
227
|
+
labelPosition={labelPosition}
|
|
206
228
|
/>
|
|
207
229
|
</RACComboBox>
|
|
208
230
|
</SelectSlotProvider>
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
* Field slot recipe — Chakra's FormControl parts (FormLabel/FormHelperText/
|
|
10
|
+
* FormErrorMessage, light mode), mapped onto react-aria-components'
|
|
11
|
+
* Label/Text/FieldError by `FieldLabel` and `FieldSupport`. Every labelled
|
|
12
|
+
* field in the library draws its chrome from here, including Select and
|
|
13
|
+
* ComboBox, whose own recipe styles only the dropdown pair; the input itself is
|
|
14
|
+
* styled by the `input` recipe (Input.recipe.ts).
|
|
15
|
+
*
|
|
16
|
+
* `root` is also the single owner of field-root *layout*: Select, ComboBox and
|
|
17
|
+
* NumberField wear it alongside their own recipe's root slot, which carries no
|
|
18
|
+
* layout of its own — two recipes fighting over `flexDirection` would leave
|
|
19
|
+
* `labelPosition` at the mercy of emission order. RadioGroup and CheckboxGroup
|
|
20
|
+
* deliberately don't wear it (their roots carry no layout at all).
|
|
21
|
+
*
|
|
22
|
+
* Registered in the base preset (base-preset.ts), which also has the
|
|
23
|
+
* `staticCss` entry that keeps the runtime-prop size variants generated.
|
|
24
|
+
*/
|
|
25
|
+
export const field = defineSlotRecipe({
|
|
26
|
+
className: "field",
|
|
27
|
+
slots: ["root", "label", "requiredIndicator", "helperText", "errorMessage"],
|
|
28
|
+
base: {
|
|
29
|
+
root: {
|
|
30
|
+
display: "flex",
|
|
31
|
+
flexDirection: "column",
|
|
32
|
+
alignItems: "stretch",
|
|
33
|
+
width: "100%",
|
|
34
|
+
},
|
|
35
|
+
label: {
|
|
36
|
+
display: "block",
|
|
37
|
+
fontSize: "md",
|
|
38
|
+
// Deliberately not Chakra FormLabel's `medium`: no font in the family's
|
|
39
|
+
// stack has a 500 face, so the two were pixel-identical on macOS and
|
|
40
|
+
// Windows, and the only call sites that cared overrode to `normal`
|
|
41
|
+
// (settings rows). See the playbook's expected behavioural deltas.
|
|
42
|
+
fontWeight: "normal",
|
|
43
|
+
marginEnd: "3",
|
|
44
|
+
mb: "2",
|
|
45
|
+
transitionProperty: "opacity",
|
|
46
|
+
transitionDuration: "normal",
|
|
47
|
+
// RAC stamps `data-disabled` on the field root and on the control, never
|
|
48
|
+
// on the label, so an `&[data-disabled]` rule here matches nothing — it
|
|
49
|
+
// has to come down from the root (gotcha #45). Direct child rather than a
|
|
50
|
+
// descendant selector, as the select recipe's invalid rule: an app's own
|
|
51
|
+
// disabled form wrapper must not be able to dim every label inside it.
|
|
52
|
+
"[data-disabled] > &": { opacity: 0.4 },
|
|
53
|
+
},
|
|
54
|
+
requiredIndicator: {
|
|
55
|
+
marginStart: "1",
|
|
56
|
+
color: "danger.500",
|
|
57
|
+
},
|
|
58
|
+
helperText: {
|
|
59
|
+
// RAC's Text renders a span, and RadioGroup/CheckboxGroup roots are not
|
|
60
|
+
// flex containers to blockify it, where an inline box would drop the
|
|
61
|
+
// margin below (gotcha #44).
|
|
62
|
+
display: "block",
|
|
63
|
+
mt: "2",
|
|
64
|
+
fontSize: "sm",
|
|
65
|
+
lineHeight: "normal",
|
|
66
|
+
color: "gray.600",
|
|
67
|
+
},
|
|
68
|
+
errorMessage: {
|
|
69
|
+
display: "flex",
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
mt: "2",
|
|
72
|
+
fontSize: "sm",
|
|
73
|
+
lineHeight: "normal",
|
|
74
|
+
color: "danger.500",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
variants: {
|
|
78
|
+
// The label follows its control's size (the control itself is sized by
|
|
79
|
+
// the `input`/`select` recipes), so one `size` prop scales the whole row.
|
|
80
|
+
// Chakra's FormLabel never scaled — a deliberate delta; see the playbook's
|
|
81
|
+
// field-chrome notes. Helper and error text stay `sm` at every size, as
|
|
82
|
+
// Chakra's did.
|
|
83
|
+
size: {
|
|
84
|
+
lg: { label: { fontSize: "lg" } },
|
|
85
|
+
md: { label: { fontSize: "md" } },
|
|
86
|
+
sm: { label: { fontSize: "sm" } },
|
|
87
|
+
},
|
|
88
|
+
// `side` puts the label beside its control — the settings-row pattern,
|
|
89
|
+
// where the label is a preference name and absorbs the free space. The
|
|
90
|
+
// control keeps its own width; give it one at the call site. Named after
|
|
91
|
+
// React Spectrum's `labelPosition`, not `orientation`, which RAC's
|
|
92
|
+
// RadioGroup already uses for the radios' own layout.
|
|
93
|
+
labelPosition: {
|
|
94
|
+
top: {},
|
|
95
|
+
side: {
|
|
96
|
+
root: {
|
|
97
|
+
flexDirection: "row",
|
|
98
|
+
alignItems: "center",
|
|
99
|
+
// Helper and error text are full-width items, so they wrap to
|
|
100
|
+
// their own line below the label/control pair.
|
|
101
|
+
flexWrap: "wrap",
|
|
102
|
+
},
|
|
103
|
+
label: { mb: "0", flex: "1 1 auto" },
|
|
104
|
+
helperText: { width: "100%" },
|
|
105
|
+
errorMessage: { width: "100%" },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
defaultVariants: {
|
|
110
|
+
size: "md",
|
|
111
|
+
labelPosition: "top",
|
|
112
|
+
},
|
|
113
|
+
});
|