Skip to content

Commit

Permalink
fix(checkbox): applies screen-reader improvements (uber#2918)
Browse files Browse the repository at this point in the history
* fix(checkbox): applies screen-reader improvements

* fix(checkbox): snapshots

* fix(checkbox): add new types to ts defs

* fix(ts): update nullability
  • Loading branch information
chasestarr authored Feb 26, 2020
1 parent b9173fb commit cc2a2a3
Show file tree
Hide file tree
Showing 21 changed files with 90 additions and 5 deletions.
9 changes: 7 additions & 2 deletions src/checkbox/checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ class StatelessCheckbox extends React.Component<PropsT, StatelessStateT> {
{this.isToggle() ? (
<ToggleTrack
role="checkbox"
aria-checked={checked}
aria-checked={isIndeterminate ? 'mixed' : checked}
aria-invalid={isError || null}
{...sharedProps}
{...getOverrideProps(ToggleTrackOverride)}
>
Expand All @@ -204,7 +205,8 @@ class StatelessCheckbox extends React.Component<PropsT, StatelessStateT> {
<Checkmark
role="checkbox"
checked={checked}
aria-checked={checked}
aria-checked={isIndeterminate ? 'mixed' : checked}
aria-invalid={isError || null}
{...sharedProps}
{...getOverrideProps(CheckmarkOverride)}
/>
Expand All @@ -214,6 +216,9 @@ class StatelessCheckbox extends React.Component<PropsT, StatelessStateT> {
name={name}
checked={checked}
required={required}
aria-checked={isIndeterminate ? 'mixed' : checked}
aria-describedby={this.props['aria-describedby']}
aria-errormessage={this.props['aria-errormessage']}
aria-invalid={isError || null}
aria-required={required || null}
disabled={disabled}
Expand Down
2 changes: 2 additions & 0 deletions src/checkbox/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export interface CheckboxOverrides {
}

export interface CheckboxProps {
'aria-describedby'?: string;
'aria-errormessage'?: string;
children?: React.ReactNode;
overrides?: CheckboxOverrides;
checked?: boolean;
Expand Down
4 changes: 4 additions & 0 deletions src/checkbox/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export type DefaultPropsT = {
};

export type PropsT = {
/** Id of element which contains a related caption */
'aria-describedby'?: string,
/** Id of element which contains a related error message */
'aria-errormessage'?: string,
/** Component or String value for label of checkbox. */
children?: React$Node,
overrides?: OverridesT,
Expand Down
8 changes: 8 additions & 0 deletions src/form-control/__tests__/form-control.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);

Expand All @@ -60,6 +61,7 @@ Object {
"$positive": false,
"children": "Error test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
});
Expand Down Expand Up @@ -180,6 +182,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
});
Expand Down Expand Up @@ -209,6 +212,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
rendered.setProps({
Expand All @@ -231,6 +235,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
expect(caption).toHaveText('Error test');
Expand Down Expand Up @@ -263,6 +268,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
});
Expand Down Expand Up @@ -295,6 +301,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
});
Expand Down Expand Up @@ -330,6 +337,7 @@ Object {
"$positive": false,
"children": "Caption test",
"data-baseweb": "form-control-caption",
"id": "bui-mock-id",
}
`);
});
Expand Down
13 changes: 11 additions & 2 deletions src/form-control/form-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ LICENSE file in the root directory of this source tree.
// @flow
import * as React from 'react';
import {getOverride, getOverrideProps} from '../helpers/overrides.js';
import getBuiId from '../utils/get-bui-id.js';
import {
Label as StyledLabel,
Caption as StyledCaption,
ControlContainer as StyledControlContainer,
} from './styled-components.js';
import type {FormControlPropsT} from './types.js';
import type {FormControlPropsT, FormControlStateT} from './types.js';

function chooseRenderedHint(caption, error, positive, sharedProps) {
if (error && typeof error !== 'boolean') {
Expand All @@ -31,14 +32,18 @@ function chooseRenderedHint(caption, error, positive, sharedProps) {
return null;
}

export default class FormControl extends React.Component<FormControlPropsT> {
export default class FormControl extends React.Component<
FormControlPropsT,
FormControlStateT,
> {
static defaultProps = {
overrides: {},
label: null,
caption: null,
error: false,
positive: false,
};
state = {captionId: getBuiId()};

render() {
const {
Expand Down Expand Up @@ -102,6 +107,9 @@ export default class FormControl extends React.Component<FormControlPropsT> {
const key = child.key || String(index);
return React.cloneElement(child, {
key,
'aria-errormessage': error ? this.state.captionId : null,
'aria-describedby':
caption || positive ? this.state.captionId : null,
disabled:
typeof onlyChildProps.disabled !== 'undefined'
? onlyChildProps.disabled
Expand All @@ -119,6 +127,7 @@ export default class FormControl extends React.Component<FormControlPropsT> {
{(caption || error || positive) && (
<Caption
data-baseweb="form-control-caption"
id={this.state.captionId}
{...sharedProps}
{...getOverrideProps(CaptionOverride)}
>
Expand Down
9 changes: 8 additions & 1 deletion src/form-control/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export interface FormControlOverrides {
ControlContainer?: Override<any>;
}

export interface FormControlState {
captionId: string;
}

export interface FormControlProps {
children: React.ReactNode;
disabled?: boolean;
Expand All @@ -22,4 +26,7 @@ export interface FormControlProps {
positive?: React.ReactNode;
}

export class FormControl extends React.Component<FormControlProps> {}
export class FormControl extends React.Component<
FormControlProps,
FormControlState
> {}
4 changes: 4 additions & 0 deletions src/form-control/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ LICENSE file in the root directory of this source tree.
import * as React from 'react';
import type {OverrideT} from '../helpers/overrides.js';

export type FormControlStateT = {|
captionId: string,
|};

export type FormControlPropsT = {
overrides: {
/** Customizes the label element. */
Expand Down
1 change: 1 addition & 0 deletions src/input/__tests__/__snapshots__/base-input.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Object {
exports[`BaseInput - basic functionality: Base input has correct props 1`] = `
Object {
"aria-describedby": null,
"aria-errormessage": null,
"aria-invalid": false,
"aria-label": null,
"aria-labelledby": null,
Expand Down
4 changes: 4 additions & 0 deletions src/input/__tests__/__snapshots__/input.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`Input - basic functionality: input has correct props 1`] = `
Object {
"aria-describedby": null,
"aria-errormessage": null,
"aria-invalid": false,
"aria-label": null,
"aria-labelledby": null,
Expand Down Expand Up @@ -83,6 +84,7 @@ Object {
$required={false}
$size="default"
aria-describedby={null}
aria-errormessage={null}
aria-invalid={false}
aria-label={null}
aria-labelledby={null}
Expand Down Expand Up @@ -147,6 +149,7 @@ Object {
$required={false}
$size="default"
aria-describedby={null}
aria-errormessage={null}
aria-invalid={false}
aria-label={null}
aria-labelledby={null}
Expand Down Expand Up @@ -211,6 +214,7 @@ Object {
$required={false}
$size="default"
aria-describedby={null}
aria-errormessage={null}
aria-invalid={false}
aria-label={null}
aria-labelledby={null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Object {
exports[`MaskedInput - basic functionality: Masked input has correct props 1`] = `
Object {
"aria-describedby": null,
"aria-errormessage": null,
"aria-invalid": false,
"aria-label": null,
"aria-labelledby": null,
Expand Down
2 changes: 2 additions & 0 deletions src/input/base-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class BaseInput<T: EventTarget> extends React.Component<
InternalStateT,
> {
static defaultProps = {
'aria-errormessage': null,
'aria-label': null,
'aria-labelledby': null,
'aria-describedby': null,
Expand Down Expand Up @@ -277,6 +278,7 @@ class BaseInput<T: EventTarget> extends React.Component<
<Before {...sharedProps} {...beforeProps} />
<Input
ref={this.inputRef}
aria-errormessage={this.props['aria-errormessage']}
aria-label={this.props['aria-label']}
aria-labelledby={this.props['aria-labelledby']}
aria-describedby={this.props['aria-describedby']}
Expand Down
2 changes: 2 additions & 0 deletions src/input/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export type InputComponentsT = {|
|};

export type BaseInputPropsT<T> = {|
/** Id of element which contains a related error message */
'aria-errormessage'?: string,
/** Sets aria-label attribute. */
'aria-label'?: string,
/** Sets aria-labelledby attribute. */
Expand Down
2 changes: 2 additions & 0 deletions src/radio/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export interface StatefulRadioGroupProps {
export const StatefulRadioGroup: React.FC<StatefulRadioGroupProps>;

export interface RadioGroupProps {
'aria-describedby'?: string;
'aria-errormessage'?: string;
'aria-label'?: string;
'aria-labelledby'?: string;
overrides?: RadioOverrides & RadioGroupOverrides;
Expand Down
3 changes: 3 additions & 0 deletions src/radio/radiogroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class StatelessRadioGroup extends React.Component<PropsT, StatelessStateT> {
return (
<RadioGroupRoot
role="radiogroup"
aria-describedby={this.props['aria-describedby']}
aria-errormessage={this.props['aria-errormessage']}
aria-invalid={this.props.isError || null}
aria-label={this.props['aria-label']}
aria-labelledby={this.props['aria-labelledby']}
$align={this.props.align}
Expand Down
4 changes: 4 additions & 0 deletions src/radio/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export type DefaultPropsT = {
};

export type PropsT = {
/** Id of element which contains a related caption */
'aria-describedby'?: string,
/** Id of element which contains a related error message */
'aria-errormessage'?: string,
/**
* Used to define a string that labels the radio group. Use this prop if the label is not
* visible on screen. If the label is visible, use the 'aria-labeledby' prop instead.
Expand Down
Loading

0 comments on commit cc2a2a3

Please sign in to comment.