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 10, 2015
1 parent 0b95ce1 commit 1d21e60
Show file tree
Hide file tree
Showing 12 changed files with 410 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" legend="Greetings" srOnly>
<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" defaultValue="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);
8 changes: 7 additions & 1 deletion docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,13 @@ 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} />
<h4><Anchor id='radio-group-props'><code>FormControls.RadioGroup</code> Props</Anchor></h4>
<PropTable component='RadioGroup' />
<h4><Anchor id='radio-button-props'><code>FormControls.RadioButton</code> Props</Anchor></h4>
<PropTable component='RadioButton' />
<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
74 changes: 74 additions & 0 deletions src/FormControls/RadioButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 = {
/**
* Disables the radio button
*/
disabled: React.PropTypes.bool,
/**
* Applies the .radio-inline class to the wrapping label
*/
inline: React.PropTypes.bool,
/**
* Wraps with radio button in a label along with the provided content
*/
label: React.PropTypes.node,
/**
* Applies a name to associate the radio button with a group.
*/
name: React.PropTypes.string,
/**
* Calls this when the checked state changes
*/
onChange: React.PropTypes.func,
/**
* Applies a value to this radio button
*/
value: React.PropTypes.any
};
85 changes: 85 additions & 0 deletions src/FormControls/RadioGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import classNames from 'classnames';

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

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

renderLegend() {
const {legend, srOnly} = this.props;
const classes = {
'sr-only': srOnly
};

return legend ?
<legend className={classNames(classes)}>{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 = {
/**
* The default value of the radio group. The RadioButton in this group with the provided value will be checked by default.
*/
defaultValue: React.PropTypes.any,
/**
* Formats the radio group with the class .radio-inline
*/
inline: React.PropTypes.bool,
/**
* Populates a legend element at the top of the fieldset
*/
legend: React.PropTypes.node,
/**
* Applies the provided name prop to all the given children.
*/
name: React.PropTypes.string.isRequired,
/**
* This is called when the selected value changes, with the new value as a parameter
*/
onChange: React.PropTypes.func,
/**
* Applies the legend, if present, with the .sr-only class
*/
srOnly: React.PropTypes.bool
};

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);
});
});
Loading

0 comments on commit 1d21e60

Please sign in to comment.