@moda/om 21.7.0 → 21.7.2
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/index.cjs.js +18 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +18 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/src/components/Field/Field.stories.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/Field/Field.stories.tsx +42 -1
- package/src/components/Field/Field.tsx +14 -2
- package/src/components/SlidingPane/SlidingPane.tsx +7 -2
- package/src/components/TextInput/TextInput.tsx +1 -0
|
@@ -6,3 +6,5 @@ export default _default;
|
|
|
6
6
|
export declare const Default: () => React.JSX.Element;
|
|
7
7
|
export declare const AribitraryChildren: () => React.JSX.Element;
|
|
8
8
|
export declare const SelectChildren: () => React.JSX.Element;
|
|
9
|
+
export declare const AccessibilityErrorState: () => React.JSX.Element;
|
|
10
|
+
export declare const AccessibilityDynamicValidation: () => React.JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { action } from 'storybook/actions';
|
|
3
3
|
import { States } from '../../utilities';
|
|
4
4
|
import { Textarea } from '../Textarea';
|
|
@@ -75,3 +75,44 @@ export const SelectChildren = () => {
|
|
|
75
75
|
</States>
|
|
76
76
|
);
|
|
77
77
|
};
|
|
78
|
+
|
|
79
|
+
export const AccessibilityErrorState = () => (
|
|
80
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '300px' }}>
|
|
81
|
+
<Field
|
|
82
|
+
label='Email address'
|
|
83
|
+
placeholder='Enter your email'
|
|
84
|
+
error='Please enter a valid email address'
|
|
85
|
+
defaultValue='invalid-email'
|
|
86
|
+
/>
|
|
87
|
+
<p style={{ fontSize: '12px', color: '#666' }}>
|
|
88
|
+
Inspect the input to see: aria-invalid="true", aria-describedby linking to the
|
|
89
|
+
error, and role="alert" on the error message for screen reader announcements.
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const AccessibilityDynamicValidation = () => {
|
|
95
|
+
const [value, setValue] = useState('');
|
|
96
|
+
const [touched, setTouched] = useState(false);
|
|
97
|
+
|
|
98
|
+
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
99
|
+
const error =
|
|
100
|
+
touched && value && !isValidEmail(value) ? 'Please enter a valid email address' : undefined;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '300px' }}>
|
|
104
|
+
<Field
|
|
105
|
+
label='Email address'
|
|
106
|
+
placeholder='Enter your email'
|
|
107
|
+
error={error}
|
|
108
|
+
value={value}
|
|
109
|
+
onChange={setValue}
|
|
110
|
+
onBlur={() => setTouched(true)}
|
|
111
|
+
/>
|
|
112
|
+
<p style={{ fontSize: '12px', color: '#666' }}>
|
|
113
|
+
Type an invalid email and blur the field. Screen readers will announce the error via
|
|
114
|
+
role="alert" when it appears.
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { JSX, useMemo } from 'react';
|
|
1
|
+
import React, { JSX, useId, useMemo } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import WarningIcon from '@moda/icons/warning-16';
|
|
4
4
|
import { TextInput, TextInputProps } from '../TextInput';
|
|
@@ -16,8 +16,14 @@ export const Field = React.forwardRef(
|
|
|
16
16
|
{ className, children, error, label, placeholder, ...rest }: FieldProps,
|
|
17
17
|
ref: React.Ref<HTMLInputElement>
|
|
18
18
|
) => {
|
|
19
|
+
const id = useId();
|
|
20
|
+
const errorId = `${id}-error`;
|
|
19
21
|
const fieldContextValue = useMemo(() => ({ displaysError: Boolean(error) }), [error]);
|
|
20
22
|
|
|
23
|
+
// Combine any existing aria-describedby with our error ID
|
|
24
|
+
const ariaDescribedBy =
|
|
25
|
+
[rest['aria-describedby'], error ? errorId : null].filter(Boolean).join(' ') || undefined;
|
|
26
|
+
|
|
21
27
|
return (
|
|
22
28
|
<FormControlContext.Provider value={fieldContextValue}>
|
|
23
29
|
<label className={classNames('Field', className)}>
|
|
@@ -33,6 +39,7 @@ export const Field = React.forwardRef(
|
|
|
33
39
|
placeholder,
|
|
34
40
|
label,
|
|
35
41
|
shiftIconLeftwards: error && children.type === Select,
|
|
42
|
+
'aria-describedby': ariaDescribedBy,
|
|
36
43
|
...rest,
|
|
37
44
|
...children.props
|
|
38
45
|
})}
|
|
@@ -50,6 +57,7 @@ export const Field = React.forwardRef(
|
|
|
50
57
|
placeholder={placeholder}
|
|
51
58
|
label={label}
|
|
52
59
|
error={error}
|
|
60
|
+
aria-describedby={ariaDescribedBy}
|
|
53
61
|
{...rest}
|
|
54
62
|
/>
|
|
55
63
|
{error && (
|
|
@@ -61,7 +69,11 @@ export const Field = React.forwardRef(
|
|
|
61
69
|
)}
|
|
62
70
|
</span>
|
|
63
71
|
|
|
64
|
-
{error &&
|
|
72
|
+
{error && (
|
|
73
|
+
<span id={errorId} className='Field__error' role='alert'>
|
|
74
|
+
{error}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
65
77
|
</label>
|
|
66
78
|
</FormControlContext.Provider>
|
|
67
79
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useId } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import ExitIcon from '@moda/icons/exit-16';
|
|
4
4
|
import { FocusOn } from 'react-focus-on';
|
|
@@ -24,6 +24,8 @@ export const SlidingPane: React.FC<SlidingPaneProps> = ({
|
|
|
24
24
|
autoFocus = true,
|
|
25
25
|
...rest
|
|
26
26
|
}) => {
|
|
27
|
+
const titleId = useId();
|
|
28
|
+
|
|
27
29
|
return (
|
|
28
30
|
<>
|
|
29
31
|
<div
|
|
@@ -40,6 +42,9 @@ export const SlidingPane: React.FC<SlidingPaneProps> = ({
|
|
|
40
42
|
onEscapeKey={onClose}
|
|
41
43
|
>
|
|
42
44
|
<div
|
|
45
|
+
role='dialog'
|
|
46
|
+
aria-modal='true'
|
|
47
|
+
aria-labelledby={titleId}
|
|
43
48
|
className={classNames('SlidingPane', className, {
|
|
44
49
|
'SlidingPane--active': visible
|
|
45
50
|
})}
|
|
@@ -51,7 +56,7 @@ export const SlidingPane: React.FC<SlidingPaneProps> = ({
|
|
|
51
56
|
direction='horizontal'
|
|
52
57
|
className='SlidingPane__top'
|
|
53
58
|
>
|
|
54
|
-
<Text treatment='h5' family='serif'>
|
|
59
|
+
<Text id={titleId} treatment='h5' family='serif'>
|
|
55
60
|
{title}
|
|
56
61
|
</Text>
|
|
57
62
|
<Clickable className='SlidingPane__close' aria-label='Close' onClick={onClose}>
|