|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import React from 'react'; |
4 | | -import { DndProvider } from 'react-dnd'; |
5 | | -import { HTML5Backend } from 'react-dnd-html5-backend'; |
| 3 | +import React, { useState } from 'react'; |
| 4 | +import { |
| 5 | + DndContext, |
| 6 | + closestCenter, |
| 7 | + KeyboardSensor, |
| 8 | + PointerSensor, |
| 9 | + useSensor, |
| 10 | + useSensors, |
| 11 | + DragEndEvent, |
| 12 | + DragStartEvent, |
| 13 | + DragOverEvent, |
| 14 | + DragOverlay, |
| 15 | +} from '@dnd-kit/core'; |
| 16 | +import { sortableKeyboardCoordinates } from '@dnd-kit/sortable'; |
6 | 17 | import { useDragDrop } from '../../hooks/useDragDrop'; |
7 | 18 | import { DragDropItem, DragDropZone } from '../../utils/dragDropUtils'; |
8 | | -import { DragPreview } from './DragPreview'; |
9 | 19 | import { DropZones } from './DropZones'; |
| 20 | +import { DragPreview } from './DragPreview'; |
10 | 21 |
|
11 | 22 | interface DragDropContainerProps { |
12 | 23 | title?: string; |
@@ -44,8 +55,113 @@ export const DragDropContainer = ({ |
44 | 55 | onAutoSave, |
45 | 56 | }); |
46 | 57 |
|
| 58 | + const [activeItem, setActiveItem] = useState<DragDropItem | null>(null); |
| 59 | + |
| 60 | + const sensors = useSensors( |
| 61 | + useSensor(PointerSensor, { |
| 62 | + activationConstraint: { |
| 63 | + distance: 5, |
| 64 | + }, |
| 65 | + }), |
| 66 | + useSensor(KeyboardSensor, { |
| 67 | + coordinateGetter: sortableKeyboardCoordinates, |
| 68 | + }) |
| 69 | + ); |
| 70 | + |
| 71 | + const handleDragStart = (event: DragStartEvent) => { |
| 72 | + const { active } = event; |
| 73 | + if (active.data.current?.type === 'COURSE_CONTENT_ITEM') { |
| 74 | + setActiveItem(active.data.current.item as DragDropItem); |
| 75 | + } |
| 76 | + }; |
| 77 | + |
| 78 | + const handleDragOver = (event: DragOverEvent) => { |
| 79 | + const { active, over } = event; |
| 80 | + if (!over) return; |
| 81 | + |
| 82 | + const activeData = active.data.current; |
| 83 | + const overData = over.data.current; |
| 84 | + |
| 85 | + if (!activeData || !overData) return; |
| 86 | + |
| 87 | + const activeItem = activeData.item as DragDropItem; |
| 88 | + const activeZoneId = activeData.zoneId as string; |
| 89 | + |
| 90 | + const overType = overData.type; |
| 91 | + |
| 92 | + if (overType === 'COURSE_CONTENT_ITEM') { |
| 93 | + const overItem = overData.item as DragDropItem; |
| 94 | + const overZoneId = overData.zoneId as string; |
| 95 | + |
| 96 | + if (activeZoneId !== overZoneId) { |
| 97 | + // Move item to new zone (temporary while dragging, or handled at dragEnd depending on preference) |
| 98 | + // For simpler implementation without intermediate state updates, we can just let handleDragEnd deal with it. |
| 99 | + // But for smooth dragging between lists, we might need to handle moving items between zones here. |
| 100 | + // In useDragDrop, state updates are batched, so calling moveToZone is fine. |
| 101 | + |
| 102 | + // Find index of over item |
| 103 | + const overZoneItems = state[overZoneId] || []; |
| 104 | + const overIndex = overZoneItems.findIndex((i) => i.id === overItem.id); |
| 105 | + |
| 106 | + moveToZone(activeItem.id, activeZoneId, overZoneId, overIndex); |
| 107 | + |
| 108 | + // Mutate active.data.current so it points to the new zoneId for subsequent events |
| 109 | + active.data.current = { |
| 110 | + ...active.data.current, |
| 111 | + zoneId: overZoneId, |
| 112 | + }; |
| 113 | + } |
| 114 | + } else if (overType === 'ZONE') { |
| 115 | + const overZone = overData.zone as DragDropZone; |
| 116 | + |
| 117 | + if (activeZoneId !== overZone.id) { |
| 118 | + const overZoneItems = state[overZone.id] || []; |
| 119 | + moveToZone(activeItem.id, activeZoneId, overZone.id, overZoneItems.length); |
| 120 | + |
| 121 | + active.data.current = { |
| 122 | + ...active.data.current, |
| 123 | + zoneId: overZone.id, |
| 124 | + }; |
| 125 | + } |
| 126 | + } |
| 127 | + }; |
| 128 | + |
| 129 | + const handleDragEnd = (event: DragEndEvent) => { |
| 130 | + setActiveItem(null); |
| 131 | + const { active, over } = event; |
| 132 | + if (!over) return; |
| 133 | + |
| 134 | + const activeData = active.data.current; |
| 135 | + const overData = over.data.current; |
| 136 | + |
| 137 | + if (!activeData || !overData) return; |
| 138 | + |
| 139 | + const activeZoneId = activeData.zoneId as string; |
| 140 | + |
| 141 | + if (overData.type === 'COURSE_CONTENT_ITEM') { |
| 142 | + const overItem = overData.item as DragDropItem; |
| 143 | + const overZoneId = overData.zoneId as string; |
| 144 | + |
| 145 | + if (activeZoneId === overZoneId) { |
| 146 | + const zoneItems = state[activeZoneId] || []; |
| 147 | + const oldIndex = zoneItems.findIndex(i => i.id === active.id); |
| 148 | + const newIndex = zoneItems.findIndex(i => i.id === over.id); |
| 149 | + |
| 150 | + if (oldIndex !== newIndex) { |
| 151 | + reorderInZone(activeZoneId, oldIndex, newIndex); |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | + }; |
| 156 | + |
47 | 157 | return ( |
48 | | - <DndProvider backend={HTML5Backend}> |
| 158 | + <DndContext |
| 159 | + sensors={sensors} |
| 160 | + collisionDetection={closestCenter} |
| 161 | + onDragStart={handleDragStart} |
| 162 | + onDragOver={handleDragOver} |
| 163 | + onDragEnd={handleDragEnd} |
| 164 | + > |
49 | 165 | <div className="rounded-2xl border border-slate-200 bg-slate-50 p-5 md:p-6"> |
50 | 166 | <div className="mb-5 flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> |
51 | 167 | <div> |
@@ -91,11 +207,11 @@ export const DragDropContainer = ({ |
91 | 207 | <DropZones |
92 | 208 | zones={zones} |
93 | 209 | state={state} |
94 | | - onReorder={reorderInZone} |
95 | | - onMoveToZone={moveToZone} |
96 | 210 | /> |
97 | 211 | </div> |
98 | | - <DragPreview /> |
99 | | - </DndProvider> |
| 212 | + <DragOverlay> |
| 213 | + {activeItem ? <DragPreview item={activeItem} /> : null} |
| 214 | + </DragOverlay> |
| 215 | + </DndContext> |
100 | 216 | ); |
101 | 217 | }; |
0 commit comments