Skip to content

Commit

Permalink
feat: [BD-46] add Layout component (#1659)
Browse files Browse the repository at this point in the history
  • Loading branch information
monteri committed Oct 21, 2022
1 parent 97020d1 commit f8cddda
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 3 deletions.
42 changes: 42 additions & 0 deletions 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 (
<Layout
lg={[{ span: 4, offset: 0 }, { span: 4, offset: 0 }, { span: 4, offset: 0 }]}
md={[{ span: 'auto', offset: 0 }, { span: 'auto', offset: 0 }, { span: 'auto', offset: 0 }]}
sm={[{ span: 8, offset: 0 }, { span: 4, offset: 0 }, { span: 6, offset: 6 }]}
xs={[{ span: 4, offset: 0 }, { span: 4, offset: 0 }, { span: 4, offset: 0 }]}
xl={[{ span: 3 }, { span: 6 }, { span: 3 }]}
{...props}
>
<Layout.Element>first block</Layout.Element>
<Layout.Element>second block</Layout.Element>
<Layout.Element>third block</Layout.Element>
</Layout>
);
}

describe('<Layout />', () => {
describe('correct rendering', () => {
it('renders correct number of children', () => {
const wrapper = mount(<TestLayout />);
const children = wrapper.find('.row div');
expect(children.length).toEqual(3);
});
it('renders correct number of children', () => {
const wrapper = mount(<TestLayout />);
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(<TestLayout lg={[{ span: 6, offset: 0 }, { span: 6, offset: 0 }]} />);
const children = wrapper.find('.row div');
expect(children.length).not.toEqual(0);
});
});
});
29 changes: 29 additions & 0 deletions 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
<Layout
lg={[{ span: 4, offset: 0 }, { span: 4, offset: 0 }, { span: 4, offset: 0 }]}
md={[{ span: 'auto', offset: 0 }, { span: 'auto', offset: 0 }, { span: 'auto', offset: 0 }]}
sm={[{ span: 8, offset: 0 }, { span: 4, offset: 0 }, { span: 6, offset: 6 }]}
xs={[{ span: 4, offset: 0 }, { span: 4, offset: 0 }, { span: 4, offset: 0 }]}
xl={[{ span: 3 }, { span: 6 }, { span: 3 }]}
>
<Layout.Element style={{ background: 'red' }}>first block</Layout.Element>
<Layout.Element style={{ background: 'green' }}>second block</Layout.Element>
<Layout.Element style={{ background: 'blue' }}>third block</Layout.Element>
</Layout>
```
94 changes: 92 additions & 2 deletions 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) => <div ref={ref} {...props} />);

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 (
<Row ref={ref}>
{layout}
</Row>
);
});

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;
2 changes: 1 addition & 1 deletion src/index.js
Expand Up @@ -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';
Expand Down

0 comments on commit f8cddda

Please sign in to comment.