import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import {
WidthProvider,
Responsive
} from 'react-grid-layout';
import { Row as BSRow } from 'reactstrap';
import { FloatGridContext } from './floatGridContext';
const ResponsiveGrid = WidthProvider(Responsive);
const responsiveBreakpoints = {
xl: 1139, lg: 959, md: 719, sm: 539, xs: 0
//xl: Number.MAX_VALUE, lg: 1199, md: 991, sm: 767, xs: 576
};
const breakPointSteps = _.keys(responsiveBreakpoints);
const TOTAL_ROW = 12;
const simplifyChildrenArray = (reactChildren) => _.map(reactChildren, child => (
{ ...child, key: child.key.replace(/\.\$/g, '') }
));
export class Row extends React.Component {
static propTypes = {
children: PropTypes.node,
columns: PropTypes.object,
onLayoutChange: PropTypes.func,
rowHeight: PropTypes.number,
gridSize: PropTypes.object
};
static contextType = FloatGridContext;
_lastLayouts = { };
state = {
trueColSizes: { },
activeLayout: 'xl'
}
initialDebounceTimeout = false;
componentDidUpdate(nextProps) {
if (!_.isEqual(nextProps.gridSize, this.props.gridSize)) {
if (!_.isEmpty(this._lastLayouts)) {
this._updateTrueColSizes(this._lastLayouts[this.state.activeLayout]);
}
}
}
render() {
const { children, rowHeight, onLayoutChange, ...otherProps } = this.props;
const { trueColSizes } = this.state;
if (this.context.active) {
const layouts = this._lastLayouts = this._calculateLayouts(children);
const adjustedChildren = simplifyChildrenArray(
React.Children.map(children, (child) =>
React.cloneElement(child, { trueSize: trueColSizes[child.props.i] })));
return (
{
// Notify the parent
onLayoutChange(this._transformForChangeHandler(allLayouts));
// Recalculate true sizes
this._updateTrueColSizes(currentLayout);
clearTimeout(this.initialDebounceTimeout);
this.initialDebounceTimeout = setTimeout(() => {
this.context.setGridReady();
}, 0);
}}
onBreakpointChange={(activeLayout) => {
this.setState({ activeLayout });
}}
onResize={
(layout, oldItem, newItem) => {
this.setState({
trueColSizes: {
...trueColSizes,
[newItem.i]: this.context.gridUnitsToPx(newItem.w, newItem.h)
}
});
}
}
{ ...otherProps }
>
{ adjustedChildren }
);
} else {
const adjustedChildren = React.Children.map(children, (child) =>
React.cloneElement(child, { active: false }));
return (
{ adjustedChildren }
);
}
}
_updateTrueColSizes = (layout) => {
const { trueColSizes } = this.state;
for (let child of layout) {
trueColSizes[child.i] = this.context.gridUnitsToPx(child.w, child.h);
}
this.setState({ trueColSizes });
}
/**
* Finds the nearest breakpoint relative to the one provided in the
* first param. For example - when the `definition` param contains
* such bps - { md: 6, xs: 8 }, for `breakpoint` - xl/md will return 6
*/
_findClosestBreakpoint = (breakpoint, definition) => {
let found = 12;
for (let bp of _.drop(breakPointSteps, _.indexOf(breakPointSteps, breakpoint))) {
if (!_.isUndefined(definition[bp])) {
found = definition[bp];
}
}
return found;
}
_calculateLayouts = (children) => {
let output = { };
const childrenArray = React.Children.toArray(children);
for (let breakPoint of breakPointSteps) {
let rowChildren = [];
let rowCounter = 0;
let y = 0;
for (let child of childrenArray) {
let bpData = { };
// Save the props for current child and breakpoint
const config = _.pick(child.props, [
'i',
'h', 'minH', 'maxH',
'minW', 'maxW',
breakPoint, `${breakPoint}MinW`, `${breakPoint}MaxW`,
'moved', 'static', 'isResizable', 'isDraggable'
]);
// Calculate the needed definition
bpData = Object.assign(bpData, {
...config,
// Add default heights when none provided
...{
// Set the x to the calculated value or take from the
// props if defined for controlled type
x: _.isUndefined(child.props[`${breakPoint}X`]) ?
rowCounter : child.props[`${breakPoint}X`],
h: child.props[`${breakPoint}H`] || config.h || 1,
minH: config.minH || (config.h || 1),
maxH: config.maxH || (config.h || 1),
},
w: config[breakPoint] ||
this._findClosestBreakpoint(breakPoint, child.props),
// Set the y to the calculated value or take from the
// props if defined for controlled type
y: _.isUndefined(child.props[`${breakPoint}Y`]) ?
y : child.props[`${breakPoint}Y`]
});
rowChildren = [...rowChildren, bpData];
rowCounter += bpData.w;
if (rowCounter + bpData.x > TOTAL_ROW) {
// Increase by the largest found height
y += _.max(_.map(rowChildren, 'h'));
rowCounter = 0;
rowChildren = [];
}
output = {
...output,
[breakPoint]: [...(output[breakPoint] || []), bpData]
}
}
}
return output;
}
/**
* Transform the calculated layout to a structure
* which is provided by `layouts` props
*/
_transformForChangeHandler = (layouts) => {
let output = { };
for (let breakPoint of breakPointSteps) {
const bpLayout = layouts[breakPoint];
for (let element of bpLayout) {
output[element.i] = {
...(output[element.i]),
...(element),
[breakPoint]: element.w,
[`${breakPoint}X`]: element.x,
[`${breakPoint}Y`]: element.y,
[`${breakPoint}H`]: element.h
}
}
}
return output;
}
}