diff --git a/packages/design-system/src/lib/components/transfer-list/TransferList.tsx b/packages/design-system/src/lib/components/transfer-list/TransferList.tsx new file mode 100644 index 000000000..67aa271b4 --- /dev/null +++ b/packages/design-system/src/lib/components/transfer-list/TransferList.tsx @@ -0,0 +1,132 @@ +import { useState } from 'react' +import { ListGroup } from 'react-bootstrap' +import { Button } from '../button/Button' +import { Row } from '../grid/Row' +import { Col } from '../grid/Col' +import { Form } from '../form/Form' + +function not(a: readonly TransferListItem[], b: readonly TransferListItem[]) { + return a.filter((item) => !b.some((bItem) => bItem.value === item.value)) +} + +function intersection(a: readonly TransferListItem[], b: readonly TransferListItem[]) { + return a.filter((item) => b.some((bItem) => bItem.value === item.value)) +} + +export interface TransferListItem { + value: string | number + label: string +} + +export interface TransferListProps { + availableItems: TransferListItem[] + defaultSelected?: TransferListItem[] + onChange?: (selected: TransferListItem[]) => void +} + +export const TransferList = ({ + availableItems, + defaultSelected = [], + onChange +}: TransferListProps) => { + const [checked, setChecked] = useState([]) + const [left, setLeft] = useState( + not(availableItems, defaultSelected) + ) + const [right, setRight] = useState( + intersection(availableItems, defaultSelected) + ) + + const leftChecked = intersection(checked, left) + const rightChecked = intersection(checked, right) + + const handleToggle = (item: TransferListItem) => () => { + const currentIndex = checked.findIndex((checkedItem) => checkedItem.value === item.value) + const newChecked = [...checked] + + if (currentIndex === -1) { + newChecked.push(item) + } else { + newChecked.splice(currentIndex, 1) + } + + setChecked(newChecked) + } + + const handleAllRight = () => { + setRight(right.concat(left)) + onChange && onChange(right.concat(left)) + setLeft([]) + } + + const handleCheckedRight = () => { + setRight(right.concat(leftChecked)) + onChange && onChange(right.concat(leftChecked)) + setLeft(not(left, leftChecked)) + setChecked(not(checked, leftChecked)) + } + + const handleCheckedLeft = () => { + setLeft(left.concat(rightChecked)) + setRight(not(right, rightChecked)) + onChange && onChange(not(right, rightChecked)) + setChecked(not(checked, rightChecked)) + } + + const handleAllLeft = () => { + setLeft(left.concat(right)) + setRight([]) + onChange && onChange([]) + } + + const customList = (items: readonly TransferListItem[]) => ( +
+ + {items.map((item: TransferListItem) => { + const labelId = `transfer-list-item-${item.value}-label` + + return ( + + + + ) + })} + +
+ ) + + return ( + + {customList(left)} + + + + + + + + + {customList(right)} + + ) +} diff --git a/packages/design-system/src/lib/index.ts b/packages/design-system/src/lib/index.ts index 6c08768a9..533931299 100644 --- a/packages/design-system/src/lib/index.ts +++ b/packages/design-system/src/lib/index.ts @@ -28,3 +28,4 @@ export { SelectAdvanced } from './components/select-advanced/SelectAdvanced' export { Card } from './components/card/Card' export { ProgressBar } from './components/progress-bar/ProgressBar' export { Stack } from './components/stack/Stack' +export { TransferList } from './components/transfer-list/TransferList' diff --git a/packages/design-system/src/lib/stories/transfer-list/TransferList.stories.tsx b/packages/design-system/src/lib/stories/transfer-list/TransferList.stories.tsx new file mode 100644 index 000000000..5bdb64fed --- /dev/null +++ b/packages/design-system/src/lib/stories/transfer-list/TransferList.stories.tsx @@ -0,0 +1,70 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { TransferList, TransferListItem } from '../../components/transfer-list/TransferList' + +/** + * ## Description + * The transfer list component is an element that allows users to transfer on or more items between two lists. + * The list on the left is the source list and the list on the right is the target list. + * The right list can be empty or have some items already. + */ + +const meta: Meta = { + title: 'Transfer List', + component: TransferList, + tags: ['autodocs'] +} + +const availableItems: TransferListItem[] = [ + { + label: 'Item 1', + value: 1 + }, + { + label: 'Item 2', + value: 2 + }, + { + label: 'Item 3', + value: 3 + }, + { + label: 'Item 4', + value: 4 + }, + { + label: 'Item 5', + value: 5 + }, + { + label: 'Item 6', + value: 6 + } +] + +const defaultSelected: TransferListItem[] = [ + { + label: 'Item 4', + value: 4 + }, + { + label: 'Item 5', + value: 5 + } +] + +export default meta +type Story = StoryObj + +const onChangeFn = (items: TransferListItem[]) => { + console.log(items) +} + +export const Single: Story = { + render: () => ( + + ) +}