Sortable Grid
DnD & InteractionGrid of items with drag-to-reorder.
Preview
[object Object]
[object Object]
[object Object]
[object Object]
Usage
example.jsx
import { SortableGrid } from "@/components/ui/sortable-grid";
export default function Example() {
return <SortableGrid />;
}Source Code
Copy this file into components/ui/sortable-grid.jsx in your project.
sortable-grid.jsx
"use client";
import { forwardRef, useState } from "react";
import { cn } from "@/lib/utils";
const SortableGrid = forwardRef(({ className, items: initialItems = [], columns = 3, renderItem, ...props }, ref) => {
const [items, setItems] = useState(initialItems);
const [dragIdx, setDragIdx] = useState(null);
const onDrop = (i) => {
if (dragIdx === null || dragIdx === i) { setDragIdx(null); return; }
const newItems = [...items];
const [moved] = newItems.splice(dragIdx, 1);
newItems.splice(i, 0, moved);
setItems(newItems);
setDragIdx(null);
};
return (
<div ref={ref} className={cn("grid gap-2",
columns === 2 && "grid-cols-2",
columns === 3 && "grid-cols-3",
columns === 4 && "grid-cols-4",
className
)} {...props}>
{items.map((item, i) => (
<div key={i} draggable
onDragStart={() => setDragIdx(i)}
onDragOver={(e) => e.preventDefault()}
onDrop={() => onDrop(i)}
onDragEnd={() => setDragIdx(null)}
className={cn("cursor-grab rounded-md border bg-background p-3 text-center text-sm transition-opacity active:cursor-grabbing",
dragIdx === i && "opacity-50"
)}
>
{renderItem ? renderItem(item, i) : String(item)}
</div>
))}
</div>
);
});
SortableGrid.displayName = "SortableGrid";
export { SortableGrid };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge