Commit afe06b15 by Ari Rizzitano Committed by GitHub

Merge branch 'master' into greenkeeper/react-transition-group-2.0.2

parents 6cebf74c 7677df1d
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots CheckBox basic usage 1`] = ` exports[`Storyshots CheckBox basic usage 1`] = `
<label <div
htmlFor="checkbox1" className="form-group"
> >
<input <input
aria-checked={false} aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false} defaultChecked={false}
disabled={undefined} disabled={false}
id="checkbox1" id="asInput1"
name="checkbox" name="checkbox"
onClick={[Function]} onChange={[Function]}
tabIndex="0"
type="checkbox" type="checkbox"
/> />
check me out! <label
</label> htmlFor="asInput1"
>
check me out!
</label>
</div>
`; `;
exports[`Storyshots CheckBox call a function 1`] = ` exports[`Storyshots CheckBox call a function 1`] = `
<label <div
htmlFor="checkbox4" className="form-group"
> >
<input <input
aria-checked={false} aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false} defaultChecked={false}
disabled={undefined} disabled={false}
id="checkbox4" id="asInput4"
name="checkbox" name="checkbox"
onClick={[Function]} onChange={[Function]}
tabIndex="0"
type="checkbox" type="checkbox"
/> />
check out the console <label
</label> htmlFor="asInput4"
>
check out the console
</label>
</div>
`; `;
exports[`Storyshots CheckBox default checked 1`] = ` exports[`Storyshots CheckBox default checked 1`] = `
<label <div
htmlFor="checkbox3" className="form-group"
> >
<input <input
aria-checked={true} aria-checked={true}
aria-describedby="checkbox"
defaultChecked={true} defaultChecked={true}
disabled={undefined} disabled={false}
id="checkbox3" id="asInput3"
name="checkbox" name="checkbox"
onClick={[Function]} onChange={[Function]}
tabIndex="0"
type="checkbox" type="checkbox"
/> />
(un)check me out <label
</label> htmlFor="asInput3"
>
(un)check me out
</label>
</div>
`; `;
exports[`Storyshots CheckBox disabled 1`] = ` exports[`Storyshots CheckBox disabled 1`] = `
<label <div
htmlFor="checkbox2" className="form-group"
> >
<input <input
aria-checked={false} aria-checked={false}
aria-describedby="checkbox"
defaultChecked={false} defaultChecked={false}
disabled={true} disabled={true}
id="checkbox2" id="asInput2"
name="checkbox" name="checkbox"
onClick={[Function]} onChange={[Function]}
tabIndex="0"
type="checkbox" type="checkbox"
/> />
you cannot check me out <label
</label> htmlFor="asInput2"
>
you cannot check me out
</label>
</div>
`; `;
exports[`Storyshots Dropdown basic usage 1`] = ` exports[`Storyshots Dropdown basic usage 1`] = `
......
import '@storybook/addon-options/register'; import '@storybook/addon-options/register';
import '@storybook/addon-knobs/register';
...@@ -7,7 +7,7 @@ import CssJail from '../src/CssJail'; ...@@ -7,7 +7,7 @@ import CssJail from '../src/CssJail';
setOptions({ setOptions({
name: '💎 PARAGON', name: '💎 PARAGON',
url: 'https://github.com/edx/paragon', url: 'https://github.com/edx/paragon',
showDownPanel: false, showDownPanel: true,
}); });
const req = require.context('../src', true, /\.stories\.jsx$/); const req = require.context('../src', true, /\.stories\.jsx$/);
......
...@@ -6,7 +6,7 @@ Paragon provides accessible React components for use within the Open edX platfor ...@@ -6,7 +6,7 @@ Paragon provides accessible React components for use within the Open edX platfor
<img src="http://i.imgur.com/uxTl3L3.gif" width="200" alt="sparkly 8-bit diamond" /> <img src="http://i.imgur.com/uxTl3L3.gif" width="200" alt="sparkly 8-bit diamond" />
Components' markup, keyboard triggers, and behavior are based on the [WAI-ARIA 1.1 Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/). Components are styled with [Bootstrap 4](https://v4-alpha.getbootstrap.com/) via CSS Modules. Documentation/demo site is available at http://paragon.cool. Components' markup, keyboard triggers, and behavior are based on the [WAI-ARIA 1.1 Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/). Components are styled with [Bootstrap 4](https://v4-alpha.getbootstrap.com/) via CSS Modules. Documentation/demo site is available at http://edx.github.io/paragon.
## Development ## Development
......
...@@ -24,9 +24,10 @@ ...@@ -24,9 +24,10 @@
"react-dom": "^15.5.4" "react-dom": "^15.5.4"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-knobs": "^3.2.0",
"@storybook/addon-options": "^3.1.6", "@storybook/addon-options": "^3.1.6",
"@storybook/addon-storyshots": "^3.1.7", "@storybook/addon-storyshots": "^3.1.7",
"@storybook/react": "3.1.8", "@storybook/react": "3.2.8",
"@storybook/storybook-deployer": "^2.0.0", "@storybook/storybook-deployer": "^2.0.0",
"babel-cli": "^6.24.1", "babel-cli": "^6.24.1",
"babel-jest": "^20.0.3", "babel-jest": "^20.0.3",
...@@ -40,9 +41,9 @@ ...@@ -40,9 +41,9 @@
"coveralls": "^2.13.1", "coveralls": "^2.13.1",
"css-loader": "^0.28.4", "css-loader": "^0.28.4",
"enzyme": "^2.8.2", "enzyme": "^2.8.2",
"eslint": "^3.19.0", "eslint": "^4.5.0",
"eslint-config-airbnb": "^15.0.1", "eslint-config-airbnb": "^15.0.1",
"eslint-config-edx": "^3.0.0", "eslint-config-edx": "^4.0.0",
"eslint-import-resolver-webpack": "^0.8.1", "eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-jsx-a11y": "^5.1.0", "eslint-plugin-jsx-a11y": "^5.1.0",
"eslint-plugin-react": "^7.1.0", "eslint-plugin-react": "^7.1.0",
......
...@@ -2,41 +2,36 @@ ...@@ -2,41 +2,36 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { withKnobs, boolean } from '@storybook/addon-knobs';
import CheckBox from './index'; import CheckBox from './index';
storiesOf('CheckBox', module) storiesOf('CheckBox', module)
.addDecorator(withKnobs)
.add('basic usage', () => ( .add('basic usage', () => (
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="check me out!" label="check me out!"
checked="false"
/> />
)) ))
.add('disabled', () => ( .add('disabled', () => (
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="you cannot check me out" label="you cannot check me out"
checked="false" disabled={boolean('disabled', true)}
disabled
/> />
)) ))
.add('default checked', () => ( .add('default checked', () => (
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="(un)check me out" label="(un)check me out"
checked="true" checked
/> />
)) ))
.add('call a function', () => ( .add('call a function', () => (
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="check out the console" label="check out the console"
checked="false"
onChange={() => console.log('the checkbox changed state')} onChange={() => console.log('the checkbox changed state')}
/> />
)); ));
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import React from 'react'; import React from 'react';
import { shallow, mount } from 'enzyme'; import { mount } from 'enzyme';
import CheckBox from './index'; import CheckBox from './index';
describe('<CheckBox />', () => { describe('<CheckBox />', () => {
it('attributes are set correctly', () => { it('attributes are set correctly', () => {
const wrapper = shallow( const wrapper = mount(
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="this is a checkbox"
label="check me out!" label="check me out!"
checked="false"
/>, />,
); );
expect(wrapper.find('[name="checkbox"]').exists()).toEqual(true); expect(wrapper.find('[name="checkbox"]').exists()).toEqual(true);
expect(wrapper.find('[type="checkbox"]').exists()).toEqual(true); expect(wrapper.find('[type="checkbox"]').exists()).toEqual(true);
expect(wrapper.find('[defaultChecked=false]').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('[aria-checked=false]').exists()).toEqual(true);
expect(wrapper.find('[tabIndex="0"]').exists()).toEqual(true);
}); });
it('aria-label changes after click', () => { it('aria-label changes after click', () => {
const wrapper = shallow( const wrapper = mount(
<CheckBox <CheckBox
name="checkbox" name="checkbox"
descibedBy="checkbox"
label="check me out!" label="check me out!"
checked="false"
/>, />,
); );
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true); 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=false]').exists()).toEqual(false);
expect(wrapper.find('[aria-checked=true]').exists()).toEqual(true); 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=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=true]').exists()).toEqual(false); expect(wrapper.find('[aria-checked=true]').exists()).toEqual(false);
}); });
it('check that callback function is triggered when clicked', () => { it('check that callback function is triggered when clicked', () => {
const spy = jest.fn(); const spy = jest.fn();
const wrapper = shallow( const wrapper = mount(
<CheckBox <CheckBox
name="checkbox" name="checkbox"
descibedBy="checkbox"
label="check me out!" label="check me out!"
checked="false"
onChange={spy} onChange={spy}
/>, />,
); );
expect(spy).toHaveBeenCalledTimes(0); expect(spy).toHaveBeenCalledTimes(0);
wrapper.find('input').simulate('click'); wrapper.find('input').simulate('change');
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
}); });
it('checks if start state can be set to checked', () => { it('checks if start state can be set to checked', () => {
const wrapper = shallow( const wrapper = mount(
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="I start checked" label="I start checked"
checked="true" checked
/>, />,
); );
expect(wrapper.find('[defaultChecked=true]').exists()).toEqual(true); expect(wrapper.find('[defaultChecked=true]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=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('[defaultChecked=false]').exists()).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true); expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
}); });
...@@ -82,18 +73,14 @@ describe('<CheckBox />', () => { ...@@ -82,18 +73,14 @@ describe('<CheckBox />', () => {
const wrapper = mount( const wrapper = mount(
<CheckBox <CheckBox
name="checkbox" name="checkbox"
describedBy="checkbox"
label="I am disabled" label="I am disabled"
checked="false"
disabled disabled
/>, />,
); );
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true); expect(wrapper.props().disabled).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).toEqual(true);
wrapper.find('input').simulate('click'); wrapper.find('input').simulate('change');
expect(wrapper.find('[defaultChecked=false]').exists()).toEqual(true); expect(wrapper.props().disabled).toEqual(true);
expect(wrapper.find('[aria-checked=false]').exists()).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
* `onChange`: 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 React from 'react';
import PropTypes from 'prop-types';
import { inputProps } from '../asInput'; import asInput from '../asInput';
import newId from '../utils/newId';
class CheckBox extends React.Component { class Check extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleClick = this.handleClick.bind(this); this.onChange = this.onChange.bind(this);
if (this.props.onChange) {
this.onChange = this.props.onChange.bind(this);
}
const id = newId('checkbox'); this.state = {
if (props.checked === 'true') { checked: props.checked || false,
this.state = { };
id,
checked: true,
};
} else {
this.state = {
id,
checked: false,
};
}
} }
handleClick() { onChange(event) {
if (this.state.checked === true) { this.setState({
this.setState({ checked: !this.state.checked,
checked: false, });
}); this.props.onChange(event);
} else {
this.setState({
checked: true,
});
}
if (this.onChange) {
this.onChange();
}
} }
...@@ -46,24 +26,29 @@ class CheckBox extends React.Component { ...@@ -46,24 +26,29 @@ class CheckBox extends React.Component {
const props = { ...this.props }; const props = { ...this.props };
return ( return (
<label htmlFor={this.state.id}> <input
<input id={props.id}
id={this.state.id} type="checkbox"
name={props.name} name={props.name}
type="checkbox" defaultChecked={this.state.checked}
defaultChecked={this.state.checked} aria-checked={this.state.checked}
aria-describedby={props.describedBy} onChange={this.onChange}
aria-checked={this.state.checked} disabled={props.disabled}
tabIndex="0" />
onClick={this.handleClick}
disabled={props.disabled}
/>
{props.label}
</label>
); );
} }
} }
CheckBox.propTypes = inputProps; Check.propTypes = {
checked: PropTypes.bool,
onChange: PropTypes.func,
};
Check.defaultProps = {
checked: false,
onChange: () => {},
};
const CheckBox = asInput(Check, false);
export default CheckBox; export default CheckBox;
...@@ -120,7 +120,7 @@ class Dropdown extends React.Component { ...@@ -120,7 +120,7 @@ class Dropdown extends React.Component {
<div <div
className={classNames([ className={classNames([
styles.dropdown, styles.dropdown,
{ [styles.show]: this.state.open }, { [styles.show]: this.state.open },
])} ])}
ref={(container) => { this.container = container; }} ref={(container) => { this.container = container; }}
> >
......
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { withKnobs, text } from '@storybook/addon-knobs';
import InputText from './index'; import InputText from './index';
storiesOf('InputText', module) storiesOf('InputText', module)
.addDecorator(withKnobs)
.add('minimal usage', () => ( .add('minimal usage', () => (
<InputText <InputText
name="name" name="name"
label="First Name" label={text('label', 'First Name')}
value="Foo Bar" value="Foo Bar"
/> />
)) ))
...@@ -16,7 +18,7 @@ storiesOf('InputText', module) ...@@ -16,7 +18,7 @@ storiesOf('InputText', module)
<InputText <InputText
name="username" name="username"
label="Username" label="Username"
description="The unique name that identifies you throughout the site." description={text('description', 'The unique name that identifies you throughout the site.')}
validator={(value) => { validator={(value) => {
let feedback = { isValid: true }; let feedback = { isValid: true };
if (value.length < 3) { if (value.length < 3) {
......
...@@ -24,7 +24,7 @@ export const inputProps = { ...@@ -24,7 +24,7 @@ export const inputProps = {
className: PropTypes.arrayOf(PropTypes.string), className: PropTypes.arrayOf(PropTypes.string),
}; };
const asInput = (WrappedComponent) => { const asInput = (WrappedComponent, labelFirst = true) => {
class NewComponent extends React.Component { class NewComponent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -85,10 +85,9 @@ const asInput = (WrappedComponent) => { ...@@ -85,10 +85,9 @@ const asInput = (WrappedComponent) => {
render() { render() {
const { description, error, describedBy } = this.getDescriptions(); const { description, error, describedBy } = this.getDescriptions();
return ( return (
<div className={styles['form-group']}> <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 <WrappedComponent
{...this.props} {...this.props}
{...this.state} {...this.state}
...@@ -100,6 +99,7 @@ const asInput = (WrappedComponent) => { ...@@ -100,6 +99,7 @@ const asInput = (WrappedComponent) => {
onChange={this.handleChange} onChange={this.handleChange}
onBlur={this.handleBlur} onBlur={this.handleBlur}
/> />
{!labelFirst && <label htmlFor={this.state.id}>{this.props.label}</label>}
{error} {error}
{description} {description}
</div> </div>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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