stories/atoms/components/listItem/ListItem.stories.mdx
import { ArgsTable, Meta, Story, Canvas } from '@storybook/addon-docs'
import { ThemeProvider } from 'ui/atoms/theme/ThemeProvider'
import { ThemeSwitcher } from 'ui/atoms/theme/ThemeSwitcher'
import { List } from 'ui/atoms/list/List'
import { ListItem } from 'ui/atoms/listItem/ListItem'
import { Typography } from 'ui/atoms/typography/Typography'
import { ProgressBar } from 'ui/atoms/progressBar/ProgressBar'
import { Icon } from 'ui/atoms/icon/Icon'
import { Button } from 'ui/atoms/button/Button'
import { Toast } from 'ui/atoms/toast/Toast'
import { useState } from 'react'
import { useTheme } from 'hook/useTheme'
<Meta
title="Atoms/ListItem"
component={ListItem}
parameters={{
actions: { argTypesRegex: '^on.*' },
docs: {
source: {
type: 'code'
}
}
}}
decorators={[
Story => (
<ThemeProvider>
<div className="story-theme-switcher">
<ThemeSwitcher />
</div>
<div className="component-story-main">
<Story />
</div>
</ThemeProvider>
)
]}
/>
# ListItem
> A primary UI component to display an item of a list of items.
[![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)
## Description
The `ListItem` allows to display an element of a [List](/docs/atoms-list--overview).
## Overview
<Story
name="Overview"
argTypes={{
title: {
table: {
type: { summary: 'string | Node' }
}
},
description: {
table: {
type: { summary: 'string | Node' }
}
},
firstElement: {
control: false,
table: {
type: { summary: 'Node' }
}
},
lastElement: {
control: false,
table: {
type: { summary: 'Node' }
}
}
}}
args={{
title: 'Item',
description: 'Description',
layout: 'list',
onClick: null
}}
>
{args => {
const style = args.layout === 'list' ? null : { margin: 'auto' }
return (
<div style={style}>
<List layout={args.layout}>
<ListItem
{...args}
firstElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="wallet" invertColor />
</div>
}
lastElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="close" invertColor />
</div>
}
/>
</List>
</div>
)
}}
</Story>
## Properties
<ArgsTable story="Overview" />
### Title
The property `title` allows you to display a text which is the main title of the item.
It can be either a simple string or a node element.
<Canvas>
<Story name="Title">
{() => (
<List>
<ListItem title="The title as a string" />
<ListItem
title={
<Typography color="inverted-text" fontWeight="bold">
The title as a node element
</Typography>
}
/>
</List>
)}
</Story>
</Canvas>
### Description
The `description` property allows you to provide additional information. Like with `title` it can be a string or a node element.
<Canvas>
<Story name="Description">
{() => (
<List>
<ListItem title="Item 1" description="The additional information of the Item 1" />
<ListItem
title="Item 2"
description={
<div>
<Typography as="div" color="inverted-text" fontSize="small">
Additional information in Item 2 that may be more complex than a simple `string`.
The description can display any content.
</Typography>
<ProgressBar label="okp4.pdf" minValue={0} maxValue={195} currentValue={23} />
</div>
}
/>
</List>
)}
</Story>
</Canvas>
## Layout
The `layout` of the listItem can be changed by passing either **list** (default) or **grid** as value.
This rearranges how the listItem positions its content.
ListItem is responsive and will adapt according to your contents size. The `title` and `description` properties carry more importance
and are therefore given more space. Depending on your content you might need to do some additional styling to get your desired effect.
<Canvas>
<Story name="Layout">
{() => {
const items = {
okp4: {
title: (
<Typography color="inverted-text" fontWeight="bold">
ØKP4
</Typography>
),
description: (
<div style={{ display: 'grid', gap: '10px' }}>
<Typography color="inverted-text">Dataset</Typography>
<Typography color="inverted-text" fontSize="small">
Data Spaces, Open source, Web3 design system
</Typography>
</div>
),
firstElement: (
<Typography as="div" color="inverted-text" fontSize="small" fontWeight="bold">
PUBLIC
</Typography>
),
lastElement: (
<Typography as="div" color="inverted-text" fontSize="x-small" fontWeight="x-light">
By ØKP4 - The data sharing protocol
</Typography>
)
},
coloredSquares: {
title: (
<div style={{ borderRadius: '5px', backgroundColor: '#99ff99', height: '30px' }}></div>
),
description: (
<div style={{ borderRadius: '5px', backgroundColor: '#9999ff', height: '140px' }}></div>
),
firstElement: (
<div
style={{
backgroundColor: '#ffff99',
borderRadius: '5px',
height: '100%',
width: '100%'
}}
>
<div style={{ height: '18px', width: '61px' }}></div>
</div>
),
lastElement: (
<div
style={{
backgroundColor: '#ff9999',
borderRadius: '5px',
height: '100%'
}}
>
<div
style={{
width: '192px',
height: '14px'
}}
></div>
</div>
)
}
}
const createList = layout => (
<List layout={layout}>
{Object.values(items).map(item => {
const { title, description, firstElement, lastElement } = item
return (
<ListItem
title={title}
description={description}
firstElement={firstElement}
lastElement={lastElement}
key={item}
/>
)
})}
</List>
)
return (
<div style={{ display: 'grid', gap: '50px' }}>
{createList('list')}
{createList('grid')}
</div>
)
}}
</Story>
</Canvas>
## On click
The `onClick` property makes the listItem clickable with a subtle animation and a change in cursor on hover.
This facilitates its use and makes it clear when something is clickable.
<Canvas>
<Story name="On click">
{() => {
const [state, setState] = useState(false)
const handleState = isOpened => () => {
setState(isOpened)
}
return (
<>
<List layout="grid">
<ListItem
title={
<Typography color="inverted-text" fontWeight="bold">
ØKP4
</Typography>
}
description={
<Typography
as="div"
style={{
display: 'grid',
textAlign: 'center',
alignItems: 'center',
height: '100%'
}}
color="inverted-text"
>
The Next Generation of Data Applications
</Typography>
}
lastElement={
<Typography
as="div"
color="inverted-text"
fontSize="x-small"
fontWeight="x-light"
style={{ textAlign: 'right' }}
>
Clickable
</Typography>
}
onClick={handleState(true)}
/>
<ListItem
title={
<Typography color="inverted-text" fontWeight="bold">
ØKP4
</Typography>
}
description={
<Typography
as="div"
style={{
display: 'grid',
textAlign: 'center',
alignItems: 'center',
height: '100%'
}}
color="inverted-text"
>
The Next Generation of Data Applications
</Typography>
}
lastElement={
<Typography
as="div"
color="inverted-text"
fontSize="x-small"
fontWeight="x-light"
style={{ textAlign: 'right' }}
>
Not clickable
</Typography>
}
/>
</List>
<Toast
isOpened={state}
onOpenChange={handleState(false)}
title="ListItem clicked !"
severityLevel="success"
/>
</>
)
}}
</Story>
</Canvas>
## First and last elements
The `firstElement` and `lastElement` properties allow you to provide any node element as icons, buttons, text, etc. to an item in the list.
The position of the properties changes depending on the `layout`, but `firstElement` will always position itself before `lastElement` in the reading flow.
They can be used to provide visual information or add interactions with an item in the list.
<Canvas>
<Story name="First and last elements">
{() => {
const { theme } = useTheme()
const isLightTheme = theme === 'light'
const [state, setState] = useState({ name: '', isOpened: false })
const handleState = (name, isOpened) => () => {
setState({ name, isOpened })
}
return (
<>
<List>
<ListItem
title="Item without element"
description="This item does not have any element."
/>
<ListItem
title="Item with first element"
description="This item only has a first element."
firstElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="profile" invertColor />
</div>
}
/>
<ListItem
title="Item with last element"
description="This item only has a last icon."
lastElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="check" invertColor />
</div>
}
/>
<ListItem
title="Item with both first and last elements"
description="This item has first and last elements."
firstElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="profile" invertColor />
</div>
}
lastElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="check" invertColor />
</div>
}
/>
<ListItem
title="Item 5"
description="Elements can be button or simple text."
firstElement={
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
<Button
label="A button"
backgroundColor="error"
size="small"
variant="icon"
icon={<Icon name="cross" size="15" invertColor={isLightTheme} />}
onClick={handleState('cross-button', true)}
/>
</div>
}
lastElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Typography as="div" color="inverted-text" fontSize="small" fontWeight="bold">
This is a simple text
</Typography>
</div>
}
/>
<ListItem
title="Item 6"
description="An other example with button"
firstElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Icon name="wallet" invertColor />
</div>
}
lastElement={
<div style={{ display: 'grid', alignItems: 'center', height: '100%' }}>
<Button
label="Select"
backgroundColor="success"
size="small"
variant="secondary"
onClick={handleState('select-button', true)}
/>
</div>
}
/>
</List>
<Toast
isOpened={Object.values(state).includes('cross-button') ? state['isOpened'] : false}
onOpenChange={handleState('cross-button', false)}
title="Cross button clicked !"
severityLevel="error"
/>
<Toast
isOpened={Object.values(state).includes('select-button') ? state['isOpened'] : false}
onOpenChange={handleState('select-button', false)}
title="Select button clicked !"
severityLevel="success"
/>
</>
)
}}
</Story>
</Canvas>