diff --git a/docs/data/material/components/masonry/Sequential.js b/docs/data/material/components/masonry/Sequential.js
new file mode 100644
index 00000000000000..be3d4731df91b6
--- /dev/null
+++ b/docs/data/material/components/masonry/Sequential.js
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { styled } from '@mui/material/styles';
+import Paper from '@mui/material/Paper';
+import Masonry from '@mui/lab/Masonry';
+
+const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80];
+
+const Item = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ padding: theme.spacing(0.5),
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+}));
+
+export default function Sequential() {
+ return (
+
+
+ {heights.map((height, index) => (
+ -
+ {index + 1}
+
+ ))}
+
+
+ );
+}
diff --git a/docs/data/material/components/masonry/Sequential.tsx b/docs/data/material/components/masonry/Sequential.tsx
new file mode 100644
index 00000000000000..be3d4731df91b6
--- /dev/null
+++ b/docs/data/material/components/masonry/Sequential.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { styled } from '@mui/material/styles';
+import Paper from '@mui/material/Paper';
+import Masonry from '@mui/lab/Masonry';
+
+const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80];
+
+const Item = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ padding: theme.spacing(0.5),
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+}));
+
+export default function Sequential() {
+ return (
+
+
+ {heights.map((height, index) => (
+ -
+ {index + 1}
+
+ ))}
+
+
+ );
+}
diff --git a/docs/data/material/components/masonry/Sequential.tsx.preview b/docs/data/material/components/masonry/Sequential.tsx.preview
new file mode 100644
index 00000000000000..ed3ecb3589951c
--- /dev/null
+++ b/docs/data/material/components/masonry/Sequential.tsx.preview
@@ -0,0 +1,14 @@
+
+ {heights.map((height, index) => (
+ -
+ {index + 1}
+
+ ))}
+
\ No newline at end of file
diff --git a/docs/data/material/components/masonry/masonry.md b/docs/data/material/components/masonry/masonry.md
index 70d1077aeab4df..333c439874680f 100644
--- a/docs/data/material/components/masonry/masonry.md
+++ b/docs/data/material/components/masonry/masonry.md
@@ -56,6 +56,13 @@ It is important to note that the value provided to the `spacing` prop is multipl
{{"demo": "ResponsiveSpacing.js", "bg": true}}
+## Sequential
+
+This example demonstrates the use of the `sequential` to configure the sequential order.
+With `sequential` enabled, items are added in order from left to right rather than adding to the shortest column.
+
+{{"demo": "Sequential.js", "bg": true}}
+
## Server-side rendering
This example demonstrates the use of the `defaultHeight`, `defaultColumns` and `defaultSpacing`, which are used to
diff --git a/docs/pages/material-ui/api/masonry.json b/docs/pages/material-ui/api/masonry.json
index 7ecdffc5c33a81..a2c25831dbbb74 100644
--- a/docs/pages/material-ui/api/masonry.json
+++ b/docs/pages/material-ui/api/masonry.json
@@ -13,6 +13,7 @@
"defaultColumns": { "type": { "name": "number" } },
"defaultHeight": { "type": { "name": "number" } },
"defaultSpacing": { "type": { "name": "number" } },
+ "sequential": { "type": { "name": "bool" }, "default": "false" },
"spacing": {
"type": {
"name": "union",
diff --git a/docs/translations/api-docs/masonry/masonry.json b/docs/translations/api-docs/masonry/masonry.json
index 1792b21244c28b..e4de79a0f03d80 100644
--- a/docs/translations/api-docs/masonry/masonry.json
+++ b/docs/translations/api-docs/masonry/masonry.json
@@ -16,6 +16,9 @@
"defaultSpacing": {
"description": "The default spacing of the component. Like spacing
, it is a factor of the theme's spacing. This is provided for server-side rendering."
},
+ "sequential": {
+ "description": "Allows using sequential order rather than adding to shortest column"
+ },
"spacing": {
"description": "Defines the space between children. It is a factor of the theme's spacing."
},
diff --git a/packages/mui-lab/src/Masonry/Masonry.d.ts b/packages/mui-lab/src/Masonry/Masonry.d.ts
index 7f338898e34051..530c4666296ca7 100644
--- a/packages/mui-lab/src/Masonry/Masonry.d.ts
+++ b/packages/mui-lab/src/Masonry/Masonry.d.ts
@@ -34,6 +34,11 @@ export interface MasonryOwnProps {
* @default 1
*/
spacing?: ResponsiveStyleValue;
+ /**
+ * Allows using sequential order rather than adding to shortest column
+ * @default false
+ */
+ sequential?: boolean;
/**
* Allows defining system overrides as well as additional CSS styles.
*/
diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js
index c8797005e813c1..7d05bac97615e4 100644
--- a/packages/mui-lab/src/Masonry/Masonry.js
+++ b/packages/mui-lab/src/Masonry/Masonry.js
@@ -182,6 +182,7 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) {
component = 'div',
columns = 4,
spacing = 1,
+ sequential = false,
defaultColumns,
defaultHeight,
defaultSpacing,
@@ -212,71 +213,84 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) {
const classes = useUtilityClasses(ownerState);
- const handleResize = (masonryChildren) => {
- if (!masonryRef.current || !masonryChildren || masonryChildren.length === 0) {
- return;
- }
+ const handleResize = React.useCallback(
+ (masonryChildren) => {
+ if (!masonryRef.current || !masonryChildren || masonryChildren.length === 0) {
+ return;
+ }
- const masonry = masonryRef.current;
- const masonryFirstChild = masonryRef.current.firstChild;
- const parentWidth = masonry.clientWidth;
- const firstChildWidth = masonryFirstChild.clientWidth;
+ const masonry = masonryRef.current;
+ const masonryFirstChild = masonryRef.current.firstChild;
+ const parentWidth = masonry.clientWidth;
+ const firstChildWidth = masonryFirstChild.clientWidth;
- if (parentWidth === 0 || firstChildWidth === 0) {
- return;
- }
+ if (parentWidth === 0 || firstChildWidth === 0) {
+ return;
+ }
- const firstChildComputedStyle = window.getComputedStyle(masonryFirstChild);
- const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft);
- const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight);
+ const firstChildComputedStyle = window.getComputedStyle(masonryFirstChild);
+ const firstChildMarginLeft = parseToNumber(firstChildComputedStyle.marginLeft);
+ const firstChildMarginRight = parseToNumber(firstChildComputedStyle.marginRight);
- const currentNumberOfColumns = Math.round(
- parentWidth / (firstChildWidth + firstChildMarginLeft + firstChildMarginRight),
- );
+ const currentNumberOfColumns = Math.round(
+ parentWidth / (firstChildWidth + firstChildMarginLeft + firstChildMarginRight),
+ );
- const columnHeights = new Array(currentNumberOfColumns).fill(0);
- let skip = false;
- masonry.childNodes.forEach((child) => {
- if (child.nodeType !== Node.ELEMENT_NODE || child.dataset.class === 'line-break' || skip) {
- return;
- }
- const childComputedStyle = window.getComputedStyle(child);
- const childMarginTop = parseToNumber(childComputedStyle.marginTop);
- const childMarginBottom = parseToNumber(childComputedStyle.marginBottom);
- // if any one of children isn't rendered yet, masonry's height shouldn't be computed yet
- const childHeight = parseToNumber(childComputedStyle.height)
- ? Math.ceil(parseToNumber(childComputedStyle.height)) + childMarginTop + childMarginBottom
- : 0;
- if (childHeight === 0) {
- skip = true;
- return;
- }
- // if there is a nested image that isn't rendered yet, masonry's height shouldn't be computed yet
- for (let i = 0; i < child.childNodes.length; i += 1) {
- const nestedChild = child.childNodes[i];
- if (nestedChild.tagName === 'IMG' && nestedChild.clientHeight === 0) {
+ const columnHeights = new Array(currentNumberOfColumns).fill(0);
+ let skip = false;
+ let nextOrder = 1;
+ masonry.childNodes.forEach((child) => {
+ if (child.nodeType !== Node.ELEMENT_NODE || child.dataset.class === 'line-break' || skip) {
+ return;
+ }
+ const childComputedStyle = window.getComputedStyle(child);
+ const childMarginTop = parseToNumber(childComputedStyle.marginTop);
+ const childMarginBottom = parseToNumber(childComputedStyle.marginBottom);
+ // if any one of children isn't rendered yet, masonry's height shouldn't be computed yet
+ const childHeight = parseToNumber(childComputedStyle.height)
+ ? Math.ceil(parseToNumber(childComputedStyle.height)) + childMarginTop + childMarginBottom
+ : 0;
+ if (childHeight === 0) {
skip = true;
- break;
+ return;
}
- }
+ // if there is a nested image that isn't rendered yet, masonry's height shouldn't be computed yet
+ for (let i = 0; i < child.childNodes.length; i += 1) {
+ const nestedChild = child.childNodes[i];
+ if (nestedChild.tagName === 'IMG' && nestedChild.clientHeight === 0) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ if (sequential) {
+ columnHeights[nextOrder - 1] += childHeight;
+ child.style.order = nextOrder;
+ nextOrder += 1;
+ if (nextOrder > currentNumberOfColumns) {
+ nextOrder = 1;
+ }
+ } else {
+ // find the current shortest column (where the current item will be placed)
+ const currentMinColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
+ columnHeights[currentMinColumnIndex] += childHeight;
+ const order = currentMinColumnIndex + 1;
+ child.style.order = order;
+ }
+ }
+ });
if (!skip) {
- // find the current shortest column (where the current item will be placed)
- const currentMinColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
- columnHeights[currentMinColumnIndex] += childHeight;
- const order = currentMinColumnIndex + 1;
- child.style.order = order;
+ // In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
+ // when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
+ // Related issue - https://github.com/facebook/react/issues/24331
+ ReactDOM.flushSync(() => {
+ setMaxColumnHeight(Math.max(...columnHeights));
+ setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0);
+ });
}
- });
- if (!skip) {
- // In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
- // when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
- // Related issue - https://github.com/facebook/react/issues/24331
- ReactDOM.flushSync(() => {
- setMaxColumnHeight(Math.max(...columnHeights));
- setNumberOfLineBreaks(currentNumberOfColumns > 0 ? currentNumberOfColumns - 1 : 0);
- });
- }
- };
+ },
+ [sequential],
+ );
useEnhancedEffect(() => {
// IE and old browsers are not supported
@@ -305,7 +319,7 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) {
resizeObserver.disconnect();
}
};
- }, [columns, spacing, children]);
+ }, [columns, spacing, children, handleResize]);
const handleRef = useForkRef(ref, masonryRef);
@@ -375,6 +389,11 @@ Masonry.propTypes /* remove-proptypes */ = {
* The default spacing of the component. Like `spacing`, it is a factor of the theme's spacing. This is provided for server-side rendering.
*/
defaultSpacing: PropTypes.number,
+ /**
+ * Allows using sequential order rather than adding to shortest column
+ * @default false
+ */
+ sequential: PropTypes.bool,
/**
* Defines the space between children. It is a factor of the theme's spacing.
* @default 1
diff --git a/packages/mui-lab/src/Masonry/Masonry.test.js b/packages/mui-lab/src/Masonry/Masonry.test.js
index 7320e3aaaf49df..a4b98bcdd70117 100644
--- a/packages/mui-lab/src/Masonry/Masonry.test.js
+++ b/packages/mui-lab/src/Masonry/Masonry.test.js
@@ -369,4 +369,35 @@ describe('', () => {
});
});
});
+
+ describe('prop: sequential', () => {
+ const pause = (timeout) =>
+ new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, timeout);
+ });
+
+ it('should place children in sequential order', async function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ // only run on browser
+ this.skip();
+ }
+
+ const { getByTestId } = render(
+
+
+
+
+ ,
+ );
+ await pause(400); // Masonry elements aren't ordered immediately, and so we need the pause to wait for them to be ordered
+ const child1 = getByTestId('child1');
+ const child2 = getByTestId('child2');
+ const child3 = getByTestId('child3');
+ expect(window.getComputedStyle(child1).order).to.equal(`1`);
+ expect(window.getComputedStyle(child2).order).to.equal(`2`);
+ expect(window.getComputedStyle(child3).order).to.equal(`1`);
+ });
+ });
});