@workday/canvas-kit-docs 16.1.3 → 16.2.0-0604-next.0
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/dist/es6/lib/docs.js +18348 -5198
- package/dist/es6/lib/specs.js +204 -20
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.js +5 -5
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.ts +5 -5
- package/dist/mdx/accessibility/AccessibilityOverview.mdx +1 -3
- package/dist/mdx/labs-react/selection-group/SelectionGroup.mdx +135 -0
- package/dist/mdx/labs-react/selection-group/examples/Basic.tsx +11 -0
- package/dist/mdx/labs-react/selection-group/examples/Caution.tsx +27 -0
- package/dist/mdx/labs-react/selection-group/examples/ColumnWidth.tsx +17 -0
- package/dist/mdx/labs-react/selection-group/examples/ContentWidth.tsx +11 -0
- package/dist/mdx/labs-react/selection-group/examples/Disabled.tsx +33 -0
- package/dist/mdx/labs-react/selection-group/examples/EqualWidth.tsx +11 -0
- package/dist/mdx/labs-react/selection-group/examples/Error.tsx +27 -0
- package/dist/mdx/labs-react/selection-group/examples/MultiSelect.tsx +41 -0
- package/dist/mdx/labs-react/selection-group/examples/RTL.tsx +13 -0
- package/dist/mdx/labs-react/selection-group/examples/SingleSelect.tsx +33 -0
- package/dist/mdx/preview-react/_examples/mdx/FormLibraryExample.mdx +1 -33
- package/dist/mdx/preview-react/_examples/mdx/examples/TextInputWithReactHookForm.tsx +65 -94
- package/dist/mdx/react/_examples/mdx/Density.mdx +3 -19
- package/dist/mdx/react/_examples/mdx/FormsWithFormik.mdx +9 -0
- package/dist/mdx/react/_examples/mdx/examples/Density.tsx +212 -139
- package/dist/mdx/react/_examples/mdx/examples/SelectWithFormik.tsx +84 -0
- package/dist/mdx/react/expandable/Expandable.mdx +55 -161
- package/package.json +6 -6
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
-
|
|
3
2
|
import TextInputWithReactHookForm from './examples/TextInputWithReactHookForm';
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Accessible form validation is not a library feature. It is a composition of **labels**, **error text
|
|
9
|
-
tied to the field**, **`aria-invalid`**, and **keyboard focus**. Canvas Kit
|
|
10
|
-
[`FormField`](?path=/docs/components-inputs-form-field--docs#accessibility) owns the first three
|
|
11
|
-
when you use its subcomponents. The application owns when validation runs and where focus goes after
|
|
12
|
-
submit.
|
|
13
|
-
|
|
14
|
-
1. **Identify every field, including required ones** Set `autoComplete` with a
|
|
15
|
-
[standard token](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill)
|
|
16
|
-
when the field collects a known personal data type (`username`, `current-password`, `email`,
|
|
17
|
-
`name`). That supports
|
|
18
|
-
[WCAG 1.3.5 Identify Input Purpose](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html).
|
|
19
|
-
2. **Move focus to the first invalid field after submit** Failed submit must not leave focus on the
|
|
20
|
-
submit button. Keyboard and screen-reader users need to land on the first field that is still
|
|
21
|
-
invalid, in **DOM order**.
|
|
22
|
-
3. **Choose when to validate** Prefer validating **on submit**, then on **blur / touched** for
|
|
23
|
-
fields the user has already visited (`mode: 'onTouched'` in this example). Validating on every
|
|
24
|
-
keystroke while the field is still incomplete is noisy and can announce `aria-invalid` too early.
|
|
25
|
-
(Relevant criteria:
|
|
26
|
-
[3.3.1 Error Identification](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html),
|
|
27
|
-
[3.3.3 Error Suggestion](https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html).)
|
|
28
|
-
4. **Do not use a live region for every field error** Field errors belong on the field
|
|
29
|
-
(`aria-invalid` + `aria-describedby`). They are announced when the field **receives focus**. A
|
|
30
|
-
page-level live region that restates every invalid field on submit is easy to overdo. Live
|
|
31
|
-
announcements cannot be replayed, can interrupt, and have uneven support. If you add a form-level
|
|
32
|
-
summary, keep it short, do not duplicate every message, and read
|
|
33
|
-
[ARIA Live Regions](?path=/docs/guides-accessibility-aria-live-regions--docs) first.
|
|
34
|
-
|
|
35
|
-
This example wires `FormField`, `TextInput`, `Select`, and `InputGroup` to
|
|
36
|
-
[React Hook Form](https://www.react-hook-form.com/) with [Yup](https://github.com/jquense/yup). The
|
|
37
|
-
same practices apply if you use Formik, a server round-trip, or hand-rolled state.
|
|
5
|
+
## Uncontrolled Text Inputs Using React Hook Form
|
|
38
6
|
|
|
39
7
|
<ExampleCodeBlock code={TextInputWithReactHookForm} />
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {SchemaOf,
|
|
2
|
+
import {FieldErrorsImpl, useForm} from 'react-hook-form';
|
|
3
|
+
import {SchemaOf, object, string} from 'yup';
|
|
4
4
|
|
|
5
5
|
import {PrimaryButton, TertiaryButton} from '@workday/canvas-kit-react/button';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
FormField,
|
|
9
|
-
useFormFieldInput,
|
|
10
|
-
useFormFieldModel,
|
|
11
|
-
} from '@workday/canvas-kit-react/form-field';
|
|
6
|
+
import {useUniqueId} from '@workday/canvas-kit-react/common';
|
|
7
|
+
import {FormField} from '@workday/canvas-kit-react/form-field';
|
|
12
8
|
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
13
9
|
import {Select} from '@workday/canvas-kit-react/select';
|
|
14
|
-
import {
|
|
15
|
-
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
10
|
+
import {TextInput} from '@workday/canvas-kit-react/text-input';
|
|
16
11
|
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
|
|
17
12
|
import {visibleIcon, visibleStrikethroughIcon} from '@workday/canvas-system-icons-web';
|
|
18
13
|
import {system} from '@workday/canvas-tokens-web';
|
|
@@ -23,30 +18,31 @@ const styles = createStyles({
|
|
|
23
18
|
alignItems: 'flex-start',
|
|
24
19
|
});
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
type YupValidationResolver = <T extends {}>(
|
|
22
|
+
validationSchema: SchemaOf<T>
|
|
23
|
+
) => (data: T) => Promise<{values: T; errors: {}} | {values: {}; errors: FieldErrorsImpl<T>}>;
|
|
24
|
+
|
|
25
|
+
const useYupValidationResolver: YupValidationResolver = validationSchema => {
|
|
26
|
+
return React.useCallback(
|
|
28
27
|
async data => {
|
|
29
28
|
try {
|
|
30
|
-
await validationSchema.validate(data, {abortEarly: false});
|
|
31
|
-
return {values
|
|
32
|
-
} catch (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return {values: {}, errors: errors as FieldErrors<T>};
|
|
29
|
+
const values = await validationSchema.validate(data, {abortEarly: false});
|
|
30
|
+
return {values, errors: {}};
|
|
31
|
+
} catch (errors) {
|
|
32
|
+
return {
|
|
33
|
+
values: {},
|
|
34
|
+
//@ts-ignore
|
|
35
|
+
errors: errors.inner.reduce(
|
|
36
|
+
(allErrors, currentError) => ({
|
|
37
|
+
...allErrors,
|
|
38
|
+
[currentError.path]: {
|
|
39
|
+
type: currentError.type ?? 'validation',
|
|
40
|
+
message: currentError.message,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
{}
|
|
44
|
+
),
|
|
45
|
+
};
|
|
50
46
|
}
|
|
51
47
|
},
|
|
52
48
|
[validationSchema]
|
|
@@ -78,9 +74,6 @@ const options = [
|
|
|
78
74
|
];
|
|
79
75
|
|
|
80
76
|
export default () => {
|
|
81
|
-
const formRef = React.useRef<HTMLFormElement>(null);
|
|
82
|
-
const [showPassword, setShowPassword] = React.useState(false);
|
|
83
|
-
const [submitAttempt, setSubmitAttempt] = React.useState(0);
|
|
84
77
|
const {
|
|
85
78
|
handleSubmit,
|
|
86
79
|
register,
|
|
@@ -91,36 +84,19 @@ export default () => {
|
|
|
91
84
|
password: 'foobarbaz',
|
|
92
85
|
role: '',
|
|
93
86
|
},
|
|
94
|
-
resolver: useYupValidationResolver
|
|
87
|
+
resolver: useYupValidationResolver(validationSchema),
|
|
95
88
|
mode: 'onTouched',
|
|
96
|
-
shouldFocusError: true,
|
|
97
89
|
});
|
|
98
90
|
|
|
99
|
-
const onSubmit = handleSubmit(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
},
|
|
107
|
-
() => {
|
|
108
|
-
// Count failed submits so we can move focus after FormField paints `aria-invalid`.
|
|
109
|
-
setSubmitAttempt(count => count + 1);
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
React.useEffect(() => {
|
|
114
|
-
if (submitAttempt === 0) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const firstInvalidField = formRef.current?.querySelector<HTMLElement>(
|
|
119
|
-
'[aria-invalid="true"]:not([aria-hidden="true"]):not([tabindex="-1"])'
|
|
120
|
-
);
|
|
121
|
-
firstInvalidField?.focus();
|
|
122
|
-
}, [submitAttempt, errors]);
|
|
91
|
+
const onSubmit = handleSubmit(values => {
|
|
92
|
+
setShowPassword(false);
|
|
93
|
+
// Send data to server
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
alert(JSON.stringify(values, null, 2));
|
|
96
|
+
}, 0);
|
|
97
|
+
});
|
|
123
98
|
|
|
99
|
+
const [showPassword, setShowPassword] = React.useState(false);
|
|
124
100
|
const passwordId = useUniqueId();
|
|
125
101
|
const passwordRef = React.useRef<HTMLInputElement | null>(null);
|
|
126
102
|
const {ref: passwordCallbackRef, ...passwordRegistration} = register('password');
|
|
@@ -128,15 +104,8 @@ export default () => {
|
|
|
128
104
|
passwordCallbackRef(ref);
|
|
129
105
|
passwordRef.current = ref;
|
|
130
106
|
};
|
|
131
|
-
// `InputGroup` renders the input, so hoist the model to keep the label, hint, and error wiring.
|
|
132
|
-
const passwordModel = useFormFieldModel({
|
|
133
|
-
id: passwordId,
|
|
134
|
-
isRequired: true,
|
|
135
|
-
error: errors.password ? 'error' : undefined,
|
|
136
|
-
});
|
|
137
|
-
const passwordInputProps = useFormFieldInput(passwordModel);
|
|
138
107
|
return (
|
|
139
|
-
<form
|
|
108
|
+
<form onSubmit={onSubmit} action="." noValidate={true}>
|
|
140
109
|
<Flex cs={styles}>
|
|
141
110
|
<FormField
|
|
142
111
|
orientation="vertical"
|
|
@@ -174,33 +143,35 @@ export default () => {
|
|
|
174
143
|
</FormField.Field>
|
|
175
144
|
<FormField.Hint>{errors.email?.message}</FormField.Hint>
|
|
176
145
|
</FormField>
|
|
177
|
-
<FormField
|
|
146
|
+
<FormField
|
|
147
|
+
orientation="vertical"
|
|
148
|
+
id={passwordId}
|
|
149
|
+
isRequired={true}
|
|
150
|
+
error={!!errors.password ? 'error' : undefined}
|
|
151
|
+
>
|
|
178
152
|
<FormField.Label>Password</FormField.Label>
|
|
179
|
-
<
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
153
|
+
<Flex cs={{gap: system.gap.md}}>
|
|
154
|
+
<FormField.Field>
|
|
155
|
+
<FormField.Input
|
|
156
|
+
as={TextInput}
|
|
157
|
+
{...passwordRegistration}
|
|
158
|
+
type={showPassword ? 'text' : 'password'}
|
|
159
|
+
autoComplete="current-password"
|
|
160
|
+
spellCheck={false}
|
|
161
|
+
ref={combinePasswordRef}
|
|
162
|
+
/>
|
|
163
|
+
</FormField.Field>
|
|
164
|
+
<TertiaryButton
|
|
165
|
+
type="button"
|
|
166
|
+
icon={showPassword ? visibleStrikethroughIcon : visibleIcon}
|
|
167
|
+
aria-label={showPassword ? 'Hide Password' : 'Show Password'}
|
|
168
|
+
aria-controls={`input-${passwordId}`}
|
|
169
|
+
onClick={() => {
|
|
170
|
+
setShowPassword(state => !state);
|
|
171
|
+
passwordRef.current?.focus();
|
|
172
|
+
}}
|
|
188
173
|
/>
|
|
189
|
-
|
|
190
|
-
<Tooltip title={showPassword ? 'Hide Password' : 'Show Password'}>
|
|
191
|
-
<TertiaryButton
|
|
192
|
-
type="button"
|
|
193
|
-
size="small"
|
|
194
|
-
icon={showPassword ? visibleStrikethroughIcon : visibleIcon}
|
|
195
|
-
aria-controls={`input-${passwordId}`}
|
|
196
|
-
onClick={() => {
|
|
197
|
-
setShowPassword(state => !state);
|
|
198
|
-
passwordRef.current?.focus();
|
|
199
|
-
}}
|
|
200
|
-
/>
|
|
201
|
-
</Tooltip>
|
|
202
|
-
</InputGroup.InnerEnd>
|
|
203
|
-
</FormField.Field>
|
|
174
|
+
</Flex>
|
|
204
175
|
<FormField.Hint>{errors.password?.message || passwordHint}</FormField.Hint>
|
|
205
176
|
</FormField>
|
|
206
177
|
|
|
@@ -4,24 +4,8 @@ import Density from './examples/Density';
|
|
|
4
4
|
|
|
5
5
|
# Canvas Kit Density Examples
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Density in this demo changes:
|
|
12
|
-
|
|
13
|
-
- **Input height and padding** (`system.size.sm` / `md` / `md + 4px`)
|
|
14
|
-
- **Label and input type** (`subtext.medium` / `subtext.large` / `body.small`)
|
|
15
|
-
- **Space between fields** (`system.gap.md` / `lg` / `xl`)
|
|
16
|
-
- **Space between a label and its input** (tighter when labels are vertical)
|
|
17
|
-
|
|
18
|
-
| Density | Input height | Field spacing | Type |
|
|
19
|
-
| ------- | ------------ | ------------- | ---- |
|
|
20
|
-
| High | 32px | 16px | `subtext.medium` |
|
|
21
|
-
| Medium | 40px | 24px | `subtext.large` |
|
|
22
|
-
| Low | 48px | 32px | `body.small` |
|
|
23
|
-
|
|
24
|
-
`Switch` does not expose a size API, so its dimensions stay the same across densities. Only its
|
|
25
|
-
label type and surrounding field spacing change.
|
|
7
|
+
Although we currently don't support a density system, it's common practice to provide varying
|
|
8
|
+
options to users, especially in the context of forms. Below is an example of how you can style our
|
|
9
|
+
from elements to achieve the desired density.
|
|
26
10
|
|
|
27
11
|
<ExampleCodeBlock code={Density} />
|