import React, {memo, useRef, useState, useCallback} from 'react';
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {move} from '@dnd-kit/helpers';
import {CollisionPriority} from '@dnd-kit/abstract';
interface Items {
[key: string]: string[];
}
const SortableItem = memo(function SortableItem({
id,
column,
index,
}: {
id: string;
column: string;
index: number;
}) {
const [element, setElement] = useState<Element | null>(null);
const handleRef = useRef<HTMLButtonElement | null>(null);
const {isDragging} = useSortable({
id,
group: column, // Group items by column
accept: 'item', // Accept items with type 'item'
type: 'item', // This is an item type
index,
element,
handle: handleRef,
data: {group: column}, // Store which column this belongs to
});
return (
<div
ref={setElement}
style={{
padding: '12px',
margin: '8px 0',
backgroundColor: isDragging ? '#f0f0f0' : 'white',
border: '1px solid #ddd',
borderRadius: '6px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{id}
<button
ref={handleRef}
style={{
cursor: 'grab',
border: 'none',
background: 'transparent',
fontSize: '18px',
}}
>
⋮⋮
</button>
</div>
);
});
const SortableColumn = memo(function SortableColumn({
id,
rows,
index,
}: {
id: string;
rows: string[];
index: number;
}) {
const [element, setElement] = useState<Element | null>(null);
const handleRef = useRef<HTMLButtonElement | null>(null);
const {isDragging} = useSortable({
id,
accept: ['column', 'item'], // Columns can accept both columns and items
collisionPriority: CollisionPriority.Low, // Lower priority for collision detection
type: 'column',
index,
element,
handle: handleRef,
});
return (
<div
ref={setElement}
style={{
padding: '16px',
backgroundColor: isDragging ? '#fafafa' : '#f8f9fa',
border: '2px solid #dee2e6',
borderRadius: '8px',
minWidth: '300px',
minHeight: '400px',
}}
>
<h2 style={{margin: '0 0 16px 0', display: 'flex', alignItems: 'center', gap: 8}}>
{id}
<button
ref={handleRef}
style={{
cursor: 'grab',
border: 'none',
background: 'transparent',
fontSize: '16px',
}}
>
⋮⋮
</button>
</h2>
<div>
{rows.map((itemId, itemIndex) => (
<SortableItem
key={itemId}
id={itemId}
column={id}
index={itemIndex}
/>
))}
</div>
</div>
);
});
export default function MultipleListsApp() {
const [items, setItems] = useState<Items>({
A: ['A1', 'A2', 'A3', 'A4', 'A5', 'A6'],
B: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6'],
C: ['C1', 'C2', 'C3', 'C4', 'C5', 'C6'],
D: [],
});
const columns = Object.keys(items);
const snapshot = useRef(structuredClone(items));
return (
<DragDropProvider
onDragStart={useCallback(() => {
snapshot.current = structuredClone(items);
}, [items])}
onDragOver={useCallback((event) => {
const {source} = event.operation;
// Don't update state for column dragging (use optimistic sorting)
if (source && source.type === 'column') {
return;
}
setItems((items) => move(items, event));
}, [])}
onDragEnd={useCallback((event) => {
if (event.canceled) {
setItems(snapshot.current);
return;
}
}, [])}
>
<div style={{display: 'flex', gap: 20, padding: 20, overflow: 'auto'}}>
{columns.map((column, columnIndex) => (
<SortableColumn
key={column}
id={column}
index={columnIndex}
rows={items[column]}
/>
))}
</div>
</DragDropProvider>
);
}