Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration/item #1170

Merged
merged 4 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .storybook/assets/stylesheets/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@forward '../../../packages/web/src/scss/components/FileUploader';
@forward '../../../packages/web/src/scss/components/Grid';
@forward '../../../packages/web/src/scss/components/Header';
@forward '../../../packages/web/src/scss/components/Item';
@forward '../../../packages/web/src/scss/components/Modal';
@forward '../../../packages/web/src/scss/components/Pagination';
@forward '../../../packages/web/src/scss/components/Pill';
Expand Down
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
1 change: 1 addition & 0 deletions packages/web-react/scripts/entryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const entryPoints = [
{ dirs: ['components', 'Header'] },
{ dirs: ['components', 'Heading'] },
{ dirs: ['components', 'Icon'] },
{ dirs: ['components', 'Item'] },
{ dirs: ['components', 'Link'] },
{ dirs: ['components', 'Modal'] },
{ dirs: ['components', 'Pagination'] },
Expand Down
46 changes: 46 additions & 0 deletions packages/web-react/src/components/Item/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 = 'button'>(props: SpiritItemProps<T>): JSX.Element => {
const {
label,
elementType: ElementTag = 'button',
iconName,
helperText,
isSelected,
isDisabled,
...restProps
} = props;
const { classProps, props: modifiedProps } = useItemStyleProps({ isSelected, isDisabled, ...restProps });
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<ElementTag
{...otherProps}
{...styleProps}
aria-selected={!!isSelected}
className={classNames(classProps.root, styleProps.className)}
disabled={!!isDisabled && ElementTag === 'button'}
>
{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` | `button` | ✕ | Type of element used as wrapper |
| `helperText` | `string` | — | ✕ | Custom helper text |
| `iconName` | `string` | — | ✕ | Icon used in item |
| `isDisabled` | `bool` | `false` | ✕ | Whether is the item disabled |
| `isSelected` | `bool` | `false` | ✕ | Whether is the item selected |
| `label` | [`string` \| `ReactNode`] | - | ✔ | 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} />, 'button');

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');
});
});
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 Item" isItem />
);

export default CheckboxItem;
12 changes: 12 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,12 @@
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/" />
<Item elementType="div" label="Item label" />
</>
);

export default ItemDefault;
13 changes: 13 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,13 @@
import React from 'react';
import Item from '../Item';

const ItemDisabled = () => {
return (
<>
<Item label="Item label" isDisabled />
<Item label="Item label" isDisabled isSelected />
</>
);
};

export default ItemDisabled;
15 changes: 15 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,15 @@
import React from 'react';
import Item from '../Item';

const ItemHelperText = () => {
return (
<>
<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 />
</>
);
};

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

const ItemIcon = () => {
return (
<>
<Item label="Item label" iconName="search" />
<Item label="Item label" iconName="search" isSelected />
</>
);
};

export default ItemIcon;
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 Item" name="item" />;

export default RadioItem;
42 changes: 42 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,42 @@
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 ItemIcon from './ItemIcon';
import CheckboxItem from './CheckboxItem';
import RadioItem from './RadioItem';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<IconsProvider value={icons}>
<DocsSection title="Default">
<ItemDefault />
</DocsSection>
<DocsSection title="Selected">
<ItemSelected />
</DocsSection>
<DocsSection title="Disabled">
<ItemDisabled />
</DocsSection>
<DocsSection title="Helper Text">
<ItemHelperText />
</DocsSection>
<DocsSection title="Icon">
<ItemIcon />
</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