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

[System] Add flag to switch negative margin approach in Grid #33484

Merged
merged 5 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
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>
);
}
14 changes: 14 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,20 @@ declare module '@mui/system' {
}
```

:::
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
## 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