react-responsive-masonry
Version:
React responsive masonry component built with css flexbox
180 lines (162 loc) • 6 kB
JavaScript
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); }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
import PropTypes from "prop-types";
import React from "react";
var Masonry = /*#__PURE__*/function (_React$Component) {
_inheritsLoose(Masonry, _React$Component);
function Masonry() {
var _this;
_this = _React$Component.call(this) || this;
_this.state = {
columns: [],
childRefs: [],
hasDistributed: false
};
return _this;
}
var _proto = Masonry.prototype;
_proto.componentDidUpdate = function componentDidUpdate() {
if (!this.state.hasDistributed && !this.props.sequential) this.distributeChildren();
};
Masonry.getDerivedStateFromProps = function getDerivedStateFromProps(props, state) {
var children = props.children,
columnsCount = props.columnsCount;
var hasColumnsChanged = columnsCount !== state.columns.length;
if (state && children === state.children && !hasColumnsChanged) return null;
return _extends({}, Masonry.getEqualCountColumns(children, columnsCount), {
children: children,
hasDistributed: false
});
};
_proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps) {
return nextProps.children !== this.state.children || nextProps.columnsCount !== this.props.columnsCount;
};
_proto.distributeChildren = function distributeChildren() {
var _this2 = this;
var _this$props = this.props,
children = _this$props.children,
columnsCount = _this$props.columnsCount;
var columnHeights = Array(columnsCount).fill(0);
var isReady = this.state.childRefs.every(function (ref) {
return ref.current.getBoundingClientRect().height;
});
if (!isReady) return;
var columns = Array.from({
length: columnsCount
}, function () {
return [];
});
var validIndex = 0;
React.Children.forEach(children, function (child) {
if (child && React.isValidElement(child)) {
// .current is undefined if ref was passed to a functional component without forwardRef
// now passing ref into a wrapper div so it should always be defined
var childHeight = _this2.state.childRefs[validIndex].current.getBoundingClientRect().height;
var minHeightColumnIndex = columnHeights.indexOf(Math.min.apply(Math, columnHeights));
columnHeights[minHeightColumnIndex] += childHeight;
columns[minHeightColumnIndex].push(child);
validIndex++;
}
});
this.setState(function (p) {
return _extends({}, p, {
columns: columns,
hasDistributed: true
});
});
};
Masonry.getEqualCountColumns = function getEqualCountColumns(children, columnsCount) {
var columns = Array.from({
length: columnsCount
}, function () {
return [];
});
var validIndex = 0;
var childRefs = [];
React.Children.forEach(children, function (child) {
if (child && React.isValidElement(child)) {
var ref = React.createRef();
childRefs.push(ref);
columns[validIndex % columnsCount].push( /*#__PURE__*/React.createElement("div", {
style: {
display: "flex",
justifyContent: "stretch"
},
key: validIndex,
ref: ref
}, child) // React.cloneElement(child, {ref}) // cannot attach refs to functional components without forwardRef
);
validIndex++;
}
});
return {
columns: columns,
childRefs: childRefs
};
};
_proto.renderColumns = function renderColumns() {
var _this$props2 = this.props,
gutter = _this$props2.gutter,
itemTag = _this$props2.itemTag,
itemStyle = _this$props2.itemStyle;
return this.state.columns.map(function (column, i) {
return React.createElement(itemTag, {
key: i,
style: _extends({
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignContent: "stretch",
flex: 1,
width: 0,
gap: gutter
}, itemStyle)
}, column.map(function (item) {
return item;
}));
});
};
_proto.render = function render() {
var _this$props3 = this.props,
gutter = _this$props3.gutter,
className = _this$props3.className,
style = _this$props3.style,
containerTag = _this$props3.containerTag;
return React.createElement(containerTag, {
style: _extends({
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignContent: "stretch",
boxSizing: "border-box",
width: "100%",
gap: gutter
}, style),
className: className
}, this.renderColumns());
};
return Masonry;
}(React.Component);
Masonry.propTypes = process.env.NODE_ENV !== "production" ? {
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
columnsCount: PropTypes.number,
gutter: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
containerTag: PropTypes.string,
itemTag: PropTypes.string,
itemStyle: PropTypes.object,
sequential: PropTypes.bool
} : {};
Masonry.defaultProps = {
columnsCount: 3,
gutter: "0",
className: null,
style: {},
containerTag: "div",
itemTag: "div",
itemStyle: {},
sequential: false
};
export default Masonry;