diff --git a/packages/mui-material/src/ToggleButton/ToggleButton.js b/packages/mui-material/src/ToggleButton/ToggleButton.js index f99216a2577de3..26866a337c631f 100644 --- a/packages/mui-material/src/ToggleButton/ToggleButton.js +++ b/packages/mui-material/src/ToggleButton/ToggleButton.js @@ -3,6 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { internal_resolveProps as resolveProps } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses'; import { alpha } from '../styles'; import ButtonBase from '../ButtonBase'; @@ -10,6 +11,8 @@ import capitalize from '../utils/capitalize'; import useThemeProps from '../styles/useThemeProps'; import styled from '../styles/styled'; import toggleButtonClasses, { getToggleButtonUtilityClass } from './toggleButtonClasses'; +import ToggleButtonGroupContext from '../ToggleButtonGroup/ToggleButtonGroupContext'; +import isValueSelected from '../ToggleButtonGroup/isValueSelected'; const useUtilityClasses = (ownerState) => { const { classes, fullWidth, selected, disabled, size, color } = ownerState; @@ -108,7 +111,13 @@ const ToggleButtonRoot = styled(ButtonBase, { }); const ToggleButton = React.forwardRef(function ToggleButton(inProps, ref) { - const props = useThemeProps({ props: inProps, name: 'MuiToggleButton' }); + // props priority: `inProps` > `contextProps` > `themeDefaultProps` + const { value: contextValue, ...contextProps } = React.useContext(ToggleButtonGroupContext); + const resolvedProps = resolveProps( + { ...contextProps, selected: isValueSelected(inProps.value, contextValue) }, + inProps, + ); + const props = useThemeProps({ props: resolvedProps, name: 'MuiToggleButton' }); const { children, className, @@ -150,7 +159,7 @@ const ToggleButton = React.forwardRef(function ToggleButton(inProps, ref) { return ( { const { classes, orientation, fullWidth, disabled } = ownerState; @@ -106,31 +105,60 @@ const ToggleButtonGroup = React.forwardRef(function ToggleButtonGroup(inProps, r const ownerState = { ...props, disabled, fullWidth, orientation, size }; const classes = useUtilityClasses(ownerState); - const handleChange = (event, buttonValue) => { - if (!onChange) { - return; - } + const handleChange = React.useCallback( + (event, buttonValue) => { + if (!onChange) { + return; + } - const index = value && value.indexOf(buttonValue); - let newValue; + const index = value && value.indexOf(buttonValue); + let newValue; - if (value && index >= 0) { - newValue = value.slice(); - newValue.splice(index, 1); - } else { - newValue = value ? value.concat(buttonValue) : [buttonValue]; - } + if (value && index >= 0) { + newValue = value.slice(); + newValue.splice(index, 1); + } else { + newValue = value ? value.concat(buttonValue) : [buttonValue]; + } - onChange(event, newValue); - }; + onChange(event, newValue); + }, + [onChange, value], + ); - const handleExclusiveChange = (event, buttonValue) => { - if (!onChange) { - return; - } + const handleExclusiveChange = React.useCallback( + (event, buttonValue) => { + if (!onChange) { + return; + } - onChange(event, value === buttonValue ? null : buttonValue); - }; + onChange(event, value === buttonValue ? null : buttonValue); + }, + [onChange, value], + ); + + const context = React.useMemo( + () => ({ + className: classes.grouped, + onChange: exclusive ? handleExclusiveChange : handleChange, + value, + size, + fullWidth, + color, + disabled, + }), + [ + classes.grouped, + exclusive, + handleExclusiveChange, + handleChange, + value, + size, + fullWidth, + color, + disabled, + ], + ); return ( - {React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return null; - } - - if (process.env.NODE_ENV !== 'production') { - if (isFragment(child)) { - console.error( - [ - "MUI: The ToggleButtonGroup component doesn't accept a Fragment as a child.", - 'Consider providing an array instead.', - ].join('\n'), - ); - } - } - - return React.cloneElement(child, { - className: clsx(classes.grouped, child.props.className), - onChange: exclusive ? handleExclusiveChange : handleChange, - selected: - child.props.selected === undefined - ? isValueSelected(child.props.value, value) - : child.props.selected, - size: child.props.size || size, - fullWidth, - color: child.props.color || color, - disabled: child.props.disabled || disabled, - }); - })} + + {children} + ); }); diff --git a/packages/mui-material/src/ToggleButtonGroup/ToggleButtonGroupContext.ts b/packages/mui-material/src/ToggleButtonGroup/ToggleButtonGroupContext.ts new file mode 100644 index 00000000000000..9149f7ed08dc3d --- /dev/null +++ b/packages/mui-material/src/ToggleButtonGroup/ToggleButtonGroupContext.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import type { ToggleButtonGroupProps } from './ToggleButtonGroup'; + +interface IToggleButtonGroupContext { + className?: string; + onChange?: ToggleButtonGroupProps['onChange']; + value?: any; + size?: ToggleButtonGroupProps['size']; + fullWidth?: boolean; + color?: ToggleButtonGroupProps['color']; + disabled?: boolean; +} + +/** + * @ignore - internal component. + */ +const ToggleButtonGroupContext = React.createContext({}); + +if (process.env.NODE_ENV !== 'production') { + ToggleButtonGroupContext.displayName = 'ToggleButtonGroupContext'; +} + +export default ToggleButtonGroupContext;