-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: first approach to sortable list
- Loading branch information
Showing
4 changed files
with
189 additions
and
43 deletions.
There are no files selected for viewing
105 changes: 79 additions & 26 deletions
105
packages/design-system/src/lib/components/transfer-list/ItemsList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,88 @@ | ||
import { useId } from 'react' | ||
import { ListGroup } from 'react-bootstrap' | ||
import { Form } from '../form/Form' | ||
import { TransferListItem } from './TransferList' | ||
import { DndContext, DragEndEvent } from '@dnd-kit/core' | ||
import { arrayMove, SortableContext } from '@dnd-kit/sortable' | ||
import { type TransferListItem } from './TransferList' | ||
import { ListItem } from './ListItem' | ||
import styles from './TransferList.module.scss' | ||
|
||
interface ListProps { | ||
items: readonly TransferListItem[] | ||
side: 'left' | 'right' | ||
checked: readonly TransferListItem[] | ||
onToggle: (item: TransferListItem) => () => void | ||
} | ||
type ListProps = | ||
| { | ||
items: TransferListItem[] | ||
side: 'left' | ||
checked: TransferListItem[] | ||
onToggle: (item: TransferListItem) => () => void | ||
rightItems?: never | ||
setRight?: never | ||
onChange?: never | ||
} | ||
| { | ||
items: TransferListItem[] | ||
side: 'right' | ||
checked: TransferListItem[] | ||
onToggle: (item: TransferListItem) => () => void | ||
rightItems: TransferListItem[] | ||
setRight: React.Dispatch<React.SetStateAction<TransferListItem[]>> | ||
onChange?: (selected: TransferListItem[]) => void | ||
} | ||
|
||
export const ItemsList = ({ | ||
items, | ||
side, | ||
checked, | ||
onToggle, | ||
rightItems, | ||
setRight, | ||
onChange | ||
}: ListProps) => { | ||
const handleDragEnd = (event: DragEndEvent) => { | ||
// Prevent sorting on the left side which is not sortable but also asserts that setRight is defined | ||
if (side === 'left') return | ||
|
||
const { active, over } = event | ||
|
||
if (over && active.id !== over.id) { | ||
const oldIndex = rightItems.findIndex((item) => item.id === active.id) | ||
const newIndex = rightItems.findIndex((item) => item.id === over.id) | ||
|
||
const newItems = arrayMove(rightItems, oldIndex, newIndex) | ||
|
||
setRight(newItems) | ||
|
||
onChange && onChange(newItems) | ||
} | ||
} | ||
|
||
export const ItemsList = ({ items, side, checked, onToggle }: ListProps) => { | ||
const uniqueID = useId() | ||
if (side === 'left') { | ||
return ( | ||
<ListGroup as="ul" className={styles['items-list']} data-testid={`${side}-list-group`}> | ||
{items.map((item: TransferListItem) => ( | ||
<ListItem | ||
item={item} | ||
side={side} | ||
checked={checked} | ||
onToggle={onToggle} | ||
key={item.value} | ||
/> | ||
))} | ||
</ListGroup> | ||
) | ||
} | ||
|
||
return ( | ||
<ListGroup as="ul" className={styles['items-list']} data-testid={`${side}-list-group`}> | ||
{items.map((item: TransferListItem) => { | ||
const labelId = `transfer-list-item-${item.value}-label-${uniqueID}` | ||
|
||
return ( | ||
<ListGroup.Item as="li" className={styles['list-item']} key={item.value}> | ||
<Form.Group.Checkbox | ||
label={item.label} | ||
onChange={onToggle(item)} | ||
id={labelId} | ||
checked={checked.indexOf(item) !== -1} | ||
<DndContext onDragEnd={handleDragEnd}> | ||
<SortableContext items={items}> | ||
<ListGroup as="ul" className={styles['items-list']} data-testid={`${side}-list-group`}> | ||
{items.map((item: TransferListItem) => ( | ||
<ListItem | ||
item={item} | ||
side={side} | ||
checked={checked} | ||
onToggle={onToggle} | ||
key={item.value} | ||
/> | ||
</ListGroup.Item> | ||
) | ||
})} | ||
</ListGroup> | ||
))} | ||
</ListGroup> | ||
</SortableContext> | ||
</DndContext> | ||
) | ||
} |
71 changes: 71 additions & 0 deletions
71
packages/design-system/src/lib/components/transfer-list/ListItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { useId } from 'react' | ||
import { useSortable } from '@dnd-kit/sortable' | ||
import { CSS } from '@dnd-kit/utilities' | ||
import { ListGroup } from 'react-bootstrap' | ||
import { Form } from '../form/Form' | ||
import { TransferListItem } from './TransferList' | ||
import { Stack } from '../stack/Stack' | ||
import styles from './TransferList.module.scss' | ||
|
||
interface ListItemProps { | ||
item: TransferListItem | ||
side: 'left' | 'right' | ||
checked: readonly TransferListItem[] | ||
onToggle: (item: TransferListItem) => () => void | ||
} | ||
|
||
export const ListItem = ({ item, side, checked, onToggle }: ListItemProps) => { | ||
const { attributes, listeners, transform, transition, setNodeRef, setActivatorNodeRef } = | ||
useSortable({ id: item.id }) | ||
|
||
const uniqueID = useId() | ||
const labelId = `transfer-list-item-${item.value}-label-${uniqueID}` | ||
|
||
if (side === 'left') { | ||
return ( | ||
<ListGroup.Item as="li" className={styles['list-item']}> | ||
<Form.Group.Checkbox | ||
label={item.label} | ||
onChange={onToggle(item)} | ||
id={labelId} | ||
checked={checked.indexOf(item) !== -1} | ||
/> | ||
</ListGroup.Item> | ||
) | ||
} | ||
|
||
const style = { | ||
transform: CSS.Transform.toString(transform), | ||
transition | ||
} | ||
|
||
// TODO:ME Limit the drag movement to the y-axis | ||
|
||
return ( | ||
<ListGroup.Item | ||
as="li" | ||
ref={setNodeRef} | ||
{...attributes} | ||
style={style} | ||
className={styles['list-item']}> | ||
<Stack direction="horizontal" gap={1}> | ||
<div className={styles['drag-grip']} ref={setActivatorNodeRef} {...listeners}> | ||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"> | ||
<circle cx="9" cy="6" r="1.5" fill="#333" /> | ||
<circle cx="15" cy="6" r="1.5" fill="#333" /> | ||
<circle cx="9" cy="12" r="1.5" fill="#333" /> | ||
<circle cx="15" cy="12" r="1.5" fill="#333" /> | ||
<circle cx="9" cy="18" r="1.5" fill="#333" /> | ||
<circle cx="15" cy="18" r="1.5" fill="#333" /> | ||
</svg> | ||
</div> | ||
<Form.Group.Checkbox | ||
label={item.label} | ||
onChange={onToggle(item)} | ||
id={labelId} | ||
checked={checked.indexOf(item) !== -1} | ||
/> | ||
</Stack> | ||
</ListGroup.Item> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters