-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[added] RadioButton and RadioGroup #962
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
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" inline legend="Greetings" srOnly> | ||
<FormControls.RadioButton value="Hi!">Hi!</FormControls.RadioButton> | ||
<FormControls.RadioButton value="Hello!">Hello!</FormControls.RadioButton> | ||
</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> | ||
<FormControls.RadioButton value="5" /> | ||
</form> | ||
); | ||
} | ||
}); | ||
|
||
React.render(<SimpleRadioGroup />, mountNode); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import React from 'react'; | ||
import classNames from 'classnames'; | ||
import { all, singlePropFrom } from '../utils/CustomPropTypes'; | ||
|
||
const singleNodeFromChildAndLabel = all([ | ||
singlePropFrom(['children', 'label']), | ||
React.PropTypes.node | ||
]); | ||
|
||
const propTypes = { | ||
/** | ||
* Wraps the radio button in a label along with the provided content. Do not use with the label prop | ||
*/ | ||
children: singleNodeFromChildAndLabel, | ||
/** | ||
* Disables the radio button | ||
*/ | ||
disabled: React.PropTypes.bool, | ||
/** | ||
* Applies the .radio-inline class to the wrapping label | ||
*/ | ||
inline: React.PropTypes.bool, | ||
/** | ||
* Wraps the radio button in a label along with the provided content. Do not use with children | ||
*/ | ||
label: singleNodeFromChildAndLabel, | ||
/** | ||
* Apply a css class directly to the wrapping label tag (if label is provided) | ||
*/ | ||
labelClassName: React.PropTypes.string, | ||
/** | ||
* 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 | ||
}; | ||
|
||
export default class RadioButton extends React.Component { | ||
getInputDOMNode() { | ||
return React.findDOMNode(this.refs.input); | ||
} | ||
|
||
getValue() { | ||
return this.props.value; | ||
} | ||
|
||
getChecked() { | ||
return this.getInputDOMNode().checked; | ||
} | ||
|
||
renderWrapper(label, inline) { | ||
return inline ? label : <div className="radio">{label}</div>; | ||
} | ||
|
||
renderLabel(input) { | ||
if (!this.props.label && !this.props.children) { | ||
return input; | ||
} | ||
|
||
const {children, id, label, inline, ...other} = this.props; | ||
const innerContent = children ? children : label; | ||
|
||
const classes = { | ||
'radio-inline': inline | ||
}; | ||
|
||
const wrapped = ( | ||
<label className={classNames(classes, this.props.labelClassName)} htmlFor={id}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the past I have had trouble adding the class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where my headache's been so far. For regular |
||
{input} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the label should probably also add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! I initially didn't implement it since the wrapping automatically associated the radio button with the label and gave me the green light through the WAVE tool. Having the id would certainly be a good enhancement, though I don't think I'll be making it a required prop. |
||
{innerContent} | ||
</label> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
const {id, label} = this.props; are not used anywhere else <label className={classNames(classes)} htmlFor={this.props.id}>
{input}
{this.props.label} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks cleaner to me this way, and babel will transform it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the clarification 🍒 |
||
); | ||
|
||
return this.renderWrapper(wrapped, inline); | ||
} | ||
|
||
render() { | ||
const {children, inline, label, ...other} = this.props; | ||
return this.renderLabel( | ||
<input type="radio" {...other} ref="input" /> | ||
); | ||
} | ||
} | ||
|
||
RadioButton.propTypes = propTypes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import React from 'react'; | ||
import classNames from 'classnames'; | ||
import ValidComponentChildren from '../utils/ValidComponentChildren'; | ||
|
||
function noop() { } | ||
|
||
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 ValidComponentChildren.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() { | ||
return ( | ||
<fieldset ref="radioGroup" className={classNames('form-group', this.props.className)}> | ||
{this.renderLegend()} | ||
{this.renderChildren()} | ||
</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: noop | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
export Static from './Static'; | ||
import RadioButton from './RadioButton'; | ||
import RadioGroup from './RadioGroup'; | ||
import Static from './Static'; | ||
|
||
export default { | ||
Static, | ||
RadioGroup, | ||
RadioButton | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ class Input extends InputBase { | |
if (this.props.type === 'static') { | ||
deprecationWarning('Input type=static', 'StaticText'); | ||
return <FormControls.Static {...this.props} />; | ||
} else if (this.props.type === 'radio') { | ||
deprecationWarning('Input type=radio', 'FormControls.RadioButton and FormControls.RadioGroup'); | ||
return <FormControls.RadioButton {...this.props} />; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs are stull using this form so two things:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
return super.render(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,9 @@ | ||
import React from 'react'; | ||
import { singlePropFrom } from './CustomPropTypes'; | ||
import { all, singlePropFrom } from './CustomPropTypes'; | ||
|
||
const propList = ['children', 'value']; | ||
|
||
export default function valueValidation(props, propName, componentName) { | ||
let error = singlePropFrom(propList)(props, propName, componentName); | ||
|
||
if (!error) { | ||
error = React.PropTypes.node(props, propName, componentName); | ||
} | ||
|
||
return error; | ||
} | ||
export default all([ | ||
singlePropFrom(propList), | ||
React.PropTypes.node | ||
]); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
"globals": { | ||
"assert": true, | ||
"expect": true, | ||
"should": true, | ||
"sinon": true | ||
}, | ||
"plugins": [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
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" /> | ||
); | ||
|
||
ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'label'); | ||
}); | ||
|
||
it('renders a div around the label if not inline', () => { | ||
const instance = ReactTestUtils.renderIntoDocument( | ||
<RadioButton label="thing" value="test" /> | ||
); | ||
|
||
React.findDOMNode(instance).tagName.toLowerCase().should.equal('div'); | ||
}); | ||
|
||
it('should not wrap the label with a div if inline', () => { | ||
const instance = ReactTestUtils.renderIntoDocument( | ||
<RadioButton inline label="thing" value="test" /> | ||
); | ||
|
||
React.findDOMNode(instance).tagName.toLowerCase().should.equal('label'); | ||
}); | ||
|
||
it('should render the label prop', function () { | ||
const testText = 'thing'; | ||
const instance = ReactTestUtils.renderIntoDocument( | ||
<RadioButton label={testText} value="test" /> | ||
); | ||
|
||
React.findDOMNode(instance).textContent.should.equal(testText); | ||
}); | ||
|
||
it('should render the children', () => { | ||
const testText = 'thing'; | ||
const instance = ReactTestUtils.renderIntoDocument( | ||
<RadioButton value="test"> | ||
{testText} | ||
</RadioButton> | ||
); | ||
|
||
React.findDOMNode(instance).textContent.should.equal(testText); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think
return {}
shoud be betterThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
({message: undefined}).message=== ({}).message
returns true