diff --git a/docs/pages/api-docs/masonry-item.js b/docs/pages/api-docs/masonry-item.js
new file mode 100644
index 00000000000000..c9dce04e3c9d82
--- /dev/null
+++ b/docs/pages/api-docs/masonry-item.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './masonry-item.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs/translations/api-docs/masonry-item',
+ false,
+ /masonry-item.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/api-docs/masonry-item.json b/docs/pages/api-docs/masonry-item.json
new file mode 100644
index 00000000000000..0789482ca0eb35
--- /dev/null
+++ b/docs/pages/api-docs/masonry-item.json
@@ -0,0 +1,19 @@
+{
+ "props": {
+ "children": { "type": { "name": "element" }, "required": true },
+ "classes": { "type": { "name": "object" } },
+ "columnSpan": { "type": { "name": "number" }, "default": "1" },
+ "component": { "type": { "name": "elementType" } },
+ "defaultHeight": { "type": { "name": "number" } },
+ "sx": { "type": { "name": "object" } }
+ },
+ "name": "MasonryItem",
+ "styles": { "classes": ["root"], "globalClasses": {}, "name": "MuiMasonryItem" },
+ "spread": true,
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/material-ui-lab/src/MasonryItem/MasonryItem.js",
+ "inheritance": null,
+ "demos": "
",
+ "styledComponent": true,
+ "cssComponent": false
+}
diff --git a/docs/pages/api-docs/masonry.js b/docs/pages/api-docs/masonry.js
new file mode 100644
index 00000000000000..6fb94801e7b9ea
--- /dev/null
+++ b/docs/pages/api-docs/masonry.js
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './masonry.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context('docs/translations/api-docs/masonry', false, /masonry.*.json$/);
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/api-docs/masonry.json b/docs/pages/api-docs/masonry.json
new file mode 100644
index 00000000000000..fc83112a6a8a9a
--- /dev/null
+++ b/docs/pages/api-docs/masonry.json
@@ -0,0 +1,31 @@
+{
+ "props": {
+ "children": { "type": { "name": "node" }, "required": true },
+ "classes": { "type": { "name": "object" } },
+ "columns": {
+ "type": {
+ "name": "union",
+ "description": "Array<number
| string>
| number
| object
| string"
+ },
+ "default": "4"
+ },
+ "component": { "type": { "name": "elementType" } },
+ "spacing": {
+ "type": {
+ "name": "union",
+ "description": "Array<number
| string>
| number
| object
| string"
+ },
+ "default": "1"
+ },
+ "sx": { "type": { "name": "object" } }
+ },
+ "name": "Masonry",
+ "styles": { "classes": ["root"], "globalClasses": {}, "name": "MuiMasonry" },
+ "spread": true,
+ "forwardsRefTo": "HTMLDivElement",
+ "filename": "/packages/material-ui-lab/src/Masonry/Masonry.js",
+ "inheritance": null,
+ "demos": "",
+ "styledComponent": true,
+ "cssComponent": false
+}
diff --git a/docs/pages/components/masonry.js b/docs/pages/components/masonry.js
new file mode 100644
index 00000000000000..2a4f8621fd449b
--- /dev/null
+++ b/docs/pages/components/masonry.js
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import {
+ demos,
+ docs,
+ demoComponents,
+} from 'docs/src/pages/components/masonry/masonry.md?@material-ui/markdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/src/pages.ts b/docs/src/pages.ts
index 12843eac4b7995..37f70258c1adbe 100644
--- a/docs/src/pages.ts
+++ b/docs/src/pages.ts
@@ -187,6 +187,7 @@ const pages: readonly MuiPage[] = [
{ pathname: '/components/time-picker' },
],
},
+ { pathname: '/components/masonry' },
{ pathname: '/components/timeline' },
{ pathname: '/components/trap-focus' },
{ pathname: '/components/tree-view' },
diff --git a/docs/src/pages/components/masonry/BasicMasonry.js b/docs/src/pages/components/masonry/BasicMasonry.js
new file mode 100644
index 00000000000000..803ca3b1380160
--- /dev/null
+++ b/docs/src/pages/components/masonry/BasicMasonry.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80];
+
+export default function BasicMasonry() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/BasicMasonry.tsx b/docs/src/pages/components/masonry/BasicMasonry.tsx
new file mode 100644
index 00000000000000..803ca3b1380160
--- /dev/null
+++ b/docs/src/pages/components/masonry/BasicMasonry.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80];
+
+export default function BasicMasonry() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/DiffColSizeMasonry.js b/docs/src/pages/components/masonry/DiffColSizeMasonry.js
new file mode 100644
index 00000000000000..de587bab4be47d
--- /dev/null
+++ b/docs/src/pages/components/masonry/DiffColSizeMasonry.js
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function DiffColSizeMasonry() {
+ return (
+
+
+ {itemData.map((item, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ { height: 150 },
+ { height: 30 },
+ { height: 90, span: 2 },
+ { height: 110 },
+ { height: 150 },
+ { height: 150 },
+ { height: 130, span: 2 },
+ { height: 80, span: 2 },
+ { height: 50 },
+ { height: 90 },
+ { height: 100, span: 2 },
+ { height: 150 },
+ { height: 50 },
+ { height: 50, span: 2 },
+ { height: 50 },
+];
diff --git a/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx b/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx
new file mode 100644
index 00000000000000..de587bab4be47d
--- /dev/null
+++ b/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function DiffColSizeMasonry() {
+ return (
+
+
+ {itemData.map((item, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ { height: 150 },
+ { height: 30 },
+ { height: 90, span: 2 },
+ { height: 110 },
+ { height: 150 },
+ { height: 150 },
+ { height: 130, span: 2 },
+ { height: 80, span: 2 },
+ { height: 50 },
+ { height: 90 },
+ { height: 100, span: 2 },
+ { height: 150 },
+ { height: 50 },
+ { height: 50, span: 2 },
+ { height: 50 },
+];
diff --git a/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.js b/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.js
new file mode 100644
index 00000000000000..628cdfb39e4f60
--- /dev/null
+++ b/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.js
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function DiffColSizeMasonryBroken() {
+ return (
+
+
+ {itemData.map((item, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ { height: 150 },
+ { height: 30 },
+ { height: 90, span: 2 },
+ { height: 70 },
+ { height: 150 },
+ { height: 120 },
+ { height: 100, span: 2 },
+ { height: 80, span: 2 },
+ { height: 35 },
+ { height: 70 },
+ { height: 100, span: 2 },
+ { height: 157 },
+ { height: 50 },
+ { height: 50, span: 2 },
+ { height: 50 },
+];
diff --git a/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.tsx b/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.tsx
new file mode 100644
index 00000000000000..628cdfb39e4f60
--- /dev/null
+++ b/docs/src/pages/components/masonry/DiffColSizeMasonryBroken.tsx
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function DiffColSizeMasonryBroken() {
+ return (
+
+
+ {itemData.map((item, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ { height: 150 },
+ { height: 30 },
+ { height: 90, span: 2 },
+ { height: 70 },
+ { height: 150 },
+ { height: 120 },
+ { height: 100, span: 2 },
+ { height: 80, span: 2 },
+ { height: 35 },
+ { height: 70 },
+ { height: 100, span: 2 },
+ { height: 157 },
+ { height: 50 },
+ { height: 50, span: 2 },
+ { height: 50 },
+];
diff --git a/docs/src/pages/components/masonry/FixedColumns.js b/docs/src/pages/components/masonry/FixedColumns.js
new file mode 100644
index 00000000000000..4ccc5c1243ecd0
--- /dev/null
+++ b/docs/src/pages/components/masonry/FixedColumns.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function FixedColumns() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/FixedColumns.tsx b/docs/src/pages/components/masonry/FixedColumns.tsx
new file mode 100644
index 00000000000000..4ccc5c1243ecd0
--- /dev/null
+++ b/docs/src/pages/components/masonry/FixedColumns.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function FixedColumns() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/FixedSpacing.js b/docs/src/pages/components/masonry/FixedSpacing.js
new file mode 100644
index 00000000000000..58489b4c539738
--- /dev/null
+++ b/docs/src/pages/components/masonry/FixedSpacing.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function FixedSpacing() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/FixedSpacing.tsx b/docs/src/pages/components/masonry/FixedSpacing.tsx
new file mode 100644
index 00000000000000..58489b4c539738
--- /dev/null
+++ b/docs/src/pages/components/masonry/FixedSpacing.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function FixedSpacing() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/ImageMasonry.js b/docs/src/pages/components/masonry/ImageMasonry.js
new file mode 100644
index 00000000000000..ebb5eda20a923e
--- /dev/null
+++ b/docs/src/pages/components/masonry/ImageMasonry.js
@@ -0,0 +1,95 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function ImageMasonry() {
+ return (
+
+
+ {itemData.map((item) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ {
+ img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
+ title: 'Fern',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627308595229-7830a5c91f9f',
+ title: 'Snacks',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
+ title: 'Mushrooms',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383',
+ title: 'Tower',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
+ title: 'Sea star',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62',
+ title: 'Honey',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
+ title: 'Basketball',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
+ title: 'Breakfast',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627328715728-7bcc1b5db87d',
+ title: 'Tree',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
+ title: 'Burger',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
+ title: 'Camera',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
+ title: 'Coffee',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627000086207-76eabf23aa2e',
+ title: 'Camping Car',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
+ title: 'Hats',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
+ title: 'Tomato basil',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627328561499-a3584d4ee4f7',
+ title: 'Mountain',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6',
+ title: 'Bike',
+ },
+];
diff --git a/docs/src/pages/components/masonry/ImageMasonry.tsx b/docs/src/pages/components/masonry/ImageMasonry.tsx
new file mode 100644
index 00000000000000..ebb5eda20a923e
--- /dev/null
+++ b/docs/src/pages/components/masonry/ImageMasonry.tsx
@@ -0,0 +1,95 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+export default function ImageMasonry() {
+ return (
+
+
+ {itemData.map((item) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+const itemData = [
+ {
+ img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
+ title: 'Fern',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627308595229-7830a5c91f9f',
+ title: 'Snacks',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
+ title: 'Mushrooms',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383',
+ title: 'Tower',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
+ title: 'Sea star',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62',
+ title: 'Honey',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
+ title: 'Basketball',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
+ title: 'Breakfast',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627328715728-7bcc1b5db87d',
+ title: 'Tree',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
+ title: 'Burger',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
+ title: 'Camera',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
+ title: 'Coffee',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627000086207-76eabf23aa2e',
+ title: 'Camping Car',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
+ title: 'Hats',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
+ title: 'Tomato basil',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1627328561499-a3584d4ee4f7',
+ title: 'Mountain',
+ },
+ {
+ img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6',
+ title: 'Bike',
+ },
+];
diff --git a/docs/src/pages/components/masonry/ResponsiveColumns.js b/docs/src/pages/components/masonry/ResponsiveColumns.js
new file mode 100644
index 00000000000000..b920086df750be
--- /dev/null
+++ b/docs/src/pages/components/masonry/ResponsiveColumns.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function ResponsiveColumns() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/ResponsiveColumns.tsx b/docs/src/pages/components/masonry/ResponsiveColumns.tsx
new file mode 100644
index 00000000000000..b920086df750be
--- /dev/null
+++ b/docs/src/pages/components/masonry/ResponsiveColumns.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function ResponsiveColumns() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/ResponsiveSpacing.js b/docs/src/pages/components/masonry/ResponsiveSpacing.js
new file mode 100644
index 00000000000000..3e65472251b86d
--- /dev/null
+++ b/docs/src/pages/components/masonry/ResponsiveSpacing.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function ResponsiveSpacing() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/ResponsiveSpacing.tsx b/docs/src/pages/components/masonry/ResponsiveSpacing.tsx
new file mode 100644
index 00000000000000..3e65472251b86d
--- /dev/null
+++ b/docs/src/pages/components/masonry/ResponsiveSpacing.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function ResponsiveSpacing() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/SSRMasonry.js b/docs/src/pages/components/masonry/SSRMasonry.js
new file mode 100644
index 00000000000000..32682ed8484f7d
--- /dev/null
+++ b/docs/src/pages/components/masonry/SSRMasonry.js
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function SSRMasonry() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/SSRMasonry.tsx b/docs/src/pages/components/masonry/SSRMasonry.tsx
new file mode 100644
index 00000000000000..32682ed8484f7d
--- /dev/null
+++ b/docs/src/pages/components/masonry/SSRMasonry.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import Masonry from '@material-ui/lab/Masonry';
+import MasonryItem from '@material-ui/lab/MasonryItem';
+
+const heights = [150, 30, 90, 70, 90, 100, 150, 30, 50, 80];
+
+export default function SSRMasonry() {
+ return (
+
+
+ {heights.map((height, index) => (
+
+
+ {index + 1}
+
+
+ ))}
+
+
+ );
+}
diff --git a/docs/src/pages/components/masonry/masonry.md b/docs/src/pages/components/masonry/masonry.md
new file mode 100644
index 00000000000000..5be1e7de3e42cd
--- /dev/null
+++ b/docs/src/pages/components/masonry/masonry.md
@@ -0,0 +1,70 @@
+---
+title: React Masonry component
+components: Masonry, MasonryItem
+githubLabel: 'component: Masonry'
+---
+
+# Masonry
+
+Masonry lays out contents of different sizes as blocks of the same width and variable height with configurable gaps.
+
+Masonry maintains a list of content blocks with a consistent width but variable height.
+The contents are ordered by row.
+If a row is already filled with the specified number of columns, the next item starts another row, and it is added to the shortest column.
+
+{{"component": "modules/components/ComponentLinkHeader.js", "design": true}}
+
+> Warning: This component has been developed with the use of CSS Grid Level 2. Unfortunately, Chrome only allows to render at most 1,000 rows for each grid.
+> Hence, with the current design, a masonry component has a maximum height of 2,000px, and the items beyond this height will fail to be rendered.
+> An [issue](https://github.com/mui-org/material-ui/issues/27934) has been created on GitHub to gather workarounds for this limitation. It is worth noting that this limitation does not exist on Firefox or Safari.
+
+## Basic masonry
+
+A simple example of a ``. `` is a container for one or more ``s. `` can receive any element including `` and ``. Also, it is important to note that each `` accepts only one element.
+
+{{"demo": "pages/components/masonry/BasicMasonry.js", "bg": true}}
+
+## Image masonry
+
+This example demonstrates the use of `` for images. `` orders its children by row.
+If you would like to order images by column, you can use ``. More details on this component can be found in [Masonry Image List](/components/image-list/#masonry-image-list).
+
+{{"demo": "pages/components/masonry/ImageMasonry.js", "bg": true}}
+
+## Columns
+
+This example demonstrates the use of the `columns` to configure the number of columns of a ``.
+
+{{"demo": "pages/components/masonry/FixedColumns.js", "bg": true}}
+
+`columns` accepts responsive values:
+
+{{"demo": "pages/components/masonry/ResponsiveColumns.js", "bg": true}}
+
+## Spacing
+
+This example demonstrates the use of the `spacing` to configure the spacing between ``s.
+It is important to note that `spacing` is a factor of the theme's spacing.
+
+{{"demo": "pages/components/masonry/FixedSpacing.js", "bg": true}}
+
+`spacing` accepts responsive values:
+
+{{"demo": "pages/components/masonry/ResponsiveSpacing.js", "bg": true}}
+
+## Column spanning
+
+This example demonstrates the use of the `columnSpan` to configure the number of columns taken up by each ``.
+
+{{"demo": "pages/components/masonry/DiffColSizeMasonry.js", "bg": true}}
+
+However, you have to choose the value of `columnSpan` for each item carefully or fine-tune heights of items so that your masonry does not break.
+
+{{"demo": "pages/components/masonry/DiffColSizeMasonryBroken.js", "bg": true}}
+
+## Server-side rendering
+
+This example demonstrates the use of the `defaultHeight` to configure a fixed height of each ``. This is used for server-side rendering.
+By default, `height: 100%` will be set to the content of ``. If you change this, there can be unwanted gap between `` and the content that you pass to it.
+
+{{"demo": "pages/components/masonry/SSRMasonry.js", "bg": true}}
diff --git a/docs/src/pagesApi.js b/docs/src/pagesApi.js
index 3fa8b63a0ca4db..8f4cd760d371c2 100644
--- a/docs/src/pagesApi.js
+++ b/docs/src/pagesApi.js
@@ -83,6 +83,8 @@ module.exports = [
{ pathname: '/api-docs/list-item-text' },
{ pathname: '/api-docs/list-subheader' },
{ pathname: '/api-docs/loading-button' },
+ { pathname: '/api-docs/masonry' },
+ { pathname: '/api-docs/masonry-item' },
{ pathname: '/api-docs/menu' },
{ pathname: '/api-docs/menu-item' },
{ pathname: '/api-docs/menu-list' },
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-de.json b/docs/translations/api-docs/masonry-item/masonry-item-de.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-de.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-es.json b/docs/translations/api-docs/masonry-item/masonry-item-es.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-es.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-fr.json b/docs/translations/api-docs/masonry-item/masonry-item-fr.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-fr.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-ja.json b/docs/translations/api-docs/masonry-item/masonry-item-ja.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-ja.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-pt.json b/docs/translations/api-docs/masonry-item/masonry-item-pt.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-pt.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-ru.json b/docs/translations/api-docs/masonry-item/masonry-item-ru.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-ru.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item-zh.json b/docs/translations/api-docs/masonry-item/masonry-item-zh.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item-zh.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry-item/masonry-item.json b/docs/translations/api-docs/masonry-item/masonry-item.json
new file mode 100644
index 00000000000000..535abe02463228
--- /dev/null
+++ b/docs/translations/api-docs/masonry-item/masonry-item.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component, normally an <img />
or a <div />
. It should be only one element.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columnSpan": "The number of columns taken up by the component",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "defaultHeight": "The initial height of the component in px. This is provided for server-side rendering.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-de.json b/docs/translations/api-docs/masonry/masonry-de.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-de.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-es.json b/docs/translations/api-docs/masonry/masonry-es.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-es.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-fr.json b/docs/translations/api-docs/masonry/masonry-fr.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-fr.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-ja.json b/docs/translations/api-docs/masonry/masonry-ja.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-ja.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-pt.json b/docs/translations/api-docs/masonry/masonry-pt.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-pt.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-ru.json b/docs/translations/api-docs/masonry/masonry-ru.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-ru.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry-zh.json b/docs/translations/api-docs/masonry/masonry-zh.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry-zh.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/api-docs/masonry/masonry.json b/docs/translations/api-docs/masonry/masonry.json
new file mode 100644
index 00000000000000..548f75a86fdaa2
--- /dev/null
+++ b/docs/translations/api-docs/masonry/masonry.json
@@ -0,0 +1,12 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "children": "The content of the component. It's recommended to be <MasonryItem />
s.",
+ "classes": "Override or extend the styles applied to the component. See CSS API below for more details.",
+ "columns": "Number of columns.",
+ "component": "The component used for the root node. Either a string to use a HTML element or a component.",
+ "spacing": "Defines the space between children. It is a factor of the theme's spacing.",
+ "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details."
+ },
+ "classDescriptions": { "root": { "description": "Styles applied to the root element." } }
+}
diff --git a/docs/translations/translations.json b/docs/translations/translations.json
index 840063aa745e2a..7b5ba99ae91880 100644
--- a/docs/translations/translations.json
+++ b/docs/translations/translations.json
@@ -260,6 +260,7 @@
"/components/date-range-picker": "Date Range Picker ⚡️",
"/components/date-time-picker": "Date Time Picker",
"/components/time-picker": "Time Picker",
+ "/components/masonry": "Masonry",
"/components/timeline": "Timeline",
"/components/trap-focus": "Trap Focus",
"/components/tree-view": "Tree View",
diff --git a/packages/material-ui-lab/src/Masonry/Masonry.d.ts b/packages/material-ui-lab/src/Masonry/Masonry.d.ts
new file mode 100644
index 00000000000000..90a5f588fa6956
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/Masonry.d.ts
@@ -0,0 +1,50 @@
+import { ResponsiveStyleValue, SxProps } from '@material-ui/system';
+import { OverridableComponent, OverrideProps } from '@material-ui/core/OverridableComponent';
+import { Theme } from '@material-ui/core/styles';
+import { MasonryClasses } from './masonryClasses';
+
+export interface MasonryTypeMap {
+ props: P & {
+ /**
+ * The content of the component. It's recommended to be ``s.
+ */
+ children: NonNullable;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * Number of columns.
+ * @default 4
+ */
+ columns?: ResponsiveStyleValue;
+ /**
+ * Defines the space between children. It is a factor of the theme's spacing.
+ * @default 1
+ */
+ spacing?: ResponsiveStyleValue;
+ /**
+ * Allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ };
+ defaultComponent: D;
+}
+/**
+ *
+ * Demos:
+ *
+ * - [Masonry](https://material-ui.com/components/masonry/)
+ *
+ * API:
+ *
+ * - [Masonry API](https://material-ui.com/api/masonry/)
+ */
+declare const Masonry: OverridableComponent;
+
+export type MasonryProps<
+ D extends React.ElementType = MasonryTypeMap['defaultComponent'],
+ P = {},
+> = OverrideProps, D>;
+
+export default Masonry;
diff --git a/packages/material-ui-lab/src/Masonry/Masonry.js b/packages/material-ui-lab/src/Masonry/Masonry.js
new file mode 100644
index 00000000000000..656aa2ffb3b63d
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/Masonry.js
@@ -0,0 +1,177 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import {
+ createUnarySpacing,
+ getValue,
+ handleBreakpoints,
+ unstable_resolveBreakpointValues as resolveBreakpointValues,
+} from '@material-ui/system';
+import { deepmerge, unstable_useForkRef as useForkRef } from '@material-ui/utils';
+import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
+import { styled, useThemeProps } from '@material-ui/core/styles';
+import { getMasonryUtilityClass } from './masonryClasses';
+import MasonryContext from './MasonryContext';
+
+const useUtilityClasses = (ownerState) => {
+ const { classes } = ownerState;
+
+ const slots = {
+ root: ['root'],
+ };
+
+ return composeClasses(slots, getMasonryUtilityClass, classes);
+};
+
+export const style = ({ ownerState, theme }) => {
+ let styles = {
+ display: 'grid',
+ gridAutoRows: 0,
+ padding: 0,
+ overflow: 'auto',
+ width: '100%',
+ rowGap: 2,
+ boxSizing: 'border-box',
+ };
+
+ const base = {};
+ Object.keys(theme.breakpoints.values).forEach((breakpoint) => {
+ if (ownerState.spacing[breakpoint] != null) {
+ base[breakpoint] = true;
+ }
+ });
+
+ const spacingValues = resolveBreakpointValues({ values: ownerState.spacing, base });
+ const transformer = createUnarySpacing(theme);
+ const spacingStyleFromPropValue = (propValue) => {
+ return {
+ columnGap: getValue(transformer, propValue),
+ };
+ };
+
+ styles = {
+ ...styles,
+ ...handleBreakpoints({ theme }, spacingValues, spacingStyleFromPropValue),
+ };
+
+ const columnValues = resolveBreakpointValues({ values: ownerState.columns, base });
+ const columnStyleFromPropValue = (propValue) => {
+ return {
+ gridTemplateColumns: `repeat(${propValue}, 1fr)`,
+ };
+ };
+
+ styles = deepmerge(styles, handleBreakpoints({ theme }, columnValues, columnStyleFromPropValue));
+
+ return styles;
+};
+
+const MasonryRoot = styled('div', {
+ name: 'MuiMasonry',
+ slot: 'Root',
+ overridesResolver: (props, styles) => {
+ return [styles.root];
+ },
+})(style);
+
+const Masonry = React.forwardRef(function Masonry(inProps, ref) {
+ const props = useThemeProps({
+ props: inProps,
+ name: 'MuiMasonry',
+ });
+
+ const masonryRef = React.useRef();
+ const { children, className, component = 'div', columns = 4, spacing = 1, ...other } = props;
+ const ownerState = { ...props, spacing, columns };
+ const classes = useUtilityClasses(ownerState);
+
+ const contextValue = React.useMemo(() => ({ spacing }), [spacing]);
+ let didWarn = false;
+ React.useEffect(() => {
+ // scroller always appears when masonry's height goes beyond 2,000px on Chrome
+ const handleScroll = () => {
+ if (masonryRef.current.clientHeight === 1998 && !didWarn) {
+ console.warn(
+ [
+ 'Material-UI: The Masonry can have the maximum height of 2,000px on Chrome browser.',
+ 'Items that go beyond this height fail to be rendered on Chrome browser.',
+ 'You can find more in this open issue: https://github.com/mui-org/material-ui/issues/27934',
+ ].join('\n'),
+ );
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ didWarn = true;
+ }
+ };
+ const container = masonryRef.current;
+ container.addEventListener('scroll', handleScroll);
+ return () => {
+ container.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ const handleRef = useForkRef(ref, masonryRef);
+ return (
+
+
+ {children}
+
+
+ );
+});
+
+Masonry.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component. It's recommended to be ``s.
+ */
+ children: PropTypes /* @typescript-to-proptypes-ignore */.node.isRequired,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * Number of columns.
+ * @default 4
+ */
+ columns: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
+ PropTypes.number,
+ PropTypes.object,
+ PropTypes.string,
+ ]),
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * Defines the space between children. It is a factor of the theme's spacing.
+ * @default 1
+ */
+ spacing: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
+ PropTypes.number,
+ PropTypes.object,
+ PropTypes.string,
+ ]),
+ /**
+ * Allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.object,
+};
+
+export default Masonry;
diff --git a/packages/material-ui-lab/src/Masonry/Masonry.test.js b/packages/material-ui-lab/src/Masonry/Masonry.test.js
new file mode 100644
index 00000000000000..48d1ab18104dab
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/Masonry.test.js
@@ -0,0 +1,114 @@
+import { expect } from 'chai';
+import * as React from 'react';
+import { createClientRender, describeConformance } from 'test/utils';
+import Masonry, { masonryClasses as classes } from '@material-ui/lab/Masonry';
+import { createTheme } from '@material-ui/core/styles';
+import defaultTheme from '@material-ui/core/styles/defaultTheme';
+import { style } from './Masonry';
+
+describe('', () => {
+ const render = createClientRender();
+
+ describeConformance(
+
+
+ ,
+ () => ({
+ classes,
+ inheritComponent: 'div',
+ render,
+ refInstanceof: window.HTMLDivElement,
+ testComponentPropWith: 'span',
+ muiName: 'MuiMasonry',
+ skip: ['componentsProp', 'themeVariants'],
+ }),
+ );
+
+ const theme = createTheme({ spacing: 8 });
+
+ describe('style attribute:', () => {
+ it('should render with correct default styles', () => {
+ expect(
+ style({
+ ownerState: {
+ columns: 4,
+ spacing: 1,
+ },
+ theme,
+ }),
+ ).to.deep.equal({
+ display: 'grid',
+ gridAutoRows: 0,
+ padding: 0,
+ overflow: 'auto',
+ width: '100%',
+ rowGap: 2,
+ columnGap: theme.spacing(1),
+ gridTemplateColumns: 'repeat(4, 1fr)',
+ boxSizing: 'border-box',
+ });
+ });
+
+ it('should render with column gap responsive to breakpoints', () => {
+ expect(
+ style({
+ ownerState: {
+ columns: 4,
+ spacing: { xs: 1, sm: 2, md: 3 },
+ },
+ theme,
+ }),
+ ).to.deep.equal({
+ '@media (min-width:0px)': {
+ columnGap: theme.spacing(1),
+ gridTemplateColumns: 'repeat(4, 1fr)',
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
+ columnGap: theme.spacing(2),
+ gridTemplateColumns: 'repeat(4, 1fr)',
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
+ columnGap: theme.spacing(3),
+ gridTemplateColumns: 'repeat(4, 1fr)',
+ },
+ display: 'grid',
+ gridAutoRows: 0,
+ padding: 0,
+ overflow: 'auto',
+ width: '100%',
+ rowGap: 2,
+ boxSizing: 'border-box',
+ });
+ });
+
+ it('should render with grid-template-columns responsive to breakpoints', () => {
+ expect(
+ style({
+ ownerState: {
+ columns: { xs: 3, sm: 5, md: 7 },
+ spacing: 1,
+ },
+ theme,
+ }),
+ ).to.deep.equal({
+ '@media (min-width:0px)': {
+ gridTemplateColumns: 'repeat(3, 1fr)',
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
+ gridTemplateColumns: 'repeat(5, 1fr)',
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
+ gridTemplateColumns: 'repeat(7, 1fr)',
+ },
+ display: 'grid',
+ gridAutoRows: 0,
+ padding: 0,
+ overflow: 'auto',
+ width: '100%',
+ columnGap: theme.spacing(1),
+ rowGap: 2,
+ boxSizing: 'border-box',
+ });
+ });
+ });
+});
diff --git a/packages/material-ui-lab/src/Masonry/MasonryContext.js b/packages/material-ui-lab/src/Masonry/MasonryContext.js
new file mode 100644
index 00000000000000..54f687f5a0d81f
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/MasonryContext.js
@@ -0,0 +1,12 @@
+import * as React from 'react';
+
+/**
+ * @ignore - internal component.
+ */
+const MasonryContext = React.createContext({});
+
+if (process.env.NODE_ENV !== 'production') {
+ MasonryContext.displayName = 'MasonryContext';
+}
+
+export default MasonryContext;
diff --git a/packages/material-ui-lab/src/Masonry/index.d.ts b/packages/material-ui-lab/src/Masonry/index.d.ts
new file mode 100644
index 00000000000000..f5e1cb096abdf2
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/index.d.ts
@@ -0,0 +1,5 @@
+export * from './Masonry';
+export { default } from './Masonry';
+
+export * from './masonryClasses';
+export { default as masonryClasses } from './masonryClasses';
diff --git a/packages/material-ui-lab/src/Masonry/index.js b/packages/material-ui-lab/src/Masonry/index.js
new file mode 100644
index 00000000000000..055003e228425e
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/index.js
@@ -0,0 +1,4 @@
+export { default } from './Masonry';
+
+export * from './masonryClasses';
+export { default as masonryClasses } from './masonryClasses';
diff --git a/packages/material-ui-lab/src/Masonry/masonryClasses.ts b/packages/material-ui-lab/src/Masonry/masonryClasses.ts
new file mode 100644
index 00000000000000..bcef28f0b67017
--- /dev/null
+++ b/packages/material-ui-lab/src/Masonry/masonryClasses.ts
@@ -0,0 +1,16 @@
+import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';
+
+export interface MasonryClasses {
+ /** Styles applied to the root element. */
+ root: string;
+}
+
+export type MasonryClassKey = keyof MasonryClasses;
+
+export function getMasonryUtilityClass(slot: string): string {
+ return generateUtilityClass('MuiMasonry', slot);
+}
+
+const masonryClasses: MasonryClasses = generateUtilityClasses('MuiMasonry', ['root']);
+
+export default masonryClasses;
diff --git a/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts b/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts
new file mode 100644
index 00000000000000..b7a773daf4e0ca
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts
@@ -0,0 +1,49 @@
+import { SxProps } from '@material-ui/system';
+import { OverridableComponent, OverrideProps } from '@material-ui/core/OverridableComponent';
+import { Theme } from '@material-ui/core/styles';
+import { MasonryItemClasses } from './masonryItemClasses';
+
+export interface MasonryItemTypeMap {
+ props: P & {
+ /**
+ * The content of the component, normally an `` or a `
`. It should be only one element.
+ */
+ children: NonNullable;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * The initial height of the component in px. This is provided for server-side rendering.
+ */
+ defaultHeight?: number;
+ /**
+ * The number of columns taken up by the component
+ * @default 1
+ */
+ columnSpan?: number;
+ /**
+ * Allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ };
+ defaultComponent: D;
+}
+/**
+ *
+ * Demos:
+ *
+ * - [Masonry](https://material-ui.com/components/masonry/)
+ *
+ * API:
+ *
+ * - [MasonryItem API](https://material-ui.com/api/masonry-item/)
+ */
+declare const MasonryItem: OverridableComponent;
+
+export type MasonryItemProps<
+ D extends React.ElementType = MasonryItemTypeMap['defaultComponent'],
+ P = {},
+> = OverrideProps, D>;
+
+export default MasonryItem;
diff --git a/packages/material-ui-lab/src/MasonryItem/MasonryItem.js b/packages/material-ui-lab/src/MasonryItem/MasonryItem.js
new file mode 100644
index 00000000000000..d69ac0eacce3f9
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/MasonryItem.js
@@ -0,0 +1,188 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import {
+ createUnarySpacing,
+ getValue,
+ handleBreakpoints,
+ unstable_resolveBreakpointValues as resolveBreakpointValues,
+} from '@material-ui/system';
+import { unstable_useForkRef as useForkRef } from '@material-ui/utils';
+import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
+import { styled, useThemeProps, useTheme } from '@material-ui/core/styles';
+import { getMasonryItemUtilityClass } from './masonryItemClasses';
+import MasonryContext from '../Masonry/MasonryContext';
+
+// dummy resize observer used to prevent crash for old browsers that do not support ResizeObserver API(e.g., 11IE)
+const MockResizeObserver = () => {
+ return {
+ observe: () => {},
+ unobserve: () => {},
+ disconnect: () => {},
+ };
+};
+
+const useUtilityClasses = (ownerState) => {
+ const { classes } = ownerState;
+
+ const slots = {
+ root: ['root'],
+ };
+
+ return composeClasses(slots, getMasonryItemUtilityClass, classes);
+};
+
+export const style = ({ ownerState, theme }) => {
+ let styles = {
+ width: '100%',
+ '& > *': {
+ // all contents should have a width of 100%
+ width: '100%',
+ boxSizing: 'inherit',
+ ...(ownerState.isSSR && { height: '100%' }),
+ },
+ visibility: ownerState.height ? 'visible' : 'hidden',
+ gridColumnEnd: `span ${ownerState.columnSpan}`,
+ boxSizing: 'inherit',
+ };
+
+ if (Array.isArray(ownerState.spacing) || typeof ownerState.spacing === 'object') {
+ const base = {};
+ Object.keys(theme.breakpoints.values).forEach((breakpoint) => {
+ if (ownerState.spacing[breakpoint] != null) {
+ base[breakpoint] = true;
+ }
+ });
+ const spacingValues = resolveBreakpointValues({ values: ownerState.spacing, base });
+ const transformer = createUnarySpacing(theme);
+ const styleFromPropValue = (propValue) => {
+ const gap = ownerState.height
+ ? Number(getValue(transformer, propValue).replace('px', ''))
+ : 0;
+ // For lazy-loaded images to load properly, masonry item should take up space greater than 1px.
+ // Taking into account a row gap of 2px, rowSpan should at least be 2.
+ const rowSpan = ownerState.height ? Math.ceil((ownerState.height + gap) / 2) : 2;
+ return {
+ gridRowEnd: `span ${rowSpan}`,
+ paddingBottom: gap === 0 ? 0 : gap - 2,
+ };
+ };
+ styles = { ...styles, ...handleBreakpoints({ theme }, spacingValues, styleFromPropValue) };
+ }
+ return styles;
+};
+
+const MasonryItemRoot = styled('div', {
+ name: 'MuiMasonryItem',
+ slot: 'Root',
+ overridesResolver: (props, styles) => {
+ return [styles.root];
+ },
+})(style);
+
+const MasonryItem = React.forwardRef(function MasonryItem(inProps, ref) {
+ const props = useThemeProps({
+ props: inProps,
+ name: 'MuiMasonryItem',
+ });
+
+ const masonryItemRef = React.useRef(null);
+
+ const { spacing = 1 } = React.useContext(MasonryContext);
+ const { children, className, component = 'div', columnSpan = 1, defaultHeight, ...other } = props;
+ const isSSR = defaultHeight !== undefined;
+
+ const [height, setHeight] = React.useState(defaultHeight);
+
+ const ownerState = {
+ ...props,
+ isSSR,
+ spacing,
+ columnSpan,
+ height: height < 0 ? 0 : height, // MasonryItems to which negative or zero height is passed will be hidden
+ };
+
+ const classes = useUtilityClasses(ownerState);
+ const resizeObserver = React.useRef(null);
+ React.useEffect(() => {
+ // do not create a resize observer in case of SSR masonry
+ if (isSSR) {
+ return () => {};
+ }
+ try {
+ resizeObserver.current = new ResizeObserver(([item]) => {
+ setHeight(item.contentRect.height);
+ });
+ } catch (err) {
+ resizeObserver.current = MockResizeObserver();
+ }
+ const item = masonryItemRef.current.firstChild;
+ resizeObserver.current.observe(item);
+ return () => {
+ resizeObserver.current.unobserve(item);
+ };
+ }, [isSSR]);
+
+ const handleRef = useForkRef(ref, masonryItemRef);
+
+ const theme = useTheme();
+ const styleProp = {};
+ if (!Array.isArray(spacing) && typeof spacing !== 'object') {
+ const gap = height ? Number(theme.spacing(spacing).replace('px', '')) : 0;
+ const rowSpan = height ? Math.ceil((height + gap) / 2) : 2;
+ styleProp.gridRowEnd = `span ${rowSpan}`;
+ styleProp.paddingBottom = gap === 0 ? 0 : gap - 2;
+ }
+
+ return (
+
+ {React.Children.only(children)}
+
+ );
+});
+
+MasonryItem.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component, normally an `` or a ``. It should be only one element.
+ */
+ children: PropTypes.element.isRequired,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The number of columns taken up by the component
+ * @default 1
+ */
+ columnSpan: PropTypes.number,
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * The initial height of the component in px. This is provided for server-side rendering.
+ */
+ defaultHeight: PropTypes.number,
+ /**
+ * Allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.object,
+};
+
+export default MasonryItem;
diff --git a/packages/material-ui-lab/src/MasonryItem/MasonryItem.test.js b/packages/material-ui-lab/src/MasonryItem/MasonryItem.test.js
new file mode 100644
index 00000000000000..c0173c121f63d6
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/MasonryItem.test.js
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import { createClientRender, describeConformance } from 'test/utils';
+import MasonryItem, { masonryItemClasses as classes } from '@material-ui/lab/MasonryItem';
+import { expect } from 'chai';
+import { createTheme } from '@material-ui/core/styles';
+import defaultTheme from '@material-ui/core/styles/defaultTheme';
+import { style } from './MasonryItem';
+
+describe('', () => {
+ const render = createClientRender();
+
+ describeConformance(
+
+
+ ,
+ () => ({
+ classes,
+ inheritComponent: 'div',
+ render,
+ refInstanceof: window.HTMLDivElement,
+ testComponentPropWith: 'span',
+ muiName: 'MuiMasonryItem',
+ skip: [
+ 'componentsProp',
+ 'themeVariants',
+ // reactTestRenderer fails due to this error: `TypeError: parameter 1 is not of type "Element"`
+ 'reactTestRenderer',
+ ],
+ }),
+ );
+
+ const children = ;
+ const theme = createTheme({
+ spacing: 8,
+ });
+
+ it('should render children by default', () => {
+ const { getByTestId } = render({children});
+ expect(getByTestId('test-children')).not.to.equal(null);
+ });
+
+ describe('style attribute:', () => {
+ it('should render with padding bottom and grid-row-end responsive to breakpoints', () => {
+ expect(
+ style({
+ ownerState: {
+ height: 100,
+ columnSpan: 1,
+ spacing: { xs: 1, sm: 2, md: 3 },
+ },
+ theme,
+ }),
+ ).to.deep.equal({
+ '@media (min-width:0px)': {
+ gridRowEnd: `span ${Math.ceil((100 + Number(theme.spacing(1).replace('px', ''))) / 2)}`,
+ paddingBottom: Number(theme.spacing(1).replace('px', '')) - 2,
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
+ gridRowEnd: `span ${Math.ceil((100 + Number(theme.spacing(2).replace('px', ''))) / 2)}`,
+ paddingBottom: Number(theme.spacing(2).replace('px', '')) - 2,
+ },
+ [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
+ gridRowEnd: `span ${Math.ceil((100 + Number(theme.spacing(3).replace('px', ''))) / 2)}`,
+ paddingBottom: Number(theme.spacing(3).replace('px', '')) - 2,
+ },
+ width: '100%',
+ [`& > *`]: {
+ width: '100%',
+ boxSizing: 'inherit',
+ },
+ visibility: 'visible',
+ gridColumnEnd: 'span 1',
+ boxSizing: 'inherit',
+ });
+ });
+
+ it('should render with given column span', () => {
+ expect(
+ style({
+ ownerState: {
+ height: 100,
+ columnSpan: 2,
+ spacing: 1,
+ },
+ theme,
+ }),
+ ).to.deep.equal({
+ width: '100%',
+ [`& > *`]: {
+ width: '100%',
+ boxSizing: 'inherit',
+ },
+ visibility: 'visible',
+ gridColumnEnd: 'span 2',
+ boxSizing: 'inherit',
+ });
+ });
+
+ it('should compute grid-row-end based on given height', () => {
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+ const computedStyle = getComputedStyle(getByTestId('test-root'));
+ expect(computedStyle['grid-row-end']).to.equal(
+ `span ${Math.ceil((150 + Number(theme.spacing(1).replace('px', ''))) / 2)}`,
+ );
+ });
+ });
+});
diff --git a/packages/material-ui-lab/src/MasonryItem/index.d.ts b/packages/material-ui-lab/src/MasonryItem/index.d.ts
new file mode 100644
index 00000000000000..5f7ad58b1ae710
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/index.d.ts
@@ -0,0 +1,5 @@
+export * from './MasonryItem';
+export { default } from './MasonryItem';
+
+export * from './masonryItemClasses';
+export { default as masonryItemClasses } from './masonryItemClasses';
diff --git a/packages/material-ui-lab/src/MasonryItem/index.js b/packages/material-ui-lab/src/MasonryItem/index.js
new file mode 100644
index 00000000000000..126c43ca7ee4c2
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/index.js
@@ -0,0 +1,4 @@
+export { default } from './MasonryItem';
+
+export * from './masonryItemClasses';
+export { default as masonryItemClasses } from './masonryItemClasses';
diff --git a/packages/material-ui-lab/src/MasonryItem/masonryItemClasses.ts b/packages/material-ui-lab/src/MasonryItem/masonryItemClasses.ts
new file mode 100644
index 00000000000000..38e9be4fa595e8
--- /dev/null
+++ b/packages/material-ui-lab/src/MasonryItem/masonryItemClasses.ts
@@ -0,0 +1,16 @@
+import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';
+
+export interface MasonryItemClasses {
+ /** Styles applied to the root element. */
+ root: string;
+}
+
+export type MasonryItemClassKey = keyof MasonryItemClasses;
+
+export function getMasonryItemUtilityClass(slot: string): string {
+ return generateUtilityClass('MuiMasonryItem', slot);
+}
+
+const masonryItemClasses: MasonryItemClasses = generateUtilityClasses('MuiMasonryItem', ['root']);
+
+export default masonryItemClasses;
diff --git a/packages/material-ui-system/src/breakpoints.js b/packages/material-ui-system/src/breakpoints.js
index 084339319d2404..b070c42e9f8f9f 100644
--- a/packages/material-ui-system/src/breakpoints.js
+++ b/packages/material-ui-system/src/breakpoints.js
@@ -113,4 +113,27 @@ export function mergeBreakpointsInOrder(breakpointsInput, ...styles) {
return removeUnusedBreakpoints(Object.keys(emptyBreakpoints), mergedOutput);
}
+export function resolveBreakpointValues({ values: breakpointValues, base }) {
+ const keys = Object.keys(base);
+
+ if (keys.length === 0) {
+ return breakpointValues;
+ }
+
+ let previous;
+
+ return keys.reduce((acc, breakpoint) => {
+ if (typeof breakpointValues === 'object') {
+ acc[breakpoint] =
+ breakpointValues[breakpoint] != null
+ ? breakpointValues[breakpoint]
+ : breakpointValues[previous];
+ } else {
+ acc[breakpoint] = breakpointValues;
+ }
+ previous = breakpoint;
+ return acc;
+ }, {});
+}
+
export default breakpoints;
diff --git a/packages/material-ui-system/src/index.js b/packages/material-ui-system/src/index.js
index c70da053d26e83..8ee5f188fe3ce7 100644
--- a/packages/material-ui-system/src/index.js
+++ b/packages/material-ui-system/src/index.js
@@ -2,7 +2,11 @@ export { css, keyframes, GlobalStyles, StyledEngineProvider } from '@material-ui
export { default as borders } from './borders';
export * from './borders';
export { default as breakpoints } from './breakpoints';
-export { handleBreakpoints, mergeBreakpointsInOrder } from './breakpoints';
+export {
+ handleBreakpoints,
+ mergeBreakpointsInOrder,
+ resolveBreakpointValues as unstable_resolveBreakpointValues,
+} from './breakpoints';
export { default as compose } from './compose';
export { default as display } from './display';
export { default as flexbox } from './flexbox';
diff --git a/packages/material-ui/src/Grid/Grid.js b/packages/material-ui/src/Grid/Grid.js
index f864f5bce7a1de..c8ca466ae313a6 100644
--- a/packages/material-ui/src/Grid/Grid.js
+++ b/packages/material-ui/src/Grid/Grid.js
@@ -12,7 +12,11 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
-import { unstable_extendSxProp as extendSxProp, handleBreakpoints } from '@material-ui/system';
+import {
+ unstable_extendSxProp as extendSxProp,
+ handleBreakpoints,
+ unstable_resolveBreakpointValues as resolveBreakpointValues,
+} from '@material-ui/system';
import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
import requirePropFactory from '../utils/requirePropFactory';
import styled from '../styles/styled';
@@ -25,27 +29,6 @@ function getOffset(val) {
return `${parse}${String(val).replace(String(parse), '') || 'px'}`;
}
-// Duplicated with Stack.js
-function resolveBreakpointValues({ values, base }) {
- const keys = Object.keys(base);
-
- if (keys.length === 0) {
- return values;
- }
-
- let previous;
-
- return keys.reduce((acc, breakpoint) => {
- if (typeof values === 'object') {
- acc[breakpoint] = values[breakpoint] != null ? values[breakpoint] : values[previous];
- } else {
- acc[breakpoint] = values;
- }
- previous = breakpoint;
- return acc;
- }, {});
-}
-
function generateGrid(globalStyles, theme, breakpoint, ownerState) {
const size = ownerState[breakpoint];
diff --git a/packages/material-ui/src/Stack/Stack.js b/packages/material-ui/src/Stack/Stack.js
index d951873c43ba34..d4f1d6a9624e9f 100644
--- a/packages/material-ui/src/Stack/Stack.js
+++ b/packages/material-ui/src/Stack/Stack.js
@@ -5,6 +5,7 @@ import {
getValue,
handleBreakpoints,
unstable_extendSxProp as extendSxProp,
+ unstable_resolveBreakpointValues as resolveBreakpointValues,
} from '@material-ui/system';
import { deepmerge } from '@material-ui/utils';
import styled from '../styles/styled';
@@ -31,27 +32,6 @@ function joinChildren(children, separator) {
}, []);
}
-// Duplicated with Grid.js
-function resolveBreakpointValues({ values, base }) {
- const keys = Object.keys(base);
-
- if (keys.length === 0) {
- return values;
- }
-
- let previous;
-
- return keys.reduce((acc, breakpoint) => {
- if (typeof values === 'object') {
- acc[breakpoint] = values[breakpoint] != null ? values[breakpoint] : values[previous];
- } else {
- acc[breakpoint] = values;
- }
- previous = breakpoint;
- return acc;
- }, {});
-}
-
const getSideFromDirection = (direction) => {
return {
row: 'Left',
diff --git a/test/regressions/index.js b/test/regressions/index.js
index 5febba1071a708..6575785d8044e6 100644
--- a/test/regressions/index.js
+++ b/test/regressions/index.js
@@ -68,6 +68,7 @@ const blacklist = [
'docs-components-hidden', // Need to dynamically resize to test
'docs-components-icons/FontAwesomeIconSize.png', // Relies on cascading network requests
'docs-components-image-list', // Image don't load
+ 'docs-components-masonry/ImageMasonry.png', // Image don't load
'docs-components-material-icons/synonyms.png', // No component
'docs-components-menus', // Need interaction
'docs-components-modal/KeepMountedModal.png', // Needs interaction