Commit 5ca6c6ad by sarahkf Committed by GitHub

Merge pull request #17 from edx/sarahkf/finish-checkbox

Finishing checkbox
parents 023cdf3b 2903ef26
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots CheckBox basic usage 1`] = `
<label
htmlFor="checkbox1"
<div
className="form-group"
>
<input
aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false}
disabled={undefined}
id="checkbox1"
disabled={false}
id="asInput1"
name="checkbox"
onClick={[Function]}
tabIndex="0"
onChange={[Function]}
type="checkbox"
/>
check me out!
</label>
<label
htmlFor="asInput1"
>
check me out!
</label>
</div>
`;
exports[`Storyshots CheckBox call a function 1`] = `
<label
htmlFor="checkbox4"
<div
className="form-group"
>
<input
aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false}
disabled={undefined}
id="checkbox4"
disabled={false}
id="asInput4"
name="checkbox"
onClick={[Function]}
tabIndex="0"
onChange={[Function]}
type="checkbox"
/>
check out the console
</label>
<label
htmlFor="asInput4"
>
check out the console
</label>
</div>
`;
exports[`Storyshots CheckBox default checked 1`] = `
<label
htmlFor="checkbox3"
<div
className="form-group"
>
<input
aria-checked={true}
aria-describedby="checkbox"
defaultChecked={true}
disabled={undefined}
id="checkbox3"
disabled={false}
id="asInput3"
name="checkbox"
onClick={[Function]}
tabIndex="0"
onChange={[Function]}
type="checkbox"
/>
(un)check me out
</label>
<label
htmlFor="asInput3"
>
(un)check me out
</label>
</div>
`;
exports[`Storyshots CheckBox disabled 1`] = `
<label
htmlFor="checkbox2"
<div
className="form-group"
>
<input
aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false}
disabled={true}
id="checkbox2"
id="asInput2"
name="checkbox"
onClick={[Function]}
tabIndex="0"
onChange={[Function]}
type="checkbox"
/>
you cannot check me out
</label>
<label
htmlFor="asInput2"
>
you cannot check me out
</label>
</div>
`;
exports[`Storyshots Dropdown basic usage 1`] = `
......
......@@ -11,34 +11,27 @@ storiesOf('CheckBox', module)
.add('basic usage', () => (
<CheckBox
name="checkbox"
describedBy="checkbox"
label="check me out!"
checked="false"
/>
))
.add('disabled', () => (
<CheckBox
name="checkbox"
describedBy="checkbox"
label="you cannot check me out"
checked="false"
disabled={boolean('disabled', true)}
/>
))
.add('default checked', () => (
<CheckBox
name="checkbox"
describedBy="checkbox"
label="(un)check me out"
checked="true"
checked
/>
))
.add('call a function', () => (
<CheckBox
name="checkbox"
describedBy="checkbox"
label="check out the console"
checked="false"
onChange={() => console.log('the checkbox changed state')}
/>
));
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow, mount } from 'enzyme';
import { mount } from 'enzyme';
import CheckBox from './index';
describe('<CheckBox />', () => {
it('attributes are set correctly', () => {
const wrapper = shallow(
const wrapper = mount(
<CheckBox
name="checkbox"
describedBy="this is a checkbox"
label="check me out!"
checked="false"
/>,
);
expect(wrapper.find('[name="checkbox"]').exists()).toEqual(true);
expect(wrapper.find('[type="checkbox"]').exists()).toEqual(true);
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-describedby="this is a checkbox"]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
expect(wrapper.find('[tabIndex="0"]').exists()).toEqual(true);
});
it('aria-label changes after click', () => {
const wrapper = shallow(
const wrapper = mount(
<CheckBox
name="checkbox"
descibedBy="checkbox"
label="check me out!"
checked="false"
/>,
);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
wrapper.find('input').simulate('click');
wrapper.find('[type="checkbox"]').simulate('change');
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(false);
expect(wrapper.find('[aria-checked=true]').exists()).toEqual(true);
wrapper.find('input').simulate('click');
wrapper.find('[type="checkbox"]').simulate('change');
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=true]').exists()).toEqual(false);
});
it('check that callback function is triggered when clicked', () => {
const spy = jest.fn();
const wrapper = shallow(
const wrapper = mount(
<CheckBox
name="checkbox"
descibedBy="checkbox"
label="check me out!"
checked="false"
onChange={spy}
/>,
);
expect(spy).toHaveBeenCalledTimes(0);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change');
expect(spy).toHaveBeenCalledTimes(1);
});
it('checks if start state can be set to checked', () => {
const wrapper = shallow(
const wrapper = mount(
<CheckBox
name="checkbox"
describedBy="checkbox"
label="I start checked"
checked="true"
checked
/>,
);
expect(wrapper.find('[defaultChecked=true]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=true]').exists()).toEqual(true);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change');
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
});
......@@ -82,18 +73,14 @@ describe('<CheckBox />', () => {
const wrapper = mount(
<CheckBox
name="checkbox"
describedBy="checkbox"
label="I am disabled"
checked="false"
disabled
/>,
);
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
expect(wrapper.props().disabled).toEqual(true);
wrapper.find('input').simulate('click');
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
wrapper.find('input').simulate('change');
expect(wrapper.props().disabled).toEqual(true);
});
});
# Checkbox Component
Checkbox based off of the [WAI-ARIA authoring guidelines for the checkbox component](https://www.w3.org/TR/wai-aria-1.1/#checkbox). The checkbox is an HTMl input element with added attributes to ensure proper functionality and accessibility. The checkbox is wrapped in an `asInput` component. The `asInput` wrapper is passed the input element as well as a second parameter set to `false` to ensure that the label is placed to the right of the checkbox.
The following parameters should be passed into every checkbox component:
* `name` (`String`): `name` attribute
* `label` (`String`): label to be placed next to the checkbox
The following parameters can optionally be passed into a checkbox component:
* `checked` (`Boolean`): `true` if the default state should be checked, `false` otherwise
* `disabled` (`Boolean`): `true` if the checkbox should be disabled, `false` otherwise
* `onChangeState`: function to be called when the checkbox changes state
The implementation of the checkbox contains the following functions:
* `constructor()`: The constructor sets the `id` for the checkbox and sets whether the initial state should be checked or unchecked
* `handleClick()`: Switches the state of the checkbox; also calls the `onChangeState()` function if one has been passed in
* '`render()`: Returns the checkbox as an input element with seven attributes: `id`, `type`, `name`, `defaultChecked`, `aria-checked`, `onClick`, and `disabled`.
import React from 'react';
import PropTypes from 'prop-types';
import { inputProps } from '../asInput';
import newId from '../utils/newId';
import asInput from '../asInput';
class CheckBox extends React.Component {
class Check extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
if (this.props.onChange) {
this.onChange = this.props.onChange.bind(this);
}
this.onChange = this.onChange.bind(this);
const id = newId('checkbox');
if (props.checked === 'true') {
this.state = {
id,
checked: true,
};
} else {
this.state = {
id,
checked: false,
};
}
this.state = {
checked: props.checked || false,
};
}
handleClick() {
if (this.state.checked === true) {
this.setState({
checked: false,
});
} else {
this.setState({
checked: true,
});
}
if (this.onChange) {
this.onChange();
}
onChange(event) {
this.setState({
checked: !this.state.checked,
});
this.props.onChange(event);
}
......@@ -46,24 +26,29 @@ class CheckBox extends React.Component {
const props = { ...this.props };
return (
<label htmlFor={this.state.id}>
<input
id={this.state.id}
name={props.name}
type="checkbox"
defaultChecked={this.state.checked}
aria-describedby={props.describedBy}
aria-checked={this.state.checked}
tabIndex="0"
onClick={this.handleClick}
disabled={props.disabled}
/>
{props.label}
</label>
<input
id={props.id}
type="checkbox"
name={props.name}
defaultChecked={this.state.checked}
aria-checked={this.state.checked}
onChange={this.onChange}
disabled={props.disabled}
/>
);
}
}
CheckBox.propTypes = inputProps;
Check.propTypes = {
checked: PropTypes.bool,
onChange: PropTypes.func,
};
Check.defaultProps = {
checked: false,
onChange: () => {},
};
const CheckBox = asInput(Check, false);
export default CheckBox;
......@@ -24,7 +24,7 @@ export const inputProps = {
className: PropTypes.arrayOf(PropTypes.string),
};
const asInput = (WrappedComponent) => {
const asInput = (WrappedComponent, labelFirst = true) => {
class NewComponent extends React.Component {
constructor(props) {
super(props);
......@@ -85,10 +85,9 @@ const asInput = (WrappedComponent) => {
render() {
const { description, error, describedBy } = this.getDescriptions();
return (
<div className={styles['form-group']}>
<label htmlFor={this.state.id}>{this.props.label}</label>
{labelFirst && <label htmlFor={this.state.id}>{this.props.label}</label>}
<WrappedComponent
{...this.props}
{...this.state}
......@@ -100,6 +99,7 @@ const asInput = (WrappedComponent) => {
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
{!labelFirst && <label htmlFor={this.state.id}>{this.props.label}</label>}
{error}
{description}
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment