All files / ResponsiveGrid ResponsiveGrid.tsx

78.57% Statements 33/42
25% Branches 4/16
80% Functions 8/10
80.48% Lines 33/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181              2x   2x         2x 2x 2x                           2x         2x   2x                                     7x 7x         7x   22x         22x 22x   10x     7x             10x                                                             7x 7x   7x 6x 5x   1x   1x                                   2x 8x   8x           4x                 2x 2x 2x 2x                       2x              
import _, { omit } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { lucidClassNames } from '../../util/style-helpers';
import { StandardProps, findTypes } from '../../util/component-types';
import Resizer from '../Resizer/Resizer';
 
const cx = lucidClassNames.bind('&-ResponsiveGrid');
 
const { string, number, arrayOf } = PropTypes;
 
export interface IResponsiveGridCellProps extends StandardProps {}
 
/** Cell */
const Cell = (_props: IResponsiveGridCellProps): null => null;
Cell.displayName = 'ResponsiveGrid.Cell';
Cell.peek = {
	description: `Renders a \`<article>\` as the grid cell.`,
};
 
/** Responsive Grid */
 
export interface IResponsiveGridProps extends IResponsiveGridWrapperProps {
	/**
	 * Width of the grid. Note: this does not set the width of the grid, and should be
	 * provided by calculating the width of the parent element.
	 */
	width: number;
}
 
const defaultProps = {
	breakPoints: [960, 1430],
};
 
export class ResponsiveGrid extends React.Component<IResponsiveGridProps> {
	static displayName = 'ResponsiveGrid';
 
	static propTypes = {
		/** 
			Width of the grid. Note: this does not set the width of the grid, and should be
			provided by calculating the width of the parent element.
		*/
		width: number,
 
		/**
			Break points for the grid to switch column layouts. Break points should be provided
			in ascending order. Currently only two break points are used. Example: [960, 1430]
		*/
		breakPoints: arrayOf(number),
 
		/**
			Appended to the component-specific class names set on the root elements.
		*/
		className: string,
	};
 
	getColumnLayout = (numberOfColumns: number) => {
		const cellProps: IResponsiveGridCellProps[] = _.map(
			findTypes(this.props, ResponsiveGridWrapper.Cell),
			'props'
		);
 
		const columns = _.reduce(
			_.map(cellProps, (props: IResponsiveGridCellProps, key: number) => (
				<article key={key} className={cx('&-Cell')}>
					{props.children}
				</article>
			)),
			(columns: React.ReactElement[][], cell, idx: number) => {
				columns[idx % numberOfColumns].push(cell);
				return columns;
			},
			_.times(numberOfColumns, () => [])
		);
 
		return (
			<div
				className={cx('&', {
					'&-one-column': numberOfColumns === 1,
				})}
			>
				{_.map(columns, (col, index) => {
					return (
						<div key={index} className={cx('&-Column')}>
							{col}
						</div>
					);
				})}
			</div>
		);
	};
 
	shouldComponentUpdate(nextProps: IResponsiveGridProps) {
		const { width, breakPoints } = this.props;
		const { width: nextWidth } = nextProps;
		const [breakOne, breakTwo] = breakPoints;
 
		if (nextWidth < width) {
			return (
				(nextWidth < breakOne && width >= breakOne) ||
				(nextWidth < breakTwo && width >= breakTwo)
			);
		} else if (nextWidth > width) {
			return (
				(nextWidth > breakOne && width <= breakOne) ||
				(nextWidth > breakTwo && width <= breakTwo)
			);
		}
 
		return false;
	}
 
	render() {
		const { width, breakPoints } = this.props;
		const [breakOne, breakTwo] = breakPoints;
 
		if (width < breakTwo) {
			if (width < breakOne) {
				return this.getColumnLayout(1);
			}
			return this.getColumnLayout(2);
		}
		return this.getColumnLayout(3);
	}
}
 
/** Responsive Grid Wrapper */
export interface IResponsiveGridWrapperProps
	extends StandardProps,
		React.DetailedHTMLProps<
			React.HTMLAttributes<HTMLDivElement>,
			HTMLDivElement
		> {
	/**
	 * Break points for the grid to switch column layouts. Break points should be provided
	 * in ascending order. Currently only two break points are used. Example: [960, 1430]
	 */
	breakPoints: number[];
}
 
const ResponsiveGridWrapper = (props: IResponsiveGridWrapperProps) => {
	const { breakPoints, children, className, ...passThroughs } = props;
 
	return (
		<Resizer
			className={cx('&', className)}
			{...(omit(passThroughs, ['breakPoints', 'initialState']) as any)}
		>
			{(width) => {
				return (
					<ResponsiveGrid width={width} breakPoints={breakPoints}>
						{children}
					</ResponsiveGrid>
				);
			}}
		</Resizer>
	);
};
ResponsiveGridWrapper.Cell = Cell;
ResponsiveGridWrapper.defaultProps = defaultProps;
ResponsiveGridWrapper.displayName = ResponsiveGrid.displayName;
ResponsiveGridWrapper.propTypes = {
	/**
		Break points for the grid to switch column layouts. Break points should be provided 
		in ascending order. Currently only two break points are used. Example: [960, 1430]
	*/
	breakPoints: arrayOf(number),
 
	/**
		Appended to the component-specific class names set on the root elements.
	*/
	className: string,
};
ResponsiveGridWrapper.peek = {
	description: `A grid container that changes the number of grid columns in response to width changes. By default, the grid cells are displayed in a single column for widths less than \`960px\`, two columns for widths greater than \`960px\` and less than \`1430px\`, and three columns for widths greater than \`1430px\`. Custom break points can be provided through the \`breakPoints\` prop.`,
	categories: ['utility'],
	madeFrom: ['Resizer'],
};
 
export default ResponsiveGridWrapper;