Skip to content
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

[ButtonGroup] Determine first, last and middle buttons to support different elements with correct styling #38520

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
393f3a0
add regression test
ZeeshanTamboli Aug 17, 2023
9dcb327
[ButtonGroup] Determine first and last buttons
ZeeshanTamboli Aug 17, 2023
01103cc
add middle button class
ZeeshanTamboli Aug 17, 2023
b9f6c7b
Merge branch 'master' into determine-first-last-buttons-in-button-group
ZeeshanTamboli Aug 17, 2023
62af815
update docs
ZeeshanTamboli Aug 17, 2023
f4bb2b9
fix style description
ZeeshanTamboli Aug 17, 2023
a7f1084
use data- attributes instead of class names
ZeeshanTamboli Aug 18, 2023
34f37ff
handle case for only a single button and if not within a button group…
ZeeshanTamboli Aug 19, 2023
8a1c416
reorganize Button group styles
ZeeshanTamboli Aug 28, 2023
367ce5a
Merge branch 'master' into determine-first-last-buttons-in-button-group
ZeeshanTamboli Aug 28, 2023
5c7de19
rename interface
ZeeshanTamboli Aug 29, 2023
5e38727
add data attributes directly
ZeeshanTamboli Aug 29, 2023
26650cf
remove redundant onlyChild
ZeeshanTamboli Aug 29, 2023
d7665e4
rename one to single
ZeeshanTamboli Aug 29, 2023
dc7f138
rename variables
ZeeshanTamboli Aug 29, 2023
77f651d
use "is" prefix in first and last button variable names
ZeeshanTamboli Aug 31, 2023
1939e1b
use classnames instead of data attributes
ZeeshanTamboli Sep 6, 2023
30b5ba8
add unit tests
ZeeshanTamboli Sep 6, 2023
69b97f6
move button position classes logic to button group component
ZeeshanTamboli Sep 6, 2023
9f3dd29
rename method name and type
ZeeshanTamboli Sep 6, 2023
e76692f
prettier
ZeeshanTamboli Sep 6, 2023
c5f8b76
Merge branch 'master' into determine-first-last-buttons-in-button-group
ZeeshanTamboli Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion packages/mui-material/src/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ButtonBase from '../ButtonBase';
import capitalize from '../utils/capitalize';
import buttonClasses, { getButtonUtilityClass } from './buttonClasses';
import ButtonGroupContext from '../ButtonGroup/ButtonGroupContext';
import ButtonGroupButtonContext from '../ButtonGroup/ButtonGroupButtonContext';

const useUtilityClasses = (ownerState) => {
const { color, disableElevation, fullWidth, size, variant, classes } = ownerState;
Expand Down Expand Up @@ -295,9 +296,23 @@ const ButtonEndIcon = styled('span', {
...commonIconStyles(ownerState),
}));

const addPositionDataAttr = (buttonGroupButtonContext) => {
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
if (buttonGroupButtonContext.firstButton) {
return { 'data-first-child': '' };
}
if (buttonGroupButtonContext.lastButton) {
return { 'data-last-child': '' };
}
if (buttonGroupButtonContext.onlyChild) {
return {};
}
return { 'data-middle-child': '' };
};

const Button = React.forwardRef(function Button(inProps, ref) {
// props priority: `inProps` > `contextProps` > `themeDefaultProps`
const contextProps = React.useContext(ButtonGroupContext);
const buttonGroupButtonContext = React.useContext(ButtonGroupButtonContext);
const resolvedProps = resolveProps(contextProps, inProps);
const props = useThemeProps({ props: resolvedProps, name: 'MuiButton' });
const {
Expand Down Expand Up @@ -345,6 +360,12 @@ const Button = React.forwardRef(function Button(inProps, ref) {
</ButtonEndIcon>
);

let extendedOtherProp = other;
if (buttonGroupButtonContext) {
const dataAttrs = addPositionDataAttr(buttonGroupButtonContext);
extendedOtherProp = { ...dataAttrs, ...other };
}
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved

return (
<ButtonRoot
ownerState={ownerState}
Expand All @@ -355,7 +376,7 @@ const Button = React.forwardRef(function Button(inProps, ref) {
focusVisibleClassName={clsx(classes.focusVisible, focusVisibleClassName)}
ref={ref}
type={type}
{...other}
{...extendedOtherProp}
classes={classes}
>
{startIcon}
Expand Down
30 changes: 27 additions & 3 deletions packages/mui-material/src/ButtonGroup/ButtonGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import buttonGroupClasses, { getButtonGroupUtilityClass } from './buttonGroupClasses';
import ButtonGroupContext from './ButtonGroupContext';
import ButtonGroupButtonContext from './ButtonGroupButtonContext';

const overridesResolver = (props, styles) => {
const { ownerState } = props;
Expand Down Expand Up @@ -81,7 +82,7 @@ const ButtonGroupRoot = styled('div', {
}),
[`& .${buttonGroupClasses.grouped}`]: {
minWidth: 40,
'&:not(:first-of-type)': {
'&[data-last-child],&[data-middle-child]': {
...(ownerState.orientation === 'horizontal' && {
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
Expand All @@ -99,7 +100,7 @@ const ButtonGroupRoot = styled('div', {
marginTop: -1,
}),
},
'&:not(:last-of-type)': {
'&[data-first-child],&[data-middle-child]': {
...(ownerState.orientation === 'horizontal' && {
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
Expand Down Expand Up @@ -243,6 +244,17 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(inProps, ref) {
],
);

const buttonGroupPositionContext = (index, childrenParam) => {
const childrenCount = React.Children.count(childrenParam);
const onlyChild = childrenCount === 1;

return {
firstButton: !onlyChild && index === 0,
lastButton: !onlyChild && index === childrenCount - 1,
onlyChild,
};
};

return (
<ButtonGroupRoot
as={component}
Expand All @@ -252,7 +264,19 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(inProps, ref) {
ownerState={ownerState}
{...other}
>
<ButtonGroupContext.Provider value={context}>{children}</ButtonGroupContext.Provider>
<ButtonGroupContext.Provider value={context}>
{React.Children.map(children, (child, index) => {
if (!React.isValidElement(child)) {
return child;
}
DiegoAndai marked this conversation as resolved.
Show resolved Hide resolved

return (
<ButtonGroupButtonContext.Provider value={buttonGroupPositionContext(index, children)}>
{child}
</ButtonGroupButtonContext.Provider>
);
})}
</ButtonGroupContext.Provider>
</ButtonGroupRoot>
);
});
Expand Down
20 changes: 20 additions & 0 deletions packages/mui-material/src/ButtonGroup/ButtonGroupButtonContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

interface IButtonGroupButtonContext {
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
firstButton?: boolean;
lastButton?: boolean;
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
onlyChild?: boolean;
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @ignore - internal component.
*/
const ButtonGroupButtonContext = React.createContext<IButtonGroupButtonContext | undefined>(
undefined,
);

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

export default ButtonGroupButtonContext;
40 changes: 40 additions & 0 deletions test/regressions/fixtures/ButtonGroup/DifferentChildren.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add a unit test to verify if the attributes are placed correctly

Copy link
Member Author

@ZeeshanTamboli ZeeshanTamboli Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the attributes are not placed correctly, the styles will be wrong which this regression test will catch. What we can test is the buttons rendered individually (outside of a button group) don't have the attributes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit tests.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';

export default function DifferentChildren() {
return (
<Stack spacing={2}>
{/* It has one button with href which is rendered as anchor tag */}
<ButtonGroup variant="contained">
<Button href="##">Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</ButtonGroup>

{/* With tooltip */}
<ButtonGroup>
<Tooltip title="tooltip">
<Button>Enabled</Button>
</Tooltip>
<Tooltip title="tooltip">
<span>
<Button disabled>Disabled</Button>
</span>
</Tooltip>
<Tooltip title="tooltip">
<span>
<Button disabled>Disabled</Button>
</span>
</Tooltip>
</ButtonGroup>

{/* One button */}
<ButtonGroup>
<Button>One Button</Button>
</ButtonGroup>
</Stack>
);
}