From d32d3d598801d0e2f41949079e2bd817b5ab9dc7 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 27 Jul 2021 11:06:31 +0100 Subject: [PATCH] Receive columnSpan prop for MasonryItem and use ResizeObserver --- docs/pages/api-docs/masonry-item.json | 1 + docs/pages/api-docs/masonry.json | 2 +- .../pages/components/masonry/BasicMasonry.js | 8 +- .../pages/components/masonry/BasicMasonry.tsx | 10 +- .../components/masonry/DiffColSizeMasonry.js | 44 ++++++ .../components/masonry/DiffColSizeMasonry.tsx | 44 ++++++ .../pages/components/masonry/ImageMasonry.js | 95 ++++++------- .../pages/components/masonry/ImageMasonry.tsx | 97 +++++++------- docs/src/pages/components/masonry/masonry.md | 6 +- .../api-docs/masonry-item/masonry-item.json | 1 + .../api-docs/masonry/masonry.json | 2 +- package.json | 1 + .../material-ui-lab/src/Masonry/Masonry.d.ts | 2 +- .../material-ui-lab/src/Masonry/Masonry.js | 81 +++++------ .../src/Masonry/Masonry.test.js | 126 ++++++++++++++++++ .../src/MasonryItem/MasonryItem.d.ts | 5 + .../src/MasonryItem/MasonryItem.js | 82 ++++++------ .../src/MasonryItem/MasonryItem.test.js | 110 ++++++++++++++- packages/material-ui-system/src/index.js | 6 +- packages/material-ui/src/Grid/Grid.js | 2 +- packages/material-ui/src/Stack/Stack.js | 2 +- yarn.lock | 5 + 22 files changed, 539 insertions(+), 193 deletions(-) create mode 100644 docs/src/pages/components/masonry/DiffColSizeMasonry.js create mode 100644 docs/src/pages/components/masonry/DiffColSizeMasonry.tsx diff --git a/docs/pages/api-docs/masonry-item.json b/docs/pages/api-docs/masonry-item.json index 7e2fb0fa099e95..6e0c37d63f423c 100644 --- a/docs/pages/api-docs/masonry-item.json +++ b/docs/pages/api-docs/masonry-item.json @@ -2,6 +2,7 @@ "props": { "children": { "type": { "name": "element" }, "required": true }, "classes": { "type": { "name": "object" } }, + "columnSpan": { "type": { "name": "number" }, "default": "1" }, "component": { "type": { "name": "elementType" } }, "sx": { "type": { "name": "object" } } }, diff --git a/docs/pages/api-docs/masonry.json b/docs/pages/api-docs/masonry.json index 2b111870fb2a3e..fc83112a6a8a9a 100644 --- a/docs/pages/api-docs/masonry.json +++ b/docs/pages/api-docs/masonry.json @@ -2,7 +2,7 @@ "props": { "children": { "type": { "name": "node" }, "required": true }, "classes": { "type": { "name": "object" } }, - "cols": { + "columns": { "type": { "name": "union", "description": "Array<number
| string>
| number
| object
| string" diff --git a/docs/src/pages/components/masonry/BasicMasonry.js b/docs/src/pages/components/masonry/BasicMasonry.js index 12f0ace16a64f1..54d8a1c01b88db 100644 --- a/docs/src/pages/components/masonry/BasicMasonry.js +++ b/docs/src/pages/components/masonry/BasicMasonry.js @@ -6,9 +6,9 @@ import MasonryItem from '@material-ui/lab/MasonryItem'; export default function BasicMasonry() { return ( - - {heights.map((height, idx) => ( - + + {heights.map((height, index) => ( + - {idx + 1} + {index + 1} ))} diff --git a/docs/src/pages/components/masonry/BasicMasonry.tsx b/docs/src/pages/components/masonry/BasicMasonry.tsx index 5368dca95c091b..54d8a1c01b88db 100644 --- a/docs/src/pages/components/masonry/BasicMasonry.tsx +++ b/docs/src/pages/components/masonry/BasicMasonry.tsx @@ -4,11 +4,11 @@ import Box from '@material-ui/core/Box'; import Masonry from '@material-ui/lab/Masonry'; import MasonryItem from '@material-ui/lab/MasonryItem'; -export default function BasicMasonry(): JSX.Element { +export default function BasicMasonry() { return ( - - {heights.map((height, idx) => ( - + + {heights.map((height, index) => ( + - {idx + 1} + {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..b02dd96d6738e8 --- /dev/null +++ b/docs/src/pages/components/masonry/DiffColSizeMasonry.js @@ -0,0 +1,44 @@ +/* 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, span: 1 }, + { height: 30, span: 1 }, + { height: 90, span: 2 }, + { height: 110, span: 1 }, + { height: 150, span: 1 }, + { height: 150, span: 1 }, + { height: 130, span: 2 }, + { height: 80, span: 2 }, + { height: 50, span: 1 }, + { height: 90, span: 1 }, + { height: 100, span: 2 }, + { height: 150, span: 1 }, + { height: 50, span: 1 }, + { height: 50, span: 2 }, + { height: 50, span: 1 }, +]; diff --git a/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx b/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx new file mode 100644 index 00000000000000..b02dd96d6738e8 --- /dev/null +++ b/docs/src/pages/components/masonry/DiffColSizeMasonry.tsx @@ -0,0 +1,44 @@ +/* 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, span: 1 }, + { height: 30, span: 1 }, + { height: 90, span: 2 }, + { height: 110, span: 1 }, + { height: 150, span: 1 }, + { height: 150, span: 1 }, + { height: 130, span: 2 }, + { height: 80, span: 2 }, + { height: 50, span: 1 }, + { height: 90, span: 1 }, + { height: 100, span: 2 }, + { height: 150, span: 1 }, + { height: 50, span: 1 }, + { height: 50, span: 2 }, + { height: 50, span: 1 }, +]; diff --git a/docs/src/pages/components/masonry/ImageMasonry.js b/docs/src/pages/components/masonry/ImageMasonry.js index 097d7d8012cf27..4aa77116218355 100644 --- a/docs/src/pages/components/masonry/ImageMasonry.js +++ b/docs/src/pages/components/masonry/ImageMasonry.js @@ -6,98 +6,103 @@ import MasonryItem from '@material-ui/lab/MasonryItem'; export default function ImageMasonry() { return ( - {imgData.map((item, idx) => ( - - {item.title} + {itemData.map((item) => ( + + {item.title} ))} ); } -const imgData = [ +const itemData = [ { - img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', - title: '1', + img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f', + title: 'Fern', }, { - img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '2', + img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25', + title: 'Mushrooms', }, { - img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', - title: '3', + img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', + title: 'Tower', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '4', + img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1', + title: 'Sea star', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - title: '5', - }, - { - img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '6', + img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', + title: 'Bridge', }, { - img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', - title: '7', + img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62', + title: 'Honey', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/19/08/32/marguerite-729510__340.jpg', - title: '8', + img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', + title: 'Lake', }, { - img: 'https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270', - title: '9', + img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6', + title: 'Basketball', }, { img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '10', + title: 'Birds', }, { - img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', - title: '11', + img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e', + title: 'Breakfast', }, { - img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '12', + img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + title: 'Sunset', }, { - img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', - title: '13', + img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d', + title: 'Burger', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '14', + img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', + title: 'Picture', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - title: '15', + img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45', + title: 'Camera', }, { - img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '16', + img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', + title: 'Landscape', }, { - img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', - title: '17', + img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c', + title: 'Coffee', }, { img: 'https://cdn.pixabay.com/photo/2015/04/19/08/32/marguerite-729510__340.jpg', - title: '18', + title: 'Flower', }, { - img: 'https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270', - title: '19', + img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8', + title: 'Hats', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '20', + img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af', + title: 'Tomato basil', + }, + { + 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 index 9ce7bea53a4bef..4aa77116218355 100644 --- a/docs/src/pages/components/masonry/ImageMasonry.tsx +++ b/docs/src/pages/components/masonry/ImageMasonry.tsx @@ -3,101 +3,106 @@ import * as React from 'react'; import Masonry from '@material-ui/lab/Masonry'; import MasonryItem from '@material-ui/lab/MasonryItem'; -export default function ImageMasonry(): JSX.Element { +export default function ImageMasonry() { return ( - {imgData.map((item, idx) => ( - - {item.title} + {itemData.map((item) => ( + + {item.title} ))} ); } -const imgData = [ +const itemData = [ { - img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', - title: '1', + img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f', + title: 'Fern', }, { - img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '2', + img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25', + title: 'Mushrooms', }, { - img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', - title: '3', + img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', + title: 'Tower', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '4', + img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1', + title: 'Sea star', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - title: '5', - }, - { - img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '6', + img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', + title: 'Bridge', }, { - img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', - title: '7', + img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62', + title: 'Honey', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/19/08/32/marguerite-729510__340.jpg', - title: '8', + img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', + title: 'Lake', }, { - img: 'https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270', - title: '9', + img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6', + title: 'Basketball', }, { img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '10', + title: 'Birds', }, { - img: 'https://images.unsplash.com/photo-1529655683826-aba9b3e77383?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8bG9uZG9ufGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&w=1000&q=80', - title: '11', + img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e', + title: 'Breakfast', }, { - img: 'https://images.pexels.com/photos/1761279/pexels-photo-1761279.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '12', + img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + title: 'Sunset', }, { - img: 'https://www.nature.com/immersive/d41586-021-00095-y/assets/3TP4N718ac/2021-01-xx_jan-iom_tree-of-life_sh-1080x1440.jpeg', - title: '13', + img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d', + title: 'Burger', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '14', + img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', + title: 'Picture', }, { - img: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - title: '15', + img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45', + title: 'Camera', }, { - img: 'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500', - title: '16', + img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', + title: 'Landscape', }, { - img: 'https://cdn.pixabay.com/photo/2016/05/05/02/37/sunset-1373171__340.jpg', - title: '17', + img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c', + title: 'Coffee', }, { img: 'https://cdn.pixabay.com/photo/2015/04/19/08/32/marguerite-729510__340.jpg', - title: '18', + title: 'Flower', }, { - img: 'https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270', - title: '19', + img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8', + title: 'Hats', }, { - img: 'https://www.gettyimages.com/gi-resources/images/500px/983794168.jpg', - title: '20', + img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af', + title: 'Tomato basil', + }, + { + img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6', + title: 'Bike', }, ]; diff --git a/docs/src/pages/components/masonry/masonry.md b/docs/src/pages/components/masonry/masonry.md index c6d66d4765c6c2..055ffcdcf7904d 100644 --- a/docs/src/pages/components/masonry/masonry.md +++ b/docs/src/pages/components/masonry/masonry.md @@ -10,7 +10,7 @@ githubLabel: 'component: Masonry' 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"}} +{{"component": "modules/components/ComponentLinkHeader.js", "design": true}} ## Masonry @@ -19,3 +19,7 @@ Masonry maintains a list of content blocks with a consistent width but variable > `` can receive not only `` elements but also `
` elements as shown in the following demo: {{"demo": "pages/components/masonry/BasicMasonry.js", "bg": true}} + +> You can configure the number of columns taken up by each `` as shown in the following demo: + +{{"demo": "pages/components/masonry/DiffColSizeMasonry.js", "bg": true}} diff --git a/docs/translations/api-docs/masonry-item/masonry-item.json b/docs/translations/api-docs/masonry-item/masonry-item.json index 308b7edb5b2474..d0d9953b91fa7c 100644 --- a/docs/translations/api-docs/masonry-item/masonry-item.json +++ b/docs/translations/api-docs/masonry-item/masonry-item.json @@ -3,6 +3,7 @@ "propDescriptions": { "children": "The content of the component, normally an <img /> or a <div />.", "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.", "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details." }, diff --git a/docs/translations/api-docs/masonry/masonry.json b/docs/translations/api-docs/masonry/masonry.json index 7b4d84fdc144ea..529c7b45599a51 100644 --- a/docs/translations/api-docs/masonry/masonry.json +++ b/docs/translations/api-docs/masonry/masonry.json @@ -3,7 +3,7 @@ "propDescriptions": { "children": "The content of the component, normally <MasonryItem />s.", "classes": "Override or extend the styles applied to the component. See CSS API below for more details.", - "cols": "Number of columns.", + "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.", "sx": "Allows defining system overrides as well as additional CSS styles. See the `sx` page for more details." diff --git a/package.json b/package.json index 4bce472e658fde..db967b874388c2 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "react-router-dom": "^5.2.0", "react-test-renderer": "^17.0.1", "remark": "^13.0.0", + "resize-observer": "^1.0.2", "rimraf": "^3.0.0", "rollup": "^2.10.8", "rollup-plugin-babel": "^4.3.3", diff --git a/packages/material-ui-lab/src/Masonry/Masonry.d.ts b/packages/material-ui-lab/src/Masonry/Masonry.d.ts index 22c51ee32b415b..9b02d573f55a65 100644 --- a/packages/material-ui-lab/src/Masonry/Masonry.d.ts +++ b/packages/material-ui-lab/src/Masonry/Masonry.d.ts @@ -17,7 +17,7 @@ export interface MasonryTypeMap

{ * Number of columns. * @default 4 */ - cols?: ResponsiveStyleValue; + columns?: ResponsiveStyleValue; /** * Defines the space between children. * @default 1 diff --git a/packages/material-ui-lab/src/Masonry/Masonry.js b/packages/material-ui-lab/src/Masonry/Masonry.js index 50b42ab9656f61..f68b9fc0c4e879 100644 --- a/packages/material-ui-lab/src/Masonry/Masonry.js +++ b/packages/material-ui-lab/src/Masonry/Masonry.js @@ -5,7 +5,7 @@ import { createUnarySpacing, getValue, handleBreakpoints, - resolveBreakpointValues, + unstable_resolveBreakpointValues as resolveBreakpointValues, } from '@material-ui/system'; import { deepmerge } from '@material-ui/utils'; import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; @@ -29,14 +29,8 @@ export const style = ({ styleProps, theme }) => { gridAutoRows: 0, padding: 0, overflow: 'auto', - width: '100% !important', + width: '100%', rowGap: 1, - ...((typeof styleProps.spacing === 'string' || typeof styleProps.spacing === 'number') && { - columnGap: theme.spacing(styleProps.spacing), - }), - ...((typeof styleProps.cols === 'string' || typeof styleProps.cols === 'number') && { - gridTemplateColumns: `repeat(${styleProps.cols}, 1fr)`, - }), }; const base = Object.keys(theme.breakpoints.values).reduce((acc, breakpoint) => { @@ -46,34 +40,27 @@ export const style = ({ styleProps, theme }) => { return acc; }, {}); - if (typeof styleProps.spacing === 'object' || Array.isArray(styleProps.spacing)) { - const spacingValues = resolveBreakpointValues({ values: styleProps.spacing, base }); - const transformer = createUnarySpacing(theme); - const spacingStyleFromPropValue = (propValue) => { - return { - columnGap: getValue(transformer, propValue), - }; + const spacingValues = resolveBreakpointValues({ values: styleProps.spacing, base }); + const transformer = createUnarySpacing(theme); + const spacingStyleFromPropValue = (propValue) => { + return { + columnGap: getValue(transformer, propValue), }; + }; + + styles = deepmerge( + styles, + handleBreakpoints({ theme }, spacingValues, spacingStyleFromPropValue), + ); - styles = deepmerge( - styles, - handleBreakpoints({ theme }, spacingValues, spacingStyleFromPropValue), - ); - } - - if (typeof styleProps.cols === 'object' || Array.isArray(styleProps.cols)) { - const columnValues = resolveBreakpointValues({ values: styleProps.cols, base }); - const columnStyleFromPropValue = (propValue) => { - return { - gridTemplateColumns: `repeat(${propValue}, 1fr)`, - }; + const columnValues = resolveBreakpointValues({ values: styleProps.columns, base }); + const columnStyleFromPropValue = (propValue) => { + return { + gridTemplateColumns: `repeat(${propValue}, 1fr)`, }; + }; - styles = deepmerge( - styles, - handleBreakpoints({ theme }, columnValues, columnStyleFromPropValue), - ); - } + styles = deepmerge(styles, handleBreakpoints({ theme }, columnValues, columnStyleFromPropValue)); return styles; }; @@ -92,8 +79,8 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { name: 'MuiMasonry', }); - const { children, className, component = 'div', cols = 4, spacing = 1, ...other } = props; - const styleProps = { ...props, spacing, cols }; + const { children, className, component = 'div', columns = 4, spacing = 1, ...other } = props; + const styleProps = { ...props, spacing, columns }; const classes = useUtilityClasses(styleProps); const [documentReady, setDocumentReady] = React.useState(false); const handleStateChange = () => { @@ -102,23 +89,25 @@ const Masonry = React.forwardRef(function Masonry(inProps, ref) { } }; React.useEffect(() => { - document.addEventListener('readystatechange', handleStateChange);; + document.addEventListener('readystatechange', handleStateChange); return () => { document.removeEventListener('readystatechange', handleStateChange); }; }); + const masonry = React.useMemo(() => ({ spacing, documentReady }), [spacing, documentReady]); + return ( - - + + {children} - - + + ); }); @@ -143,7 +132,7 @@ Masonry.propTypes /* remove-proptypes */ = { * Number of columns. * @default 4 */ - cols: PropTypes.oneOfType([ + columns: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), PropTypes.number, PropTypes.object, diff --git a/packages/material-ui-lab/src/Masonry/Masonry.test.js b/packages/material-ui-lab/src/Masonry/Masonry.test.js index a4c04a62cb9e20..6b4e18f8082feb 100644 --- a/packages/material-ui-lab/src/Masonry/Masonry.test.js +++ b/packages/material-ui-lab/src/Masonry/Masonry.test.js @@ -1,6 +1,10 @@ +import { expect } from 'chai'; import * as React from 'react'; import { createClientRender, describeConformanceV5 } 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(); @@ -20,4 +24,126 @@ describe('', () => { skip: ['componentsProp'], }), ); + + const itemsData = [ + { + img: '/fake1.png', + title: 'fake1', + }, + { + img: '/fake2.png', + title: 'fake2', + }, + ]; + const theme = createTheme({ spacing: 8 }); + const children = itemsData.map((item, idx) => ( +

+ {item.title} +
+ )); + + describe('style attribute:', () => { + it('should render with correct default styles', () => { + expect( + style({ + styleProps: { + columns: 4, + spacing: 1, + }, + theme, + }), + ).to.deep.equal({ + display: 'grid', + gridAutoRows: 0, + padding: 0, + overflow: 'auto', + width: '100%', + rowGap: 1, + columnGap: theme.spacing(1), + gridTemplateColumns: 'repeat(4, 1fr)', + }); + }); + + it('should render with column gap responsive to breakpoints', () => { + expect( + style({ + styleProps: { + 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: 1, + }); + }); + + it('should render with grid-template-columns responsive to breakpoints', () => { + expect( + style({ + styleProps: { + 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: 1, + }); + }); + }); + + describe('props:', () => { + describe('prop: component', () => { + it('should render a div by default', () => { + const { container } = render({children}); + expect(container.firstChild).to.have.property('nodeName', 'DIV'); + }); + + it('should render a different component', () => { + const { container } = render({children}); + expect(container.firstChild).to.have.property('nodeName', 'SPAN'); + }); + }); + + describe('prop: className', () => { + it('should append the className to the root element', () => { + const { container } = render({children}); + expect(container.firstChild).to.have.class(classes.root); + expect(container.firstChild).to.have.class('foo'); + }); + }); + }); }); diff --git a/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts b/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts index efdab2c34a854c..dd08b9bf0da1af 100644 --- a/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts +++ b/packages/material-ui-lab/src/MasonryItem/MasonryItem.d.ts @@ -13,6 +13,11 @@ export interface MasonryItemTypeMap

* Override or extend the styles applied to the component. */ classes?: Partial; + /** + * The number of columns taken up by the component + * @default 1 + */ + columnSpan?: number; /** * Allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/material-ui-lab/src/MasonryItem/MasonryItem.js b/packages/material-ui-lab/src/MasonryItem/MasonryItem.js index 16a760b7c46684..a9d7807a23ea3f 100644 --- a/packages/material-ui-lab/src/MasonryItem/MasonryItem.js +++ b/packages/material-ui-lab/src/MasonryItem/MasonryItem.js @@ -5,11 +5,12 @@ import { createUnarySpacing, getValue, handleBreakpoints, - resolveBreakpointValues, + 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 { ResizeObserver } from 'resize-observer'; import { getMasonryItemUtilityClass } from './masonryItemClasses'; import MasonryContext from '../Masonry/MasonryContext'; @@ -28,37 +29,30 @@ export const style = ({ styleProps, theme }) => { width: '100%', [`& > *`]: { // all contents should have a width of 100% - objectFit: 'cover', width: '100%', }, visibility: styleProps.contentHeight ? 'visible' : 'hidden', - ...((typeof styleProps.spacing === 'string' || typeof styleProps.spacing === 'number') && { - gridRowEnd: `span ${ - styleProps.contentHeight + Number(theme.spacing(styleProps.spacing).replace('px', '')) - }`, - paddingBottom: Number(theme.spacing(styleProps.spacing).replace('px', '')) - 1, - }), + gridColumnEnd: `span ${styleProps.columnSpan}`, }; - if (typeof styleProps.spacing === 'object' || Array.isArray(styleProps.spacing)) { - const base = Object.keys(theme.breakpoints.values).reduce((acc, breakpoint) => { - if (styleProps.spacing[breakpoint] != null) { - acc[breakpoint] = true; - } - return acc; - }, {}); - const spacingValues = resolveBreakpointValues({ values: styleProps.spacing, base }); - const transformer = createUnarySpacing(theme); - const styleFromPropValue = (propValue) => { - const gap = Number(getValue(transformer, propValue).replace('px', '')); - const rowSpan = styleProps.contentHeight ? Math.ceil(styleProps.contentHeight + gap) : 0; - return { - gridRowEnd: `span ${rowSpan}`, - paddingBottom: gap - 1, - }; + const base = Object.keys(theme.breakpoints.values).reduce((acc, breakpoint) => { + if (styleProps.spacing[breakpoint] != null) { + acc[breakpoint] = true; + } + return acc; + }, {}); + const spacingValues = resolveBreakpointValues({ values: styleProps.spacing, base }); + const transformer = createUnarySpacing(theme); + const styleFromPropValue = (propValue) => { + const gap = Number(getValue(transformer, propValue).replace('px', '')); + const rowSpan = styleProps.contentHeight ? Math.ceil(styleProps.contentHeight + gap) : 0; + return { + gridRowEnd: `span ${rowSpan}`, + paddingBottom: gap - 1, }; - styles = deepmerge(styles, handleBreakpoints({ theme }, spacingValues, styleFromPropValue)); - } + }; + styles = deepmerge(styles, handleBreakpoints({ theme }, spacingValues, styleFromPropValue)); + return styles; }; @@ -77,15 +71,13 @@ const MasonryItem = React.forwardRef(function MasonryItem(inProps, ref) { }); const masonryItemRef = React.useRef(null); - const handleRef = useForkRef(ref, masonryItemRef); - const { - spacing = 1, - documentReady = false, - } = React.useContext(MasonryContext); - const { children, className, component = 'div', ...other } = props; + + const { spacing = 1, documentReady = false } = React.useContext(MasonryContext); + const { children, className, component = 'div', columnSpan = 1, ...other } = props; const [styleProps, setStyleProps] = React.useState({ ...props, spacing, + columnSpan, }); const classes = useUtilityClasses(styleProps); @@ -97,19 +89,26 @@ const MasonryItem = React.forwardRef(function MasonryItem(inProps, ref) { contentHeight: child?.getBoundingClientRect().height, }); }; + const resizeObserver = React.useRef(new ResizeObserver(computeHeight)); + const resizedItemRef = React.useCallback( + (item) => { + if (item !== null) { + resizeObserver.current.observe(item); + } else if (resizeObserver.current) { + resizeObserver.current.disconnect(); + } + }, + [resizeObserver], + ); React.useEffect(() => { if (documentReady) { computeHeight(); + resizeObserver.current.observe(masonryItemRef.current); } }, [documentReady]); // eslint-disable-line - - React.useEffect(() => { - window.addEventListener('resize', computeHeight);; - return () => { - window.removeEventListener('resize', computeHeight); - }; - }); + const handleOwnRef = useForkRef(masonryItemRef, resizedItemRef); + const handleRef = useForkRef(ref, handleOwnRef); return ( ', () => { const render = createClientRender(); @@ -20,4 +24,108 @@ describe('', () => { skip: ['componentsProp'], }), ); + + const children =

; + const theme = createTheme({ + spacing: 8, + }); + + it('should render children by default', () => { + let item = null; + act(() => { + const { getByTestId } = render({children}); + item = getByTestId('test-children'); + }); + expect(item).not.to.equal(null); + }); + + describe('style attribute:', () => { + it('should render with padding bottom and grid-row-end responsive to breakpoints', () => { + expect( + style({ + styleProps: { + contentHeight: 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', '')))}`, + paddingBottom: Number(theme.spacing(1).replace('px', '')) - 1, + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + gridRowEnd: `span ${Math.ceil(100 + Number(theme.spacing(2).replace('px', '')))}`, + paddingBottom: Number(theme.spacing(2).replace('px', '')) - 1, + }, + [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: { + gridRowEnd: `span ${Math.ceil(100 + Number(theme.spacing(3).replace('px', '')))}`, + paddingBottom: Number(theme.spacing(3).replace('px', '')) - 1, + }, + width: '100%', + [`& > *`]: { + width: '100%', + }, + visibility: 'visible', + gridColumnEnd: 'span 1', + }); + }); + + it('should render with given column span', () => { + expect( + style({ + styleProps: { + contentHeight: 100, + columnSpan: 2, + spacing: 1, + }, + theme, + }), + ).to.deep.equal({ + gridRowEnd: `span ${Math.ceil(100 + Number(theme.spacing(1).replace('px', '')))}`, + paddingBottom: Number(theme.spacing(1).replace('px', '')) - 1, + width: '100%', + [`& > *`]: { + width: '100%', + }, + visibility: 'visible', + gridColumnEnd: 'span 2', + }); + }); + }); + + describe('props:', () => { + describe('prop: component', () => { + it('should render a div by default', () => { + let item = null; + act(() => { + const { container } = render({children}); + item = container; + }); + expect(item.firstChild).to.have.property('nodeName', 'DIV'); + }); + + it('should render a different component', () => { + let item = null; + act(() => { + const { container } = render({children}); + item = container; + }); + expect(item.firstChild).to.have.property('nodeName', 'SPAN'); + }); + }); + + describe('prop: className', () => { + it('should append the className to the root element', () => { + let item = null; + act(() => { + const { container } = render({children}); + item = container; + }); + expect(item.firstChild).to.have.class(classes.root); + expect(item.firstChild).to.have.class('foo'); + }); + }); + }); }); diff --git a/packages/material-ui-system/src/index.js b/packages/material-ui-system/src/index.js index e0918b6471e4b2..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, resolveBreakpointValues } 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 496972eb0ce02b..1c9b92b44124a8 100644 --- a/packages/material-ui/src/Grid/Grid.js +++ b/packages/material-ui/src/Grid/Grid.js @@ -15,7 +15,7 @@ import clsx from 'clsx'; import { unstable_extendSxProp as extendSxProp, handleBreakpoints, - resolveBreakpointValues, + unstable_resolveBreakpointValues as resolveBreakpointValues, } from '@material-ui/system'; import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; import requirePropFactory from '../utils/requirePropFactory'; diff --git a/packages/material-ui/src/Stack/Stack.js b/packages/material-ui/src/Stack/Stack.js index cfad8e98ad7391..c7fa9348ac4250 100644 --- a/packages/material-ui/src/Stack/Stack.js +++ b/packages/material-ui/src/Stack/Stack.js @@ -5,7 +5,7 @@ import { getValue, handleBreakpoints, unstable_extendSxProp as extendSxProp, - resolveBreakpointValues, + unstable_resolveBreakpointValues as resolveBreakpointValues, } from '@material-ui/system'; import { deepmerge } from '@material-ui/utils'; import styled from '../styles/styled'; diff --git a/yarn.lock b/yarn.lock index ed0f734a667492..7bd1cf70ff41b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14118,6 +14118,11 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== +resize-observer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/resize-observer/-/resize-observer-1.0.2.tgz#9a87697b275343d12d8b371940e02e9a93e1f452" + integrity sha512-X0lHFNsxItpBRIRsdwOTkl/VguTaLGx7Gz9xoTGix9ObBN3jRYq9J/rSIuYDrey8AdU3IkfgIMpCeVSEW1QS0Q== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"