Skip to content

Commit

Permalink
[Grid]: New Responsive Grid element (#877)
Browse files Browse the repository at this point in the history
* feat(Grid): grid component WIP

* feat(Grid): code cleanup and add tests

* add changeset file
  • Loading branch information
AbhinavMV authored Nov 8, 2022
1 parent f6778c1 commit cfc46dc
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-eyes-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@web3uikit/core': patch
---

Add new grid element
51 changes: 51 additions & 0 deletions packages/core/src/lib/Grid/Grid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Grid from './Grid';
import styles from './Grid.styles';

const { DivStyledBox } = styles;

export default {
title: '3.Layout/Grid',
component: Grid,
} as ComponentMeta<typeof Grid>;

const Template: ComponentStory<typeof Grid> = (args) => <Grid {...args} />;

const children = (
<>
<Grid type="item" xs={12} sm={6} md={4} lg={3}>
<DivStyledBox>Box 1</DivStyledBox>
</Grid>
<Grid type="item" xs={12} sm={6} md={4} lg={3}>
<DivStyledBox>Box 2</DivStyledBox>
</Grid>
<Grid type="item" xs={12} sm={6} md={4} lg={3}>
<DivStyledBox>Box 3</DivStyledBox>
</Grid>
<Grid type="item" xs={12} sm={6} md={4} lg={3}>
<DivStyledBox>Box 4</DivStyledBox>
</Grid>
</>
);

export const Basic = Template.bind({});
Basic.args = {
type: 'container',
spacing: 12,
_isRulerVisible: false,
justifyContent: 'flex-start',
alignItems: 'flex-start',
children: children,
style: { height: '400px' },
};

export const BasicWithRuler = Template.bind({});
BasicWithRuler.args = {
type: 'container',
spacing: 2,
_isRulerVisible: true,
justifyContent: 'flex-start',
alignItems: 'flex-start',
children: children,
};
108 changes: 108 additions & 0 deletions packages/core/src/lib/Grid/Grid.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import styled, { css } from 'styled-components';
import { breakpoints, resetCSS, color } from '@web3uikit/styles';
import { IGridProps } from './types';

const getStylesAtBreakpoint = (breakpoint: string, size?: number) =>
size
? css`
@media screen and (min-width: ${breakpoint}) {
flex-basis: ${(size / 12) * 100}%;
}
`
: null;

type TStyleProps = Pick<
IGridProps,
| 'alignItems'
| 'flexGrow'
| 'justifyContent'
| 'lg'
| 'md'
| 'sm'
| 'spacing'
| 'type'
| 'xs'
>;
const DivStyled = styled.div<TStyleProps>`
${resetCSS};
${(props) => css`
${props.type === 'container' &&
css`
display: flex;
flex-wrap: wrap;
`};
${props.type === 'item' &&
css`
display: block;
`};
${props.xs &&
css`
flex-basis: ${(props.xs / 12) * 100}%;
`};
${props.justifyContent &&
css`
justify-content: ${props.justifyContent};
`};
${props.alignItems &&
css`
align-items: ${props.alignItems};
`};
${props.flexGrow &&
css`
flex-grow: ${props.flexGrow};
`}
${props.spacing &&
css`
// This removes extra spacing around the boxes
margin: -${props.spacing}px;
& > .grid-item {
padding: ${props.spacing}px;
}
`};
${getStylesAtBreakpoint(breakpoints.sm, props.sm)};
${getStylesAtBreakpoint(breakpoints.md, props.md)};
${getStylesAtBreakpoint(breakpoints.lg, props.lg)};
`}
`;

DivStyled.displayName = 'Grid';

/**
* HELPER STYLES
*/
const DivStyledBox = styled.div`
${resetCSS};
background-color: cornflowerblue;
border-radius: 4px;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25);
color: ${color.white};
font-weight: 700;
height: 100%;
width: 100%;
padding: 16px;
text-align: center;
`;

const DivStyledRuler = styled.div<Pick<IGridProps, 'spacing'>>`
display: grid;
grid-template-columns: repeat(12, 1fr);
position: absolute;
width: 100%;
height: 100vh;
& > div {
background-color: rgba(0, 0, 0, 0.1);
}
${(props) =>
props.spacing &&
css`
gap: ${props.spacing * 2}px;
`}
`;
//-------------

export default {
DivStyledBox,
DivStyled,
DivStyledRuler,
};
75 changes: 75 additions & 0 deletions packages/core/src/lib/Grid/Grid.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import { cleanup, render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Grid.stories';
import { describe, it } from 'vitest';

const { Basic, BasicWithRuler } = composeStories(stories);
const testGridId = 'test-Grid';
const testGridRulerId = 'test-GridRuler';
const totalGridCols = 12;
let container: HTMLDivElement;

describe('Renders Basic Grid', () => {
beforeEach(() => {
container = document.createElement('div');

render(<Basic />, {
container: document.body.appendChild(container),
});
});
afterEach(() => {
cleanup();
});

it('Renders grid container', () => {
const gridContainer: HTMLDivElement | null = container.querySelector(
`[data-testid="${testGridId}"]`,
);
expect(gridContainer).not.toBeNull();
});

it('Renders grid items', () => {
const gridItems: NodeListOf<
Element
> | null = container.querySelectorAll('.grid-item');
expect(gridItems).not.toBeNull();
expect(gridItems?.length).toBeGreaterThan(1);
});
});

describe('Renders Basic with Ruler Grid', () => {
beforeEach(() => {
container = document.createElement('div');

render(<BasicWithRuler />, {
container: document.body.appendChild(container),
});
});
afterEach(() => {
cleanup();
});

it('Renders grid container', () => {
const gridContainer: HTMLDivElement | null = container.querySelector(
`[data-testid="${testGridId}"]`,
);
expect(gridContainer).not.toBeNull();
});

it('Renders grid items', () => {
const gridItems: NodeListOf<
Element
> | null = container.querySelectorAll('.grid-item');
expect(gridItems).not.toBeNull();
expect(gridItems?.length).toBeGreaterThan(1);
});

it('Renders grid Ruler', () => {
const gridRuler: HTMLDivElement | null = container.querySelector(
`[data-testId="${testGridRulerId}"]`,
);
expect(gridRuler).not.toBeNull();
expect(gridRuler?.childElementCount).toBe(totalGridCols);
});
});
48 changes: 48 additions & 0 deletions packages/core/src/lib/Grid/Grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { IGridProps, Spacing } from './types';
import styles from './Grid.styles';

const { DivStyled, DivStyledRuler } = styles;

export const Grid: React.FC<IGridProps &
React.HTMLAttributes<HTMLDivElement>> = ({
_isRulerVisible = false,
children,
...props
}) => {
let className = '';
if (props.type) className += `grid-${props.type}`;
// This is for testing purposes only for user
if (_isRulerVisible) {
return (
<div style={{ position: 'relative' }}>
<GridRuler spacing={props.spacing}></GridRuler>
<DivStyled
className={className}
data-testid="test-Grid"
{...props}
>
{children}
</DivStyled>
</div>
);
}

return (
<DivStyled className={className} data-testid="test-Grid" {...props}>
{children}
</DivStyled>
);
};

export const GridRuler: React.FC<{ spacing?: Spacing }> = ({ spacing }) => {
return (
<DivStyledRuler spacing={spacing} data-testid="test-GridRuler">
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((number) => (
<div key={number} />
))}
</DivStyledRuler>
);
};

export default Grid;
2 changes: 2 additions & 0 deletions packages/core/src/lib/Grid/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Grid } from './Grid';
export type { IGridProps } from './types';
62 changes: 62 additions & 0 deletions packages/core/src/lib/Grid/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNode } from 'react';
import { CSSProperties } from 'styled-components';

type GridSize = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
export type Spacing = GridSize | 13 | 14 | 15 | 16;

export interface IGridProps {
/**
* to align items in a grid
*/
alignItems?: CSSProperties['alignItems'];

/**
* children of a grid element
*/
children: ReactNode;

/**
* display a ruler for testing purposes only
*/
_isRulerVisible?: boolean;

/**
* flex grow property of a grid item
*/
flexGrow?: CSSProperties['flexGrow'];

/**
* to justify content in a grid
*/
justifyContent?: CSSProperties['justifyContent'];

/**
* size for screens from => 1025 - ...
*/
lg?: GridSize;

/**
* size for screens from => 768px - 1025px
*/
md?: GridSize;

/**
* size for screens from => 576px - 768px
*/
sm?: GridSize;

/**
* spacing between grid items
*/
spacing?: Spacing;

/**
* use container for a Grid and item to specify items inside a container
*/
type: 'container' | 'item';

/**
* for size for screens from => 0px - 576px
*/
xs?: GridSize;
}
1 change: 1 addition & 0 deletions packages/core/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* PLOP_INJECT_EXPORT */
export * from './Grid';
export * from './Pagination';
export * from './Slider';
export * from './Accordion';
Expand Down

0 comments on commit cfc46dc

Please sign in to comment.