|
| 1 | +import { DragDropContext, Droppable, type DropResult } from "@hello-pangea/dnd" |
| 2 | +import { useCallback } from "react" |
| 3 | + |
| 4 | +export type DnDListProps<T> = { |
| 5 | + items?: Array<T> |
| 6 | + onOrderChanged?: (items: Array<T>) => void |
| 7 | + onIndexChanged?: (oldIndex: number, newIndex: number) => void |
| 8 | + |
| 9 | + children: JSX.Element[] |
| 10 | +} |
| 11 | + |
| 12 | +export function DnDList<T>({ |
| 13 | + items, |
| 14 | + onOrderChanged, |
| 15 | + onIndexChanged, |
| 16 | + children, |
| 17 | +}: DnDListProps<T>) { |
| 18 | + const onDragEnd = useCallback( |
| 19 | + (result: DropResult) => { |
| 20 | + // dropped outside the list |
| 21 | + if (!result.destination) { |
| 22 | + return |
| 23 | + } |
| 24 | + if (onIndexChanged) { |
| 25 | + onIndexChanged(result.source.index, result.destination.index) |
| 26 | + } |
| 27 | + if (items && onOrderChanged) { |
| 28 | + const [removed] = items.splice(result.source.index, 1) |
| 29 | + const newOrder = items.slice() |
| 30 | + newOrder.splice(result.destination.index, 0, removed) |
| 31 | + onOrderChanged(newOrder) |
| 32 | + } |
| 33 | + }, |
| 34 | + [items, onIndexChanged, onOrderChanged], |
| 35 | + ) |
| 36 | + |
| 37 | + return ( |
| 38 | + <div |
| 39 | + className={`${ |
| 40 | + children.length > 0 |
| 41 | + ? "block border-border hover:border-border-bold" |
| 42 | + : "opacity-0 border-transparent" |
| 43 | + } box-border border`} |
| 44 | + > |
| 45 | + <DragDropContext onDragEnd={onDragEnd}> |
| 46 | + <Droppable droppableId="droppable"> |
| 47 | + {(provided) => ( |
| 48 | + <div |
| 49 | + {...provided.droppableProps} |
| 50 | + ref={provided.innerRef} |
| 51 | + className="flex flex-col w-full" |
| 52 | + > |
| 53 | + {children} |
| 54 | + {provided.placeholder} |
| 55 | + </div> |
| 56 | + )} |
| 57 | + </Droppable> |
| 58 | + </DragDropContext> |
| 59 | + </div> |
| 60 | + ) |
| 61 | +} |
0 commit comments