Skip to content

Commit

Permalink
Merge pull request #25 from storybookjs/jsomsanith/fix/radio_a11y
Browse files Browse the repository at this point in the history
fix: radio accessiblity
  • Loading branch information
kylesuss authored Jun 24, 2019
2 parents c6ddb36 + 113b33e commit a5a3312
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 34 deletions.
116 changes: 88 additions & 28 deletions src/components/Radio.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,70 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { rgba } from 'polished';
import { color, typography } from './shared/styles';

const Label = styled.label`
cursor: pointer;
font-size: ${typography.size.s2}px;
font-weight: ${typography.weight.bold};
min-height: 1em;
position: relative;
display: block;
height: 1em;
display: flex;
align-items: center;
`;

const OptionalText = styled.span`
${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 Error = styled.span`
font-weight: ${typography.weight.regular};
font-size: ${typography.size.s2}px;
color: ${color.negative};
margin-left: 6px;
height: 1em;
display: flex;
align-items: center;
`;

const LabelText = styled.div``;
const SublabelText = styled.div`
const LabelText = styled.span``;

const Description = styled.div`
font-size: ${typography.size.s1}px;
font-weight: ${typography.weight.regular};
margin-top: 4px;
color: ${color.mediumdark};
margin-top: 4px;
margin-left: calc(${typography.size.s2}px + 0.4em);
width: 100%;
`;

const Input = styled.input.attrs({ type: 'radio' })`
float: left;
margin: 0 0.6em 0 0;
visibility: hidden;
margin: 0 0.4em 0 0;
font-size: initial;
opacity: 0;
& + ${LabelText} {
display: block;
line-height: 1;
overflow: hidden;
&:before,
&:after {
transition: all 150ms ease-out;
position: absolute;
top: 0;
left: 0;
height: 14px;
width: 14px;
height: 1em;
width: 1em;
content: '';
display: block;
border-radius: 3em;
Expand All @@ -54,11 +75,20 @@ const Input = styled.input.attrs({ type: 'radio' })`
box-shadow: ${color.mediumdark} 0 0 0 1px inset;
}
&:focus + ${LabelText}:before {
box-shadow: ${color.primary} 0 0 0 1px inset;
}
&:checked + ${LabelText}:before {
box-shadow: ${color.primary} 0 0 0 1px inset;
}
&:checked:focus + ${LabelText}:before {
box-shadow: ${color.primary} 0 0 0 1px inset, ${rgba(color.primary, 0.3)} 0 0 5px 2px;
}
& + ${LabelText}:after {
transition: all 150ms ease-out;
transform: scale3d(0, 0, 1);
height: 10px;
Expand All @@ -76,32 +106,62 @@ const Input = styled.input.attrs({ type: 'radio' })`
}
`;

export function Radio({ value, label, sublabel, error, className, ...props }) {
const RadioWrapper = styled.div`
display: flex;
align-items: center;
flex-wrap: wrap;
`;

export function Radio({ id, label, description, error, hideLabel, value, className, ...props }) {
let errorId;
let descriptionId;
let ariaDescribedBy;

if (error) {
errorId = `${id}-error`;
ariaDescribedBy = errorId;
}
if (description) {
descriptionId = `${id}-description`;
ariaDescribedBy = `${ariaDescribedBy} ${descriptionId}`;
}

return (
<Label className={className}>
<Input value={value} {...props} type="radio" />

<LabelText>
{label}
{error && <Error>{error}</Error>}
{sublabel && <SublabelText>{sublabel}</SublabelText>}
</LabelText>
</Label>
<RadioWrapper>
<Label className={className}>
<Input
{...props}
id={id}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
type="radio"
value={value}
/>
<LabelText>
<OptionalText hideLabel={hideLabel}>{label}</OptionalText>
</LabelText>
</Label>
{error && <Error id={errorId}>{error}</Error>}
{description && <Description id={descriptionId}>{description}</Description>}
</RadioWrapper>
);
}

Radio.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string,
label: PropTypes.string,
sublabel: PropTypes.string,
hideLabel: PropTypes.bool,
description: PropTypes.string,
error: PropTypes.string,
className: PropTypes.string,
};

Radio.defaultProps = {
value: '',
label: null,
sublabel: null,
hideLabel: false,
description: null,
error: null,
className: null,
};
16 changes: 10 additions & 6 deletions src/components/Radio.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ storiesOf('Design System|forms/Radio', module)
.addParameters({ component: Radio })
.add('all radios', () => (
<form>
<Radio label="Mice" value="mice" checked onChange={onChange} />
<Radio label="Dogs" value="dogs" onChange={onChange} />
<Radio label="Cats" onChange={onChange} error="There's a snake in my boots" />
<Radio label="Dogs" sublabel="15 canines" value="dogs" onChange={onChange} />
<Radio id="Mice" label="Mice" value="mice" checked onChange={onChange} />
<Radio id="Dogs" label="Dogs" value="dogs" onChange={onChange} />
<Radio id="Cats" label="Cats" onChange={onChange} error="There's a snake in my boots" />
<Radio id="Dogs" label="Dogs" description="15 canines" value="dogs" onChange={onChange} />
</form>
))
.add('unchecked', () => <Radio value="mice" onChange={onChange} />)
.add('checked', () => <Radio value="dogs" checked onChange={onChange} />);
.add('unchecked', () => (
<Radio id="Mice" label="Mice" hideLabel value="mice" onChange={onChange} />
))
.add('checked', () => (
<Radio id="Dogs" label="Dogs" hideLabel value="dogs" checked onChange={onChange} />
));

0 comments on commit a5a3312

Please sign in to comment.