├── .gitignore ├── LICENSE ├── build.js ├── demo ├── demo.built.js ├── demo.js └── index.html ├── dist ├── react-masonry-css.cjs.js ├── react-masonry-css.module.js └── react-masonry-css.umd.js ├── index.d.ts ├── package.json ├── readme.md └── src └── react-masonry-css.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .tool-versions 4 | yarn.lock 5 | *.log 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Paul Collett 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var rollup = require('rollup'); 3 | var babel = require('@rollup/plugin-babel').default 4 | var resolve = require('@rollup/plugin-node-resolve').default 5 | var commonjs = require('@rollup/plugin-commonjs') 6 | var replace = require('@rollup/plugin-replace') 7 | 8 | 9 | const build = rollup.rollup({ 10 | input: './src/react-masonry-css.js', 11 | external: ['react'], 12 | plugins: [babel({ 13 | babelHelpers: 'inline', 14 | plugins: [ 15 | "@babel/plugin-transform-react-jsx", 16 | "@babel/plugin-transform-object-assign", 17 | "@babel/plugin-proposal-object-rest-spread" 18 | ] 19 | })] 20 | }) 21 | 22 | const files = build 23 | .then(bundle => Promise.all([ 24 | bundle.generate({ 25 | format: 'es', 26 | }) 27 | .then((result) => result.output[0].code) 28 | .then((code) => write('./dist/react-masonry-css.module.js', code)), 29 | bundle.generate({ 30 | format: 'cjs', 31 | exports: 'auto' 32 | }) 33 | .then((result) => result.output[0].code) 34 | .then((code) => write('./dist/react-masonry-css.cjs.js', code)), 35 | bundle.generate({ 36 | format: 'umd', 37 | globals: { react: 'react' }, 38 | name: 'Masonry' // window.Masonry 39 | }) 40 | .then((result) => result.output[0].code) 41 | .then((code) => write('./dist/react-masonry-css.umd.js', code)) 42 | ])) 43 | .catch(logError) 44 | 45 | 46 | // Demo 47 | files.then(() => rollup.rollup({ 48 | input: './demo/demo.js', 49 | plugins: [ 50 | replace({ 51 | preventAssignment: true, 52 | 'process.env.NODE_ENV': '"production"' 53 | }), 54 | babel({ 55 | babelHelpers: 'bundled', 56 | plugins: ["@babel/plugin-transform-react-jsx"] 57 | }), 58 | // needed to import react/other node_modules 59 | commonjs(), 60 | resolve(), 61 | ] 62 | })) 63 | .then(bundle => 64 | bundle.generate({ 65 | format: 'iife', 66 | //name: 'Masonry', 67 | }) 68 | .then((result) => result.output[0].code) 69 | .then((code) => write('./demo/demo.built.js', code)) 70 | ) 71 | .catch(logError) 72 | 73 | 74 | function write(dest, code) { 75 | return new Promise(function (resolve, reject) { 76 | fs.writeFile(dest, code, function (err) { 77 | if (err) return reject(err); 78 | console.log(blue(dest) + ' ' + getSize(code)); 79 | resolve() 80 | }); 81 | }); 82 | } 83 | 84 | function getSize(code) { 85 | return (code.length / 1024).toFixed(2) + 'kb'; 86 | } 87 | 88 | function logError(e) { 89 | console.log(e); 90 | } 91 | 92 | function blue(str) { 93 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'; 94 | } 95 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | import Masonry from '../dist/react-masonry-css.cjs' 4 | 5 | // Placeholder text library 6 | import Dummy from 'dummyjs' 7 | 8 | class App extends React.Component { 9 | constructor (props) { 10 | super(props); 11 | 12 | this.state = { 13 | breakpointColumnsObj: { 14 | default: 4, 15 | 1100: 3, 16 | 700: 2, 17 | 500: 1 18 | } 19 | }; 20 | } 21 | 22 | handleChange() { 23 | // Previously changed? Revert back to initial state 24 | if(this.initialState) { 25 | this.setState(this.initialState); 26 | 27 | delete this.initialState; 28 | 29 | // Capture initial state and update 30 | } else { 31 | this.initialState = this.state; 32 | 33 | this.setState({ 34 | breakpointColumnsObj: { 35 | default: 5, 36 | 1100: 4, 37 | 700: 3, 38 | 500: 2 39 | } 40 | }); 41 | } 42 | } 43 | 44 | render () { 45 | const breakpointColumnsObj = this.state.breakpointColumnsObj; 46 | 47 | const items = new Array(8).fill().map((item, i) => { 48 | return ( 49 |
50 | Item #{i + 1} 51 |
52 |
{Dummy.text('20,60')}
53 |
54 | ) 55 | }); 56 | 57 | return ( 58 |
59 | 60 | {' '} 61 | 62 |
63 | 69 | {items} 70 | 71 |
Placeholder images and dummy text from "dummyjs" react module. See DummyJs.com
72 |
73 | ); 74 | } 75 | } 76 | 77 | ReactDom.render(, document.getElementById('app')) 78 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Masonry Css Example 6 | 7 | 8 | 29 | 30 | 31 | ← GitHub / Docs 32 |

react-masonry-css example

33 |

A React masonry component powered by CSS to rendered fast and be free of jQuery or other dependencies. Build specifically for React projects.

34 | 35 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /dist/react-masonry-css.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 6 | 7 | var React__default = /*#__PURE__*/_interopDefaultLegacy(React); 8 | 9 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 10 | 11 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 12 | 13 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 14 | 15 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } 16 | 17 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 18 | 19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 20 | const defaultProps = { 21 | breakpointCols: undefined, 22 | // optional, number or object { default: number, [key: number]: number } 23 | className: undefined, 24 | // required, string 25 | columnClassName: undefined, 26 | // optional, string 27 | // Any React children. Typically an array of JSX items 28 | children: undefined, 29 | // Custom attributes, however it is advised against 30 | // using these to prevent unintended issues and future conflicts 31 | // ...any other attribute, will be added to the container 32 | columnAttrs: undefined, 33 | // object, added to the columns 34 | // Deprecated props 35 | // The column property is deprecated. 36 | // It is an alias of the `columnAttrs` property 37 | column: undefined 38 | }; 39 | const DEFAULT_COLUMNS = 2; 40 | 41 | class Masonry extends React__default['default'].Component { 42 | constructor(props) { 43 | super(props); // Correct scope for when methods are accessed externally 44 | 45 | this.reCalculateColumnCount = this.reCalculateColumnCount.bind(this); 46 | this.reCalculateColumnCountDebounce = this.reCalculateColumnCountDebounce.bind(this); // default state 47 | 48 | let columnCount; 49 | 50 | if (this.props.breakpointCols && this.props.breakpointCols.default) { 51 | columnCount = this.props.breakpointCols.default; 52 | } else { 53 | columnCount = parseInt(this.props.breakpointCols) || DEFAULT_COLUMNS; 54 | } 55 | 56 | this.state = { 57 | columnCount 58 | }; 59 | } 60 | 61 | componentDidMount() { 62 | this.reCalculateColumnCount(); // window may not be available in some environments 63 | 64 | if (window) { 65 | window.addEventListener('resize', this.reCalculateColumnCountDebounce); 66 | } 67 | } 68 | 69 | componentDidUpdate() { 70 | this.reCalculateColumnCount(); 71 | } 72 | 73 | componentWillUnmount() { 74 | if (window) { 75 | window.removeEventListener('resize', this.reCalculateColumnCountDebounce); 76 | } 77 | } 78 | 79 | reCalculateColumnCountDebounce() { 80 | if (!window || !window.requestAnimationFrame) { 81 | // IE10+ 82 | this.reCalculateColumnCount(); 83 | return; 84 | } 85 | 86 | if (window.cancelAnimationFrame) { 87 | // IE10+ 88 | window.cancelAnimationFrame(this._lastRecalculateAnimationFrame); 89 | } 90 | 91 | this._lastRecalculateAnimationFrame = window.requestAnimationFrame(() => { 92 | this.reCalculateColumnCount(); 93 | }); 94 | } 95 | 96 | reCalculateColumnCount() { 97 | const windowWidth = window && window.innerWidth || Infinity; 98 | let breakpointColsObject = this.props.breakpointCols; // Allow passing a single number to `breakpointCols` instead of an object 99 | 100 | if (typeof breakpointColsObject !== 'object') { 101 | breakpointColsObject = { 102 | default: parseInt(breakpointColsObject) || DEFAULT_COLUMNS 103 | }; 104 | } 105 | 106 | let matchedBreakpoint = Infinity; 107 | let columns = breakpointColsObject.default || DEFAULT_COLUMNS; 108 | 109 | for (let breakpoint in breakpointColsObject) { 110 | const optBreakpoint = parseInt(breakpoint); 111 | const isCurrentBreakpoint = optBreakpoint > 0 && windowWidth <= optBreakpoint; 112 | 113 | if (isCurrentBreakpoint && optBreakpoint < matchedBreakpoint) { 114 | matchedBreakpoint = optBreakpoint; 115 | columns = breakpointColsObject[breakpoint]; 116 | } 117 | } 118 | 119 | columns = Math.max(1, parseInt(columns) || 1); 120 | 121 | if (this.state.columnCount !== columns) { 122 | this.setState({ 123 | columnCount: columns 124 | }); 125 | } 126 | } 127 | 128 | itemsInColumns() { 129 | const currentColumnCount = this.state.columnCount; 130 | const itemsInColumns = new Array(currentColumnCount); // Force children to be handled as an array 131 | 132 | const items = React__default['default'].Children.toArray(this.props.children); 133 | 134 | for (let i = 0; i < items.length; i++) { 135 | const columnIndex = i % currentColumnCount; 136 | 137 | if (!itemsInColumns[columnIndex]) { 138 | itemsInColumns[columnIndex] = []; 139 | } 140 | 141 | itemsInColumns[columnIndex].push(items[i]); 142 | } 143 | 144 | return itemsInColumns; 145 | } 146 | 147 | renderColumns() { 148 | const { 149 | column, 150 | columnAttrs = {}, 151 | columnClassName 152 | } = this.props; 153 | const childrenInColumns = this.itemsInColumns(); 154 | const columnWidth = `${100 / childrenInColumns.length}%`; 155 | let className = columnClassName; 156 | 157 | if (className && typeof className !== 'string') { 158 | this.logDeprecated('The property "columnClassName" requires a string'); // This is a deprecated default and will be removed soon. 159 | 160 | if (typeof className === 'undefined') { 161 | className = 'my-masonry-grid_column'; 162 | } 163 | } 164 | 165 | const columnAttributes = _objectSpread(_objectSpread(_objectSpread({}, column), columnAttrs), {}, { 166 | style: _objectSpread(_objectSpread({}, columnAttrs.style), {}, { 167 | width: columnWidth 168 | }), 169 | className 170 | }); 171 | 172 | return childrenInColumns.map((items, i) => { 173 | return /*#__PURE__*/React__default['default'].createElement("div", _extends({}, columnAttributes, { 174 | key: i 175 | }), items); 176 | }); 177 | } 178 | 179 | logDeprecated(message) { 180 | console.error('[Masonry]', message); 181 | } 182 | 183 | render() { 184 | const _this$props = this.props, 185 | { 186 | // ignored 187 | children, 188 | breakpointCols, 189 | columnClassName, 190 | columnAttrs, 191 | column, 192 | // used 193 | className 194 | } = _this$props, 195 | rest = _objectWithoutProperties(_this$props, ["children", "breakpointCols", "columnClassName", "columnAttrs", "column", "className"]); 196 | 197 | let classNameOutput = className; 198 | 199 | if (typeof className !== 'string') { 200 | this.logDeprecated('The property "className" requires a string'); // This is a deprecated default and will be removed soon. 201 | 202 | if (typeof className === 'undefined') { 203 | classNameOutput = 'my-masonry-grid'; 204 | } 205 | } 206 | 207 | return /*#__PURE__*/React__default['default'].createElement("div", _extends({}, rest, { 208 | className: classNameOutput 209 | }), this.renderColumns()); 210 | } 211 | 212 | } 213 | 214 | Masonry.defaultProps = defaultProps; 215 | 216 | module.exports = Masonry; 217 | -------------------------------------------------------------------------------- /dist/react-masonry-css.module.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 4 | 5 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 6 | 7 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 8 | 9 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } 10 | 11 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 12 | 13 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 14 | const defaultProps = { 15 | breakpointCols: undefined, 16 | // optional, number or object { default: number, [key: number]: number } 17 | className: undefined, 18 | // required, string 19 | columnClassName: undefined, 20 | // optional, string 21 | // Any React children. Typically an array of JSX items 22 | children: undefined, 23 | // Custom attributes, however it is advised against 24 | // using these to prevent unintended issues and future conflicts 25 | // ...any other attribute, will be added to the container 26 | columnAttrs: undefined, 27 | // object, added to the columns 28 | // Deprecated props 29 | // The column property is deprecated. 30 | // It is an alias of the `columnAttrs` property 31 | column: undefined 32 | }; 33 | const DEFAULT_COLUMNS = 2; 34 | 35 | class Masonry extends React.Component { 36 | constructor(props) { 37 | super(props); // Correct scope for when methods are accessed externally 38 | 39 | this.reCalculateColumnCount = this.reCalculateColumnCount.bind(this); 40 | this.reCalculateColumnCountDebounce = this.reCalculateColumnCountDebounce.bind(this); // default state 41 | 42 | let columnCount; 43 | 44 | if (this.props.breakpointCols && this.props.breakpointCols.default) { 45 | columnCount = this.props.breakpointCols.default; 46 | } else { 47 | columnCount = parseInt(this.props.breakpointCols) || DEFAULT_COLUMNS; 48 | } 49 | 50 | this.state = { 51 | columnCount 52 | }; 53 | } 54 | 55 | componentDidMount() { 56 | this.reCalculateColumnCount(); // window may not be available in some environments 57 | 58 | if (window) { 59 | window.addEventListener('resize', this.reCalculateColumnCountDebounce); 60 | } 61 | } 62 | 63 | componentDidUpdate() { 64 | this.reCalculateColumnCount(); 65 | } 66 | 67 | componentWillUnmount() { 68 | if (window) { 69 | window.removeEventListener('resize', this.reCalculateColumnCountDebounce); 70 | } 71 | } 72 | 73 | reCalculateColumnCountDebounce() { 74 | if (!window || !window.requestAnimationFrame) { 75 | // IE10+ 76 | this.reCalculateColumnCount(); 77 | return; 78 | } 79 | 80 | if (window.cancelAnimationFrame) { 81 | // IE10+ 82 | window.cancelAnimationFrame(this._lastRecalculateAnimationFrame); 83 | } 84 | 85 | this._lastRecalculateAnimationFrame = window.requestAnimationFrame(() => { 86 | this.reCalculateColumnCount(); 87 | }); 88 | } 89 | 90 | reCalculateColumnCount() { 91 | const windowWidth = window && window.innerWidth || Infinity; 92 | let breakpointColsObject = this.props.breakpointCols; // Allow passing a single number to `breakpointCols` instead of an object 93 | 94 | if (typeof breakpointColsObject !== 'object') { 95 | breakpointColsObject = { 96 | default: parseInt(breakpointColsObject) || DEFAULT_COLUMNS 97 | }; 98 | } 99 | 100 | let matchedBreakpoint = Infinity; 101 | let columns = breakpointColsObject.default || DEFAULT_COLUMNS; 102 | 103 | for (let breakpoint in breakpointColsObject) { 104 | const optBreakpoint = parseInt(breakpoint); 105 | const isCurrentBreakpoint = optBreakpoint > 0 && windowWidth <= optBreakpoint; 106 | 107 | if (isCurrentBreakpoint && optBreakpoint < matchedBreakpoint) { 108 | matchedBreakpoint = optBreakpoint; 109 | columns = breakpointColsObject[breakpoint]; 110 | } 111 | } 112 | 113 | columns = Math.max(1, parseInt(columns) || 1); 114 | 115 | if (this.state.columnCount !== columns) { 116 | this.setState({ 117 | columnCount: columns 118 | }); 119 | } 120 | } 121 | 122 | itemsInColumns() { 123 | const currentColumnCount = this.state.columnCount; 124 | const itemsInColumns = new Array(currentColumnCount); // Force children to be handled as an array 125 | 126 | const items = React.Children.toArray(this.props.children); 127 | 128 | for (let i = 0; i < items.length; i++) { 129 | const columnIndex = i % currentColumnCount; 130 | 131 | if (!itemsInColumns[columnIndex]) { 132 | itemsInColumns[columnIndex] = []; 133 | } 134 | 135 | itemsInColumns[columnIndex].push(items[i]); 136 | } 137 | 138 | return itemsInColumns; 139 | } 140 | 141 | renderColumns() { 142 | const { 143 | column, 144 | columnAttrs = {}, 145 | columnClassName 146 | } = this.props; 147 | const childrenInColumns = this.itemsInColumns(); 148 | const columnWidth = `${100 / childrenInColumns.length}%`; 149 | let className = columnClassName; 150 | 151 | if (className && typeof className !== 'string') { 152 | this.logDeprecated('The property "columnClassName" requires a string'); // This is a deprecated default and will be removed soon. 153 | 154 | if (typeof className === 'undefined') { 155 | className = 'my-masonry-grid_column'; 156 | } 157 | } 158 | 159 | const columnAttributes = _objectSpread(_objectSpread(_objectSpread({}, column), columnAttrs), {}, { 160 | style: _objectSpread(_objectSpread({}, columnAttrs.style), {}, { 161 | width: columnWidth 162 | }), 163 | className 164 | }); 165 | 166 | return childrenInColumns.map((items, i) => { 167 | return /*#__PURE__*/React.createElement("div", _extends({}, columnAttributes, { 168 | key: i 169 | }), items); 170 | }); 171 | } 172 | 173 | logDeprecated(message) { 174 | console.error('[Masonry]', message); 175 | } 176 | 177 | render() { 178 | const _this$props = this.props, 179 | { 180 | // ignored 181 | children, 182 | breakpointCols, 183 | columnClassName, 184 | columnAttrs, 185 | column, 186 | // used 187 | className 188 | } = _this$props, 189 | rest = _objectWithoutProperties(_this$props, ["children", "breakpointCols", "columnClassName", "columnAttrs", "column", "className"]); 190 | 191 | let classNameOutput = className; 192 | 193 | if (typeof className !== 'string') { 194 | this.logDeprecated('The property "className" requires a string'); // This is a deprecated default and will be removed soon. 195 | 196 | if (typeof className === 'undefined') { 197 | classNameOutput = 'my-masonry-grid'; 198 | } 199 | } 200 | 201 | return /*#__PURE__*/React.createElement("div", _extends({}, rest, { 202 | className: classNameOutput 203 | }), this.renderColumns()); 204 | } 205 | 206 | } 207 | 208 | Masonry.defaultProps = defaultProps; 209 | 210 | export default Masonry; 211 | -------------------------------------------------------------------------------- /dist/react-masonry-css.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) : 3 | typeof define === 'function' && define.amd ? define(['react'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Masonry = factory(global.react)); 5 | }(this, (function (React) { 'use strict'; 6 | 7 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 8 | 9 | var React__default = /*#__PURE__*/_interopDefaultLegacy(React); 10 | 11 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 12 | 13 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 14 | 15 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 16 | 17 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } 18 | 19 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 20 | 21 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 22 | const defaultProps = { 23 | breakpointCols: undefined, 24 | // optional, number or object { default: number, [key: number]: number } 25 | className: undefined, 26 | // required, string 27 | columnClassName: undefined, 28 | // optional, string 29 | // Any React children. Typically an array of JSX items 30 | children: undefined, 31 | // Custom attributes, however it is advised against 32 | // using these to prevent unintended issues and future conflicts 33 | // ...any other attribute, will be added to the container 34 | columnAttrs: undefined, 35 | // object, added to the columns 36 | // Deprecated props 37 | // The column property is deprecated. 38 | // It is an alias of the `columnAttrs` property 39 | column: undefined 40 | }; 41 | const DEFAULT_COLUMNS = 2; 42 | 43 | class Masonry extends React__default['default'].Component { 44 | constructor(props) { 45 | super(props); // Correct scope for when methods are accessed externally 46 | 47 | this.reCalculateColumnCount = this.reCalculateColumnCount.bind(this); 48 | this.reCalculateColumnCountDebounce = this.reCalculateColumnCountDebounce.bind(this); // default state 49 | 50 | let columnCount; 51 | 52 | if (this.props.breakpointCols && this.props.breakpointCols.default) { 53 | columnCount = this.props.breakpointCols.default; 54 | } else { 55 | columnCount = parseInt(this.props.breakpointCols) || DEFAULT_COLUMNS; 56 | } 57 | 58 | this.state = { 59 | columnCount 60 | }; 61 | } 62 | 63 | componentDidMount() { 64 | this.reCalculateColumnCount(); // window may not be available in some environments 65 | 66 | if (window) { 67 | window.addEventListener('resize', this.reCalculateColumnCountDebounce); 68 | } 69 | } 70 | 71 | componentDidUpdate() { 72 | this.reCalculateColumnCount(); 73 | } 74 | 75 | componentWillUnmount() { 76 | if (window) { 77 | window.removeEventListener('resize', this.reCalculateColumnCountDebounce); 78 | } 79 | } 80 | 81 | reCalculateColumnCountDebounce() { 82 | if (!window || !window.requestAnimationFrame) { 83 | // IE10+ 84 | this.reCalculateColumnCount(); 85 | return; 86 | } 87 | 88 | if (window.cancelAnimationFrame) { 89 | // IE10+ 90 | window.cancelAnimationFrame(this._lastRecalculateAnimationFrame); 91 | } 92 | 93 | this._lastRecalculateAnimationFrame = window.requestAnimationFrame(() => { 94 | this.reCalculateColumnCount(); 95 | }); 96 | } 97 | 98 | reCalculateColumnCount() { 99 | const windowWidth = window && window.innerWidth || Infinity; 100 | let breakpointColsObject = this.props.breakpointCols; // Allow passing a single number to `breakpointCols` instead of an object 101 | 102 | if (typeof breakpointColsObject !== 'object') { 103 | breakpointColsObject = { 104 | default: parseInt(breakpointColsObject) || DEFAULT_COLUMNS 105 | }; 106 | } 107 | 108 | let matchedBreakpoint = Infinity; 109 | let columns = breakpointColsObject.default || DEFAULT_COLUMNS; 110 | 111 | for (let breakpoint in breakpointColsObject) { 112 | const optBreakpoint = parseInt(breakpoint); 113 | const isCurrentBreakpoint = optBreakpoint > 0 && windowWidth <= optBreakpoint; 114 | 115 | if (isCurrentBreakpoint && optBreakpoint < matchedBreakpoint) { 116 | matchedBreakpoint = optBreakpoint; 117 | columns = breakpointColsObject[breakpoint]; 118 | } 119 | } 120 | 121 | columns = Math.max(1, parseInt(columns) || 1); 122 | 123 | if (this.state.columnCount !== columns) { 124 | this.setState({ 125 | columnCount: columns 126 | }); 127 | } 128 | } 129 | 130 | itemsInColumns() { 131 | const currentColumnCount = this.state.columnCount; 132 | const itemsInColumns = new Array(currentColumnCount); // Force children to be handled as an array 133 | 134 | const items = React__default['default'].Children.toArray(this.props.children); 135 | 136 | for (let i = 0; i < items.length; i++) { 137 | const columnIndex = i % currentColumnCount; 138 | 139 | if (!itemsInColumns[columnIndex]) { 140 | itemsInColumns[columnIndex] = []; 141 | } 142 | 143 | itemsInColumns[columnIndex].push(items[i]); 144 | } 145 | 146 | return itemsInColumns; 147 | } 148 | 149 | renderColumns() { 150 | const { 151 | column, 152 | columnAttrs = {}, 153 | columnClassName 154 | } = this.props; 155 | const childrenInColumns = this.itemsInColumns(); 156 | const columnWidth = `${100 / childrenInColumns.length}%`; 157 | let className = columnClassName; 158 | 159 | if (className && typeof className !== 'string') { 160 | this.logDeprecated('The property "columnClassName" requires a string'); // This is a deprecated default and will be removed soon. 161 | 162 | if (typeof className === 'undefined') { 163 | className = 'my-masonry-grid_column'; 164 | } 165 | } 166 | 167 | const columnAttributes = _objectSpread(_objectSpread(_objectSpread({}, column), columnAttrs), {}, { 168 | style: _objectSpread(_objectSpread({}, columnAttrs.style), {}, { 169 | width: columnWidth 170 | }), 171 | className 172 | }); 173 | 174 | return childrenInColumns.map((items, i) => { 175 | return /*#__PURE__*/React__default['default'].createElement("div", _extends({}, columnAttributes, { 176 | key: i 177 | }), items); 178 | }); 179 | } 180 | 181 | logDeprecated(message) { 182 | console.error('[Masonry]', message); 183 | } 184 | 185 | render() { 186 | const _this$props = this.props, 187 | { 188 | // ignored 189 | children, 190 | breakpointCols, 191 | columnClassName, 192 | columnAttrs, 193 | column, 194 | // used 195 | className 196 | } = _this$props, 197 | rest = _objectWithoutProperties(_this$props, ["children", "breakpointCols", "columnClassName", "columnAttrs", "column", "className"]); 198 | 199 | let classNameOutput = className; 200 | 201 | if (typeof className !== 'string') { 202 | this.logDeprecated('The property "className" requires a string'); // This is a deprecated default and will be removed soon. 203 | 204 | if (typeof className === 'undefined') { 205 | classNameOutput = 'my-masonry-grid'; 206 | } 207 | } 208 | 209 | return /*#__PURE__*/React__default['default'].createElement("div", _extends({}, rest, { 210 | className: classNameOutput 211 | }), this.renderColumns()); 212 | } 213 | 214 | } 215 | 216 | Masonry.defaultProps = defaultProps; 217 | 218 | return Masonry; 219 | 220 | }))); 221 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface MasonryProps { 4 | breakpointCols?: number | { default: number, [key: number]: number } | { [key: number]: number }; 5 | columnClassName?: string; 6 | className: string; 7 | } 8 | 9 | export default class Masonry extends React.Component, any> { 10 | render(): JSX.Element; 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-masonry-css", 3 | "version": "1.0.16", 4 | "description": "React Masonry component leveraging CSS to be fast and responsive", 5 | "main": "dist/react-masonry-css.cjs.js", 6 | "module": "dist/react-masonry-css.module.js", 7 | "umd:main": "dist/react-masonry-css.umd.js", 8 | "scripts": { 9 | "build": "node ./build.js", 10 | "start": "npm run build && serve ./demo", 11 | "preversion": "npm run build", 12 | "prepublishOnly": "npm run build", 13 | "publish-package": "git push && git push --tags && npm publish" 14 | }, 15 | "files": [ 16 | "index.d.ts", 17 | "dist" 18 | ], 19 | "types": "index.d.ts", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/paulcollett/react-masonry-css.git" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "masonry", 27 | "component", 28 | "css", 29 | "masonary", 30 | "grid" 31 | ], 32 | "author": "Paul Collett", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/paulcollett/react-masonry-css/issues" 36 | }, 37 | "homepage": "https://github.com/paulcollett/react-masonry-css#readme", 38 | "peerDependencies": { 39 | "react": ">=16.0.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.13.14", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.13.8", 44 | "@babel/plugin-transform-object-assign": "^7.12.13", 45 | "@babel/plugin-transform-react-jsx": "^7.13.12", 46 | "@rollup/plugin-babel": "^5.3.0", 47 | "dummyjs": "^1.1.7", 48 | "react": "^17.0.2", 49 | "react-dom": "^17.0.2", 50 | "rollup": "^2.44.0", 51 | "serve": "^11.3.2", 52 | "@rollup/plugin-commonjs": "^18.0.0", 53 | "@rollup/plugin-node-resolve": "^11.2.1", 54 | "@rollup/plugin-replace": "^2.4.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | A Masonry component leveraging CSS and native React rendering, for fast, responsive masonry layouts 2 | 3 | ![image](https://user-images.githubusercontent.com/1904774/30821174-491e9670-a1d9-11e7-8b18-250d54858c4f.png) 4 | 5 | ### 😎 Why? 6 | 7 | Existing solutions like React wrapped DeSandro Masonry, while popular, don't actually leverage React's highly optimized Virtual DOM renderer and in DeSandro Masonry's case, actually renders elements twice before showing the layout. All of this is ok but we found it to lead to a slow, "laggy" user experience that would occasionally miss-render our layout. 8 | 9 | Our need for a simple Masonry layout that was fast, used React's Virtual DOM without needing jQuery or other dependencies led us to explore what we could do with the latest techniques using just CSS within a React Component. 10 | 11 | Between flexbox, css columns, css grid we settled on plain ol' div's and a dab of flexbox that allows for "fluid" responsive layouts by default but most importantly is true to Reacts rendering lifecycle. 12 | 13 | *`react-masonry-css`* Is a React Component with a simple interface to order items into the desired columns at specified breakpoints. With minimal CSS this leads to a quick, reliable solution that also has great browser support along with rendering performance. 14 | 15 | 16 | #### 😄 What does this do 17 | * Responsive! ..always 18 | * IE 10+ 19 | * No Dependencies - Which means no need for jQuery! 20 | * Works with existing CSS animations on your elements, like fading in on first load 21 | * CSS powered (Faster to render) 22 | * Allows for Gaps (Gutters) between elements 23 | 24 | #### 🏳️ What doesn't this do 25 | 26 | * Works with elements with different widths 27 | * Sorting based on height - This kills performance, so if you don't need it we're here for you 28 | 29 | ### 😲 Simple Usage 30 | 31 | Add `react-masonry-css` to your project: 32 | 33 | ```bash 34 | npm install react-masonry-css 35 | ``` 36 | 37 | In your React Component... 38 | ```jsx 39 | import Masonry from 'react-masonry-css' 40 | 41 | //... 42 | 43 | 47 | {/* array of JSX items */} 48 | 49 | ``` 50 | 51 | And, CSS: 52 | ```css 53 | .my-masonry-grid { 54 | display: -webkit-box; /* Not needed if autoprefixing */ 55 | display: -ms-flexbox; /* Not needed if autoprefixing */ 56 | display: flex; 57 | margin-left: -30px; /* gutter size offset */ 58 | width: auto; 59 | } 60 | .my-masonry-grid_column { 61 | padding-left: 30px; /* gutter size */ 62 | background-clip: padding-box; 63 | } 64 | 65 | /* Style your items */ 66 | .my-masonry-grid_column > div { /* change div to reference your elements you put in */ 67 | background: grey; 68 | margin-bottom: 30px; 69 | } 70 | ``` 71 | 72 | ### Responsive Breakpoints 73 | 74 | Different columns can be specified by passing an object containing key's of the window widths and their value as the number of columns. To have a fallback value, use the `default` key. 75 | 76 | ```jsx 77 | 78 | const breakpointColumnsObj = { 79 | default: 4, 80 | 1100: 3, 81 | 700: 2, 82 | 500: 1 83 | }; 84 | 85 | //... 86 | 87 | 92 |
My Element
93 |
My Element
94 |
My Element
95 |
My Element
96 |
97 | ``` 98 | 99 | ### Configuration Props 100 | 101 | * `breakpointCols={{default: 4, 800: 2}}` optional (defaults to 2 columns) 102 | * `className` for the container 103 | * `columnClassName` class name added to each generated column 104 | 105 | ### Example Demo 106 | 107 | https://paulcollett.github.io/react-masonry-css/demo/ 108 | 109 | ### Common usage 110 | 111 | **outputting an array of items:** 112 | ```jsx 113 | var items = [ 114 | {id: 1, name: 'My First Item'}, 115 | {id: 2, name: 'Another item'}, 116 | {id: 3, name: 'Third Item'}, 117 | {id: 4, name: 'Here is the Fourth'}, 118 | {id: 5, name: 'High Five'} 119 | ]; 120 | 121 | // Convert array to JSX items 122 | items = items.map(function(item) { 123 | return
{item.name}
124 | }); 125 | 126 | 131 | {items} 132 | 133 | ``` 134 | 135 | ### Optional, Responsive gutters 136 | We can add the following to the above CSS to further adjust the layout between screen sizes. 137 | ```css 138 | /* Optional, different gutter size on mobile */ 139 | @media (max-width: 800px) { 140 | .my-masonry-grid { 141 | margin-left: -15px; /* gutter size offset */ 142 | } 143 | .my-masonry-grid_column { 144 | padding-left: 15px; /* gutter size offset */ 145 | } 146 | .my-masonry-grid_column > div { 147 | margin-bottom: 15px; /* space between items */ 148 | } 149 | } 150 | ``` 151 | 152 | ### Use with Preact 153 | You can use `react-masonry-css` with [Preact](https://github.com/preactjs/preact) when using `preact/compat` 154 | 155 | ### DummyJS 156 | Improve your frontend builds with dynamic placeholder images and dummy text from [DummyJs.com](https://dummyjs.com/). 157 | https://www.npmjs.com/package/dummyjs 158 | 159 | ### Suggestions & Issues 160 | https://github.com/paulcollett/react-masonry-css/issues/ 161 | 162 | **Contact me direct:** 163 | * https://github.com/paulcollett 164 | * http://paulcollett.com 165 | -------------------------------------------------------------------------------- /src/react-masonry-css.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const defaultProps = { 4 | breakpointCols: undefined, // optional, number or object { default: number, [key: number]: number } 5 | className: undefined, // required, string 6 | columnClassName: undefined, // optional, string 7 | 8 | // Any React children. Typically an array of JSX items 9 | children: undefined, 10 | 11 | // Custom attributes, however it is advised against 12 | // using these to prevent unintended issues and future conflicts 13 | // ...any other attribute, will be added to the container 14 | columnAttrs: undefined, // object, added to the columns 15 | 16 | // Deprecated props 17 | // The column property is deprecated. 18 | // It is an alias of the `columnAttrs` property 19 | column: undefined 20 | }; 21 | 22 | const DEFAULT_COLUMNS = 2; 23 | 24 | class Masonry extends React.Component { 25 | constructor(props) { 26 | super(props); 27 | 28 | // Correct scope for when methods are accessed externally 29 | this.reCalculateColumnCount = this.reCalculateColumnCount.bind(this); 30 | this.reCalculateColumnCountDebounce = this.reCalculateColumnCountDebounce.bind(this); 31 | 32 | // default state 33 | let columnCount 34 | if (this.props.breakpointCols && this.props.breakpointCols.default) { 35 | columnCount = this.props.breakpointCols.default 36 | } else { 37 | columnCount = parseInt(this.props.breakpointCols) || DEFAULT_COLUMNS 38 | } 39 | 40 | this.state = { 41 | columnCount 42 | }; 43 | } 44 | 45 | componentDidMount() { 46 | this.reCalculateColumnCount(); 47 | 48 | // window may not be available in some environments 49 | if(window) { 50 | window.addEventListener('resize', this.reCalculateColumnCountDebounce); 51 | } 52 | } 53 | 54 | componentDidUpdate() { 55 | this.reCalculateColumnCount(); 56 | } 57 | 58 | componentWillUnmount() { 59 | if(window) { 60 | window.removeEventListener('resize', this.reCalculateColumnCountDebounce); 61 | } 62 | } 63 | 64 | reCalculateColumnCountDebounce() { 65 | if(!window || !window.requestAnimationFrame) { // IE10+ 66 | this.reCalculateColumnCount(); 67 | return; 68 | } 69 | 70 | if(window.cancelAnimationFrame) { // IE10+ 71 | window.cancelAnimationFrame(this._lastRecalculateAnimationFrame); 72 | } 73 | 74 | this._lastRecalculateAnimationFrame = window.requestAnimationFrame(() => { 75 | this.reCalculateColumnCount(); 76 | }); 77 | } 78 | 79 | reCalculateColumnCount() { 80 | const windowWidth = window && window.innerWidth || Infinity; 81 | let breakpointColsObject = this.props.breakpointCols; 82 | 83 | // Allow passing a single number to `breakpointCols` instead of an object 84 | if(typeof breakpointColsObject !== 'object') { 85 | breakpointColsObject = { 86 | default: parseInt(breakpointColsObject) || DEFAULT_COLUMNS 87 | } 88 | } 89 | 90 | let matchedBreakpoint = Infinity; 91 | let columns = breakpointColsObject.default || DEFAULT_COLUMNS; 92 | 93 | for(let breakpoint in breakpointColsObject) { 94 | const optBreakpoint = parseInt(breakpoint); 95 | const isCurrentBreakpoint = optBreakpoint > 0 && windowWidth <= optBreakpoint; 96 | 97 | if(isCurrentBreakpoint && optBreakpoint < matchedBreakpoint) { 98 | matchedBreakpoint = optBreakpoint; 99 | columns = breakpointColsObject[breakpoint]; 100 | } 101 | } 102 | 103 | columns = Math.max(1, parseInt(columns) || 1); 104 | 105 | if(this.state.columnCount !== columns) { 106 | this.setState({ 107 | columnCount: columns 108 | }); 109 | } 110 | } 111 | 112 | itemsInColumns() { 113 | const currentColumnCount = this.state.columnCount; 114 | const itemsInColumns = new Array(currentColumnCount); 115 | 116 | // Force children to be handled as an array 117 | const items = React.Children.toArray(this.props.children) 118 | 119 | for (let i = 0; i < items.length; i++) { 120 | const columnIndex = i % currentColumnCount; 121 | 122 | if(!itemsInColumns[columnIndex]) { 123 | itemsInColumns[columnIndex] = []; 124 | } 125 | 126 | itemsInColumns[columnIndex].push(items[i]); 127 | } 128 | 129 | return itemsInColumns; 130 | } 131 | 132 | renderColumns() { 133 | const { column, columnAttrs = {}, columnClassName } = this.props; 134 | const childrenInColumns = this.itemsInColumns(); 135 | const columnWidth = `${100 / childrenInColumns.length}%`; 136 | let className = columnClassName; 137 | 138 | if(className && typeof className !== 'string') { 139 | this.logDeprecated('The property "columnClassName" requires a string'); 140 | 141 | // This is a deprecated default and will be removed soon. 142 | if(typeof className === 'undefined') { 143 | className = 'my-masonry-grid_column'; 144 | } 145 | } 146 | 147 | const columnAttributes = { 148 | // NOTE: the column property is undocumented and considered deprecated. 149 | // It is an alias of the `columnAttrs` property 150 | ...column, 151 | ...columnAttrs, 152 | style: { 153 | ...columnAttrs.style, 154 | width: columnWidth 155 | }, 156 | className 157 | }; 158 | 159 | return childrenInColumns.map((items, i) => { 160 | return
165 | {items} 166 |
; 167 | }); 168 | } 169 | 170 | logDeprecated(message) { 171 | console.error('[Masonry]', message); 172 | } 173 | 174 | render() { 175 | const { 176 | // ignored 177 | children, 178 | breakpointCols, 179 | columnClassName, 180 | columnAttrs, 181 | column, 182 | 183 | // used 184 | className, 185 | 186 | ...rest 187 | } = this.props; 188 | 189 | let classNameOutput = className; 190 | 191 | if(typeof className !== 'string') { 192 | this.logDeprecated('The property "className" requires a string'); 193 | 194 | // This is a deprecated default and will be removed soon. 195 | if(typeof className === 'undefined') { 196 | classNameOutput = 'my-masonry-grid'; 197 | } 198 | } 199 | 200 | return ( 201 |
205 | {this.renderColumns()} 206 |
207 | ); 208 | } 209 | } 210 | 211 | Masonry.defaultProps = defaultProps; 212 | 213 | export default Masonry; 214 | --------------------------------------------------------------------------------