Skip to content

Commit

Permalink
Feat(web-react): Introduce Box component #DS-1595
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed Dec 23, 2024
1 parent aa65927 commit 4241e5b
Show file tree
Hide file tree
Showing 17 changed files with 662 additions and 0 deletions.
34 changes: 34 additions & 0 deletions packages/web-react/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import classNames from 'classnames';
import React, { ElementType } from 'react';
import { useStyleProps } from '../../hooks';
import { SpiritBoxProps } from '../../types';
import { useBoxStyleProps } from './useBoxStyleProps';

const defaultProps: Partial<SpiritBoxProps> = {
elementType: 'div',
};

const Box = <T extends ElementType = 'div'>(props: SpiritBoxProps<T>) => {
const propsWithDefaults = { ...defaultProps, ...props };
const { elementType: ElementTag = 'div', children, ...restProps } = propsWithDefaults;

const { classProps, props: modifiedProps, styleProps: boxStyle } = useBoxStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

const boxStyleProps = {
style: {
...styleProps.style,
...boxStyle,
},
};

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

export default Box;
82 changes: 82 additions & 0 deletions packages/web-react/src/components/Box/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Box

The Box component is a simple container around content or other components.

```jsx
<Box>{/* Content go here */}</Box>
```

## Border

You can define border width, color, and radius using the `borderColor`, `borderRadius`, and `borderWidth` props.

```jsx
<Box borderColor="basic" borderRadius="200" borderWidth="100">
{/* Content go here */}
</Box>
```

## Padding

You can define padding using the `padding` prop.

```jsx
<Box padding="space-200">{/* Content go here */}</Box>
```

It is also possible to define padding for horizontal and vertical sides using the `paddingX` and `paddingY` props.

```jsx
<Box paddingX="space-200" paddingY="space-300">
{/* Content go here */}
</Box>
```

You can also define padding for each side using the `paddingTop`, `paddingRight`, `paddingBottom`, and `paddingLeft` props.

```jsx
<Box paddingTop="space-200" paddingRight="space-300" paddingBottom="space-400" paddingLeft="space-500">
{/* Content go here */}
</Box>
```

Responsive values can be set for each prop using an object:

```jsx
<Box padding={{ mobile: 'space-200', tablet: 'space-300', desktop: 'space-400' }}>{/* Content go here */}</Box>
```

## Background Color

You can define background color using the `backgroundColor` prop.

```jsx
<Box backgroundColor="basic">{/* Content go here */}</Box>
```

## API

| Name | Type | Default | Required | Description |
| ----------------- | ----------------------------------------------------------------- | ------- | -------- | ----------------------------- |
| `backgroundColor` | [Background Color dictionary][dictionary-background-color] | - || Background color of the Box |
| `borderColor` | [Border Color dictionary][dictionary-border-color] | - || Border color of the Box |
| `borderRadius` | `string` | - || Border radius of the Box |
| `borderWidth` | `string` | - || Border width of the Box |
| `elementType` | `ElementType` | `div` || Type of element |
| `padding` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Padding of the Box |
| `paddingX` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Horizontal padding of the Box |
| `paddingY` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Vertical padding of the Box |
| `paddingTop` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Padding top of the Box |
| `paddingRight` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Padding right of the Box |
| `paddingBottom` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Padding bottom of the Box |
| `paddingLeft` | \[`SpaceToken` \| `Partial<Record<BreakpointToken, SpaceToken>>`] | - || Padding left of the Box |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].

[dictionary-background-color]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#color
[dictionary-border-color]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#color
[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props
123 changes: 123 additions & 0 deletions packages/web-react/src/components/Box/__tests__/Box.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import Box from '../Box';

const dataProvider = [
{
prop: 'backgroundColor',
value: 'primary',
className: 'bg-primary',
description: 'background color',
},
{
prop: 'borderColor',
value: 'basic',
className: 'border-basic',
description: 'border color',
},
{
prop: 'borderWidth',
value: '100',
className: 'border-100',
description: 'border width',
},
{
prop: 'padding',
value: 'space-800',
className: 'p-800',
description: 'padding',
},
{
prop: 'paddingX',
value: 'space-800',
className: 'px-800',
description: 'horizontal padding',
},
{
prop: 'paddingY',
value: 'space-800',
className: 'py-800',
description: 'vertical padding',
},
{
prop: 'paddingTop',
value: 'space-800',
className: 'pt-800',
description: 'padding top',
},
{
prop: 'paddingBottom',
value: 'space-800',
className: 'pb-800',
description: 'padding bottom',
},
{
prop: 'paddingLeft',
value: 'space-800',
className: 'pl-800',
description: 'padding left',
},
{
prop: 'paddingRight',
value: 'space-800',
className: 'pr-800',
description: 'padding right',
},
{
prop: 'padding',
value: { mobile: 'space-600', tablet: 'space-800', desktop: 'space-1000' },
className: 'p-600 p-tablet-800 p-desktop-1000',
description: 'responsive padding',
},
];

describe('Box', () => {
stylePropsTest(Box);

restPropsTest(Box, 'div');

it('should render children', () => {
render(<Box>Content</Box>);

expect(screen.getByText('Content')).toBeInTheDocument();
});

it('should render with background color', () => {
render(<Box backgroundColor="primary">Content</Box>);

expect(screen.getByText('Content')).toHaveStyle('background-color: var(--color-primary)');
});

it('should render with border radius and width', () => {
render(
<Box borderRadius="200" borderWidth="100" data-testid="Box">
Content
</Box>,
);

expect(screen.getByTestId('Box')).toHaveClass('rounded-200');
});

it('should not render correct border radius class when border width is not set', () => {
render(
<Box borderRadius="200" data-testid="Box">
Content
</Box>,
);

expect(screen.getByTestId('Box')).not.toHaveClass('rounded-200');
});

it.each(dataProvider)('should render with $description', ({ prop, value, className }) => {
render(
<Box {...{ [prop]: value }} data-testid="Box">
Content
</Box>,
);

expect(screen.getByTestId('Box')).toHaveClass(className);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { renderHook } from '@testing-library/react';
import { SpiritBoxProps } from '../../../types';
import { useBoxStyleProps } from '../useBoxStyleProps';

describe('useBoxStyleProps', () => {
it('should return defaults', () => {
const props: SpiritBoxProps = {};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('');
});

it('should return background classProps', () => {
const props: SpiritBoxProps = {
backgroundColor: 'secondary',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('bg-secondary');
});

it('should return padding classProps', () => {
const props: SpiritBoxProps = {
padding: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('p-400');
});

it('should return paddingX classProps', () => {
const props: SpiritBoxProps = {
paddingX: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('px-400');
});

it('should return paddingY classProps', () => {
const props: SpiritBoxProps = {
paddingY: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('py-400');
});

it('should return paddingTop classProps', () => {
const props: SpiritBoxProps = {
paddingTop: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('pt-400');
});

it('should return paddingBottom classProps', () => {
const props: SpiritBoxProps = {
paddingBottom: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('pb-400');
});

it('should return paddingLeft classProps', () => {
const props: SpiritBoxProps = {
paddingLeft: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('pl-400');
});

it('should return paddingRight classProps', () => {
const props: SpiritBoxProps = {
paddingRight: 'space-400',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('pr-400');
});

it('should return responsive padding classProps', () => {
const props: SpiritBoxProps = {
padding: { mobile: 'space-400', tablet: 'space-500', desktop: 'space-600' },
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('p-400 p-tablet-500 p-desktop-600');
});

it('should return border radius classProps', () => {
const props: SpiritBoxProps = {
borderRadius: '200',
borderWidth: '100',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('border-basic rounded-200 border-100');
});

it('should not return border radius classProps if border with is not set', () => {
const props: SpiritBoxProps = {
borderRadius: '200',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).not.toBe('rounded-200');
});

it('should return border color classProps', () => {
const props: SpiritBoxProps = {
borderColor: 'basic',
borderWidth: '100',
};
const { result } = renderHook(() => useBoxStyleProps(props));

expect(result.current.classProps).toBe('border-basic border-100');
});
});
6 changes: 6 additions & 0 deletions packages/web-react/src/components/Box/demo/BoxDefault.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import Box from '../Box';

const BoxDefault = () => <Box>Content</Box>;

export default BoxDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import Box from '../Box';

const BoxWithBackgroundColor = () => (
<>
<fieldset className="mb-0" style={{ border: 0 }}>
<legend className="mb-500">For demo purposes the box is bordered</legend>
</fieldset>
<Box padding="space-800" borderWidth="100">
Primary Background
</Box>
<Box padding="space-800" borderWidth="100" backgroundColor="secondary">
Secondary Background
</Box>
<Box padding="space-800" borderWidth="100" backgroundColor="tertiary">
Tertiary Background
</Box>
</>
);

export default BoxWithBackgroundColor;
Loading

0 comments on commit 4241e5b

Please sign in to comment.