Skip to content

Commit

Permalink
Feat(web-react): Introduce Item component #DS-1049
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelklibani committed Nov 23, 2023
1 parent 301fd2e commit 33d685c
Show file tree
Hide file tree
Showing 19 changed files with 451 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/web-react/config/vite/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import handlebars from 'vite-plugin-handlebars';
import react from '@vitejs/plugin-react';
import { getNestedDirs } from '../../scripts/build';

const hiddenDemoComponents = ['Field', 'Dialog', 'Icon', 'TextFieldBase', 'VisuallyHidden', 'Item'];
const hiddenDemoComponents = ['Field', 'Dialog', 'Icon', 'TextFieldBase', 'VisuallyHidden'];

export default defineConfig({
plugins: [
Expand Down
32 changes: 32 additions & 0 deletions packages/web-react/src/components/Item/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { ElementType } from 'react';
import classNames from 'classnames';
import { SpiritItemProps } from '../../types';
import { useStyleProps } from '../../hooks';
import { HelperText } from '../Field';
import { Icon } from '../Icon';
import { useItemStyleProps } from './useItemStyleProps';

const Item = <T extends ElementType = 'div'>(props: SpiritItemProps<T>): JSX.Element => {
const { label, elementType: ElementTag = 'div', iconName, helperText, isSelected, ...restProps } = props;
const { classProps, props: modifiedProps } = useItemStyleProps({ isSelected, ...restProps });
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<ElementTag {...otherProps} {...styleProps} className={classNames(classProps.root, styleProps.className)}>
{iconName && (
<span className={classNames(classProps.icon.root, classProps.icon.start)}>
<Icon name={iconName} />
</span>
)}
<span className={classProps.label}>{label}</span>
<HelperText className={classProps.helperText} elementType="span" helperText={helperText} />
{isSelected && (
<span className={classNames(classProps.icon.root, classProps.icon.end)}>
<Icon name="check-plain" />
</span>
)}
</ElementTag>
);
};

export default Item;
94 changes: 90 additions & 4 deletions packages/web-react/src/components/Item/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,94 @@
# Item

To implement Item component with content of `Radio` or `Checkbox`,
you need to use directly these components with prop `isItem`.
The Item component is used to display a single item in a list. It can be used in Dropdown or similar.

See `web` package [Item documentation][item-documentation] for more info.
To use Item with checkbox or radio please use components [Checkbox][checkbox] or [Radio][radio]
with `isItem` property. We do this to avoid repeating the same code and to simplify the API.

[item-documentation]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/src/scss/components/Item/README.md
Simple Item example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" />;
```

Item with icon example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" iconName="search" />;
```

Item in selected state example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" isSelected />;
```

Item with Helper text example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" helperText="Helper text" />;
```

Item in disabled state example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" isDisabled />;
```

Item with icon and helper text in selected state example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" iconName="search" helperText="Helper text" isSelected />;
```

Item as a link example:

```jsx
import { Item } from '@lmc-eu/spirit-web-react';

<Item label="Item" elementType="a" href="#" />;
```

Radio as Item:

```jsx
import { Radio } from '@lmc-eu/spirit-web-react';

<Radio id="radioItem" name="example" label="Radio Label" isItem />;
```

Checkbox as Item:

```jsx
import { Checkbox } from '@lmc-eu/spirit-web-react';

<Checkbox id="checkboxItem" name="example" label="Checkbox Label" isItem />;
```

## API

| Name | Type | Default | Required | Description |
| ------------------ | --------------- | ------- | -------- | ------------------------------- |
| `elementType` | `ElementType` | `div` || Wrapper custom style |
| `helperText` | `string` ||| Type of element used as wrapper |
| `iconName` | `string` ||| Custom helper text |
| `isDisabled` | `bool` | `false` || Whether is the item disabled |
| `isSelected` | `bool` | `false` || Whether is item pane selected |
| `label` | `string` | - || Label of the item |
| `UNSAFE_className` | `string` ||| Wrapper custom class name |
| `UNSAFE_style` | `CSSProperties` ||| Wrapper custom style |

[checkbox]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Checkbox/README.md
[radio]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Radio/README.md
60 changes: 60 additions & 0 deletions packages/web-react/src/components/Item/__tests__/Item.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { SpiritItemProps } from '../../../types';
import Item from '../Item';

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

restPropsTest((props: SpiritItemProps) => <Item {...props} />, 'div');

it('should render label', () => {
const label = 'Item label';
const dom = render(<Item label={label} />);

const element = dom.container.querySelector('.Item > .Item__label') as HTMLElement;
expect(element).toHaveTextContent(label);
});

it('should render helperText', () => {
const helperText = 'Helper text';
const dom = render(<Item label="Item label" helperText={helperText} />);

const element = dom.container.querySelector('.Item > .Item__helperText') as HTMLElement;
expect(element).toHaveTextContent(helperText);
});

it('should render icon', () => {
const iconName = 'search';
const dom = render(<Item label="Item label" iconName={iconName} />);

const element = dom.container.querySelector('.Item > .Item__icon') as HTMLElement;
expect(element).toHaveClass('Item__icon--start');
});

it('should be selected and render end icon', () => {
const dom = render(<Item label="Item label" isSelected />);

const element = dom.container.querySelector('.Item') as HTMLElement;
const iconElement = dom.container.querySelector('.Item > .Item__icon') as HTMLElement;
expect(element).toHaveClass('Item--selected');
expect(iconElement).toHaveClass('Item__icon--end');
});

it('should be disabled', () => {
const dom = render(<Item label="Item label" isDisabled />);

const element = dom.container.querySelector('.Item') as HTMLElement;
expect(element).toHaveClass('Item--disabled');
});

it('should render as anchor', () => {
const dom = render(<Item label="Item label" elementType="a" />);

const element = dom.container.querySelector('a') as HTMLElement;
expect(element).toHaveClass('Item');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { renderHook } from '@testing-library/react-hooks';
import { SpiritItemProps } from '../../../types';
import { useItemStyleProps } from '../useItemStyleProps';

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

expect(result.current.classProps).toEqual({
helperText: 'Item__helperText',
icon: {
root: 'Item__icon',
start: 'Item__icon--start',
end: 'Item__icon--end',
},
label: 'Item__label',
root: 'Item',
});
});

it('should return selected item', () => {
const props = { isSelected: true } as SpiritItemProps;
const { result } = renderHook(() => useItemStyleProps(props));

expect(result.current.classProps.root).toBe('Item Item--selected');
});

it('should return disabled item', () => {
const props = { isDisabled: true } as SpiritItemProps;
const { result } = renderHook(() => useItemStyleProps(props));

expect(result.current.classProps.root).toBe('Item Item--disabled');
});
});
8 changes: 8 additions & 0 deletions packages/web-react/src/components/Item/demo/CheckboxItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { Checkbox } from '../../Checkbox';

const CheckboxItem = () => (
<Checkbox id="checkboxItemDefault" name="checkboxItemDefault" label="Checkbox Label" isItem />
);

export default CheckboxItem;
11 changes: 11 additions & 0 deletions packages/web-react/src/components/Item/demo/ItemDefault.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import Item from '../Item';

const ItemDefault = () => (
<>
<Item label="Item label" />
<Item elementType="a" label="Item label" href="https://www.example.com/" />
</>
);

export default ItemDefault;
6 changes: 6 additions & 0 deletions packages/web-react/src/components/Item/demo/ItemDisabled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import Item from '../Item';

const ItemDisabled = () => <Item label="Item label" isDisabled />;

export default ItemDisabled;
19 changes: 19 additions & 0 deletions packages/web-react/src/components/Item/demo/ItemHelperText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import Item from '../Item';

const ItemHelperText = () => {
return (
<>
<Item label="Item label" isDisabled />
<Item label="Item label" isDisabled isSelected />
<Item label="Item label" helperText="Helper text" />
<Item label="Item label" helperText="Helper text" isSelected />
<Item label="Item label" helperText="Helper text" isDisabled />
<Item label="Item label" helperText="Helper text" isSelected isDisabled />
<Item label="Item label" iconName="search" />
<Item label="Item label" iconName="search" isSelected />
</>
);
};

export default ItemHelperText;
6 changes: 6 additions & 0 deletions packages/web-react/src/components/Item/demo/ItemSelected.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import Item from '../Item';

const ItemSelected = () => <Item label="Item label" isSelected />;

export default ItemSelected;
6 changes: 6 additions & 0 deletions packages/web-react/src/components/Item/demo/RadioItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { Radio } from '../../Radio';

const RadioItem = () => <Radio id="radioItemDefault" isItem label="Radio Label" name="item" />;

export default RadioItem;
38 changes: 38 additions & 0 deletions packages/web-react/src/components/Item/demo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No declaration file
import icons from '@lmc-eu/spirit-icons/dist/icons';
import DocsSection from '../../../../docs/DocsSections';
import { IconsProvider } from '../../../context';
import ItemDefault from './ItemDefault';
import ItemSelected from './ItemSelected';
import ItemDisabled from './ItemDisabled';
import ItemHelperText from './ItemHelperText';
import CheckboxItem from './CheckboxItem';
import RadioItem from './RadioItem';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<IconsProvider value={icons}>
<DocsSection title="Default" stackAlignment="stretch">
<ItemDefault />
</DocsSection>
<DocsSection title="Selected" stackAlignment="stretch">
<ItemSelected />
</DocsSection>
<DocsSection title="Disabled" stackAlignment="stretch">
<ItemDisabled />
</DocsSection>
<DocsSection title="Default" stackAlignment="stretch">
<ItemHelperText />
</DocsSection>
<DocsSection title="Checkbox Item">
<CheckboxItem />
</DocsSection>
<DocsSection title="Radio Item">
<RadioItem />
</DocsSection>
</IconsProvider>
</React.StrictMode>,
);
1 change: 1 addition & 0 deletions packages/web-react/src/components/Item/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{> demo}}
3 changes: 3 additions & 0 deletions packages/web-react/src/components/Item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Item';
export * from './useItemStyleProps';
export { default as Item } from './Item';
Loading

0 comments on commit 33d685c

Please sign in to comment.