Skip to content

Commit

Permalink
[System] Add flag to switch negative margin approach in Grid (#33484)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp authored Jul 14, 2022
1 parent ec25e6c commit ed32fe6
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 1 deletion.
54 changes: 54 additions & 0 deletions docs/data/system/components/grid/OverflowGrid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react';
import Box from '@mui/system/Box';
import Grid from '@mui/system/Unstable_Grid';
import styled from '@mui/system/styled';

const Item = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
border: '1px solid',
borderColor: theme.palette.mode === 'dark' ? '#444d58' : '#ced7e0',
padding: theme.spacing(1),
borderRadius: '4px',
textAlign: 'center',
}));

export default function AutoGrid() {
return (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: 3,
'& > div': {
overflow: 'auto hidden',
'&::-webkit-scrollbar': { height: 10, WebkitAppearance: 'none' },
'&::-webkit-scrollbar-thumb': {
borderRadius: 8,
border: '2px solid',
borderColor: theme.palette.mode === 'dark' ? '' : '#E7EBF0',
backgroundColor: 'rgba(0 0 0 / 0.5)',
},
},
})}
>
<Box
sx={{
width: 200,
}}
>
<Grid container spacing={3}>
<Grid xs={12}>
<Item>Scroll bar appears</Item>
</Grid>
</Grid>
</Box>
<Box sx={{ width: 200, overflow: 'scroll' }}>
<Grid container spacing={3} disableEqualOverflow>
<Grid xs={12}>
<Item>`disableEqualOverflow` prevents scrollbar</Item>
</Grid>
</Grid>
</Box>
</Box>
);
}
54 changes: 54 additions & 0 deletions docs/data/system/components/grid/OverflowGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react';
import Box from '@mui/system/Box';
import Grid from '@mui/system/Unstable_Grid';
import styled from '@mui/system/styled';

const Item = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
border: '1px solid',
borderColor: theme.palette.mode === 'dark' ? '#444d58' : '#ced7e0',
padding: theme.spacing(1),
borderRadius: '4px',
textAlign: 'center',
}));

export default function AutoGrid() {
return (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: 3,
'& > div': {
overflow: 'auto hidden',
'&::-webkit-scrollbar': { height: 10, WebkitAppearance: 'none' },
'&::-webkit-scrollbar-thumb': {
borderRadius: 8,
border: '2px solid',
borderColor: theme.palette.mode === 'dark' ? '' : '#E7EBF0',
backgroundColor: 'rgba(0 0 0 / 0.5)',
},
},
})}
>
<Box
sx={{
width: 200,
}}
>
<Grid container spacing={3}>
<Grid xs={12}>
<Item>Scroll bar appears</Item>
</Grid>
</Grid>
</Box>
<Box sx={{ width: 200, overflow: 'scroll' }}>
<Grid container spacing={3} disableEqualOverflow>
<Grid xs={12}>
<Item>`disableEqualOverflow` prevents scrollbar</Item>
</Grid>
</Grid>
</Box>
</Box>
);
}
12 changes: 12 additions & 0 deletions docs/data/system/components/grid/grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ declare module '@mui/system' {
}
```

## Prevent scrollbar

If you use grid as a container in a small viewport, you might see a horizontal scrollbar because the negative margin is applied on all sides of the grid container.

To prevent the scrollbar, set `disableEqualOverflow` prop to `true`. It will enable negative margin only on the top and left sides of the grid which remove overflow on the right-hand side.

{{"demo": "OverflowGrid.js", "bg": true}}

:::warning
You should avoid adding borders or background to the grid when `disableEqualOverflow: true` because the negative margin (applied only at the top and left sides) makes the grid visually misaligned.
:::

## Limitations

### direction column and column-reverse
Expand Down
1 change: 1 addition & 0 deletions docs/pages/system/api/grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"description": "'column-reverse'<br>&#124;&nbsp;'column'<br>&#124;&nbsp;'row-reverse'<br>&#124;&nbsp;'row'<br>&#124;&nbsp;Array&lt;'column-reverse'<br>&#124;&nbsp;'column'<br>&#124;&nbsp;'row-reverse'<br>&#124;&nbsp;'row'&gt;<br>&#124;&nbsp;object"
}
},
"disableEqualOverflow": { "type": { "name": "bool" } },
"lg": {
"type": { "name": "union", "description": "'auto'<br>&#124;&nbsp;number<br>&#124;&nbsp;bool" }
},
Expand Down
1 change: 1 addition & 0 deletions docs/translations/api-docs/grid/grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"columnSpacing": "Defines the horizontal space between the type <code>item</code> components. It overrides the value of the <code>spacing</code> prop.",
"container": "If <code>true</code>, the component will have the flex <em>container</em> behavior. You should be wrapping <em>items</em> with a <em>container</em>.",
"direction": "Defines the <code>flex-direction</code> style property. It is applied for all screen sizes.",
"disableEqualOverflow": "If <code>true</code>, the negative margin and padding are apply only to the top and left sides of the grid.",
"lg": "If a number, it sets the number of columns the grid item uses. It can&#39;t be greater than the total number of columns of the container (12 by default). If &#39;auto&#39;, the grid item&#39;s width matches its content. If false, the prop is ignored. If true, the grid item&#39;s width grows to use the space available in the grid container. The value is applied for the <code>lg</code> breakpoint and wider screens if not overridden.",
"lgOffset": "If a number, it sets the margin-left equals to the number of columns the grid item uses. If &#39;auto&#39;, the grid item push itself to the right-end of the container. The value is applied for the <code>lg</code> breakpoint and wider screens if not overridden.",
"md": "If a number, it sets the number of columns the grid item uses. It can&#39;t be greater than the total number of columns of the container (12 by default). If &#39;auto&#39;, the grid item&#39;s width matches its content. If false, the prop is ignored. If true, the grid item&#39;s width grows to use the space available in the grid container. The value is applied for the <code>md</code> breakpoint and wider screens if not overridden.",
Expand Down
65 changes: 65 additions & 0 deletions packages/mui-system/src/Unstable_Grid/Grid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,71 @@ describe('System <Grid />', () => {
});
});

describe('prop: disableEqualOverflow', () => {
it('should apply to top and left sides only', function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}
const { container } = render(
<Grid container disableEqualOverflow spacing={2}>
<Grid />
</Grid>,
);

expect(container.firstChild).toHaveComputedStyle({
marginTop: '-16px',
marginLeft: '-16px',
});
expect(container.firstChild.firstChild).toHaveComputedStyle({
paddingTop: '16px',
paddingLeft: '16px',
});
});

it('should use the value from theme and nestable', function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}
const { container } = render(
<ThemeProvider
theme={createTheme({
components: {
MuiGrid: {
defaultProps: {
disableEqualOverflow: true,
},
},
},
})}
>
<Grid container spacing={2}>
<Grid container disableEqualOverflow={false} spacing={3}>
<Grid />
</Grid>
</Grid>
</ThemeProvider>,
);
expect(container.firstChild).toHaveComputedStyle({
marginTop: '-16px',
marginLeft: '-16px',
});
expect(container.firstChild.firstChild).toHaveComputedStyle({
marginTop: '-12px',
marginLeft: '-12px',
marginRight: '-12px',
marginBottom: '-12px',
paddingTop: '16px',
paddingLeft: '16px',
});
expect(container.firstChild.firstChild.firstChild).toHaveComputedStyle({
paddingTop: '12px',
paddingLeft: '12px',
paddingRight: '12px',
paddingBottom: '12px',
});
});
});

describe('Custom breakpoints', () => {
it('should apply the custom breakpoint class', () => {
const { container } = render(
Expand Down
4 changes: 4 additions & 0 deletions packages/mui-system/src/Unstable_Grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Grid.propTypes /* remove-proptypes */ = {
PropTypes.arrayOf(PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row'])),
PropTypes.object,
]),
/**
* If `true`, the negative margin and padding are apply only to the top and left sides of the grid.
*/
disableEqualOverflow: PropTypes.bool,
/**
* If a number, it sets the number of columns the grid item uses.
* It can't be greater than the total number of columns of the container (12 by default).
Expand Down
4 changes: 4 additions & 0 deletions packages/mui-system/src/Unstable_Grid/GridProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export interface GridBaseProps extends Breakpoints {
* @default 'row'
*/
direction?: ResponsiveStyleValue<GridDirection>;
/**
* If `true`, the negative margin and padding are apply only to the top and left sides of the grid.
*/
disableEqualOverflow?: boolean;
/**
* Defines the vertical space between the type `item` components.
* It overrides the value of the `spacing` prop.
Expand Down
22 changes: 21 additions & 1 deletion packages/mui-system/src/Unstable_Grid/createGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default function createGrid(
} = options;

const NestedContext = React.createContext(false);
const OverflowContext = React.createContext<boolean | undefined>(undefined);

const useUtilityClasses = (ownerState: GridOwnerState, theme: typeof defaultTheme) => {
const { container, direction, spacing, wrap, gridSize } = ownerState;
Expand Down Expand Up @@ -93,6 +94,7 @@ export default function createGrid(
const themeProps = useThemeProps<typeof inProps & { component?: React.ElementType }>(inProps);
const props = extendSxProp(themeProps) as Omit<typeof themeProps, 'color'>; // `color` type conflicts with html color attribute.
const nested = React.useContext(NestedContext);
const overflow = React.useContext(OverflowContext);
const {
className,
columns: columnsProp = 12,
Expand All @@ -103,9 +105,15 @@ export default function createGrid(
spacing: spacingProp = 0,
rowSpacing: rowSpacingProp = spacingProp,
columnSpacing: columnSpacingProp = spacingProp,
disableEqualOverflow: themeDisableEqualOverflow,
...rest
} = props;
// collect breakpoints related props because they can be custom from the theme.
// Because `disableEqualOverflow` can be set from the theme's defaultProps, the **nested** grid should look at the instance props instead.
let disableEqualOverflow = themeDisableEqualOverflow;
if (nested && themeDisableEqualOverflow !== undefined) {
disableEqualOverflow = inProps.disableEqualOverflow;
}
// collect breakpoints related props because they can be customized from the theme.
const gridSize = {} as GridOwnerState['gridSize'];
const gridOffset = {} as GridOwnerState['gridOffset'];
const other: Record<string, any> = {};
Expand Down Expand Up @@ -138,6 +146,8 @@ export default function createGrid(
columnSpacing,
gridSize,
gridOffset,
disableEqualOverflow: disableEqualOverflow ?? overflow ?? false, // use context value if exists.
parentDisableEqualOverflow: overflow, // for nested grid
};

const classes = useUtilityClasses(ownerState, theme);
Expand All @@ -156,6 +166,15 @@ export default function createGrid(
result = <NestedContext.Provider value>{result}</NestedContext.Provider>;
}

if (disableEqualOverflow !== undefined && disableEqualOverflow !== (overflow ?? false)) {
// There are 2 possibilities that should wrap with the OverflowContext to communicate with the nested grids:
// 1. It is the root grid with `disableEqualOverflow`.
// 2. It is a nested grid with different `disableEqualOverflow` from the context.
result = (
<OverflowContext.Provider value={disableEqualOverflow}>{result}</OverflowContext.Provider>
);
}

return result;
}) as OverridableComponent<GridTypeMap>;

Expand All @@ -180,6 +199,7 @@ export default function createGrid(
PropTypes.arrayOf(PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row'])),
PropTypes.object,
]),
disableEqualOverflow: PropTypes.bool,
lg: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
lgOffset: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
md: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
Expand Down
43 changes: 43 additions & 0 deletions packages/mui-system/src/Unstable_Grid/gridGenerator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,49 @@ describe('grid generator', () => {
padding: `calc(var(--Grid-nested-rowSpacing) / 2) calc(var(--Grid-nested-columnSpacing) / 2)`,
});
});

it('root container with disableEqualOverflow', () => {
const result = generateGridStyles({
ownerState: { container: true, nested: true, disableEqualOverflow: true },
});
sinon.assert.match(result, {
margin: `calc(var(--Grid-rowSpacing) * -1) 0px 0px calc(var(--Grid-columnSpacing) * -1)`,
padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
});
});

it('nested container without disableEqualOverflow but parent has', () => {
const result = generateGridStyles({
ownerState: {
container: true,
nested: true,
disableEqualOverflow: false,
parentDisableEqualOverflow: true,
},
});
sinon.assert.match(result, {
margin: `calc(var(--Grid-rowSpacing) / -2) calc(var(--Grid-columnSpacing) / -2)`,
padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
});
});

it('item', () => {
const result = generateGridStyles({ ownerState: { container: false, nested: false } });
expect(result).to.deep.equal({
minWidth: 0,
boxSizing: 'border-box',
padding: `calc(var(--Grid-rowSpacing) / 2) calc(var(--Grid-columnSpacing) / 2)`,
});
});

it('item with disableEqualOverflow', () => {
const result = generateGridStyles({
ownerState: { container: false, disableEqualOverflow: true },
});
sinon.assert.match(result, {
padding: `calc(var(--Grid-rowSpacing)) 0px 0px calc(var(--Grid-columnSpacing))`,
});
});
});

describe('generateGridSizeStyles', () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/mui-system/src/Unstable_Grid/gridGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,15 @@ export const generateGridStyles = ({ ownerState }: Props): {} => {
flexWrap: ownerState.wrap,
}),
margin: `calc(var(--Grid-rowSpacing) / -2) calc(var(--Grid-columnSpacing) / -2)`,
...(ownerState.disableEqualOverflow && {
margin: `calc(var(--Grid-rowSpacing) * -1) 0px 0px calc(var(--Grid-columnSpacing) * -1)`,
}),
...(ownerState.nested
? {
padding: `calc(var(--Grid-nested-rowSpacing) / 2) calc(var(--Grid-nested-columnSpacing) / 2)`,
...((ownerState.disableEqualOverflow || ownerState.parentDisableEqualOverflow) && {
padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
}),
}
: {
'--Grid-nested-rowSpacing': 'var(--Grid-rowSpacing)',
Expand All @@ -205,6 +211,9 @@ export const generateGridStyles = ({ ownerState }: Props): {} => {
}
: {
padding: `calc(var(--Grid-rowSpacing) / 2) calc(var(--Grid-columnSpacing) / 2)`,
...(ownerState.disableEqualOverflow && {
padding: `calc(var(--Grid-rowSpacing)) 0px 0px calc(var(--Grid-columnSpacing))`,
}),
}),
};
};
Expand Down

0 comments on commit ed32fe6

Please sign in to comment.