import React, { memo, useCallback, useRef, useState } from 'react';
import { CollisionPriority } from '@dnd-kit/abstract';
import { DragDropProvider } from '@dnd-kit/react';
import { useSortable } from '@dnd-kit/react/sortable';
import { move } from '@dnd-kit/helpers';
import { Feedback } from '@dnd-kit/dom';
function createRange(length: number) {
return Array.from({ length }, (_, i) => i + 1);
}
const SortableItem = memo(function SortableItem({
id,
column,
index
}: {
id: string;
column: string;
index: number;
}) {
const { handleRef, ref, isDragging } = useSortable({
id,
group: column,
accept: 'item',
type: 'item',
plugins: [Feedback.configure({ feedback: 'clone' })],
index,
data: { group: column }
});
return (
<div
ref={ref}
className="item"
style={{ opacity: isDragging ? 0.5 : 1 }}
>
{id}
<button ref={handleRef} className="handle">⋮⋮</button>
</div>
);
});
const SortableColumn = memo(function SortableColumn({
id,
index,
items
}: {
id: string;
index: number;
items: string[];
}) {
const { handleRef, ref, isDragging } = useSortable({
id,
accept: ['column', 'item'],
collisionPriority: CollisionPriority.Low,
type: 'column',
index
});
return (
<div
ref={ref}
className="column"
style={{ opacity: isDragging ? 0.5 : 1 }}
>
<h2>
Column {id}
<button ref={handleRef} className="handle">⋮⋮</button>
</h2>
<ul>
{items.map((itemId, itemIndex) => (
<SortableItem
key={itemId}
id={itemId}
column={id}
index={itemIndex}
/>
))}
</ul>
</div>
);
});
export default function App() {
const [items, setItems] = useState({
A: createRange(6).map((id) => `A${id}`),
B: createRange(6).map((id) => `B${id}`),
C: createRange(6).map((id) => `C${id}`),
D: []
});
const [columns] = useState(Object.keys(items));
const snapshot = useRef(structuredClone(items));
return (
<DragDropProvider
onDragStart={useCallback(() => {
snapshot.current = structuredClone(items);
}, [items])}
onDragOver={useCallback((event) => {
const { source } = event.operation;
if (source && source.type === 'column') return;
setItems((items) => move(items, event));
}, [])}
onDragEnd={useCallback((event) => {
if (event.canceled) {
setItems(snapshot.current);
}
}, [])}
>
<div className="board">
{columns.map((column, columnIndex) => (
<SortableColumn
key={column}
id={column}
index={columnIndex}
items={items[column]}
/>
))}
</div>
</DragDropProvider>
);
}