Skip to content

Commit

Permalink
Merge pull request #27 from storybookjs/jsomsanith/fix/select_a11y
Browse files Browse the repository at this point in the history
fix: select accessibility
  • Loading branch information
kylesuss authored Jun 24, 2019
2 parents ee38c64 + 9f37fb4 commit 3b56f36
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 24 deletions.
82 changes: 58 additions & 24 deletions src/components/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,28 @@ import { jiggle } from './shared/animation';
import { Icon } from './Icon';
import { Spinner } from './Spinner';

const Label = styled.span`
const Label = styled.label`
font-weight: ${typography.weight.extrabold};
font-size: ${typography.size.s2}px;
`;

const LabelWrapper = styled.div`
margin-bottom: 0.5em;
${props =>
props.hideLabel &&
css`
border: 0px !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(100%) !important;
clip-path: inset(100%) !important;
height: 1px !important;
overflow: hidden !important;
padding: 0px !important;
position: absolute !important;
white-space: nowrap !important;
width: 1px !important;
`}
`;

const Selector = styled.select`
Expand Down Expand Up @@ -51,6 +66,20 @@ const SelectSpinner = styled(Spinner)`
z-index: 1;
`;

const SelectError = styled.div`
position: absolute;
right: 0;
top: 0
transition: all 300ms cubic-bezier(0.175, 0.885, 0.335, 1.05);
font-size: ${typography.size.s1}px;
font-family: ${typography.type.primary};
color: ${color.negative};
line-height: 38px;
padding-right: 2.75em;
`;

const SelectWrapper = styled.span`
display: inline-block;
height: 40px;
Expand Down Expand Up @@ -177,20 +206,6 @@ const SelectWrapper = styled.span`
animation: ${jiggle} 700ms ease-out;
path { fill: ${color.negative}; }
}
&:before {
transition: all 300ms cubic-bezier(0.175, 0.885, 0.335, 1.05);
font-size: ${typography.size.s1}px;
font-family: ${typography.type.primary};
color: ${color.negative};
content: attr(data-error);
line-height: 38px;
padding-right: 2.75em;
width: auto;
min-width: 2em;
}
`}
`;

Expand All @@ -204,24 +219,36 @@ Option.propTypes = {
};

export function Select({
id,
options,
value,
appearance,
label,
hideLabel,
error,
icon,
className,
inProgress,
disabled,
...other
}) {
let spinnerId;
let errorId;
let ariaDescribedBy;
if (inProgress) {
spinnerId = `${id}-in-progress`;
ariaDescribedBy = spinnerId;
}
if (error) {
errorId = `${id}-error`;
ariaDescribedBy = `${ariaDescribedBy || ''} ${errorId}`;
}

return (
<div className={className}>
{label && (
<LabelWrapper>
<Label>{label}</Label>
</LabelWrapper>
)}
<LabelWrapper hideLabel={hideLabel}>
<Label htmlFor={id}>{label}</Label>
</LabelWrapper>
<SelectWrapper
appearance={appearance}
icon={icon}
Expand All @@ -231,27 +258,34 @@ export function Select({
>
{!inProgress && <Arrow />}
<Selector
id={id}
value={value}
{...other}
disabled={disabled || inProgress}
inProgress={inProgress}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-busy={inProgress}
>
{options.map(option => (
<Option {...option} key={option.value} />
))}
</Selector>
{icon && <SelectIcon icon={icon} />}
{inProgress && <SelectSpinner inForm />}
{icon && <SelectIcon icon={icon} aria-hidden />}
{error && <SelectError id={errorId}>{error}</SelectError>}
{inProgress && <SelectSpinner id={spinnerId} aria-label="Loading" inForm />}
</SelectWrapper>
</div>
);
}

Select.propTypes = {
id: PropTypes.string.isRequired,
options: PropTypes.arrayOf(PropTypes.shape(Option.propTypes)),
value: PropTypes.string,
appearance: PropTypes.oneOf(['default', 'secondary', 'tertiary']),
label: PropTypes.string,
label: PropTypes.string.isRequired,
hideLabel: PropTypes.bool,
error: PropTypes.string,
icon: PropTypes.string,
className: PropTypes.string,
Expand All @@ -263,7 +297,7 @@ Select.defaultProps = {
value: 'loading',
options: [{ title: 'Loading', value: 'loading' }],
appearance: 'default',
label: null,
hideLabel: false,
error: null,
icon: null,
className: null,
Expand Down
50 changes: 50 additions & 0 deletions src/components/Select.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ storiesOf('Design System|forms/Select', module)
.add('All selects', () => (
<form style={{ background: '#EEEEEE', padding: '3em' }}>
<Select
id="Primary"
value="value1"
options={[
{ label: 'Default', value: 'value1' },
{ label: 'Dog', value: 'value2' },
{ label: 'Mouse', value: 'value3' },
]}
onChange={onChange}
label="Animal"
hideLabel
/>
<Select
id="Secondary"
value="value1"
options={[
{ label: 'Secondary', value: 'value1' },
Expand All @@ -27,8 +31,11 @@ storiesOf('Design System|forms/Select', module)
]}
appearance="secondary"
onChange={onChange}
label="Animal"
hideLabel
/>
<Select
id="Tertiary"
value="value1"
options={[
{ label: 'Tertiary', value: 'value1' },
Expand All @@ -37,49 +44,70 @@ storiesOf('Design System|forms/Select', module)
]}
appearance="tertiary"
onChange={onChange}
label="Animal"
hideLabel
/>
</form>
))
.add('default', () => (
<form style={{ background: '#EEEEEE', padding: '3em' }}>
<Select
id="Primary"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
onChange={onChange}
/>
<Select
id="Primary-disabled"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
disabled
onChange={onChange}
/>
<Select
id="Primary-with-iconn"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
onChange={onChange}
/>
<Select
id="Primary-in-progress"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
onChange={onChange}
inProgress
/>
<Select
id="Primary-with-error"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
error="There's a snake in my boots"
onChange={onChange}
/>
<Select
id="Primary-with-icon-and-error"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
error="There's a snake in my boots"
onChange={onChange}
/>
<Select
id="Primary-with-label"
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
Expand All @@ -91,33 +119,48 @@ storiesOf('Design System|forms/Select', module)
.add('secondary', () => (
<form style={{ background: '#FFFFFF', padding: '3em' }}>
<Select
id="Secondary"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
appearance="secondary"
onChange={onChange}
/>
<Select
id="Secondary-disabled"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
disabled
appearance="secondary"
onChange={onChange}
/>
<Select
id="Secondary-with-icon"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
appearance="secondary"
onChange={onChange}
/>
<Select
id="Secondary-with-error"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
error="There's a snake in my boots"
appearance="secondary"
onChange={onChange}
/>
<Select
id="Secondary-with-icon-and-error"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
Expand All @@ -126,6 +169,7 @@ storiesOf('Design System|forms/Select', module)
onChange={onChange}
/>
<Select
id="Secondary-with-label"
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
icon="chroma"
Expand All @@ -138,12 +182,18 @@ storiesOf('Design System|forms/Select', module)
.add('tertiary', () => (
<form style={{ background: '#EEEEEE', padding: '3em' }}>
<Select
id="Tertiary"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
appearance="tertiary"
onChange={onChange}
/>
<Select
id="Tertiary-disabled"
label="Animal"
hideLabel
value="value1"
options={[{ label: 'Default', value: 'value1' }, { label: 'Dog', value: 'value2' }]}
disabled
Expand Down

0 comments on commit 3b56f36

Please sign in to comment.