diff --git a/package.json b/package.json index 138d2e8..159a390 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,10 @@ "start:bun": "NODE_ENV=production bun ./.next/standalone/server.js" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@monaco-editor/react": "^4.7.0", "@xyflow/react": "^12.10.1", "bcryptjs": "^3.0.3", diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js index a31713c..629432c 100644 --- a/src/app/(dashboard)/dashboard/combos/page.js +++ b/src/app/(dashboard)/dashboard/combos/page.js @@ -1,6 +1,10 @@ "use client"; import { useState, useEffect, useCallback } from "react"; +import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; +import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { restrictToVerticalAxis, restrictToParentElement } from "@dnd-kit/modifiers"; import { Card, Button, Modal, Input, CardSkeleton, ModelSelectModal, Toggle, ConfirmModal } from "@/shared/components"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers"; @@ -283,15 +287,20 @@ function ComboCard({ combo, copied, onCopy, onEdit, onDelete, roundRobinEnabled, ); } -// Inline editable model item -function ModelItem({ index, model, isFirst, isLast, onEdit, onMoveUp, onMoveDown, onRemove }) { +function ModelItem({ id, index, model, isFirst, isLast, onEdit, onMoveUp, onMoveDown, onRemove }) { + const { attributes, listeners, setNodeRef, transform, isDragging } = useSortable({ id }); + const style = { + transform: CSS.Transform.toString(transform), + // no transition — prevents the CSS settle animation fighting React's re-render on drop + opacity: isDragging ? 0.4 : 1, + zIndex: isDragging ? 999 : undefined, + }; const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(model); - const commit = () => { const trimmed = draft.trim(); if (trimmed && trimmed !== model) onEdit(trimmed); - else setDraft(model); // revert if empty or unchanged + else setDraft(model); setEditing(false); }; @@ -301,7 +310,26 @@ function ModelItem({ index, model, isFirst, isLast, onEdit, onMoveUp, onMoveDown }; return ( -
No models added yet