From 0907f96b58491d0cc0f0766e737704072f915790 Mon Sep 17 00:00:00 2001 From: Ki3mON Date: Tue, 16 Jun 2026 18:00:26 +0200 Subject: [PATCH] fix(ios): drag-and-drop --- .../components/quiz/questions/Ordering.tsx | 165 ++++++++++++------ frontend/package.json | 3 + pnpm-lock.yaml | 56 ++++++ 3 files changed, 171 insertions(+), 53 deletions(-) diff --git a/frontend/app/components/quiz/questions/Ordering.tsx b/frontend/app/components/quiz/questions/Ordering.tsx index 43c2350..5c5fc90 100644 --- a/frontend/app/components/quiz/questions/Ordering.tsx +++ b/frontend/app/components/quiz/questions/Ordering.tsx @@ -1,5 +1,22 @@ 'use client'; +import { + closestCenter, + DndContext, + type DragEndEvent, + 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 { GripVertical } from 'lucide-react'; import { useState } from 'react'; @@ -19,6 +36,69 @@ interface OrderingProps { onSubmit?: (order: number[]) => void; } +interface OrderedItem { + text: string; + origIdx: number; +} + +function SortableRow({ + item, + position, +}: { + item: OrderedItem; + position: number; +}) { + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: item.origIdx }); + + return ( +
+ + + {position} + + + {item.text} + +
+ ); +} + export default function Ordering({ questionNumber, totalQuestions, @@ -28,16 +108,25 @@ export default function Ordering({ items, onSubmit, }: OrderingProps) { - const [ordered, setOrdered] = useState(() => + const [ordered, setOrdered] = useState(() => items.map((text, origIdx) => ({ text, origIdx })), ); - const [dragging, setDragging] = useState(null); - const moveItem = (from: number, to: number) => { - const next = [...ordered]; - const [item] = next.splice(from, 1); - next.splice(to, 0, item); - setOrdered(next); + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const handleDragEnd = ({ active, over }: DragEndEvent) => { + if (over && active.id !== over.id) { + setOrdered((prev) => { + const oldIndex = prev.findIndex((o) => o.origIdx === active.id); + const newIndex = prev.findIndex((o) => o.origIdx === over.id); + return arrayMove(prev, oldIndex, newIndex); + }); + } }; return ( @@ -48,52 +137,22 @@ export default function Ordering({ > -
- {ordered.map(({ text, origIdx }, i) => { - const isDragging = dragging === i; - return ( -
setDragging(i)} - onDragOver={(e) => { - e.preventDefault(); - if (dragging !== null && dragging !== i) { - moveItem(dragging, i); - setDragging(i); - } - }} - onDragEnd={() => setDragging(null)} - className={cn( - 'flex w-full cursor-grab items-center gap-3 rounded-2xl px-4 py-3.5 transition-all', - isDragging - ? 'border-2 border-[var(--selected-border)] bg-[var(--selected-bg)] shadow-[0_4px_12px_rgba(246,162,0,0.25)]' - : 'border-[1.5px] border-[var(--border)] bg-[var(--card-bg)]', - )} - > - - - {i + 1} - - - {text} - -
- ); - })} -
+ + o.origIdx)} + strategy={verticalListSortingStrategy} + > +
+ {ordered.map((item, i) => ( + + ))} +
+
+
=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@emnapi/core@1.9.0': resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} @@ -3232,6 +3263,31 @@ snapshots: '@date-fns/tz@1.5.0': {} + '@dnd-kit/accessibility@3.1.1(react@19.2.3)': + dependencies: + react: 19.2.3 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + react: 19.2.3 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.2.3)': + dependencies: + react: 19.2.3 + tslib: 2.8.1 + '@emnapi/core@1.9.0': dependencies: '@emnapi/wasi-threads': 1.2.0