├── .commitlintrc.js
├── .czrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .lintstagedrc
├── .nvm
├── .prettierrc
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENCE.md
├── README.md
├── babel-jest.config.js
├── demo
└── src
│ ├── Examples
│ ├── Masonry
│ │ ├── index.js
│ │ └── index.md
│ ├── Responsive
│ │ ├── index.js
│ │ └── index.md
│ └── index.js
│ ├── example.gif
│ ├── index.js
│ └── routes.js
├── example
├── .gitignore
├── LICENSE
├── README.md
├── gatsby-config.js
├── package.json
├── src
│ └── pages
│ │ └── index.js
├── static
│ └── favicon.ico
└── yarn.lock
├── jest.config.js
├── nwb.config.js
├── package.json
├── src
├── Masonry
│ ├── index.js
│ └── index.test.js
├── ResponsiveMasonry
│ ├── index.js
│ └── index.test.js
└── index.js
├── tsconfig.json
├── types
└── index.d.ts
└── yarn.lock
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"],
3 | }
4 |
--------------------------------------------------------------------------------
/.czrc:
--------------------------------------------------------------------------------
1 | {
2 | "path": "cz-conventional-changelog"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.md]
12 | indent_size = 4
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | node: true,
6 | jest: true,
7 | "jest/globals": true,
8 | },
9 | parserOptions: {
10 | sourceType: "module",
11 | ecmaVersion: 2019,
12 | },
13 | plugins: ["jest"],
14 | extends: ["eslint:recommended", "plugin:react/recommended", "prettier"],
15 | settings: {
16 | react: {
17 | version: "detect",
18 | },
19 | },
20 | rules: {
21 | "no-control-regex": 0,
22 | "react/prop-types": "off",
23 | "react/display-name": "off",
24 | },
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /demo/dist
3 | /es
4 | /lib
5 | /node_modules
6 | /umd
7 | *.log
8 | /.yarn
9 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.js": [
3 | "prettier --no-bracket-spacing --no-semi --trailing-comma=es5 --write",
4 | "git add"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.nvm:
--------------------------------------------------------------------------------
1 | v12
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "semi": false,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 | node_js: 13
5 |
6 | before_install:
7 | - npm install codecov
8 |
9 | after_success:
10 | - cat ./coverage/lcov.info | ./node_modules/.bin/codecov
11 |
12 | branches:
13 | only:
14 | - master
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.7.0 - 2025-01-20
2 |
3 | - add responsive gutters by @kriskuiper
4 |
5 | # 2.6.0 - 2024-12-07
6 |
7 | - Added: add `sequential` prop to distribute children by height sequentially by @yangchristina
8 |
9 | # 2.4.0 - 2024-10-12
10 |
11 | - Added: add typescript types by @zvonimirr
12 |
13 | # 2.3.0 - 2024-07-25
14 |
15 | - Added: allow custom masonry and item HTML tags by @zvonimirr
16 |
17 | # 2.2.0 - 2024-02-19
18 |
19 | - Added: add inner width as default by @artemijkurganov
20 | - Fixed: Un even column items when Masonry child is invalid by @sarathjasrin
21 |
22 | # 2.0.0 - 2017-12-18
23 |
24 | - Added: ES6 modules build
25 | - Added: CommonJS build
26 | - Added: UMD build
27 | - Added: Demo
28 | - Added: Tests
29 | - Added: `style` prop
30 |
31 | # 1.5.0 - 2017-12-12
32 |
33 | - Fixed: SSR
34 |
35 | # 1.4.0 - 2017-11-14
36 |
37 | - Added: img alt for a11y accessibility
38 | - Updated: react v16 as a peer dependency
39 |
40 | # 1.3.3 - 2017-09-28
41 |
42 | - Added: custom className support
43 | - Added: single node children support
44 | - Fixed: do not reset this.container
45 |
46 | # 1.2.3 - 2017-06-14
47 |
48 | - Updated: handle default columns count
49 |
50 | # 1.2.2 - 2017-06-03
51 |
52 | - Added: `files` to package.json
53 |
54 | # 1.2.1 - 2017-06-03
55 |
56 | - Updated: Use `npm` to publish package
57 |
58 | # 1.2.0 - 2017-04-14
59 |
60 | - Updated: `react` version to `15.5`
61 | - Added: `prop-types` package in `peerDependencies`
62 | - Removed: `browser` field in `package.json`
63 | - Removed: `dist` task in `package.json`
64 | - Removed: `watch` task in `package.json`
65 | - Removed: `webpack` dev dependency
66 |
67 | # 1.1.0 - 2017-04-13
68 |
69 | - Added: `gutter` prop on `Mansory` component
70 |
71 | # 1.0.0 - 2017-04-05
72 |
73 | - Added: `Masonry` component
74 | - Added: `ResponsiveMasonry` component
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Prerequisites
4 |
5 | [Node.js](http://nodejs.org/) >= v4 must be installed.
6 |
7 | ## Installation
8 |
9 | * Running `yarn` or `npm install` in the components's root directory will install everything you need for development.
10 |
11 | ## Demo Development Server
12 |
13 | * `npm run start` will run a development server with the component's demo app at [http://localhost:1190](http://localhost:1190) with hot module reloading.
14 |
15 | ## Linting
16 |
17 | * `npm run lint` will lint the `src` and `demo/src` folders
18 |
19 | ## Running Tests
20 |
21 | * `npm test` will run the tests once and produce a coverage report in `coverage/`.
22 |
23 | * `npm run test:watch` will run the tests on every change.
24 |
25 | ## Building
26 |
27 | * `npm run build` will build the component for publishing to npm and also bundle the demo app.
28 |
29 | * `npm run clean` will delete built resources.
30 |
31 | > **Builds:**
32 | >
33 | > * CommonJS build => `/lib`,
34 | > * ES6 modules build => `/es`
35 | > * UMD build => `/umd`
36 | > * Demo build => `/demo/dist`
37 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Cédric Delpoux
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-responsive-masonry
2 |
3 | [![npm package][npm-badge]][npm] [![Travis][build-badge]][build]
4 | [![Codecov][codecov-badge]][codecov] ![Module formats][module-formats]
5 |
6 | A lightweight React responsive masonry component built with css flexbox.
7 |
8 | ## Getting started
9 |
10 | [](https://nodei.co/npm/react-responsive-masonry/)
11 |
12 | You can download `react-responsive-masonry` from the NPM registry via the `npm` or
13 | `yarn` commands
14 |
15 | ```shell
16 | yarn add react-responsive-masonry
17 | npm install react-responsive-masonry --save
18 | ```
19 |
20 | If you don't use package manager and you want to include `react-responsive-masonry`
21 | directly in your html, you could get it from the UNPKG CDN
22 |
23 | ```html
24 | https://unpkg.com/react-responsive-masonry/umd/react-responsive-masonry.js
25 | ```
26 |
27 | ## Demo
28 |
29 | See [Demo page][github-page]
30 |
31 | ## Example
32 |
33 | 
34 |
35 | ## Usage
36 |
37 | If you want the number of columns change by resizing the window, you need to wrap the `Masonry` component by the `ResponsiveMasonry` component.
38 | Otherwise, you only need to use the `Masonry` component.
39 |
40 | ```js
41 | import React from "react"
42 | import Masonry, {ResponsiveMasonry} from "react-responsive-masonry"
43 |
44 | // The number of columns and the gutter change by resizing the window
45 | class MyWrapper extends React.Component {
46 | render() {
47 | return (
48 |
52 |
53 |
54 |
55 | {/* Children */}
56 |
57 |
58 |
59 |
60 | )
61 | }
62 | }
63 |
64 | // The number of columns and the gutter don't change by resizing the window
65 | class MyWrapper extends Component {
66 | render() {
67 | return (
68 |
69 |
70 |
71 | {/* Children */}
72 |
73 |
74 |
75 | )
76 | }
77 | }
78 | ```
79 |
80 | ## Props
81 |
82 | ### Masonry component
83 |
84 | | Name | PropType | Description | Default |
85 | | ------------ | -------- | ------------------------------------------------------ | ------- |
86 | | columnsCount | Number | Injected by ResponsiveMasonry | 3 |
87 | | gutter | String | Margin surrounding each item e.g. "10px" or "1.5rem" | "0" |
88 | | className | String | Custom CSS class applied to the container element | null |
89 | | style | Object | Style object for customizing the container element | {} |
90 | | containerTag | String | Tag name of the container element | "div" |
91 | | itemTag | String | Tag name of the item element | "div" |
92 | | itemStyle | Object | Style object applied to each item | {} |
93 | | sequential | Boolean | If true, items are placed in the order they are passed | false |
94 |
95 | ### ResponsiveMasonry component
96 |
97 | | Name | PropType | Description | Default |
98 | | ----------------------- | -------- | ---------------------------------------------------------------------------------------- | ------------------------ |
99 | | columnsCountBreakPoints | Object | Keys are breakpoints in px, values are the columns number | {350: 1, 750: 2, 900: 3} |
100 | | gutterBreakPoints | Object | Keys are breakpoints in px, values are the gutter value in any valid CSS value for 'gap' | |
101 |
102 | ## Contributing
103 |
104 | - ⇄ Pull/Merge requests and ★ Stars are always welcome.
105 | - For bugs and feature requests, please [create an issue][github-issue].
106 | - Pull requests must be accompanied by passing automated tests (`npm test`).
107 |
108 | See [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines
109 |
110 | ## Changelog
111 |
112 | See [changelog](./CHANGELOG.md)
113 |
114 | ## License
115 |
116 | This project is licensed under the MIT License - see the
117 | [LICENCE.md](./LICENCE.md) file for details
118 |
119 | [npm-badge]: https://img.shields.io/npm/v/react-responsive-masonry.svg?style=flat-square
120 | [npm]: https://www.npmjs.org/package/react-responsive-masonry
121 | [build-badge]: https://img.shields.io/travis/cedricdelpoux/react-responsive-masonry/master.svg?style=flat-square
122 | [build]: https://travis-ci.org/cedricdelpoux/react-responsive-masonry
123 | [codecov-badge]: https://img.shields.io/codecov/c/github/cedricdelpoux/react-responsive-masonry.svg?style=flat-square
124 | [codecov]: https://codecov.io/gh/cedricdelpoux/react-responsive-masonry
125 | [module-formats]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20esm-green.svg?style=flat-square
126 | [github-page]: https://cedricdelpoux.github.io/react-responsive-masonry
127 | [github-issue]: https://github.com/cedricdelpoux/react-responsive-masonry/issues/new
128 |
--------------------------------------------------------------------------------
/babel-jest.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | module.exports = {
3 | presets: ["@babel/preset-env", "@babel/preset-react"],
4 | }
5 |
--------------------------------------------------------------------------------
/demo/src/Examples/Masonry/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Html} from "react-demo-page"
3 |
4 | import html from "./index.md"
5 | import Masonry from "../../../../src"
6 |
7 | const images = [
8 | "https://picsum.photos/200/300?image=1050",
9 | null,
10 | "https://picsum.photos/400/400?image=1039",
11 | null,
12 | "https://picsum.photos/400/400?image=1080",
13 | null,
14 | "https://picsum.photos/200/200?image=997",
15 | "https://picsum.photos/500/400?image=287",
16 | "",
17 | "https://picsum.photos/400/500?image=955",
18 | "",
19 | "",
20 | "https://picsum.photos/200/300?image=916",
21 | null,
22 | "https://picsum.photos/300/300?image=110",
23 | "https://picsum.photos/300/300?image=206",
24 | ]
25 |
26 | export default class ExampleMasonry extends React.Component {
27 | render() {
28 | return (
29 |
30 |
31 |
32 | {images.map((image, i) =>
33 | image ? (
34 |
39 | ) : (
40 | ""
41 | )
42 | )}
43 |
44 |
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/demo/src/Examples/Masonry/index.md:
--------------------------------------------------------------------------------
1 | # Masonry
2 |
3 | The column count do not change dynamically
4 |
5 | ## Code
6 |
7 | ```js
8 | import React from "react"
9 | import Masonry from "react-responsive-masonry"
10 |
11 | const images = [
12 | "https://picsum.photos/200/300?image=1050",
13 | //...
14 | "https://picsum.photos/300/300?image=206",
15 | ]
16 |
17 | class MyWrapper extends React.Component {
18 | render() {
19 | return (
20 |
21 | {images.map((image, i) => (
22 |
27 | ))}
28 |
29 | )
30 | }
31 | }
32 | ```
33 |
34 | ## Demo
35 |
--------------------------------------------------------------------------------
/demo/src/Examples/Responsive/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Html} from "react-demo-page"
3 |
4 | import html from "./index.md"
5 | import Masonry, {ResponsiveMasonry} from "../../../../src"
6 |
7 | const images = [
8 | "https://picsum.photos/200/300?image=1050",
9 | "",
10 | "https://picsum.photos/400/400?image=1039",
11 | null,
12 | "https://picsum.photos/400/400?image=1080",
13 | "https://picsum.photos/200/200?image=997",
14 | "https://picsum.photos/500/400?image=287",
15 | null,
16 | "https://picsum.photos/400/500?image=955",
17 | "https://picsum.photos/200/300?image=916",
18 | "https://picsum.photos/300/300?image=110",
19 | "https://picsum.photos/300/300?image=206",
20 | ]
21 |
22 | export default class ExampleResponsiveMasonry extends React.Component {
23 | render() {
24 | return (
25 |
26 |
27 |
31 |
32 | {images.map((image, i) =>
33 | image ? (
34 |
39 | ) : (
40 | ""
41 | )
42 | )}
43 |
44 |
45 |
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/demo/src/Examples/Responsive/index.md:
--------------------------------------------------------------------------------
1 | # ResponsiveMasonry
2 |
3 | The column count change dynamically. `ResponsiveMasonry` component inject it in `Masonry` component.
4 |
5 | ## Code
6 |
7 | ```js
8 | import React from "react"
9 | import Masonry, {ResponsiveMasonry} from "react-responsive-masonry"
10 |
11 | const images = [
12 | "https://picsum.photos/200/300?image=1050",
13 | //...
14 | "https://picsum.photos/300/300?image=206",
15 | ]
16 |
17 | class MyWrapper extends React.Component {
18 | render() {
19 | return (
20 |
24 |
25 | {images.map((image, i) => (
26 |
32 | ))}
33 |
34 |
35 | )
36 | }
37 | }
38 | ```
39 |
40 | ## Demo
41 |
--------------------------------------------------------------------------------
/demo/src/Examples/index.js:
--------------------------------------------------------------------------------
1 | import ExampleMasonry from "./Masonry"
2 | import ExampleResponsiveMasonry from "./Responsive"
3 |
4 | export {ExampleMasonry, ExampleResponsiveMasonry}
5 |
--------------------------------------------------------------------------------
/demo/src/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cedricdelpoux/react-responsive-masonry/517f694ba74b3ae4aec6e4a9571e33c84b84a9fe/demo/src/example.gif
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {render} from "react-dom"
3 |
4 | import ReactDemoPage from "react-demo-page"
5 | import routes from "./routes"
6 | import pkg from "../../package.json"
7 |
8 | const header = {
9 | title: pkg.name,
10 | buttons: [
11 | {label: "Github", url: pkg.homepage},
12 | {label: "Npm", url: `https://www.npmjs.com/package/${pkg.name}`},
13 | {label: "Download", url: `${pkg.homepage}/archive/master.zip`},
14 | ],
15 | }
16 |
17 | const footer = {
18 | author: pkg.author,
19 | }
20 |
21 | const Demo = () => (
22 |
29 | )
30 |
31 | // eslint-disable-next-line
32 | render(, document.querySelector("#demo"))
33 |
--------------------------------------------------------------------------------
/demo/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import readmeHtml from "../../README.md"
4 | import {ExampleMasonry, ExampleResponsiveMasonry} from "./Examples"
5 |
6 | const routes = [
7 | {
8 | path: "/",
9 | exact: true,
10 | component: (
11 |
12 |
13 |
14 |
15 | ),
16 | label: "Demo",
17 | },
18 | {
19 | path: "/readme",
20 | html: readmeHtml,
21 | label: "Read me",
22 | },
23 | ]
24 |
25 | export default routes
26 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .cache
3 | public
4 | node_modules
5 |
6 |
7 |
--------------------------------------------------------------------------------
/example/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD Zero Clause License (0BSD)
2 |
3 | Copyright (c) 2020 Gatsby Inc.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Gatsby's hello-world starter
9 |
10 |
11 | Kick off your project with this hello-world boilerplate. This starter ships with the main Gatsby configuration files you might need to get up and running blazing fast with the blazing fast app generator for React.
12 |
13 | _Have another more specific idea? You may want to check out our vibrant collection of [official and community-created starters](https://www.gatsbyjs.com/docs/gatsby-starters/)._
14 |
15 | ## 🚀 Quick start
16 |
17 | 1. **Create a Gatsby site.**
18 |
19 | Use the Gatsby CLI to create a new site, specifying the hello-world starter.
20 |
21 | ```shell
22 | # create a new Gatsby site using the hello-world starter
23 | gatsby new my-hello-world-starter https://github.com/gatsbyjs/gatsby-starter-hello-world
24 | ```
25 |
26 | 1. **Start developing.**
27 |
28 | Navigate into your new site’s directory and start it up.
29 |
30 | ```shell
31 | cd my-hello-world-starter/
32 | gatsby develop
33 | ```
34 |
35 | 1. **Open the source code and start editing!**
36 |
37 | Your site is now running at `http://localhost:8000`!
38 |
39 | _Note: You'll also see a second link: _`http://localhost:8000/___graphql`_. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.com/tutorial/part-five/#introducing-graphiql)._
40 |
41 | Open the `my-hello-world-starter` directory in your code editor of choice and edit `src/pages/index.js`. Save your changes and the browser will update in real time!
42 |
43 | ## 🧐 What's inside?
44 |
45 | A quick look at the top-level files and directories you'll see in a Gatsby project.
46 |
47 | .
48 | ├── node_modules
49 | ├── src
50 | ├── .gitignore
51 | ├── .prettierrc
52 | ├── gatsby-browser.js
53 | ├── gatsby-config.js
54 | ├── gatsby-node.js
55 | ├── gatsby-ssr.js
56 | ├── LICENSE
57 | ├── package-lock.json
58 | ├── package.json
59 | └── README.md
60 |
61 | 1. **`/node_modules`**: This directory contains all of the modules of code that your project depends on (npm packages) are automatically installed.
62 |
63 | 2. **`/src`**: This directory will contain all of the code related to what you will see on the front-end of your site (what you see in the browser) such as your site header or a page template. `src` is a convention for “source code”.
64 |
65 | 3. **`.gitignore`**: This file tells git which files it should not track / not maintain a version history for.
66 |
67 | 4. **`.prettierrc`**: This is a configuration file for [Prettier](https://prettier.io/). Prettier is a tool to help keep the formatting of your code consistent.
68 |
69 | 5. **`gatsby-browser.js`**: This file is where Gatsby expects to find any usage of the [Gatsby browser APIs](https://www.gatsbyjs.com/docs/browser-apis/) (if any). These allow customization/extension of default Gatsby settings affecting the browser.
70 |
71 | 6. **`gatsby-config.js`**: This is the main configuration file for a Gatsby site. This is where you can specify information about your site (metadata) like the site title and description, which Gatsby plugins you’d like to include, etc. (Check out the [config docs](https://www.gatsbyjs.com/docs/gatsby-config/) for more detail).
72 |
73 | 7. **`gatsby-node.js`**: This file is where Gatsby expects to find any usage of the [Gatsby Node APIs](https://www.gatsbyjs.com/docs/node-apis/) (if any). These allow customization/extension of default Gatsby settings affecting pieces of the site build process.
74 |
75 | 8. **`gatsby-ssr.js`**: This file is where Gatsby expects to find any usage of the [Gatsby server-side rendering APIs](https://www.gatsbyjs.com/docs/ssr-apis/) (if any). These allow customization of default Gatsby settings affecting server-side rendering.
76 |
77 | 9. **`LICENSE`**: This Gatsby starter is licensed under the 0BSD license. This means that you can see this file as a placeholder and replace it with your own license.
78 |
79 | 10. **`package-lock.json`** (See `package.json` below, first). This is an automatically generated file based on the exact versions of your npm dependencies that were installed for your project. **(You won’t change this file directly).**
80 |
81 | 11. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the project’s name, author, etc). This manifest is how npm knows which packages to install for your project.
82 |
83 | 12. **`README.md`**: A text file containing useful reference information about your project.
84 |
85 | ## 🎓 Learning Gatsby
86 |
87 | Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.com/). Here are some places to start:
88 |
89 | - **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.com/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process.
90 |
91 | - **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.com/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar.
92 |
93 | ## 💫 Deploy
94 |
95 | [](https://app.netlify.com/start/deploy?repository=https://github.com/gatsbyjs/gatsby-starter-hello-world)
96 |
97 | [](https://vercel.com/import/project?template=https://github.com/gatsbyjs/gatsby-starter-hello-world)
98 |
99 |
100 |
--------------------------------------------------------------------------------
/example/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | flags: {
3 | FAST_DEV: true,
4 | DEV_SSR: true,
5 | },
6 | plugins: [],
7 | }
8 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-responsive-masonry-example",
3 | "private": true,
4 | "scripts": {
5 | "build": "gatsby build",
6 | "dev": "gatsby develop",
7 | "serve": "gatsby serve",
8 | "clean": "gatsby clean"
9 | },
10 | "dependencies": {
11 | "gatsby": "^4.1.4",
12 | "react": "link:../node_modules/react",
13 | "react-dom": "link:../node_modules/react-dom"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import Masonry, {ResponsiveMasonry} from "../../../src"
4 |
5 | const images = [
6 | "https://picsum.photos/200/300?image=1050",
7 | "https://picsum.photos/400/400?image=1039",
8 | "https://picsum.photos/400/400?image=1080",
9 | "https://picsum.photos/200/200?image=997",
10 | "https://picsum.photos/500/400?image=287",
11 | "https://picsum.photos/400/500?image=955",
12 | "https://picsum.photos/200/300?image=916",
13 | "https://picsum.photos/300/300?image=110",
14 | "https://picsum.photos/300/300?image=206",
15 | ]
16 |
17 | const PageIndex = () => (
18 |
19 |
20 | {images.map((image, i) => (
21 |
27 | ))}
28 |
29 |
30 | )
31 |
32 | export default PageIndex
33 |
--------------------------------------------------------------------------------
/example/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cedricdelpoux/react-responsive-masonry/517f694ba74b3ae4aec6e4a9571e33c84b84a9fe/example/static/favicon.ico
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | coverageDirectory: "./coverage/",
3 | collectCoverage: true,
4 | moduleNameMapper: {
5 | "\\.(css)$": "/node_modules/jest-css-modules",
6 | },
7 | testEnvironment: "jsdom",
8 | transform: {
9 | "\\.js$": ["babel-jest", {configFile: "./babel-jest.config.js"}],
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/nwb.config.js:
--------------------------------------------------------------------------------
1 | const extraWebpackConfig = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.md$/,
6 | loader: "html-loader!markdown-loader",
7 | exclude: /node_modules/,
8 | },
9 | ],
10 | },
11 | }
12 |
13 | // eslint-disable-next-line
14 | module.exports = {
15 | type: "react-component",
16 | npm: {
17 | cjs: true,
18 | esModules: true,
19 | umd: {
20 | global: "ReactResponsiveMasonry",
21 | externals: {
22 | react: "React",
23 | "prop-types": "PropTypes",
24 | },
25 | },
26 | },
27 | webpack: {
28 | extra: extraWebpackConfig,
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-responsive-masonry",
3 | "version": "2.7.1",
4 | "author": {
5 | "name": "Cédric Delpoux",
6 | "email": "cedric.delpoux@gmail.com"
7 | },
8 | "description": "React responsive masonry component built with css flexbox",
9 | "files": [
10 | "es",
11 | "lib",
12 | "umd"
13 | ],
14 | "homepage": "https://github.com/cedricdelpoux/react-responsive-masonry#readme",
15 | "keywords": [
16 | "react",
17 | "masonry",
18 | "css",
19 | "flexbox",
20 | "responsive"
21 | ],
22 | "license": "MIT",
23 | "main": "lib/index.js",
24 | "module": "es/index.js",
25 | "types": "types/index.d.ts",
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/cedricdelpoux/react-responsive-masonry.git"
29 | },
30 | "scripts": {
31 | "openssl:legacy": "NODE_OPTIONS=--openssl-legacy-provider",
32 | "build": "yarn run openssl:legacy nwb build-react-component",
33 | "clean": "nwb clean-module && nwb clean-demo",
34 | "deploy": "gh-pages -d demo/dist",
35 | "lint": "eslint src demo/src",
36 | "prepublish": "yarn run clean && yarn run build",
37 | "start": "yarn run openssl:legacy nwb serve-react-demo --port 1190",
38 | "test": "jest --colors --no-cache",
39 | "test:watch": "yarn test -- --watch",
40 | "prepare": "husky install"
41 | },
42 | "devDependencies": {
43 | "@babel/core": "^7.25.8",
44 | "@babel/preset-env": "^7.25.8",
45 | "@babel/preset-react": "^7.25.7",
46 | "@commitlint/cli": "^14.1.0",
47 | "@commitlint/config-conventional": "^14.1.0",
48 | "babel-eslint": "^10.1.0",
49 | "babel-jest": "^27.3.1",
50 | "cz-conventional-changelog": "3.3.0",
51 | "enzyme": "^3.11.0",
52 | "enzyme-adapter-react-16": "^1.15.6",
53 | "eslint": "^8.2.0",
54 | "eslint-config-prettier": "^8.3.0",
55 | "eslint-plugin-jest": "^25.2.4",
56 | "eslint-plugin-prettier": "^4.0.0",
57 | "eslint-plugin-react": "^7.37.1",
58 | "gh-pages": "^3.2.3",
59 | "html-loader": "^1.1.0",
60 | "husky": "^7.0.0",
61 | "jest": "^27.3.1",
62 | "jest-css-modules": "^2.1.0",
63 | "jsdom": "^18.1.0",
64 | "lint-staged": "^12.0.2",
65 | "markdown-loader": "^6.0.0",
66 | "nwb": "^0.25.2",
67 | "prettier": "^2.4.1",
68 | "prop-types": "^15.7.2",
69 | "react": "^16.13.1",
70 | "react-demo-page": "^0.3.6",
71 | "react-dom": "^16.13.1",
72 | "react-test-renderer": "^16.13.1"
73 | },
74 | "dependencies": {}
75 | }
76 |
--------------------------------------------------------------------------------
/src/Masonry/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types"
2 | import React from "react"
3 |
4 | class Masonry extends React.Component {
5 | constructor() {
6 | super()
7 | this.state = {columns: [], childRefs: [], hasDistributed: false}
8 | }
9 |
10 | componentDidUpdate() {
11 | if (!this.state.hasDistributed && !this.props.sequential)
12 | this.distributeChildren()
13 | }
14 |
15 | static getDerivedStateFromProps(props, state) {
16 | const {children, columnsCount} = props
17 | const hasColumnsChanged = columnsCount !== state.columns.length
18 | if (state && children === state.children && !hasColumnsChanged) return null
19 | return {
20 | ...Masonry.getEqualCountColumns(children, columnsCount),
21 | children,
22 | hasDistributed: false,
23 | }
24 | }
25 |
26 | shouldComponentUpdate(nextProps) {
27 | return (
28 | nextProps.children !== this.state.children ||
29 | nextProps.columnsCount !== this.props.columnsCount
30 | )
31 | }
32 |
33 | distributeChildren() {
34 | const {children, columnsCount} = this.props
35 | const columnHeights = Array(columnsCount).fill(0)
36 |
37 | const isReady = this.state.childRefs.every(
38 | (ref) => ref.current.getBoundingClientRect().height
39 | )
40 |
41 | if (!isReady) return
42 |
43 | const columns = Array.from({length: columnsCount}, () => [])
44 | let validIndex = 0
45 | React.Children.forEach(children, (child) => {
46 | if (child && React.isValidElement(child)) {
47 | // .current is undefined if ref was passed to a functional component without forwardRef
48 | // now passing ref into a wrapper div so it should always be defined
49 | const childHeight =
50 | this.state.childRefs[validIndex].current.getBoundingClientRect()
51 | .height
52 | const minHeightColumnIndex = columnHeights.indexOf(
53 | Math.min(...columnHeights)
54 | )
55 | columnHeights[minHeightColumnIndex] += childHeight
56 | columns[minHeightColumnIndex].push(child)
57 | validIndex++
58 | }
59 | })
60 |
61 | this.setState((p) => ({...p, columns, hasDistributed: true}))
62 | }
63 |
64 | static getEqualCountColumns(children, columnsCount) {
65 | const columns = Array.from({length: columnsCount}, () => [])
66 | let validIndex = 0
67 | const childRefs = []
68 | React.Children.forEach(children, (child) => {
69 | if (child && React.isValidElement(child)) {
70 | const ref = React.createRef()
71 | childRefs.push(ref)
72 | columns[validIndex % columnsCount].push(
73 |
78 | {child}
79 |
80 | // React.cloneElement(child, {ref}) // cannot attach refs to functional components without forwardRef
81 | )
82 | validIndex++
83 | }
84 | })
85 | return {columns, childRefs}
86 | }
87 |
88 | renderColumns() {
89 | const {gutter, itemTag, itemStyle} = this.props
90 | return this.state.columns.map((column, i) =>
91 | React.createElement(
92 | itemTag,
93 | {
94 | key: i,
95 | style: {
96 | display: "flex",
97 | flexDirection: "column",
98 | justifyContent: "flex-start",
99 | alignContent: "stretch",
100 | flex: 1,
101 | width: 0,
102 | gap: gutter,
103 | ...itemStyle,
104 | },
105 | },
106 | column.map((item) => item)
107 | )
108 | )
109 | }
110 |
111 | render() {
112 | const {gutter, className, style, containerTag} = this.props
113 |
114 | return React.createElement(
115 | containerTag,
116 | {
117 | style: {
118 | display: "flex",
119 | flexDirection: "row",
120 | justifyContent: "center",
121 | alignContent: "stretch",
122 | boxSizing: "border-box",
123 | width: "100%",
124 | gap: gutter,
125 | ...style,
126 | },
127 | className,
128 | },
129 | this.renderColumns()
130 | )
131 | }
132 | }
133 |
134 | Masonry.propTypes = {
135 | children: PropTypes.oneOfType([
136 | PropTypes.arrayOf(PropTypes.node),
137 | PropTypes.node,
138 | ]).isRequired,
139 | columnsCount: PropTypes.number,
140 | gutter: PropTypes.string,
141 | className: PropTypes.string,
142 | style: PropTypes.object,
143 | containerTag: PropTypes.string,
144 | itemTag: PropTypes.string,
145 | itemStyle: PropTypes.object,
146 | sequential: PropTypes.bool,
147 | }
148 |
149 | Masonry.defaultProps = {
150 | columnsCount: 3,
151 | gutter: "0",
152 | className: null,
153 | style: {},
154 | containerTag: "div",
155 | itemTag: "div",
156 | itemStyle: {},
157 | sequential: false,
158 | }
159 |
160 | export default Masonry
161 |
--------------------------------------------------------------------------------
/src/Masonry/index.test.js:
--------------------------------------------------------------------------------
1 | import {configure, mount} from "enzyme"
2 | import Adapter from "enzyme-adapter-react-16"
3 | import React from "react"
4 |
5 | import Masonry from "./"
6 |
7 | configure({adapter: new Adapter()})
8 |
9 | const content = "Content"
10 | const MasonryFixture = (
11 |
12 | {content}
13 |
14 | )
15 |
16 | const CustomTagsFixture = (
17 |
18 | {content}
19 |
20 | )
21 |
22 | describe("Masonry", () => {
23 | it("renders", () => {
24 | mount(MasonryFixture)
25 | })
26 |
27 | it("renders with custom tags", () => {
28 | mount(CustomTagsFixture)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/src/ResponsiveMasonry/index.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | useCallback,
3 | useEffect,
4 | useLayoutEffect,
5 | useMemo,
6 | useState,
7 | } from "react"
8 |
9 | import PropTypes from "prop-types"
10 |
11 | const DEFAULT_COLUMNS_COUNT = 1
12 | const DEFAULT_GUTTER = "10px"
13 |
14 | const useIsomorphicLayoutEffect =
15 | typeof window !== "undefined" ? useLayoutEffect : useEffect
16 |
17 | const useHasMounted = () => {
18 | const [hasMounted, setHasMounted] = useState(false)
19 | useIsomorphicLayoutEffect(() => {
20 | setHasMounted(true)
21 | }, [])
22 | return hasMounted
23 | }
24 |
25 | const useWindowWidth = () => {
26 | const hasMounted = useHasMounted()
27 | const [width, setWidth] = useState(
28 | typeof window !== "undefined" ? window.innerWidth : 0
29 | )
30 |
31 | const handleResize = useCallback(() => {
32 | if (!hasMounted) return
33 | setWidth(window.innerWidth)
34 | }, [hasMounted])
35 |
36 | useIsomorphicLayoutEffect(() => {
37 | if (hasMounted) {
38 | window.addEventListener("resize", handleResize)
39 | handleResize()
40 | return () => window.removeEventListener("resize", handleResize)
41 | }
42 | }, [hasMounted, handleResize])
43 |
44 | return width
45 | }
46 |
47 | const MasonryResponsive = ({
48 | columnsCountBreakPoints = {
49 | 350: 1,
50 | 750: 2,
51 | 900: 3,
52 | },
53 | gutterBreakPoints = {},
54 | children,
55 | className = null,
56 | style = null,
57 | }) => {
58 | const windowWidth = useWindowWidth()
59 |
60 | const getResponsiveValue = useCallback(
61 | (breakPoints, defaultValue) => {
62 | const sortedBreakPoints = Object.keys(breakPoints).sort((a, b) => a - b)
63 | let value =
64 | sortedBreakPoints.length > 0
65 | ? breakPoints[sortedBreakPoints[0]]
66 | : defaultValue
67 |
68 | sortedBreakPoints.forEach((breakPoint) => {
69 | if (breakPoint < windowWidth) {
70 | value = breakPoints[breakPoint]
71 | }
72 | })
73 |
74 | return value
75 | },
76 | [windowWidth]
77 | )
78 |
79 | const columnsCount = useMemo(
80 | () => getResponsiveValue(columnsCountBreakPoints, DEFAULT_COLUMNS_COUNT),
81 | [getResponsiveValue, columnsCountBreakPoints]
82 | )
83 | const gutter = useMemo(
84 | () => getResponsiveValue(gutterBreakPoints, DEFAULT_GUTTER),
85 | [getResponsiveValue, gutterBreakPoints]
86 | )
87 |
88 | return (
89 |
90 | {React.Children.map(children, (child, index) =>
91 | React.cloneElement(child, {
92 | key: index,
93 | columnsCount,
94 | gutter,
95 | })
96 | )}
97 |
98 | )
99 | }
100 |
101 | MasonryResponsive.propTypes = {
102 | children: PropTypes.oneOfType([
103 | PropTypes.arrayOf(PropTypes.node),
104 | PropTypes.node,
105 | ]).isRequired,
106 | columnsCountBreakPoints: PropTypes.object,
107 | className: PropTypes.string,
108 | style: PropTypes.object,
109 | }
110 |
111 | export default MasonryResponsive
112 |
--------------------------------------------------------------------------------
/src/ResponsiveMasonry/index.test.js:
--------------------------------------------------------------------------------
1 | import {configure, mount} from "enzyme"
2 | import Adapter from "enzyme-adapter-react-16"
3 | import React from "react"
4 | import {renderToString} from "react-dom/server"
5 | import {act} from "react-dom/test-utils"
6 |
7 | import Masonry from "../"
8 | import ResponsiveMasonry from "./"
9 |
10 | configure({adapter: new Adapter()})
11 |
12 | const columnsCountBreakPoints = {350: 1, 750: 2, 900: 3}
13 | const gutterBreakPoints = { 350: '10px', 750: '20px', 900: '30px' }
14 | const content = "Content"
15 | const ResponsiveFixture = (
16 |
17 |
18 | {content}
19 |
20 |
21 | )
22 | const ResponsiveCustomTagsFixture = (
23 |
24 |
25 | {content}
26 | {content}
27 |
28 |
29 | )
30 |
31 | describe("ResponsiveMasonry", () => {
32 | it("renders", () => {
33 | mount(ResponsiveFixture)
34 | })
35 |
36 | it("should render on server", () => {
37 | const result = renderToString(ResponsiveFixture)
38 | expect(result.match(RegExp(content))).not.toBeNull()
39 | })
40 |
41 | it("call resize event", () => {
42 | var resizeEvent = new Event("resize")
43 |
44 | act(() => {
45 | window.dispatchEvent(resizeEvent)
46 | })
47 | })
48 | })
49 |
50 | describe("ResponsiveMasonry with custom tags", () => {
51 | it("renders", () => {
52 | mount(ResponsiveCustomTagsFixture)
53 | })
54 |
55 | it("should render on server", () => {
56 | const result = renderToString(ResponsiveCustomTagsFixture)
57 |
58 | expect(result.match(RegExp(content))).not.toBeNull()
59 | expect(result.match(RegExp(" {
64 | var resizeEvent = new Event("resize")
65 |
66 | act(() => {
67 | window.dispatchEvent(resizeEvent)
68 | })
69 | })
70 | })
71 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Masonry from "./Masonry"
2 | import ResponsiveMasonry from "./ResponsiveMasonry"
3 |
4 | export default Masonry
5 | export {ResponsiveMasonry}
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "node16",
4 | "lib": [
5 | "es6"
6 | ],
7 | "noImplicitAny": true,
8 | "noImplicitThis": true,
9 | "strictFunctionTypes": true,
10 | "strictNullChecks": true,
11 | "typeRoots": ["./types"],
12 | "noEmit": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "jsx": "react"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "react-responsive-masonry" {
2 | import React from "react"
3 |
4 | /**
5 | * List of props for the Masonry component.
6 | *
7 | * @typedef {Object} MasonryProps
8 | * @property {React.ReactNode} children
9 | * @property {number} columnsCount
10 | * @property {string} gutter
11 | * @property {string | null} className
12 | * @property {React.CSSProperties} style
13 | * @property {string} containerTag
14 | * @property {string} itemTag
15 | * @property {React.CSSProperties} itemStyle
16 | */
17 | export interface MasonryProps {
18 | /**
19 | * Children to be rendered in the Masonry component as items.
20 | *
21 | * @type {React.ReactNode}
22 | */
23 | children: React.ReactNode
24 | /**
25 | * Number of columns to be rendered in the Masonry component.
26 | * Default is 3.
27 | *
28 | * @type {number}
29 | */
30 | columnsCount?: number
31 | /**
32 | * Gutter size between the columns.
33 | * Default is "0".
34 | *
35 | * @type {string}
36 | */
37 | gutter?: string
38 | /**
39 | * Class name for the Masonry component container.
40 | * Default is null.
41 | *
42 | * @type {string | null}
43 | */
44 | className?: string | null
45 | /**
46 | * Style object for the Masonry component container.
47 | * Default is {}.
48 | *
49 | * @type {React.CSSProperties}
50 | */
51 | style?: React.CSSProperties
52 | /**
53 | * Tag name for the Masonry component container.
54 | * Default is "div".
55 | *
56 | * @type {string}
57 | */
58 | containerTag?: string
59 | /**
60 | * Tag name for the Masonry component item.
61 | * Default is "div".
62 | *
63 | * @type {string}
64 | */
65 | itemTag?: string
66 | /**
67 | * Style object for the Masonry component item.
68 | * Default is {}.
69 | *
70 | * @type {React.CSSProperties}
71 | */
72 | itemStyle?: React.CSSProperties
73 | }
74 |
75 | /**
76 | * List of props for the ResponsiveMasonry component.
77 | *
78 | * @typedef {Object} ResponsiveMasonryProps
79 | * @property {React.ReactNode} children
80 | * @property {{[key: number]: number}} columnsCountBreakPoints
81 | * @property {string | null} className
82 | * @property {React.CSSProperties | null} style
83 | */
84 | export interface ResponsiveMasonryProps {
85 | /**
86 | * Children to be rendered in the ResponsiveMasonry component as items.
87 | *
88 | * @type {React.ReactNode}
89 | */
90 | children: React.ReactNode
91 | /**
92 | * Breakpoints for the number of columns to be rendered in the ResponsiveMasonry component.
93 | * Default is { 350: 1, 750: 2, 900: 3 }
94 | *
95 | * @type {{[breakpoint: number]: number}}
96 | */
97 | columnsCountBreakPoints?: {[breakpoint: number]: number}
98 | /**
99 | * Breakpoints for the gutter size in the ResponsiveMasonry component.
100 | *
101 | * @type {{[breakpoint: number]: number}}
102 | */
103 | gutterBreakPoints?: {[breakpoint: number]: number}
104 | /**
105 | * Class name for the ResponsiveMasonry component container.
106 | * Default is null.
107 | *
108 | * @type {string | null}
109 | */
110 | className?: string | null
111 | /**
112 | * Style object for the ResponsiveMasonry component container.
113 | * Default is null.
114 | *
115 | * @type {React.CSSProperties | null}
116 | */
117 | style?: React.CSSProperties | null
118 | }
119 |
120 | const Masonry: React.FC
121 | const ResponsiveMasonry: React.FC
122 |
123 | export default Masonry
124 | export {ResponsiveMasonry}
125 | }
126 |
--------------------------------------------------------------------------------