├── .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 |
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