@microbit/ui 0.1.0-alpha.18 → 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 +12 -0
- package/src/CheckboxGroup.tsx +6 -1
- package/src/ComboBox.tsx +12 -1
- package/src/Input.recipe.ts +4 -2
- package/src/NativeSelectField.tsx +4 -1
- package/src/NumberField.tsx +13 -0
- package/src/Radio.recipe.ts +4 -0
- package/src/RadioGroup.tsx +6 -1
- package/src/Select.recipe.ts +5 -2
- package/src/Select.tsx +15 -2
- package/src/Skeleton.tsx +5 -3
- package/src/TextField.tsx +5 -0
- package/src/Toast.tsx +17 -2
- package/src/base-preset.ts +42 -9
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
|
@@ -8,10 +8,12 @@ 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";
|
|
14
15
|
import { FieldHelperText } from "./Field";
|
|
16
|
+
import { uiMessage } from "./messages";
|
|
15
17
|
|
|
16
18
|
/** What a render-prop child is told about the checkbox. */
|
|
17
19
|
export interface CheckboxState {
|
|
@@ -67,14 +69,24 @@ export const Checkbox = ({
|
|
|
67
69
|
}: CheckboxProps) => {
|
|
68
70
|
const slots = checkbox({ size });
|
|
69
71
|
const helperId = useId();
|
|
72
|
+
const intl = useIntl();
|
|
70
73
|
const describedBy =
|
|
71
74
|
[rest["aria-describedby"], helperText != null ? helperId : undefined]
|
|
72
75
|
.filter(Boolean)
|
|
73
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);
|
|
74
85
|
const checkboxElement = (
|
|
75
86
|
<RACCheckbox
|
|
76
87
|
className={cx(slots.root, cssProp ? css(cssProp) : undefined, className)}
|
|
77
88
|
{...rest}
|
|
89
|
+
aria-label={ariaLabel}
|
|
78
90
|
aria-describedby={describedBy}
|
|
79
91
|
>
|
|
80
92
|
{({ isSelected, isFocusVisible, isDisabled }) => {
|
package/src/CheckboxGroup.tsx
CHANGED
|
@@ -20,6 +20,8 @@ export interface CheckboxGroupProps
|
|
|
20
20
|
* `aria-label` instead where the design has none.
|
|
21
21
|
*/
|
|
22
22
|
label?: ReactNode;
|
|
23
|
+
/** Label style overrides. */
|
|
24
|
+
labelCss?: SystemStyleObject;
|
|
23
25
|
/** Per-instance style overrides. */
|
|
24
26
|
css?: SystemStyleObject;
|
|
25
27
|
className?: string;
|
|
@@ -36,6 +38,7 @@ export interface CheckboxGroupProps
|
|
|
36
38
|
*/
|
|
37
39
|
export const CheckboxGroup = ({
|
|
38
40
|
label,
|
|
41
|
+
labelCss,
|
|
39
42
|
helperText,
|
|
40
43
|
errorMessage,
|
|
41
44
|
helperTextCss,
|
|
@@ -52,7 +55,9 @@ export const CheckboxGroup = ({
|
|
|
52
55
|
{(renderProps) => (
|
|
53
56
|
<>
|
|
54
57
|
{label != null && (
|
|
55
|
-
<FieldLabel isRequired={rest.isRequired}
|
|
58
|
+
<FieldLabel isRequired={rest.isRequired} css={labelCss}>
|
|
59
|
+
{label}
|
|
60
|
+
</FieldLabel>
|
|
56
61
|
)}
|
|
57
62
|
{typeof children === "function" ? children(renderProps) : children}
|
|
58
63
|
<FieldSupport
|
package/src/ComboBox.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
Popover,
|
|
21
21
|
PopoverProps,
|
|
22
22
|
} from "react-aria-components";
|
|
23
|
+
import { useIntl } from "react-intl";
|
|
23
24
|
import { RiArrowDownSLine } from "react-icons/ri";
|
|
24
25
|
import { css, cx } from "styled-system/css";
|
|
25
26
|
import { field, select, SelectVariantProps } from "styled-system/recipes";
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
FieldSupportProps,
|
|
32
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>
|
|
@@ -40,6 +42,8 @@ export interface ComboBoxProps<T extends object>
|
|
|
40
42
|
FieldLayoutProps {
|
|
41
43
|
/** Visible label. Use `aria-label` instead where the design has none. */
|
|
42
44
|
label?: ReactNode;
|
|
45
|
+
/** Label style overrides. */
|
|
46
|
+
labelCss?: SystemStyleObject;
|
|
43
47
|
placeholder?: string;
|
|
44
48
|
/**
|
|
45
49
|
* Rendered inside the control, before the input — an icon for the current
|
|
@@ -105,6 +109,7 @@ export interface ComboBoxProps<T extends object>
|
|
|
105
109
|
const ComboBoxInner = <T extends object>(
|
|
106
110
|
{
|
|
107
111
|
label,
|
|
112
|
+
labelCss,
|
|
108
113
|
placeholder,
|
|
109
114
|
startContent,
|
|
110
115
|
children,
|
|
@@ -126,6 +131,7 @@ const ComboBoxInner = <T extends object>(
|
|
|
126
131
|
) => {
|
|
127
132
|
// As Select: forward whatever variant groups the merged recipe has.
|
|
128
133
|
const [variantProps, rest] = select.splitVariantProps(props);
|
|
134
|
+
const intl = useIntl();
|
|
129
135
|
const slots = select(variantProps);
|
|
130
136
|
const fieldSlots = field({ size: variantProps.size, labelPosition });
|
|
131
137
|
// Anchor the card to the whole control, not to the bare input inside it —
|
|
@@ -163,6 +169,7 @@ const ComboBoxInner = <T extends object>(
|
|
|
163
169
|
size={variantProps.size}
|
|
164
170
|
labelPosition={labelPosition}
|
|
165
171
|
isRequired={props.isRequired}
|
|
172
|
+
css={labelCss}
|
|
166
173
|
>
|
|
167
174
|
{label}
|
|
168
175
|
</FieldLabel>
|
|
@@ -181,7 +188,10 @@ const ComboBoxInner = <T extends object>(
|
|
|
181
188
|
className={slots.value}
|
|
182
189
|
/>
|
|
183
190
|
{indicator !== null && (
|
|
184
|
-
<RACButton
|
|
191
|
+
<RACButton
|
|
192
|
+
aria-label={intl.formatMessage(uiMessage("ui.combobox-trigger"))}
|
|
193
|
+
className={slots.indicator}
|
|
194
|
+
>
|
|
185
195
|
{indicator ?? <Icon as={RiArrowDownSLine} />}
|
|
186
196
|
</RACButton>
|
|
187
197
|
)}
|
|
@@ -198,6 +208,7 @@ const ComboBoxInner = <T extends object>(
|
|
|
198
208
|
)}
|
|
199
209
|
>
|
|
200
210
|
<RACListBox
|
|
211
|
+
aria-label={intl.formatMessage(uiMessage("ui.combobox-listbox"))}
|
|
201
212
|
className={slots.list}
|
|
202
213
|
renderEmptyState={
|
|
203
214
|
emptyState
|
package/src/Input.recipe.ts
CHANGED
|
@@ -44,10 +44,12 @@ export const input = defineRecipe({
|
|
|
44
44
|
transitionProperty: transitionCommon,
|
|
45
45
|
transitionDuration: "normal",
|
|
46
46
|
border: "1px solid",
|
|
47
|
-
|
|
47
|
+
// The accessible outline stops: gray.400 is the ramp's 3:1-on-white
|
|
48
|
+
// boundary grey (WCAG 1.4.11), and hover steps darker, never lighter.
|
|
49
|
+
borderColor: "gray.400",
|
|
48
50
|
bg: "inherit",
|
|
49
51
|
color: "inherit",
|
|
50
|
-
_hover: { borderColor: "gray.
|
|
52
|
+
_hover: { borderColor: "gray.500" },
|
|
51
53
|
"&&:is([data-invalid], :user-invalid)": {
|
|
52
54
|
borderColor: "danger.500",
|
|
53
55
|
boxShadow: "0 0 0 1px token(colors.danger.500)",
|
|
@@ -15,6 +15,8 @@ export interface NativeSelectFieldProps
|
|
|
15
15
|
FieldLayoutProps {
|
|
16
16
|
/** Visible label, associated with the select via `htmlFor`. */
|
|
17
17
|
label: ReactNode;
|
|
18
|
+
/** Label style overrides. */
|
|
19
|
+
labelCss?: SystemStyleObject;
|
|
18
20
|
/** Help text below the field, wired to the select's `aria-describedby`. */
|
|
19
21
|
helperText?: ReactNode;
|
|
20
22
|
/** Per-instance style overrides for the field root. */
|
|
@@ -37,7 +39,7 @@ export const NativeSelectField = forwardRef<
|
|
|
37
39
|
HTMLSelectElement,
|
|
38
40
|
NativeSelectFieldProps
|
|
39
41
|
>(function NativeSelectField(
|
|
40
|
-
{ label, helperText, labelPosition, rootCss, id, ...rest },
|
|
42
|
+
{ label, labelCss, helperText, labelPosition, rootCss, id, ...rest },
|
|
41
43
|
ref,
|
|
42
44
|
) {
|
|
43
45
|
const generatedId = useId();
|
|
@@ -62,6 +64,7 @@ export const NativeSelectField = forwardRef<
|
|
|
62
64
|
size={rest.size}
|
|
63
65
|
labelPosition={labelPosition}
|
|
64
66
|
isRequired={rest.required}
|
|
67
|
+
css={labelCss}
|
|
65
68
|
>
|
|
66
69
|
{label}
|
|
67
70
|
</FieldLabel>
|
package/src/NumberField.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
NumberField as RACNumberField,
|
|
12
12
|
NumberFieldProps as RACNumberFieldProps,
|
|
13
13
|
} from "react-aria-components";
|
|
14
|
+
import { useIntl } from "react-intl";
|
|
14
15
|
import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
|
|
15
16
|
import { css, cx } from "styled-system/css";
|
|
16
17
|
import {
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
FieldSupportProps,
|
|
28
29
|
} from "./Field";
|
|
29
30
|
import { Icon } from "./Icon";
|
|
31
|
+
import { uiMessage } from "./messages";
|
|
30
32
|
|
|
31
33
|
export interface NumberFieldProps
|
|
32
34
|
extends Omit<RACNumberFieldProps, "className" | "children" | "style">,
|
|
@@ -73,8 +75,19 @@ export const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(
|
|
|
73
75
|
const [variantProps, rest] = input.splitVariantProps(props);
|
|
74
76
|
const slots = numberField({ size: variantProps.size });
|
|
75
77
|
const fieldSlots = field({ size: variantProps.size, labelPosition });
|
|
78
|
+
const intl = useIntl();
|
|
79
|
+
// Fold the field's name into the stepper labels as react-aria does; the
|
|
80
|
+
// trim eats the leftover space when there is no usable name.
|
|
81
|
+
const fieldLabel =
|
|
82
|
+
rest["aria-label"] ?? (typeof label === "string" ? label : "");
|
|
76
83
|
return (
|
|
77
84
|
<RACNumberField
|
|
85
|
+
incrementAriaLabel={intl
|
|
86
|
+
.formatMessage(uiMessage("ui.numberfield-increase"), { fieldLabel })
|
|
87
|
+
.trim()}
|
|
88
|
+
decrementAriaLabel={intl
|
|
89
|
+
.formatMessage(uiMessage("ui.numberfield-decrease"), { fieldLabel })
|
|
90
|
+
.trim()}
|
|
78
91
|
{...rest}
|
|
79
92
|
className={cx(
|
|
80
93
|
fieldSlots.root,
|
package/src/Radio.recipe.ts
CHANGED
|
@@ -28,6 +28,10 @@ export const radio = defineSlotRecipe({
|
|
|
28
28
|
verticalAlign: "top",
|
|
29
29
|
cursor: "pointer",
|
|
30
30
|
position: "relative",
|
|
31
|
+
// As the checkbox: the accessible outline stop (WCAG 1.4.11), on the
|
|
32
|
+
// root so the control's `borderColor: inherit` reads it and call
|
|
33
|
+
// sites can still tint at the root.
|
|
34
|
+
borderColor: "gray.400",
|
|
31
35
|
"&[data-disabled]": { cursor: "not-allowed" },
|
|
32
36
|
},
|
|
33
37
|
control: {
|
package/src/RadioGroup.tsx
CHANGED
|
@@ -20,6 +20,8 @@ export interface RadioGroupProps
|
|
|
20
20
|
* `aria-label` instead where the design has none.
|
|
21
21
|
*/
|
|
22
22
|
label?: ReactNode;
|
|
23
|
+
/** Label style overrides. */
|
|
24
|
+
labelCss?: SystemStyleObject;
|
|
23
25
|
/** Per-instance style overrides, merged after the recipe. */
|
|
24
26
|
css?: SystemStyleObject;
|
|
25
27
|
className?: string;
|
|
@@ -33,6 +35,7 @@ export interface RadioGroupProps
|
|
|
33
35
|
*/
|
|
34
36
|
export const RadioGroup = ({
|
|
35
37
|
label,
|
|
38
|
+
labelCss,
|
|
36
39
|
helperText,
|
|
37
40
|
errorMessage,
|
|
38
41
|
helperTextCss,
|
|
@@ -49,7 +52,9 @@ export const RadioGroup = ({
|
|
|
49
52
|
{(renderProps) => (
|
|
50
53
|
<>
|
|
51
54
|
{label != null && (
|
|
52
|
-
<FieldLabel isRequired={rest.isRequired}
|
|
55
|
+
<FieldLabel isRequired={rest.isRequired} css={labelCss}>
|
|
56
|
+
{label}
|
|
57
|
+
</FieldLabel>
|
|
53
58
|
)}
|
|
54
59
|
{typeof children === "function" ? children(renderProps) : children}
|
|
55
60
|
<FieldSupport
|
package/src/Select.recipe.ts
CHANGED
|
@@ -63,13 +63,16 @@ export const select = defineSlotRecipe({
|
|
|
63
63
|
transitionProperty: transitionCommon,
|
|
64
64
|
transitionDuration: "normal",
|
|
65
65
|
border: "1px solid",
|
|
66
|
-
|
|
66
|
+
// As the input recipe: the ramp's accessible outline stop, with hover
|
|
67
|
+
// stepping darker. The dropdown card below keeps the light gray.200 —
|
|
68
|
+
// it's a surface edge, not a form-control boundary.
|
|
69
|
+
borderColor: "gray.400",
|
|
67
70
|
bg: "white",
|
|
68
71
|
color: "inherit",
|
|
69
72
|
// As the input recipe, so a Select, a NativeSelect and a TextField in one
|
|
70
73
|
// form all tint together on hover. (react-aria's TextField has no hover
|
|
71
74
|
// effect, but matching the family beats matching their docs.)
|
|
72
|
-
_hover: { borderColor: "gray.
|
|
75
|
+
_hover: { borderColor: "gray.500" },
|
|
73
76
|
// `data-invalid` lands on the root — and, in a ComboBox, on the input —
|
|
74
77
|
// but never on the trigger: a RAC Button has no validity state, and our
|
|
75
78
|
// ComboBox control is a plain div. So it comes down from the parent.
|
package/src/Select.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
SelectProps as RACSelectProps,
|
|
16
16
|
SelectValue,
|
|
17
17
|
} from "react-aria-components";
|
|
18
|
+
import { useIntl } from "react-intl";
|
|
18
19
|
import { RiArrowDownSLine } from "react-icons/ri";
|
|
19
20
|
import { css, cx } from "styled-system/css";
|
|
20
21
|
import { field, select, SelectVariantProps } from "styled-system/recipes";
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
FieldSupportProps,
|
|
27
28
|
} from "./Field";
|
|
28
29
|
import { Icon } from "./Icon";
|
|
30
|
+
import { uiMessage } from "./messages";
|
|
29
31
|
|
|
30
32
|
export type SelectSlots = ReturnType<typeof select>;
|
|
31
33
|
|
|
@@ -47,7 +49,12 @@ export interface SelectProps<T extends object>
|
|
|
47
49
|
FieldLayoutProps {
|
|
48
50
|
/** Visible label. Use `aria-label` instead where the design has none. */
|
|
49
51
|
label?: ReactNode;
|
|
50
|
-
/**
|
|
52
|
+
/** Label style overrides. */
|
|
53
|
+
labelCss?: SystemStyleObject;
|
|
54
|
+
/**
|
|
55
|
+
* Shown in the trigger while nothing is chosen (Chakra's placeholder).
|
|
56
|
+
* Defaults to a translated "Select an item".
|
|
57
|
+
*/
|
|
51
58
|
placeholder?: string;
|
|
52
59
|
/** `SelectOption`s. */
|
|
53
60
|
children: ReactNode;
|
|
@@ -80,6 +87,7 @@ export interface SelectProps<T extends object>
|
|
|
80
87
|
*/
|
|
81
88
|
export const Select = <T extends object>({
|
|
82
89
|
label,
|
|
90
|
+
labelCss,
|
|
83
91
|
placeholder,
|
|
84
92
|
children,
|
|
85
93
|
indicator,
|
|
@@ -97,6 +105,7 @@ export const Select = <T extends object>({
|
|
|
97
105
|
// splitVariantProps, not a hand-picked list: an app preset can add variant
|
|
98
106
|
// groups to the recipe and they have to reach it (playbook gotcha #37).
|
|
99
107
|
const [variantProps, rest] = select.splitVariantProps(props);
|
|
108
|
+
const intl = useIntl();
|
|
100
109
|
const slots = select(variantProps);
|
|
101
110
|
const fieldSlots = field({ size: variantProps.size, labelPosition });
|
|
102
111
|
return (
|
|
@@ -110,6 +119,7 @@ export const Select = <T extends object>({
|
|
|
110
119
|
size={variantProps.size}
|
|
111
120
|
labelPosition={labelPosition}
|
|
112
121
|
isRequired={props.isRequired}
|
|
122
|
+
css={labelCss}
|
|
113
123
|
>
|
|
114
124
|
{label}
|
|
115
125
|
</FieldLabel>
|
|
@@ -122,7 +132,10 @@ export const Select = <T extends object>({
|
|
|
122
132
|
>
|
|
123
133
|
<SelectValue className={slots.value}>
|
|
124
134
|
{({ isPlaceholder, defaultChildren }) =>
|
|
125
|
-
isPlaceholder
|
|
135
|
+
isPlaceholder
|
|
136
|
+
? placeholder ??
|
|
137
|
+
intl.formatMessage(uiMessage("ui.select-placeholder"))
|
|
138
|
+
: defaultChildren
|
|
126
139
|
}
|
|
127
140
|
</SelectValue>
|
|
128
141
|
{indicator !== null && (
|
package/src/Skeleton.tsx
CHANGED
|
@@ -12,12 +12,14 @@ import { SystemStyleObject } from "styled-system/types";
|
|
|
12
12
|
* caller's `css` is merged into one `css()` call and its overrides win
|
|
13
13
|
* (playbook gotcha #8).
|
|
14
14
|
*
|
|
15
|
-
* The colours
|
|
16
|
-
* call site can retint one skeleton without knowing how the animation
|
|
15
|
+
* The colours flow through the same pair of custom properties Chakra used,
|
|
16
|
+
* so a call site can retint one skeleton without knowing how the animation
|
|
17
|
+
* works.
|
|
17
18
|
*/
|
|
18
19
|
const skeletonBase: SystemStyleObject = {
|
|
19
20
|
"--skeleton-start-color": "token(colors.gray.100)",
|
|
20
|
-
|
|
21
|
+
// gray.350, the decorative-fill stop, as the Avatar default.
|
|
22
|
+
"--skeleton-end-color": "token(colors.gray.350)",
|
|
21
23
|
background: "var(--skeleton-start-color)",
|
|
22
24
|
borderColor: "var(--skeleton-end-color)",
|
|
23
25
|
opacity: 0.7,
|
package/src/TextField.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
TextFieldProps as RACTextFieldProps,
|
|
11
11
|
} from "react-aria-components";
|
|
12
12
|
import { field, input, InputVariantProps } from "styled-system/recipes";
|
|
13
|
+
import { SystemStyleObject } from "styled-system/types";
|
|
13
14
|
import {
|
|
14
15
|
FieldLabel,
|
|
15
16
|
FieldLayoutProps,
|
|
@@ -27,6 +28,8 @@ export interface TextFieldProps
|
|
|
27
28
|
FieldLayoutProps {
|
|
28
29
|
/** Visible label (Chakra's FormLabel; asterisk added when `isRequired`). */
|
|
29
30
|
label: ReactNode;
|
|
31
|
+
/** Label style overrides. */
|
|
32
|
+
labelCss?: SystemStyleObject;
|
|
30
33
|
onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
|
|
31
34
|
/** Input autocapitalize attribute (react-aria's TextField omits it). */
|
|
32
35
|
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters";
|
|
@@ -41,6 +44,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
|
|
41
44
|
function TextField(
|
|
42
45
|
{
|
|
43
46
|
label,
|
|
47
|
+
labelCss,
|
|
44
48
|
helperText,
|
|
45
49
|
errorMessage,
|
|
46
50
|
helperTextCss,
|
|
@@ -61,6 +65,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
|
|
61
65
|
size={variantProps.size}
|
|
62
66
|
labelPosition={labelPosition}
|
|
63
67
|
isRequired={rest.isRequired}
|
|
68
|
+
css={labelCss}
|
|
64
69
|
>
|
|
65
70
|
{label}
|
|
66
71
|
</FieldLabel>
|
package/src/Toast.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { ReactNode, useMemo } from "react";
|
|
6
|
+
import { ReactNode, useMemo, useSyncExternalStore } from "react";
|
|
7
7
|
import {
|
|
8
8
|
Button as RACButton,
|
|
9
9
|
UNSTABLE_Toast as RACToast,
|
|
@@ -55,6 +55,10 @@ const statusIcon: Record<ToastStatus, IconType> = {
|
|
|
55
55
|
error: RiErrorWarningFill,
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
// Module scope so useSyncExternalStore doesn't resubscribe every render.
|
|
59
|
+
const subscribeToQueue = (fn: () => void) => toastQueue.subscribe(fn);
|
|
60
|
+
const getVisibleCount = () => toastQueue.visibleToasts.length;
|
|
61
|
+
|
|
58
62
|
/**
|
|
59
63
|
* Mount once near the app root, inside the IntlProvider (the close button's
|
|
60
64
|
* label and the status announcements are react-intl messages).
|
|
@@ -63,8 +67,19 @@ const statusIcon: Record<ToastStatus, IconType> = {
|
|
|
63
67
|
export const ToastProvider = () => {
|
|
64
68
|
const intl = useIntl();
|
|
65
69
|
const slots = toastRecipe();
|
|
70
|
+
// The region's landmark label counts the visible toasts, so it has to
|
|
71
|
+
// track the queue.
|
|
72
|
+
const count = useSyncExternalStore(
|
|
73
|
+
subscribeToQueue,
|
|
74
|
+
getVisibleCount,
|
|
75
|
+
getVisibleCount,
|
|
76
|
+
);
|
|
66
77
|
return (
|
|
67
|
-
<RACToastRegion
|
|
78
|
+
<RACToastRegion
|
|
79
|
+
queue={toastQueue}
|
|
80
|
+
aria-label={intl.formatMessage(uiMessage("ui.toast-region"), { count })}
|
|
81
|
+
className={slots.region}
|
|
82
|
+
>
|
|
68
83
|
{({ toast }) => {
|
|
69
84
|
const status = toast.content.status ?? "info";
|
|
70
85
|
return (
|