Skip to content

Commit

Permalink
New: Radio and RadioGroup components
Browse files Browse the repository at this point in the history
  • Loading branch information
lightbringer1991 committed May 29, 2018
1 parent 6a477f9 commit ef78c7a
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 26 deletions.
2 changes: 2 additions & 0 deletions docs/components/Layout/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import AlertExample from '../../examples/AlertExample';
import CheckboxExample from '../../examples/CheckboxExample';
import CheckboxGroupExample from '../../examples/CheckboxGroupExample';
import RadioExample from '../../examples/RadioExample';
import RadioGroupExample from '../../examples/RadioGroupExample';
import SelectExample from '../../examples/SelectExample';
import DatePickerExample from '../../examples/DatePickerExample';
import BorderedWellExample from '../../examples/BorderedWellExample';
Expand Down Expand Up @@ -178,6 +179,7 @@ class PageLayout extends React.Component {
<CheckboxExample />
<CheckboxGroupExample />
<RadioExample />
<RadioGroupExample />
<SelectExample />
<DatePickerExample />

Expand Down
74 changes: 50 additions & 24 deletions docs/examples/RadioExample.jsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,78 @@
import React from 'react';
import Example from '../components/Example';
import { Radio, RadioGroup } from '../../src';
import Radio from 'adslot-ui/Radio';

class RadioExample extends React.PureComponent {
onChange(event) {
_.noop();
}

render() {
return (
<RadioGroup name="yesNo" className="radiogroup-stacked">
<Radio label="Yes" value="true" />
<Radio label="No" value="false" />
</RadioGroup>
<Radio
name="Radio button name"
label="Radio button label"
dts="radio-button-data-test-selector"
value="Radio button value"
onChange={this.onChange}
/>
);
}
}

const exampleProps = {
componentName: 'Radio',
designNotes: (
<p>
<span className="text-bold">Radio buttons</span> used for making a single selection from multiple options. Only
one selection can ever be made from the radio button group at a time.
</p>
),
notes: (
<p>
See <a href="https://github.com/luqin/react-icheck">React iCheck Documentation</a>
</p>
),
exampleCodeSnippet: `
<RadioGroup name="yesNo" className="radiogroup-stacked">
<Radio label="Yes" value="true" />
<Radio label="No" value="false" />
</RadioGroup>`,
notes: '',
exampleCodeSnippet: `<Radio
name="Radio button name"
label="Radio button label"
dts="radio-button-data-test-selector"
value="Radio button value"
onChange={this.onChange}
/>`,
propTypeSectionArray: [
{
propTypes: [
{
propType: 'id',
type: 'string',
},
{
propType: 'className',
type: 'string',
note: 'This class will be applied to the input element',
},
{
propType: 'name',
type: 'string',
},
{
propType: 'label',
type: 'node',
note: 'Usually fine to rely on a string but can pass HTML e.g. for a url.',
type: 'string',
},
{
propType: 'value',
propType: 'dts',
type: 'string',
note: 'render `data-test-selector` onto the component. It can be useful for testing.',
},
{
propType: 'disabled',
type: 'bool',
defaultValue: <code>false</code>,
},
{
propType: 'checked',
type: 'bool',
defaultValue: <code>false</code>,
},
{
propType: 'onChange',
type: 'func',
},
{
propType: 'value',
type: 'string',
},
],
},
],
Expand Down
89 changes: 89 additions & 0 deletions docs/examples/RadioGroupExample.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import Example from '../components/Example';
import RadioGroup from 'adslot-ui/RadioGroup';
import Radio from 'adslot-ui/Radio';

class RadioGroupExample extends React.PureComponent {
onChangeGroup(event) {
_.noop();
}

onChangeIndividual(event) {
_.noop();
}

render() {
return (
<RadioGroup name="hobbies" value="badminton" onChange={this.onChangeGroup} dts="radio-group-dts">
<Radio value="swimming" label="Swimming" dts="radio-dts" />
<Radio value="soccer" label="Soccer" onChange={this.onChangeIndividual} />
<Radio value="badminton" label="Badminton" />
</RadioGroup>
);
}
}

const exampleProps = {
componentName: 'RadioGroup',
designNotes: (
<p>
<span className="text-bold">Radio buttons</span> used for making a single selection from multiple options. Only
one selection can ever be made from the radio button group at a time.
</p>
),
notes: '',
exampleCodeSnippet: `<RadioGroup name="hobbies" value="badminton" onChange={this.onChangeGroup} dts="radio-group-dts">
<Radio value="swimming" label="Swimming" dts="radio-dts" />
<Radio value="soccer" label="Soccer" onChange={this.onChangeIndividual} />
<Radio value="badminton" label="Badminton" />
</RadioGroup>`,
propTypeSectionArray: [
{
propTypes: [
{
propType: 'name',
type: 'string',
note: (
<span>
<strong>Required.</strong> All Radio buttons within this group will have the same name
</span>
),
},
{
propType: 'value',
type: 'string',
note: 'value of the selected radio button',
},
{
propType: 'onChange',
type: 'func',
note: 'Triggers when selection changes.',
},
{
propType: 'children',
type: 'node',
note: `Should be an array of <Radio /> components`,
},
{
propType: 'className',
type: 'string',
},
{
propType: 'dts',
type: 'string',
note: 'render `data-test-selector` onto the component. It can be useful for testing.',
},
{
propType: 'id',
type: 'string',
},
],
},
],
};

export default () => (
<Example {...exampleProps}>
<RadioGroupExample />
</Example>
);
68 changes: 68 additions & 0 deletions src/components/adslot-ui/Radio/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { expandDts } from 'lib/utils';

class RadioButton extends React.Component {
static getDerivedStateFromProps(newProps, prevState) {
return newProps.checked === prevState.checked ? null : { checked: newProps.checked };
}

constructor(props) {
super(props);
this.state = { checked: props.checked };

this.onChangeDefault = this.onChangeDefault.bind(this);
}

onChangeDefault(event) {
this.setState({ checked: Boolean(event.target.checked) });
if (this.props.onChange) {
this.props.onChange(event);
}
}

render() {
const { name, className, label, dts, disabled, id, value } = this.props;

const radioInputProps = {
type: 'radio',
name,
checked: this.state.checked,
disabled,
onChange: this.onChangeDefault,
value,
};

const optionalProps = _.pickBy({
id,
className,
});

return (
<div className="radio-component" {...expandDts(dts)}>
<input {...radioInputProps} {...optionalProps} />
{label ? <span>{label}</span> : null}
</div>
);
}
}

RadioButton.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
name: PropTypes.string,
label: PropTypes.node,
dts: PropTypes.string,
disabled: PropTypes.bool,
checked: PropTypes.bool,
onChange: PropTypes.func,
value: PropTypes.string,
};

RadioButton.defaultProps = {
disabled: false,
checked: false,
};

export default RadioButton;
81 changes: 81 additions & 0 deletions src/components/adslot-ui/Radio/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import _ from 'lodash';
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import Radio from '.';

describe('<Radio />', () => {
let props;

beforeEach(() => {
props = {
name: 'radio-name',
value: 'radio-value',
label: 'Radio 1',
dts: 'radio-dts',
id: 'radio-id',
className: 'radio-class',
disabled: false,
checked: false,
onChange: sinon.spy(),
};
});

it('should render with props', () => {
const component = shallow(<Radio {...props} />);
expect(component.find('input[type="radio"]')).to.have.length(1);
expect(component.text()).to.equal('Radio 1');
expect(component.find('[name="radio-name"]')).to.have.length(1);
expect(component.find('[value="radio-value"]')).to.have.length(1);
expect(component.find('[data-test-selector="radio-dts"]')).to.have.length(1);
});

it('should not render label if props.label is undefined', () => {
delete props.label;
const component = shallow(<Radio {...props} />);
expect(component.text()).to.equal('');
});

it('should trigger state change and `props.onChange` when change event is triggered', () => {
const component = shallow(<Radio {...props} />);
const event = { target: { checked: true } };

expect(component.state('checked')).to.equal(false);

component.find('input').simulate('change', event);
expect(component.state('checked')).to.equal(true);
expect(props.onChange.calledOnce).to.equal(true);
});

it('should still trigger state change when `props.onChange` is not present', () => {
delete props.onChange;
const component = shallow(<Radio {...props} />);
const event = { target: { checked: true } };

expect(component.state('checked')).to.equal(false);

component.find('input').simulate('change', event);
expect(component.state('checked')).to.equal(true);
});

it('should override state value when `prop.value` changes', () => {
const component = shallow(<Radio {...props} />);
expect(component.state('checked')).to.equal(false);

props.checked = true;
component.setProps(props);
expect(component.state('checked')).to.equal(true);
});

it('should NOT override state value when other props change', () => {
const component = shallow(<Radio {...props} />);
expect(component.state('checked')).to.equal(false);

_.assign(props, {
name: 'some-other-name',
label: 'New Label',
});
component.setProps(props);
expect(component.state('checked')).to.equal(false);
});
});
Loading

0 comments on commit ef78c7a

Please sign in to comment.