Drag & Drop
Drag and drop uses @dnd-kit for sortable lists (timeline reordering, storyboard arrangement).
Basic Sortable Setup
import { DndContext, closestCenter } from '@dnd-kit/core';
import {
SortableContext,
verticalListSortingStrategy,
useSortable,
arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';Sortable List
function SortableList({ items, onReorder }) {
const handleDragEnd = useCallback((event) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
const oldIndex = items.findIndex(i => i.id === active.id);
const newIndex = items.findIndex(i => i.id === over.id);
onReorder(arrayMove(items, oldIndex, newIndex));
}, [items, onReorder]);
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext
items={items.map(i => i.id)}
strategy={verticalListSortingStrategy}
>
{items.map(item => (
<SortableItem key={item.id} item={item} />
))}
</SortableContext>
</DndContext>
);
}Sortable Item
function SortableItem({ item }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{item.name}
</div>
);
}The CSS.Transform.toString utility converts the dnd-kit transform object into a CSS transform string.
Horizontal Sorting
For horizontal lists (storyboard frames), use horizontalListSortingStrategy:
<SortableContext
items={frames.map(f => f.id)}
strategy={horizontalListSortingStrategy}
>
{frames.map(frame => <SortableFrame key={frame.id} frame={frame} />)}
</SortableContext>Rules
- Pass
itemsas an array of IDs toSortableContext, not full objects - Use
arrayMovefrom@dnd-kit/sortablefor reordering — never mutate the array directly - Use
CSS.Transform.toStringfor the style transform — do not construct CSS strings manually - Choose the strategy that matches your layout:
verticalListSortingStrategyorhorizontalListSortingStrategy - Guard
handleDragEndwithif (!over || active.id === over.id) returnto avoid no-op reorders
Last updated on