"use client";
import React, { useState } from "react";
import {
motion,
AnimatePresence,
Reorder,
useDragControls,
} from "framer-motion";
import { GripVertical, X, Plus } from "lucide-react";
import { cn } from "../lib/utils";
export interface ReorderItem {
id: string;
label: string;
description?: string;
icon?: React.ReactNode;
}
interface DraggableReorderListProps {
/** Initial items */
items: ReorderItem[];
/** Callback with new order when reordered */
onReorder?: (items: ReorderItem[]) => void;
/** Allow removing items */
removable?: boolean;
/** Additional classes */
className?: string;
}
function Item({
item,
onRemove,
removable,
}: {
item: ReorderItem;
onRemove: (id: string) => void;
removable: boolean;
}) {
const dragControls = useDragControls();
return (
e.preventDefault()}
className={cn(
"flex items-center gap-3 rounded-xl border bg-background px-4 py-3",
"shadow-sm hover:shadow-md transition-shadow cursor-default select-none"
)}
>
{/* Drag Handle */}
dragControls.start(e)}
className="flex-shrink-0 cursor-grab active:cursor-grabbing touch-none text-muted-foreground/40 hover:text-muted-foreground transition-colors"
whileHover={{ scale: 1.1 }}
>
{/* Icon */}
{item.icon && (
{item.icon}
)}
{/* Content */}
{item.label}
{item.description && (
{item.description}
)}
{/* Remove Button */}
{removable && (
onRemove(item.id)}
aria-label={`Remove ${item.label}`}
className="flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground/40 hover:text-destructive hover:bg-destructive/10 transition-colors focus:outline-none"
whileHover={{ scale: 1.15 }}
whileTap={{ scale: 0.9 }}
>
)}
);
}
export function DraggableReorderList({
items: initialItems,
onReorder,
removable = true,
className,
}: DraggableReorderListProps) {
const [items, setItems] = useState(initialItems);
const handleReorder = (newOrder: ReorderItem[]) => {
setItems(newOrder);
onReorder?.(newOrder);
};
const handleRemove = (id: string) => {
const next = items.filter((item) => item.id !== id);
setItems(next);
onReorder?.(next);
};
return (
{items.map((item) => (
))}
{items.length === 0 && (
All items removed
)}
);
}