Skip to content

Commit

Permalink
[ToggleButtonGroup] Support different elements under ToggleButtonGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
Methuselah96 committed Dec 17, 2023
1 parent e7b9793 commit 3159eb9
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 53 deletions.
13 changes: 11 additions & 2 deletions packages/mui-material/src/ToggleButton/ToggleButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
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';
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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -150,7 +159,7 @@ const ToggleButton = React.forwardRef(function ToggleButton(inProps, ref) {

return (
<ToggleButtonRoot
className={clsx(classes.root, className)}
className={clsx(contextProps.className, classes.root, className)}
disabled={disabled}
focusRipple={!disableFocusRipple}
ref={ref}
Expand Down
104 changes: 53 additions & 51 deletions packages/mui-material/src/ToggleButtonGroup/ToggleButtonGroup.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
'use client';
import * as React from 'react';
import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import capitalize from '../utils/capitalize';
import isValueSelected from './isValueSelected';
import toggleButtonGroupClasses, {
getToggleButtonGroupUtilityClass,
} from './toggleButtonGroupClasses';
import ToggleButtonGroupContext from './ToggleButtonGroupContext';

const useUtilityClasses = (ownerState) => {
const { classes, orientation, fullWidth, disabled } = ownerState;
Expand Down Expand Up @@ -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 (
<ToggleButtonGroupRoot
Expand All @@ -140,35 +168,9 @@ const ToggleButtonGroup = React.forwardRef(function ToggleButtonGroup(inProps, r
ownerState={ownerState}
{...other}
>
{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,
});
})}
<ToggleButtonGroupContext.Provider value={context}>
{children}
</ToggleButtonGroupContext.Provider>
</ToggleButtonGroupRoot>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IToggleButtonGroupContext>({});

if (process.env.NODE_ENV !== 'production') {
ToggleButtonGroupContext.displayName = 'ToggleButtonGroupContext';
}

export default ToggleButtonGroupContext;

0 comments on commit 3159eb9

Please sign in to comment.