Skip to content

Commit

Permalink
Feat(web-react): Introduce Grid Item component #DS-961
Browse files Browse the repository at this point in the history
  • Loading branch information
crishpeen committed Oct 24, 2023
1 parent d3fb1ef commit 2cf07c9
Show file tree
Hide file tree
Showing 16 changed files with 766 additions and 12 deletions.
18 changes: 11 additions & 7 deletions packages/web-react/docs/stories/examples/LoginForm.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import React from 'react';
import { TextField, Checkbox, Button, Container, Stack } from '../../../src/components';
import { Button, Checkbox, Container, Grid, GridItem, Stack, TextField } from '../../../src/components';

export default {
title: 'Examples/Forms',
};

export const LoginForm = () => (
<Container>
<Stack hasSpacing>
<TextField type="text" id="name" label="Name" isFluid />
<TextField type="password" id="password" label="Password" isFluid />
<Checkbox id="keep-logged" label="Stay Logged In" />
<Button isBlock>Login</Button>
</Stack>
<Grid>
<GridItem columnStart={{ mobile: 1, tablet: 5 }} columnEnd={{ mobile: -1, tablet: 9 }}>
<Stack hasSpacing>
<TextField type="text" id="name" label="Name" isFluid />
<TextField type="password" id="password" label="Password" isFluid />
<Checkbox id="keep-logged" label="Stay Logged In" />
<Button isBlock>Login</Button>
</Stack>
</GridItem>
</Grid>
</Container>
);
26 changes: 26 additions & 0 deletions packages/web-react/src/components/Grid/GridItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { ElementType } from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { SpiritGridItemProps } from '../../types';
import { useGridItemStyleProps } from './useGridItemStyleProps';

export const GridItem = <T extends ElementType = 'div'>(props: SpiritGridItemProps<T>): JSX.Element => {
const { elementType: ElementTag = 'div', children, ...restProps } = props;
const { classProps, styleProps: gridItemStyle, props: modifiedProps } = useGridItemStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

const gridItemStyleProps = {
style: {
...styleProps.style,
...gridItemStyle,
},
};

return (
<ElementTag {...otherProps} {...gridItemStyleProps} className={classNames(classProps, styleProps.className)}>
{children}
</ElementTag>
);
};

export default GridItem;
2 changes: 1 addition & 1 deletion packages/web-react/src/components/Grid/GridSpan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useStyleProps } from '../../hooks/styleProps';
import { SpiritGridSpanProps } from '../../types';
import { useGridSpanStyleProps } from './useGridSpanStyleProps';

const GridSpan = <T extends ElementType = 'div'>(props: SpiritGridSpanProps<T>): JSX.Element => {
export const GridSpan = <T extends ElementType = 'div'>(props: SpiritGridSpanProps<T>): JSX.Element => {
const { elementType: ElementTag = 'div', children, ...restProps } = props;
const { classProps, props: modifiedProps } = useGridSpanStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);
Expand Down
64 changes: 64 additions & 0 deletions packages/web-react/src/components/Grid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,68 @@ Advanced example usage:
| `over` | [`2` \| `4` \| `6` \| `8` \| `10` \| `12`] | `null` || Number of columns to span over |
| `tablet` | [`2` \| `4` \| `6` \| `8` \| `10` \| `12`] | `null` || Number of columns to span over on tablet |

## GridItem

Grid Item is a wrapper for Grid items. It allows you to configure your Grid structure
as you wish. Use props to set a column and rows where the Grid Item should start or end.
Numeric values are used as a coordinates in the grid.

If you want to set how to item should span over columns or rows, set the value as `span X`
where X is the number of columns or rows the item should span, like this `columnStart="span 2"`
or `rowEnd="span 3"`. Span could be used with responsive props as well and for both start and end.
To understand how to use `span` read one of many articles about CSS Grid,
eg. [CSS Grid Layout: The Span Keyword][digitalocean-span].

If you need to set a layout with repetitive columns, you can set this on the `Grid`
component itself using the [`cols` prop](#api) and might not need to set columns on the items. Eg. article
listing with 3 columns is easier to set using `cols="3"` on the `Grid` component than setting
`columnStart` and `columnEnd` on each `GridItem`.

Basic example usage:

```jsx
<Grid>
<GridItem columnStart={1} columnEnd={4}>
14
</GridItem>
<GridItem columnStart={5} columnEnd={9}>
59
</GridItem>
<GridItem columnStart={10} columnEnd="span 2" rowStart={2}>
1012
</GridItem>
</Grid>
```

### Responsive example usage:

Pass an object to props to set different values for different breakpoints. The values will
be applied from mobile to desktop and if not set for a breakpoint, the value from the
previous breakpoint will be used.

```jsx
<Grid elementType="ul">
<GridItem elementType="li" columnStart={1} columnEnd={4}>
14
</GridItem>
<GridItem elementType="li" columnStart={5} columnEnd={{ mobile: 8, tablet: 9 }}>
59 (58 on mobile)
</GridItem>
<GridItem elementType="li" columnStart={{ mobile: 9, tablet: 10 }} columnEnd={{ mobile: 'span 3', tablet: 'span 2' }}>
1012 (912 on mobile)
</GridItem>
</Grid>
```

### API

| Name | Type | Default | Required | Description |
| ------------- | ------------------------------------ | ------- | -------- | ---------------------------------- |
| `elementType` | `string` | `div` || HTML tag to render |
| `columnEnd` | [`number` \| `span \d+` \| `object`] | `null` || Column where the item should end |
| `columnStart` | [`number` \| `span \d+` \| `object`] | `null` || Column where the item should start |
| `rowEnd` | [`number` \| `span \d+` \| `object`] | `null` || Row where the item should end |
| `rowStart` | [`number` \| `span \d+` \| `object`] | `null` || Row where the item should start |

[Grid]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/src/scss/components/Grid/README.md
[digitalocean-span]: https://www.digitalocean.com/community/tutorials/css-css-grid-layout-span-keyword
244 changes: 244 additions & 0 deletions packages/web-react/src/components/Grid/__tests__/GridItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import GridItem from '../GridItem';

describe('Grid', () => {
classNamePrefixProviderTest(GridItem, 'GridItem');

stylePropsTest(GridItem);

restPropsTest(GridItem, 'div');

it('should render text children', () => {
const dom = render(<GridItem>Hello World</GridItem>);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.textContent).toBe('Hello World');
});

it('should have start CSS variable', () => {
const dom = render(<GridItem columnStart={4} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start')).toBe('4');
});

it('should have start CSS variable for mobile', () => {
const dom = render(<GridItem columnStart={{ mobile: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start')).toBe('4');
});

it('should have start CSS variable for tablet', () => {
const dom = render(<GridItem columnStart={{ tablet: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start-tablet')).toBe('4');
});

it('should have start CSS variable for desktop', () => {
const dom = render(<GridItem columnStart={{ desktop: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start-desktop')).toBe('4');
});

it('should have end CSS variable', () => {
const dom = render(<GridItem columnEnd={4} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('4');
});

it('should have end CSS variable for mobile', () => {
const dom = render(<GridItem columnEnd={{ mobile: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('4');
});

it('should have end CSS variable for tablet', () => {
const dom = render(<GridItem columnEnd={{ tablet: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end-tablet')).toBe('4');
});

it('should have end CSS variable for desktop', () => {
const dom = render(<GridItem columnEnd={{ desktop: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end-desktop')).toBe('4');
});

it('should have end CSS variable with span', () => {
const dom = render(<GridItem columnEnd="span 4" />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('span 4');
});

it('should have end CSS variable for mobile with span', () => {
const dom = render(<GridItem columnEnd={{ mobile: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('span 4');
});

it('should have end CSS variable for tablet with span', () => {
const dom = render(<GridItem columnEnd={{ tablet: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end-tablet')).toBe('span 4');
});

it('should have end CSS variable for desktop with span', () => {
const dom = render(<GridItem columnEnd={{ desktop: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-end-desktop')).toBe('span 4');
});

it('should have all CSS variables', () => {
const dom = render(<GridItem columnStart={1} columnEnd={2} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('2');
});

it('should have mobile, tablet and desktop CSS variables', () => {
const dom = render(
<GridItem columnStart={{ mobile: 1, tablet: 2, desktop: 3 }} columnEnd={{ mobile: 4, tablet: 5, desktop: 6 }} />,
);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-column-start-tablet')).toBe('2');
expect(element.style.getPropertyValue('--grid-item-column-start-desktop')).toBe('3');
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('4');
expect(element.style.getPropertyValue('--grid-item-column-end-tablet')).toBe('5');
expect(element.style.getPropertyValue('--grid-item-column-end-desktop')).toBe('6');
});

it('should have start CSS variable for row', () => {
const dom = render(<GridItem rowStart={4} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start')).toBe('4');
});

it('should have start CSS variable for mobile row', () => {
const dom = render(<GridItem rowStart={{ mobile: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start')).toBe('4');
});

it('should have start CSS variable for tablet row', () => {
const dom = render(<GridItem rowStart={{ tablet: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start-tablet')).toBe('4');
});

it('should have start CSS variable for desktop row', () => {
const dom = render(<GridItem rowStart={{ desktop: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start-desktop')).toBe('4');
});

it('should have end CSS variable for row', () => {
const dom = render(<GridItem rowEnd={4} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('4');
});

it('should have end CSS variable for mobile row', () => {
const dom = render(<GridItem rowEnd={{ mobile: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('4');
});

it('should have end CSS variable for tablet row', () => {
const dom = render(<GridItem rowEnd={{ tablet: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end-tablet')).toBe('4');
});

it('should have end CSS variable for desktop row', () => {
const dom = render(<GridItem rowEnd={{ desktop: 4 }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end-desktop')).toBe('4');
});

it('should have end CSS variable with span for row', () => {
const dom = render(<GridItem rowEnd="span 4" />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('span 4');
});

it('should have end CSS variable for mobile with span for row', () => {
const dom = render(<GridItem rowEnd={{ mobile: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('span 4');
});

it('should have end CSS variable for tablet with span for row', () => {
const dom = render(<GridItem rowEnd={{ tablet: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end-tablet')).toBe('span 4');
});

it('should have end CSS variable for desktop with span for row', () => {
const dom = render(<GridItem rowEnd={{ desktop: 'span 4' }} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-end-desktop')).toBe('span 4');
});

it('should have all CSS variables for row', () => {
const dom = render(<GridItem rowStart={1} rowEnd={2} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('2');
});

it('should have mobile, tablet and desktop CSS variables for row', () => {
const dom = render(
<GridItem rowStart={{ mobile: 1, tablet: 2, desktop: 3 }} rowEnd={{ mobile: 4, tablet: 5, desktop: 6 }} />,
);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-row-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-row-start-tablet')).toBe('2');
expect(element.style.getPropertyValue('--grid-item-row-start-desktop')).toBe('3');
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('4');
expect(element.style.getPropertyValue('--grid-item-row-end-tablet')).toBe('5');
expect(element.style.getPropertyValue('--grid-item-row-end-desktop')).toBe('6');
});

it('should have all CSS variables for row and column', () => {
const dom = render(<GridItem columnStart={1} columnEnd={2} rowStart={1} rowEnd={2} />);

const element = dom.container.querySelector('div') as HTMLElement;
expect(element.style.getPropertyValue('--grid-item-column-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-column-end')).toBe('2');
expect(element.style.getPropertyValue('--grid-item-row-start')).toBe('1');
expect(element.style.getPropertyValue('--grid-item-row-end')).toBe('2');
});
});
Loading

0 comments on commit 2cf07c9

Please sign in to comment.