diff --git a/packages/material-ui/src/Radio/Radio.js b/packages/material-ui/src/Radio/Radio.js index c78d02649442b8..096261d8b2037f 100644 --- a/packages/material-ui/src/Radio/Radio.js +++ b/packages/material-ui/src/Radio/Radio.js @@ -4,8 +4,9 @@ import clsx from 'clsx'; import SwitchBase from '../internal/SwitchBase'; import RadioButtonUncheckedIcon from '../internal/svg-icons/RadioButtonUnchecked'; import RadioButtonCheckedIcon from '../internal/svg-icons/RadioButtonChecked'; -import { capitalize } from '../utils/helpers'; +import { capitalize, createChainedFunction } from '../utils/helpers'; import withStyles from '../styles/withStyles'; +import RadioGroupContext from '../RadioGroup/RadioGroupContext'; export const styles = theme => ({ /* Styles applied to the root element. */ @@ -37,7 +38,28 @@ export const styles = theme => ({ }); const Radio = React.forwardRef(function Radio(props, ref) { - const { classes, color, ...other } = props; + const { + checked: checkedProp, + classes, + color, + name: nameProp, + onChange: onChangeProp, + ...other + } = props; + const radioGroup = React.useContext(RadioGroupContext); + + let checked = checkedProp; + const onChange = createChainedFunction(onChangeProp, radioGroup && radioGroup.onChange); + let name = nameProp; + + if (radioGroup) { + if (typeof checked === 'undefined') { + checked = radioGroup.value === props.value; + } + if (typeof name === 'undefined') { + name = radioGroup.name; + } + } return ( @@ -97,6 +122,10 @@ Radio.propTypes = { * Use that property to pass a ref callback to the native input component. */ inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + /** + * Name attribute of the `input` element. + */ + name: PropTypes.string, /** * Callback fired when the state is changed. * diff --git a/packages/material-ui/src/Radio/Radio.test.js b/packages/material-ui/src/Radio/Radio.test.js index 1aaca1e7d2ed27..3644212be11b5a 100644 --- a/packages/material-ui/src/Radio/Radio.test.js +++ b/packages/material-ui/src/Radio/Radio.test.js @@ -2,22 +2,14 @@ import React from 'react'; import { assert } from 'chai'; import RadioButtonCheckedIcon from '../internal/svg-icons/RadioButtonChecked'; import RadioButtonUncheckedIcon from '../internal/svg-icons/RadioButtonUnchecked'; -import { - getClasses, - createShallow, - createMount, - describeConformance, -} from '@material-ui/core/test-utils'; -import SwitchBase from '../internal/SwitchBase'; +import { getClasses, createMount, describeConformance } from '@material-ui/core/test-utils'; import Radio from './Radio'; describe('', () => { - let shallow; let classes; let mount; before(() => { - shallow = createShallow({ dive: true }); classes = getClasses(); mount = createMount(); }); @@ -42,11 +34,6 @@ describe('', () => { }); }); - it('should be using SwitchBase', () => { - const wrapper = shallow(); - assert.strictEqual(wrapper.type(), SwitchBase); - }); - describe('prop: unchecked', () => { it('should render an unchecked icon', () => { const wrapper = mount(); diff --git a/packages/material-ui/src/RadioGroup/RadioGroup.js b/packages/material-ui/src/RadioGroup/RadioGroup.js index 94c8be29c07abf..30228cf0385876 100644 --- a/packages/material-ui/src/RadioGroup/RadioGroup.js +++ b/packages/material-ui/src/RadioGroup/RadioGroup.js @@ -4,40 +4,31 @@ import React from 'react'; import PropTypes from 'prop-types'; import warning from 'warning'; import FormGroup from '../FormGroup'; -import { createChainedFunction, find } from '../utils/helpers'; +import { setRef } from '../utils/reactHelpers'; +import RadioGroupContext from './RadioGroupContext'; const RadioGroup = React.forwardRef(function RadioGroup(props, ref) { - const { actions, children, defaultValue, name, value: valueProp, onChange, ...other } = props; - const radiosRef = React.useRef([]); + const { actions, children, name, value: valueProp, onChange, ...other } = props; + const rootRef = React.useRef(); const { current: isControlled } = React.useRef(props.value != null); const [valueState, setValue] = React.useState(() => { if (!isControlled) { - return defaultValue; + return props.defaultValue; } return null; }); React.useImperativeHandle(actions, () => ({ focus: () => { - const radios = radiosRef.current; - if (!radios.length) { - return; - } - - const focusRadios = radios.filter(n => !n.disabled); + let input = rootRef.current.querySelector('input:not(:disabled):checked'); - if (!focusRadios.length) { - return; + if (!input) { + input = rootRef.current.querySelector('input:not(:disabled)'); } - const selectedRadio = find(focusRadios, n => n.checked); - - if (selectedRadio) { - selectedRadio.focus(); - return; + if (input) { + input.focus(); } - - focusRadios[0].focus(); }, })); @@ -67,34 +58,18 @@ const RadioGroup = React.forwardRef(function RadioGroup(props, ref) { onChange(event, event.target.value); } }; + const context = { name, onChange: handleChange, value }; - radiosRef.current = []; return ( - - {React.Children.map(children, child => { - if (!React.isValidElement(child)) { - return null; - } - - warning( - child.type !== React.Fragment, - [ - "Material-UI: the RadioGroup component doesn't accept a Fragment as a child.", - 'Consider providing an array instead.', - ].join('\n'), - ); - - return React.cloneElement(child, { - name, - inputRef: node => { - if (node) { - radiosRef.current.push(node); - } - }, - checked: value === child.props.value, - onChange: createChainedFunction(child.props.onChange, handleChange), - }); - })} + { + setRef(ref, nodeRef); + setRef(rootRef, nodeRef); + }} + {...other} + > + {children} ); }); diff --git a/packages/material-ui/src/RadioGroup/RadioGroup.test.js b/packages/material-ui/src/RadioGroup/RadioGroup.test.js index 36af95fa928ab4..e9725168426869 100644 --- a/packages/material-ui/src/RadioGroup/RadioGroup.test.js +++ b/packages/material-ui/src/RadioGroup/RadioGroup.test.js @@ -117,49 +117,35 @@ describe('', () => { it('should focus the selected radio', () => { const actionsRef = React.createRef(); - const zeroRadioOnFocus = spy(); - const oneRadioOnFocus = spy(); const twoRadioOnFocus = spy(); - const threeRadioOnFocus = spy(); mount( - - + + - + , ); actionsRef.current.focus(); - - assert.strictEqual(zeroRadioOnFocus.callCount, 0); - assert.strictEqual(oneRadioOnFocus.callCount, 0); assert.strictEqual(twoRadioOnFocus.callCount, 1); - assert.strictEqual(threeRadioOnFocus.callCount, 0); }); it('should focus the non-disabled radio rather than the disabled selected radio', () => { const actionsRef = React.createRef(); - const zeroRadioOnFocus = spy(); - const oneRadioOnFocus = spy(); - const twoRadioOnFocus = spy(); const threeRadioOnFocus = spy(); mount( - - - + + + , ); actionsRef.current.focus(); - - assert.strictEqual(zeroRadioOnFocus.callCount, 0); - assert.strictEqual(oneRadioOnFocus.callCount, 0); - assert.strictEqual(twoRadioOnFocus.callCount, 0); assert.strictEqual(threeRadioOnFocus.callCount, 1); }); diff --git a/packages/material-ui/src/RadioGroup/RadioGroupContext.js b/packages/material-ui/src/RadioGroup/RadioGroupContext.js new file mode 100644 index 00000000000000..22f5760984d9db --- /dev/null +++ b/packages/material-ui/src/RadioGroup/RadioGroupContext.js @@ -0,0 +1,8 @@ +import React from 'react'; + +/** + * @ignore - internal component. + */ +const RadioGroupContext = React.createContext(); + +export default RadioGroupContext; diff --git a/packages/material-ui/src/utils/helpers.js b/packages/material-ui/src/utils/helpers.js index 65ca07fed7d061..6b9ae1fe4bc34c 100644 --- a/packages/material-ui/src/utils/helpers.js +++ b/packages/material-ui/src/utils/helpers.js @@ -32,11 +32,6 @@ export function findIndex(arr, pred) { return -1; } -export function find(arr, pred) { - const index = findIndex(arr, pred); - return index > -1 ? arr[index] : undefined; -} - /** * Safe chained function * diff --git a/packages/material-ui/src/utils/helpers.test.js b/packages/material-ui/src/utils/helpers.test.js index fe1f9e2a8f3118..ff01647c9c6ecc 100644 --- a/packages/material-ui/src/utils/helpers.test.js +++ b/packages/material-ui/src/utils/helpers.test.js @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { capitalize, contains, find } from './helpers'; +import { capitalize, contains } from './helpers'; describe('utils/helpers.js', () => { describe('capitalize', () => { @@ -14,16 +14,6 @@ describe('utils/helpers.js', () => { }); }); - describe('find(arr, pred)', () => { - it('should search for an item in an array containing the predicate', () => { - const array = ['woofHelpers', 'meow', { foo: 'bar' }, { woofHelpers: 'meow' }]; - assert.strictEqual(find(array, 'lol'), undefined); - assert.strictEqual(find(array, 'woofHelpers'), array[0]); - assert.strictEqual(find(array, { foo: 'bar' }), array[2]); - assert.strictEqual(find(array, n => n && n.woofHelpers === 'meow'), array[3]); - }); - }); - describe('contains(obj, pred)', () => { it('should check if an object contains the partial object', () => { const obj = { woofHelpers: 'meow', cat: 'dog' }; diff --git a/pages/api/radio.md b/pages/api/radio.md index b314778cfb5fcc..0549f70e6306bf 100644 --- a/pages/api/radio.md +++ b/pages/api/radio.md @@ -28,6 +28,7 @@ import Radio from '@material-ui/core/Radio'; | id | string |   | The id of the `input` element. | | inputProps | object |   | Attributes applied to the `input` element. | | inputRef | union: func |
 object
|   | Use that property to pass a ref callback to the native input component. | +| name | string |   | Name attribute of the `input` element. | | onChange | func |   | Callback fired when the state is changed.

**Signature:**
`function(event: object, checked: boolean) => void`
*event:* The event source of the callback. You can pull out the new value by accessing `event.target.value`.
*checked:* The `checked` value of the switch | | type | string |   | The input component property `type`. | | value | union: string |
 number |
 bool
|   | The value of the component. |