Skip to content

Commit 150cbbe

Browse files
tjguptamergify[bot]
authored andcommitted
fix: various form accessibility improvements (#1688)
* fix: various form accessibility improvements * fix: various form accessibility improvements * fix: various form accessibility improvements
1 parent 3d4d59e commit 150cbbe

File tree

10 files changed

+86
-18
lines changed

10 files changed

+86
-18
lines changed

examples/src/SelectorDropdownExamples.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class SelectorDropdownContainer extends Component {
5656
const dropdownTitle = <div>This is a Title</div>;
5757

5858
return (
59-
<div>
59+
<div style={{ paddingBottom: '330px' }}>
6060
<SelectorDropdown
6161
onSelect={this.handleItemSelection}
6262
selector={

src/components/pill-selector-dropdown/PillSelector.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import * as React from 'react';
33
import classNames from 'classnames';
4+
import uniqueId from 'lodash/uniqueId';
45

56
import Tooltip from '../tooltip';
67
import { KEYS } from '../../constants';
@@ -140,6 +141,8 @@ class PillSelector extends React.Component<Props, State> {
140141
}
141142
};
142143

144+
errorMessageID = uniqueId('errorMessage');
145+
143146
hiddenRef = (hiddenEl: ?HTMLSpanElement) => {
144147
if (hiddenEl) {
145148
this.hiddenEl = hiddenEl;
@@ -172,15 +175,20 @@ class PillSelector extends React.Component<Props, State> {
172175
...rest
173176
} = this.props;
174177
const suggestedPillsEnabled = suggestedPillsData && suggestedPillsData.length > 0;
178+
const hasError = !!error;
175179
const classes = classNames('pill-selector-input-wrapper', {
176180
'is-disabled': disabled,
177181
'is-focused': isFocused,
178-
'show-error': !!error,
182+
'show-error': hasError,
179183
'pill-selector-suggestions-enabled': suggestedPillsEnabled,
180184
});
185+
const ariaAttrs = {
186+
'aria-invalid': hasError,
187+
'aria-errormessage': this.errorMessageID,
188+
};
181189

182190
return (
183-
<Tooltip isShown={!!error} text={error || ''} position="middle-right" theme="error">
191+
<Tooltip isShown={hasError} text={error || ''} position="middle-right" theme="error">
184192
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
185193
<span
186194
className={classes}
@@ -207,8 +215,10 @@ class PillSelector extends React.Component<Props, State> {
207215
onBlur={this.resetSelectedIndex}
208216
ref={this.hiddenRef}
209217
tabIndex={-1}
218+
data-testid="pill-selection-helper"
210219
/>
211220
<textarea
221+
{...ariaAttrs}
212222
{...rest}
213223
{...inputProps}
214224
autoComplete="off"
@@ -227,6 +237,9 @@ class PillSelector extends React.Component<Props, State> {
227237
suggestedPillsData={suggestedPillsData}
228238
title={suggestedPillsTitle}
229239
/>
240+
<span id={this.errorMessageID} className="accessibility-hidden" role="alert">
241+
{error}
242+
</span>
230243
</span>
231244
</Tooltip>
232245
);

src/components/pill-selector-dropdown/__tests__/PillSelector.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
137137
).toBe(true);
138138
});
139139

140-
test('should render hidden element', () => {
140+
test('should render hidden pill selection helper', () => {
141141
const wrapper = shallow(<PillSelector onInput={onInputStub} onRemove={onRemoveStub} />);
142-
const hidden = wrapper.find('.accessibility-hidden');
142+
const hidden = wrapper.find('[data-testid="pill-selection-helper"]');
143143
const instance = wrapper.instance();
144144

145145
expect(hidden.length).toBe(1);
@@ -328,7 +328,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
328328
<PillSelector onInput={onInputStub} onRemove={onRemoveStub} selectedOptions={options} />,
329329
);
330330

331-
sandbox.mock(wrapper.find('.accessibility-hidden').getDOMNode()).expects('focus');
331+
sandbox.mock(wrapper.find('[data-testid="pill-selection-helper"]').getDOMNode()).expects('focus');
332332

333333
wrapper.simulate('keyDown', {
334334
key: 'ArrowLeft',

src/components/pill-selector-dropdown/__tests__/__snapshots__/PillSelector.test.js.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ exports[`components/pill-selector-dropdown/PillSelector render() should render d
2020
<span
2121
aria-hidden="true"
2222
className="accessibility-hidden"
23+
data-testid="pill-selection-helper"
2324
onBlur={[Function]}
2425
tabIndex={-1}
2526
/>
2627
<textarea
28+
aria-errormessage="errorMessage2"
29+
aria-invalid={false}
2730
autoComplete="off"
2831
className="pill-selector-input"
2932
disabled={true}
@@ -33,6 +36,11 @@ exports[`components/pill-selector-dropdown/PillSelector render() should render d
3336
<SuggestedPillsRow
3437
selectedPillsValues={Array []}
3538
/>
39+
<span
40+
className="accessibility-hidden"
41+
id="errorMessage2"
42+
role="alert"
43+
/>
3644
</span>
3745
</Tooltip>
3846
`;

src/components/text-area/TextArea.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import * as React from 'react';
33
import classNames from 'classnames';
4+
import uniqueId from 'lodash/uniqueId';
45

56
import Label from '../label';
67
import Tooltip from '../tooltip';
@@ -34,21 +35,33 @@ const TextArea = ({
3435
textareaRef,
3536
...rest
3637
}: Props) => {
38+
const hasError = !!error;
3739
const classes = classNames(className, 'text-area-container', {
38-
'show-error': !!error,
40+
'show-error': hasError,
3941
});
4042

43+
const errorMessageID = React.useRef(uniqueId('errorMessage')).current;
44+
const ariaAttrs = {
45+
'aria-invalid': hasError,
46+
'aria-required': isRequired,
47+
'aria-errormessage': errorMessageID,
48+
};
49+
4150
return (
4251
<div className={classes}>
4352
<Label hideLabel={hideLabel} showOptionalText={!hideOptionalLabel && !isRequired} text={label}>
44-
<Tooltip isShown={!!error} position="bottom-left" text={error || ''} theme="error">
53+
<Tooltip isShown={hasError} position="bottom-left" text={error || ''} theme="error">
4554
<textarea
4655
ref={textareaRef}
4756
required={isRequired}
4857
style={{ resize: isResizable ? '' : 'none' }}
58+
{...ariaAttrs}
4959
{...rest}
5060
/>
5161
</Tooltip>
62+
<span id={errorMessageID} className="accessibility-hidden" role="alert">
63+
{error}
64+
</span>
5265
</Label>
5366
</div>
5467
);

src/components/text-area/__tests__/__snapshots__/TextArea.test.js.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ exports[`components/text-area/TextArea should not show optional text when hideOp
1818
theme="error"
1919
>
2020
<textarea
21+
aria-errormessage="errorMessage10"
22+
aria-invalid={false}
2123
style={
2224
Object {
2325
"resize": "none",
2426
}
2527
}
2628
/>
2729
</Tooltip>
30+
<span
31+
className="accessibility-hidden"
32+
id="errorMessage10"
33+
role="alert"
34+
/>
2835
</Label>
2936
</div>
3037
`;
@@ -47,13 +54,20 @@ exports[`components/text-area/TextArea should show optional text when hideOption
4754
theme="error"
4855
>
4956
<textarea
57+
aria-errormessage="errorMessage11"
58+
aria-invalid={false}
5059
style={
5160
Object {
5261
"resize": "none",
5362
}
5463
}
5564
/>
5665
</Tooltip>
66+
<span
67+
className="accessibility-hidden"
68+
id="errorMessage11"
69+
role="alert"
70+
/>
5771
</Label>
5872
</div>
5973
`;

src/components/text-input/TextInput.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import * as React from 'react';
33
import classNames from 'classnames';
4+
import uniqueId from 'lodash/uniqueId';
45

56
import IconVerified from '../../icons/general/IconVerified';
67

@@ -49,10 +50,18 @@ const TextInput = ({
4950
labelTooltip,
5051
...rest
5152
}: Props) => {
53+
const hasError = !!error;
5254
const classes = classNames(className, 'text-input-container', {
53-
'show-error': !!error,
55+
'show-error': hasError,
5456
});
5557

58+
const errorMessageID = React.useRef(uniqueId('errorMessage')).current;
59+
const ariaAttrs = {
60+
'aria-invalid': hasError,
61+
'aria-required': isRequired,
62+
'aria-errormessage': errorMessageID,
63+
};
64+
5665
return (
5766
<div className={classes}>
5867
<Label
@@ -62,11 +71,14 @@ const TextInput = ({
6271
tooltip={labelTooltip}
6372
>
6473
{!!description && <i className="text-input-description">{description}</i>}
65-
<Tooltip isShown={!!error} position={errorPosition || 'middle-right'} text={error || ''} theme="error">
66-
<input ref={inputRef} required={isRequired} {...rest} />
74+
<Tooltip isShown={hasError} position={errorPosition || 'middle-right'} text={error || ''} theme="error">
75+
<input ref={inputRef} required={isRequired} {...ariaAttrs} {...rest} />
6776
</Tooltip>
6877
{isLoading && !isValid && <LoadingIndicator className="text-input-loading" />}
6978
{isValid && !isLoading && <IconVerified className="text-input-verified" />}
79+
<span id={errorMessageID} className="accessibility-hidden" role="alert">
80+
{error}
81+
</span>
7082
</Label>
7183
</div>
7284
);

src/components/text-input/__tests__/__snapshots__/TextInput.test.js.snap

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@ exports[`components/text-input/TextInput should render text input with descripti
2121
text=""
2222
theme="error"
2323
>
24-
<input />
24+
<input
25+
aria-errormessage="errorMessage20"
26+
aria-invalid={false}
27+
/>
2528
</Tooltip>
29+
<span
30+
className="accessibility-hidden"
31+
id="errorMessage20"
32+
role="alert"
33+
/>
2634
</Label>
2735
</div>
2836
`;

src/features/unified-share-modal/EmailForm.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ class EmailForm extends React.Component<Props, State> {
338338
<FormattedMessage {...commonMessages.cancel} />
339339
</Button>
340340
<PrimaryButton
341-
isDisabled={submitting || selectedContacts.length === 0 || contactsFieldError} // Check selectedContacts.length === 0 for initial render when contactsFieldError is empty
341+
isDisabled={submitting}
342342
isLoading={submitting}
343343
type="submit"
344344
{...sendButtonProps}

src/features/unified-share-modal/__tests__/__snapshots__/EmailForm.test.js.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ exports[`features/unified-share-modal/EmailForm render() should render default c
214214
/>
215215
</Button>
216216
<PrimaryButton
217-
isDisabled={true}
217+
isDisabled={false}
218218
isLoading={false}
219219
type="submit"
220220
>
@@ -292,7 +292,7 @@ exports[`features/unified-share-modal/EmailForm render() should render default c
292292
/>
293293
</Button>
294294
<PrimaryButton
295-
isDisabled={true}
295+
isDisabled={false}
296296
isLoading={false}
297297
type="submit"
298298
>
@@ -420,7 +420,7 @@ exports[`features/unified-share-modal/EmailForm render() should render default c
420420
/>
421421
</Button>
422422
<PrimaryButton
423-
isDisabled={true}
423+
isDisabled={false}
424424
isLoading={false}
425425
type="submit"
426426
>
@@ -503,7 +503,7 @@ exports[`features/unified-share-modal/EmailForm render() should render default c
503503
/>
504504
</Button>
505505
<PrimaryButton
506-
isDisabled={true}
506+
isDisabled={false}
507507
isLoading={false}
508508
type="submit"
509509
>
@@ -597,7 +597,7 @@ exports[`features/unified-share-modal/EmailForm render() should render default c
597597
/>
598598
</Button>
599599
<PrimaryButton
600-
isDisabled={true}
600+
isDisabled={false}
601601
isLoading={false}
602602
type="submit"
603603
>

0 commit comments

Comments
 (0)