Skip to content

Commit

Permalink
[changed] Deprecating Input type=radio in favor of new RadioButton an…
Browse files Browse the repository at this point in the history
…d RadioGroup components
  • Loading branch information
aabenoja committed Jul 9, 2015
1 parent 0b95ce1 commit da5fbef
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 40 deletions.
38 changes: 38 additions & 0 deletions docs/examples/RadioButtonGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const SimpleRadioGroup = React.createClass({
getInitialState() {
return {message: undefined};
},

handleChange(newValue) {
this.setState({message: newValue});
},

render() {
const message = this.state.message ?
<FormControls.Static>{this.state.message}</FormControls.Static> :
null;

const handleChange = this.handleChange;

return (
<form>
{message}
<FormControls.RadioGroup name="greeting">
<FormControls.RadioButton label="Hi!" value="Hi!" />
<FormControls.RadioButton label="Hello!" value="Hello!" />
</FormControls.RadioGroup>
<FormControls.RadioGroup name="farewell" onChange={handleChange}>
<FormControls.RadioButton label="Bye!" value="Bye!" />
<FormControls.RadioButton label="Goodbye!" value="Goodbye!" />
</FormControls.RadioGroup>
<FormControls.RadioGroup name="thing" initialValue="2">
<FormControls.RadioButton label="1" value="1" disabled />
<FormControls.RadioButton label="2" value="2" disabled />
<FormControls.RadioButton label="3" value="3" disabled />
</FormControls.RadioGroup>
</form>
);
}
});

React.render(<SimpleRadioGroup />, mountNode);
4 changes: 3 additions & 1 deletion docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,9 @@ const ComponentsPage = React.createClass({
<h3><Anchor id='button-input-types'>Button Input Types</Anchor></h3>
<p>Form buttons are encapsulated by <code>ButtonInput</code>. Pass in <code>type="reset"</code> or <code>type="submit"</code> to suit your needs. Styling is the same as <code>Button</code>.</p>
<ReactPlayground codeText={Samples.ButtonInput} />

<h3><Anchor id='radio-group'>Radio Group</Anchor></h3>
<p>Radio buttons can be created with <code>FormControls.RadioButton</code>. <code>FormControls.RadioGroup</code> will wrap your group of radio buttons in a fieldset.</p>
<ReactPlayground codeText={Samples.RadioButtonGroup} />
<h3><Anchor id='input-addons'>Add-ons</Anchor></h3>
<p>Use <code>addonBefore</code> and <code>addonAfter</code> for normal addons, <code>buttonBefore</code> and <code>buttonAfter</code> for button addons.
Exotic configurations may require some css on your side.</p>
Expand Down
1 change: 1 addition & 0 deletions docs/src/Samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default {
InputTypes: require('fs').readFileSync(__dirname + '/../examples/InputTypes.js', 'utf8'),
StaticText: require('fs').readFileSync(__dirname + '/../examples/StaticText.js', 'utf8'),
ButtonInput: require('fs').readFileSync(__dirname + '/../examples/ButtonInput.js', 'utf8'),
RadioButtonGroup: require('fs').readFileSync(__dirname + '/../examples/RadioButtonGroup.js', 'utf8'),
InputAddons: require('fs').readFileSync(__dirname + '/../examples/InputAddons.js', 'utf8'),
InputSizes: require('fs').readFileSync(__dirname + '/../examples/InputSizes.js', 'utf8'),
InputValidation: require('fs').readFileSync(__dirname + '/../examples/InputValidation.js', 'utf8'),
Expand Down
57 changes: 57 additions & 0 deletions src/FormControls/RadioButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import classNames from 'classnames';

export default class RadioButton extends React.Component {
constructor(props) {
super(props);
}

getInputDOMNode() {
return React.findDOMNode(this.refs.input);
}

getValue() {
return this.props.value;
}

getChecked() {
return this.getInputDOMNode().checked;
}

renderLabel(input) {
if (!this.props.label) {
return input;
}

const {id, label} = this.props;

const classes = {
'radio': !this.props.inline,
'radio-inline': this.props.inline
};

return (
<label className={classNames(classes)} htmlFor={id}>
{input}
{label}
</label>
);
}

render() {
const {inline, label, ...other} = this.props;
return this.renderLabel(
<input type="radio" {...other} ref="input" />
);
}
}

RadioButton.propTypes = {
disabled: React.PropTypes.bool,
id: React.PropTypes.string,
inline: React.PropTypes.bool,
label: React.PropTypes.node,
name: React.PropTypes.string,
onChange: React.PropTypes.func,
value: React.PropTypes.any
};
60 changes: 60 additions & 0 deletions src/FormControls/RadioGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';

export default class RadioGroup extends React.Component {
constructor(props) {
super(props);
this.state = {value: this.props.initialValue};
this.handleChange = this.handleChange.bind(this);
}

getValue() {
return this.state.value;
}

renderLegend() {
return this.props.legend ?
<legend>{this.props.legend}</legend> :
null;
}

handleChange(evt) {
if (!evt.target) {
return;
}

const {value} = evt.target;
this.setState({value});
this.props.onChange(value);
}

renderChildren() {
const {name, inline} = this.props;
return React.Children.map(this.props.children, child => {
const {value} = child.props;
const checked = this.state.value ? this.state.value === value : false;
return React.cloneElement(child, {name, inline, checked, onChange: this.handleChange});
});
}

render() {
const children = this.renderChildren();
return (
<fieldset ref="radioGroup" className="radio">
{this.renderLegend()}
{children}
</fieldset>
);
}
}

RadioGroup.propTypes = {
initialValue: React.PropTypes.any,
inline: React.PropTypes.bool,
legend: React.PropTypes.node,
name: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func
};

RadioGroup.defaultProps = {
onChange: () => {}
};
6 changes: 5 additions & 1 deletion src/FormControls/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import RadioButton from './RadioButton';
import RadioGroup from './RadioGroup';
import Static from './Static';

export default {
Static
Static,
RadioGroup,
RadioButton
};
5 changes: 4 additions & 1 deletion src/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ class Input extends InputBase {
deprecationWarning(`Input type=${this.props.type}`, 'ButtonInput');
return <ButtonInput {...this.props} />;
} else if (this.props.type === 'static') {
deprecationWarning('Input type=static', 'StaticText');
deprecationWarning('Input type=static', 'Static');
return <FormControls.Static {...this.props} />;
} else if (this.props.types === 'radio') {
deprecationWarning('Input type=radio', 'FormControls.RadioButton and FormControls.RadioGroup');
return <FormControls.RadioButton {...this.props} />;
}

return super.render();
Expand Down
1 change: 1 addition & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"globals": {
"assert": true,
"expect": true,
"should": true,
"sinon": true
},
"plugins": [
Expand Down
49 changes: 49 additions & 0 deletions test/FormControls/RadioButtonSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import RadioButton from '../../src/FormControls/RadioButton';

describe('FormControls.RadioButton', function () {
it('does not apply htmlFor to the label if no id is provided', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioButton label="test" value="test" />
);

const label = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'label');
should.not.exist(label.props.htmlFor);
});

it('does not apply htmlFor to the label if no id is provided', function () {
const testId = 'thing';
const instance = ReactTestUtils.renderIntoDocument(
<RadioButton id={testId} label="test" value="test" />
);

const label = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'label');
label.props.htmlFor.should.eql(testId);
});

it('does not render a label if no label prop is provided', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioButton value="test" />
);

React.findDOMNode(instance).tagName.toLowerCase().should.eql('input');
});

it('renders a label if label prop is provided', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioButton label="thing" value="test" />
);

React.findDOMNode(instance).tagName.toLowerCase().should.eql('label');
});

it('should render the label content', function () {
const testText = 'thing';
const instance = ReactTestUtils.renderIntoDocument(
<RadioButton label={testText} value="test" />
);

React.findDOMNode(instance).textContent.should.eql(testText);
});
});
87 changes: 87 additions & 0 deletions test/FormControls/RadioGroupSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import RadioButton from '../../src/FormControls/RadioButton';
import RadioGroup from '../../src/FormControls/RadioGroup';

describe('RadioGroup', function () {
it('applies an on change to children', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name="test">
<RadioButton label="thing" value="1" />
<RadioButton label="pants" value="1" />
<RadioButton label="jeans" value="1" />
</RadioGroup>
);

const buttons = ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, 'INPUT');
buttons.every(button => button.props.onChange === instance.handleChange).should.be.true;
});

it('invokes a callback when a selection is changed', function() {
const callback = sinon.spy();

const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name="test" onChange={callback}>
<RadioButton label="thing" value="thing" id="thing" className="thing" />
<RadioButton label="jeans" value="jeans" id="jeans" />
</RadioGroup>
);

const thing = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'thing');
ReactTestUtils.Simulate.change(thing, {target: {value: 5}});
callback.should.have.been.calledWith(5);
});

it('applies the name property to all children', function () {
const testName = 'test';
const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name={testName}>
<RadioButton label="thing" value="1" />
<RadioButton label="pants" value="1" />
<RadioButton label="jeans" value="1" />
</RadioGroup>
);

const buttons = ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, 'INPUT');
buttons.every(button => button.props.name === testName).should.be.true;
});

it('does not inline if the inline property is false', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name="test" inline={false}>
<RadioButton label="thing" value="1" />
<RadioButton label="pants" value="1" />
<RadioButton label="jeans" value="1" />
</RadioGroup>
);

const labels = ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, 'LABEL');
labels.every(label => label.props.className === 'radio').should.be.true;
});

it('applies the radio-inline class when the inline prop is used', function () {
const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name="test" inline>
<RadioButton label="thing" value="1" />
<RadioButton label="pants" value="1" />
<RadioButton label="jeans" value="1" />
</RadioGroup>
);

const labels = ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, 'LABEL');
labels.every(label => label.props.className === 'radio-inline').should.be.true;
});

it('applies the initialState', function() {
const instance = ReactTestUtils.renderIntoDocument(
<RadioGroup name="test" initialValue="2" >
<RadioButton label="thing" value="1" />
<RadioButton className="theAnswer" label="pants" value="2" />
<RadioButton label="jeans" value="3" />
</RadioGroup>
);

const button = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'theAnswer');
button.props.checked.should.be.true;
});
});
35 changes: 35 additions & 0 deletions test/FormControls/StaticSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import Static from '../../src/FormControls/Static';

describe('FormControls.Static', function () {
it('renders a p element wrapped around the given value', function () {
const instance = ReactTestUtils.renderIntoDocument(
<Static value='v' />
);

const result = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'p');
result.props.children.should.equal('v');
});

it('getValue() pulls from either value or children', function () {
let instance = ReactTestUtils.renderIntoDocument(
<Static value='v' />
);

instance.getValue().should.equal('v');

instance = ReactTestUtils.renderIntoDocument(
<Static>5</Static>
);

instance.getValue().should.equal('5');
});

it('throws an error if both value and children are provided', function () {
const testData = { value: 'blah', children: 'meh' };
const result = Static.propTypes.children(testData, 'children', 'Static');

result.should.be.instanceOf(Error);
});
});
Loading

0 comments on commit da5fbef

Please sign in to comment.