diff --git a/src/Layout/Layout.test.jsx b/src/Layout/Layout.test.jsx new file mode 100644 index 0000000000..4a301c3f28 --- /dev/null +++ b/src/Layout/Layout.test.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Layout from './index'; + +function TestLayout(props) { + return ( + + first block + second block + third block + + ); +} + +describe('', () => { + describe('correct rendering', () => { + it('renders correct number of children', () => { + const wrapper = mount(); + const children = wrapper.find('.row div'); + expect(children.length).toEqual(3); + }); + it('renders correct number of children', () => { + const wrapper = mount(); + const children = wrapper.find('.row div'); + expect(children.at(0).hasClass( + 'col-xl-3 col-lg-4 col-md-auto col-sm-8 col-4 offset-xl-0 offset-lg-0 offset-md-0 offset-sm-0 offset-0', + )).toEqual(true); + }); + it('renders although dimensions are incorrect', () => { + const wrapper = mount(); + const children = wrapper.find('.row div'); + expect(children.length).not.toEqual(0); + }); + }); +}); diff --git a/src/Layout/README.md b/src/Layout/README.md new file mode 100644 index 0000000000..d438190beb --- /dev/null +++ b/src/Layout/README.md @@ -0,0 +1,29 @@ +--- +title: 'Layout' +type: 'component' +components: +- Layout +categories: +- Layout +status: 'New' +designStatus: 'Done' +devStatus: 'Done' +--- + +A wrapper component that allows to control the size of child blocks on different screen sizes. + +## Basic usage + +```jsx live + + first block + second block + third block + +``` diff --git a/src/Layout/index.jsx b/src/Layout/index.jsx index a03bc9a6f8..46fdbb553b 100644 --- a/src/Layout/index.jsx +++ b/src/Layout/index.jsx @@ -1,2 +1,92 @@ -export { default as Col } from 'react-bootstrap/Col'; -export { default as Row } from 'react-bootstrap/Row'; +import React from 'react'; +import Col from 'react-bootstrap/Col'; +import Row from 'react-bootstrap/Row'; +import PropTypes from 'prop-types'; + +const COL_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'auto']; +const SIZES = ['xs', 'sm', 'md', 'lg', 'xl']; + +const LayoutElement = React.forwardRef((props, ref) =>
); + +const Layout = React.forwardRef(({ children, ...props }, ref) => { + const childrenLength = children.length; + + const isValidDimensions = (dataList, validLength) => !dataList || dataList.length === validLength; + const errors = {}; + + const layout = React.Children.map(children, (child, index) => { + const newProps = { ...child.props }; + SIZES.forEach(size => { + const sizeProps = props[size]; + const { span = 0, offset = 0 } = (sizeProps && sizeProps[index]) || {}; + if (errors[size] === undefined) { + errors[size] = false; + if (!isValidDimensions(sizeProps, childrenLength)) { + errors[size] = `${size} prop accepts array which length must be equal to the number of children.`; + } + } + newProps[size] = { span, offset }; + }); + newProps.ref = child.ref; + return React.createElement(Col, newProps, child.props.children); + }); + + Object.keys(errors).forEach(breakpoint => { + if (errors[breakpoint]) { + // eslint-disable-next-line no-console + console.error(errors[breakpoint]); + } + }); + + return ( + + {layout} + + ); +}); + +Layout.defaultProps = { + xs: undefined, + sm: undefined, + md: undefined, + lg: undefined, + xl: undefined, +}; + +Layout.propTypes = { + children: PropTypes.node.isRequired, + xs: PropTypes.arrayOf(PropTypes.shape({ + span: PropTypes.oneOf(COL_VALUES).isRequired, + offset: PropTypes.oneOf(COL_VALUES), + })), + sm: PropTypes.arrayOf(PropTypes.shape({ + span: PropTypes.oneOf(COL_VALUES).isRequired, + offset: PropTypes.oneOf(COL_VALUES), + })), + md: PropTypes.arrayOf(PropTypes.shape({ + span: PropTypes.oneOf(COL_VALUES).isRequired, + offset: PropTypes.oneOf(COL_VALUES), + })), + lg: PropTypes.arrayOf(PropTypes.shape({ + span: PropTypes.oneOf(COL_VALUES).isRequired, + offset: PropTypes.oneOf(COL_VALUES), + })), + xl: PropTypes.arrayOf(PropTypes.shape({ + span: PropTypes.oneOf(COL_VALUES).isRequired, + offset: PropTypes.oneOf(COL_VALUES), + })), +}; + +const sizeDefaultProps = { span: [], offset: [] }; + +SIZES.forEach(size => { + // eslint-disable-next-line react/default-props-match-prop-types + Layout.defaultProps[size] = sizeDefaultProps; +}); + +export { + Col, + Row, +}; +Layout.Element = LayoutElement; +export default Layout; diff --git a/src/index.js b/src/index.js index a84f2e736b..28892d7df7 100644 --- a/src/index.js +++ b/src/index.js @@ -24,7 +24,7 @@ export { default as CheckBoxGroup } from './CheckBoxGroup'; export { default as Chip } from './Chip'; export { default as CloseButton } from './CloseButton'; export { default as Container } from './Container'; -export { Col, Row } from './Layout'; +export { default as Layout, Col, Row } from './Layout'; export { default as Collapse } from './Collapse'; export { default as Collapsible } from './Collapsible'; export { default as Scrollable } from './Scrollable';