@btfash/react-native-reanimated-dnd 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1238 -0
  3. package/documentation/web-docs/README.md +41 -0
  4. package/example-app/.expo/README.md +8 -0
  5. package/example-app/README.md +515 -0
  6. package/lib/components/Draggable.d.ts +5 -0
  7. package/lib/components/Draggable.js +1 -0
  8. package/lib/components/Droppable.d.ts +3 -0
  9. package/lib/components/Droppable.js +1 -0
  10. package/lib/components/Sortable.d.ts +3 -0
  11. package/lib/components/Sortable.js +1 -0
  12. package/lib/components/SortableItem.d.ts +6 -0
  13. package/lib/components/SortableItem.js +1 -0
  14. package/lib/components/sortableUtils.d.ts +33 -0
  15. package/lib/components/sortableUtils.js +1 -0
  16. package/lib/context/DropContext.d.ts +3 -0
  17. package/lib/context/DropContext.js +1 -0
  18. package/lib/hooks/index.d.ts +6 -0
  19. package/lib/hooks/index.js +1 -0
  20. package/lib/hooks/useDraggable.d.ts +2 -0
  21. package/lib/hooks/useDraggable.js +1 -0
  22. package/lib/hooks/useDroppable.d.ts +2 -0
  23. package/lib/hooks/useDroppable.js +1 -0
  24. package/lib/hooks/useHorizontalSortable.d.ts +2 -0
  25. package/lib/hooks/useHorizontalSortable.js +1 -0
  26. package/lib/hooks/useHorizontalSortableList.d.ts +4 -0
  27. package/lib/hooks/useHorizontalSortableList.js +1 -0
  28. package/lib/hooks/useSortable.d.ts +50 -0
  29. package/lib/hooks/useSortable.js +1 -0
  30. package/lib/hooks/useSortableList.d.ts +27 -0
  31. package/lib/hooks/useSortableList.js +1 -0
  32. package/lib/index.d.ts +13 -0
  33. package/lib/index.js +1 -0
  34. package/lib/types/context.d.ts +67 -0
  35. package/lib/types/context.js +1 -0
  36. package/lib/types/draggable.d.ts +54 -0
  37. package/lib/types/draggable.js +1 -0
  38. package/lib/types/droppable.d.ts +26 -0
  39. package/lib/types/droppable.js +1 -0
  40. package/lib/types/index.d.ts +4 -0
  41. package/lib/types/index.js +1 -0
  42. package/lib/types/sortable.d.ts +233 -0
  43. package/lib/types/sortable.js +1 -0
  44. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,1238 @@
1
+ # React Native Reanimated DnD 🎯
2
+
3
+ <p align="center">
4
+ <img src="https://github.com/user-attachments/assets/dba6226e-c407-4a12-9feb-e8f588d6c1e3" alt="React Native Reanimated DnD Demo" style="max-width: 100%; width: 100%;" />
5
+ </p>
6
+ <div align="center">
7
+
8
+ **A drag-and-drop library that _finally_ works on React Native** ✨
9
+
10
+ _Powerful, performant, and built for the modern React Native developer_
11
+
12
+ [![npm version](https://badge.fury.io/js/react-native-reanimated-dnd.svg)](https://badge.fury.io/js/react-native-reanimated-dnd)
13
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
15
+ [![React Native](https://img.shields.io/badge/React%20Native-0.60+-green.svg)](https://reactnative.dev/)
16
+
17
+ <br />
18
+
19
+ <a href="https://www.npmjs.com/package/react-native-reanimated-dnd" target="_blank">
20
+ <img src="https://img.shields.io/badge/📦%20View%20on%20NPM-cb3837?style=for-the-badge&logo=npm&logoColor=white&labelColor=1e293b&fontSize=24" alt="NPM Package" height="36"/>
21
+ </a>
22
+ <a href="https://react-native-reanimated-dnd.netlify.app/" target="_blank">
23
+ <img src="https://img.shields.io/badge/📖%20Read%20the%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b&color=6366f1&fontSize=24" alt="Documentation" height="36"/>
24
+ </a>
25
+ <a href="#-interactive-examples" target="_blank">
26
+ <img src="https://img.shields.io/badge/📱%20Try%20Live%20Demo-fcba03?style=for-the-badge&logo=expo&logoColor=white&labelColor=1e293b&fontSize=24" alt="Live Demo" height="36"/>
27
+ </a>
28
+
29
+ </div>
30
+
31
+ ---
32
+
33
+ ## 🚀 Why This Library?
34
+
35
+ After countless attempts with drag-and-drop solutions that don't work or are simply outdated, this is something that _finally_ works. And it is not just another DnD library, but a **complete ecosystem** built from the ground up for React Native, offering a **best-in-class developer experience** and **production-ready performance**.
36
+
37
+ **Highly feature-packed** with every interaction pattern you'll ever need, yet **simple enough** to get started in minutes. Built for developers who demand both power and simplicity.
38
+
39
+ ## ✨ Features
40
+
41
+ - 🚀 **High Performance** - Built with Reanimated 3 for buttery-smooth 60fps animations
42
+ - 🏗️ **Full RN Fabric Support** - Works seamlessly with both New Architecture and Old Architecture
43
+ - 📦 **Expo Compatible** - Zero configuration needed, works out of the box with Expo
44
+ - 🪶 **Tiny Bundle Size** - Only 70kb unpacked size, won't bloat your app
45
+ - 🎯 **Flexible API** - From simple drag-and-drop to complex sortable lists
46
+ - 📱 **React Native First** - Designed specifically for mobile, not ported from web
47
+ - 🔧 **TypeScript Ready** - Full type safety with comprehensive definitions
48
+ - 🎨 **Infinitely Customizable** - Every animation, behavior, and style is configurable
49
+ - 📦 **Complete Component Suite** - Draggable, Droppable, Sortable, and more
50
+ - 🎪 **Smart Collision Detection** - Multiple algorithms (center, intersect, contain)
51
+ - 📜 **Vertical & Horizontal Sortable Lists** - Drag and drop to sort lists in any direction with automatic scrolling
52
+ - ⚡ **FlatList Performance** - Optional FlatList rendering for large datasets with virtualization
53
+ - 🎭 **Drag Handles** - Precise control with dedicated drag areas
54
+ - 🎬 **Custom Animations** - Spring, timing, or bring your own animation functions
55
+ - 📐 **Pixel-Perfect Positioning** - 9-point alignment system with custom offsets
56
+ - 📦 **Boundary Constraints** - Keep draggables within specific areas
57
+ - ⚡ **State Management** - Complete lifecycle tracking and callbacks
58
+ - 🎯 **Developer Experience** - Intuitive APIs, helpful warnings, and extensive examples
59
+
60
+ ## 📱 Interactive Examples
61
+
62
+ **See it in action!** A comprehensive example app with **15 interactive demos** showcasing every feature and use case.
63
+
64
+ <div align="center">
65
+
66
+ ### 🎮 Try the Example App
67
+
68
+ <table>
69
+ <tr>
70
+ <td align="center" width="50%">
71
+
72
+ **📱 Scan & Play**
73
+
74
+ <img src="https://github.com/user-attachments/assets/80f923f6-7c5f-42e9-9817-7770ee27a70b" alt="Expo QR Code" width="200" height="200" />
75
+
76
+ _Scan with your camera or Expo Go app_
77
+
78
+ </td>
79
+ <td align="center" width="50%">
80
+
81
+ **🚀 Quick Start**
82
+
83
+ 1. Install [Expo Go](https://expo.dev/client) on your phone
84
+ 2. Scan the QR code with your camera
85
+ 3. Open the link in Expo Go
86
+ 4. Explore 15 interactive examples!
87
+
88
+ **Or browse the code:**
89
+ [**📂 View Example App →**](./example-app/README.md)
90
+
91
+ </td>
92
+ </tr>
93
+ </table>
94
+
95
+ ### 📚 Complete Documentation
96
+
97
+ <a href="https://react-native-reanimated-dnd.netlify.app/" target="_blank">
98
+ <img src="https://img.shields.io/badge/📖%20Documentation-Visit%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b" alt="Documentation" />
99
+ </a>
100
+
101
+ _Comprehensive guides, API reference, and interactive examples_
102
+
103
+ </div>
104
+
105
+ The example app includes:
106
+
107
+ - 🎵 **Sortable Music Queue** - Complete list reordering with handles
108
+ - 🎯 **Collision Detection** - Different algorithms in action
109
+ - 🎬 **Custom Animations** - Spring, timing, and easing variations
110
+ - 📦 **Boundary Constraints** - Axis-locked and bounded dragging
111
+ - ✨ **Visual Feedback** - Active styles and state management
112
+ - ⚙️ **Advanced Patterns** - Custom implementations and hooks
113
+
114
+ ## 🎬 Video Showcase
115
+
116
+ **See the library in action** with these demos showcasing some of the key features and use cases.
117
+
118
+ <div align="center">
119
+
120
+ <table>
121
+ <tr>
122
+ <td align="center" width="50%">
123
+
124
+ ### 📋 Sortable Lists
125
+
126
+ _Drag and drop to reorder items with smooth animations_
127
+
128
+ https://github.com/user-attachments/assets/1cd1929c-724b-4dda-a916-f3e69f917f7b
129
+
130
+ **Features:** Auto-scrolling • Drag handles • Smooth transitions
131
+
132
+ </td>
133
+ <td align="center" width="50%">
134
+
135
+ ### 🎯 Collision Detection
136
+
137
+ _Multiple algorithms for precise drop targeting_
138
+
139
+ https://github.com/user-attachments/assets/379040d7-8489-430b-bae4-3fcbde34264e
140
+
141
+ **Algorithms:** Center • Intersect • Contain
142
+
143
+ </td>
144
+ </tr>
145
+ <tr>
146
+ <td align="center" width="50%">
147
+
148
+ ### 🎪 Drag Handles
149
+
150
+ _Precise control with dedicated drag areas_
151
+
152
+ https://github.com/user-attachments/assets/ec051d5b-8ba0-41b7-86ae-379de26a97dd
153
+
154
+ **Features:** Touch-friendly • Visual feedback • Accessibility
155
+
156
+ </td>
157
+ <td align="center" width="50%">
158
+
159
+ ### 📦 Bounded Dragging
160
+
161
+ _Constrain movement within specific boundaries_
162
+
163
+ https://github.com/user-attachments/assets/7bd5045b-47c4-4d9b-a0c5-eb89122ec9c0
164
+
165
+ **Constraints:** Axis-locked • Container bounds • Custom limits
166
+
167
+ </td>
168
+ </tr>
169
+ <tr>
170
+ <td align="center" width="50%">
171
+
172
+ ### ✨ Active Drop Styles
173
+
174
+ _Visual feedback during drag operations_
175
+
176
+ https://github.com/user-attachments/assets/3b8a3d00-38ad-4532-bd42-173037ea61b9
177
+
178
+ **Feedback:** Hover states • Drop zones • Visual cues
179
+
180
+ </td>
181
+ <td align="center" width="50%">
182
+
183
+ ### 🔄 State Management
184
+
185
+ _Complete lifecycle tracking and callbacks_
186
+
187
+ https://github.com/user-attachments/assets/da5e526f-f2d2-4dc5-96b5-3fecc4faf57a
188
+
189
+ **States:** Idle • Dragging • Animating • Dropped
190
+
191
+ </td>
192
+ </tr>
193
+ </table>
194
+
195
+ </div>
196
+
197
+ ## 🚀 Installation
198
+
199
+ ```bash
200
+ npm install react-native-reanimated-dnd
201
+ ```
202
+
203
+ ### Peer Dependencies
204
+
205
+ ```bash
206
+ npm install react-native-reanimated react-native-gesture-handler
207
+ ```
208
+
209
+ Follow the setup guides:
210
+
211
+ - [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation)
212
+ - [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/installation)
213
+
214
+ ## 📋 Requirements
215
+
216
+ ### Data Structure
217
+
218
+ All items in your data array **MUST** have an `id` property of type string:
219
+
220
+ ```typescript
221
+ interface YourDataType {
222
+ id: string; // Required!
223
+ // ... your other properties
224
+ }
225
+ ```
226
+
227
+ This is essential for the library to track items during reordering.
228
+
229
+ **Example:**
230
+
231
+ ```typescript
232
+ // ✅ Good - Each item has a unique string id
233
+ const tasks = [
234
+ { id: "1", title: "Learn React Native", completed: false },
235
+ { id: "2", title: "Build an app", completed: false },
236
+ { id: "3", title: "Deploy to store", completed: true },
237
+ ];
238
+
239
+ // ❌ Bad - Missing id properties
240
+ const badTasks = [{ title: "Task 1" }, { title: "Task 2" }];
241
+
242
+ // ❌ Bad - Non-string ids
243
+ const badTasksWithNumbers = [
244
+ { id: 1, title: "Task 1" },
245
+ { id: 2, title: "Task 2" },
246
+ ];
247
+ ```
248
+
249
+ The library includes runtime validation in development mode that will warn you if items are missing valid ID properties.
250
+
251
+ ## State Management Guidelines
252
+
253
+ **IMPORTANT**: Sortable components maintain their own internal state for optimal performance and animation consistency.
254
+
255
+ ### Do NOT Do This
256
+
257
+ - Never update external state (arrays, Redux, Zustand, etc.) directly in `onMove` callbacks
258
+ - Never call `setItems()`, `setTasks()`, or similar functions during drag operations
259
+ - Never manually splice or reorder external arrays in response to drag events
260
+
261
+ ### Correct Approach
262
+
263
+ - Use `onMove` for logging, analytics, or side effects only
264
+ - Use `onDrop` with `allPositions` parameter for read-only position tracking
265
+ - Let sortable components handle their internal reordering automatically
266
+ - Use external state only for the initial data and for non-reordering updates
267
+
268
+ ### Future Features
269
+
270
+ Programmatic list operations (add, update, delete, reorder items) that work correctly with internal state management will be added in upcoming releases. This will provide safe methods to modify sortable lists externally.
271
+
272
+ ## 🏃‍♂️ Quick Start
273
+
274
+ ### Basic Draggable
275
+
276
+ ```tsx
277
+ import React from "react";
278
+ import { View, Text, StyleSheet } from "react-native";
279
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
280
+ import { Draggable, DropProvider } from "react-native-reanimated-dnd";
281
+
282
+ export default function App() {
283
+ return (
284
+ <GestureHandlerRootView style={styles.container}>
285
+ <DropProvider>
286
+ <View style={styles.content}>
287
+ <Draggable data={{ id: "1", title: "Drag me!" }}>
288
+ <View style={styles.draggableItem}>
289
+ <Text style={styles.itemText}>🎯 Drag me around!</Text>
290
+ </View>
291
+ </Draggable>
292
+ </View>
293
+ </DropProvider>
294
+ </GestureHandlerRootView>
295
+ );
296
+ }
297
+
298
+ const styles = StyleSheet.create({
299
+ container: {
300
+ flex: 1,
301
+ backgroundColor: "#000000",
302
+ },
303
+ content: {
304
+ flex: 1,
305
+ padding: 20,
306
+ justifyContent: "center",
307
+ alignItems: "center",
308
+ },
309
+ draggableItem: {
310
+ padding: 20,
311
+ backgroundColor: "#1C1C1E",
312
+ borderRadius: 12,
313
+ borderWidth: 1,
314
+ borderColor: "#3A3A3C",
315
+ shadowColor: "#000",
316
+ shadowOffset: { width: 0, height: 2 },
317
+ shadowOpacity: 0.25,
318
+ shadowRadius: 4,
319
+ elevation: 3,
320
+ },
321
+ itemText: {
322
+ color: "#FFFFFF",
323
+ fontSize: 16,
324
+ fontWeight: "600",
325
+ textAlign: "center",
326
+ },
327
+ });
328
+ ```
329
+
330
+ ### Drag & Drop with Multiple Zones
331
+
332
+ ```tsx
333
+ import React from "react";
334
+ import { Alert, StyleSheet, Text, View } from "react-native";
335
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
336
+ import {
337
+ Draggable,
338
+ Droppable,
339
+ DropProvider,
340
+ } from "react-native-reanimated-dnd";
341
+
342
+ export default function DragDropExample() {
343
+ const handleDrop = (data: any, zoneId: string) => {
344
+ Alert.alert("Item Dropped", `"${data.title}" dropped in ${zoneId}`);
345
+ };
346
+
347
+ return (
348
+ <GestureHandlerRootView style={styles.container}>
349
+ <DropProvider>
350
+ <View style={styles.content}>
351
+ {/* Drop Zones */}
352
+ <View style={styles.dropZonesSection}>
353
+ <Text style={styles.sectionTitle}>Drop Zones</Text>
354
+
355
+ <Droppable
356
+ onDrop={(data) => handleDrop(data, "Zone 1")}
357
+ activeStyle={styles.dropZoneActive}
358
+ style={styles.droppable}
359
+ >
360
+ <View style={[styles.dropZoneBlue, styles.dropZone]}>
361
+ <Text style={styles.dropZoneText}>🎯 Zone 1</Text>
362
+ <Text style={styles.dropZoneSubtext}>Drop here</Text>
363
+ </View>
364
+ </Droppable>
365
+
366
+ <Droppable
367
+ onDrop={(data) => handleDrop(data, "Zone 2")}
368
+ activeStyle={styles.dropZoneActive}
369
+ style={styles.droppable}
370
+ >
371
+ <View style={[styles.dropZone, styles.dropZoneGreen]}>
372
+ <Text style={styles.dropZoneText}>🎯 Zone 2</Text>
373
+ <Text style={styles.dropZoneSubtext}>Drop here</Text>
374
+ </View>
375
+ </Droppable>
376
+ </View>
377
+
378
+ {/* Draggable Item */}
379
+ <View style={styles.draggableSection}>
380
+ <Text style={styles.sectionTitle}>Draggable Item</Text>
381
+ <Draggable data={{ id: "1", title: "Task Item" }}>
382
+ <View style={styles.draggableItem}>
383
+ <Text style={styles.itemText}>📦 Drag me to a zone</Text>
384
+ </View>
385
+ </Draggable>
386
+ </View>
387
+ </View>
388
+ </DropProvider>
389
+ </GestureHandlerRootView>
390
+ );
391
+ }
392
+
393
+ const styles = StyleSheet.create({
394
+ container: {
395
+ flex: 1,
396
+ backgroundColor: "#000000",
397
+ },
398
+ content: {
399
+ flex: 1,
400
+ padding: 20,
401
+ justifyContent: "space-between",
402
+ },
403
+ sectionTitle: {
404
+ color: "#FFFFFF",
405
+ fontSize: 18,
406
+ fontWeight: "700",
407
+ marginBottom: 20,
408
+ textAlign: "center",
409
+ },
410
+ draggableSection: {
411
+ alignItems: "center",
412
+ paddingVertical: 40,
413
+ },
414
+ draggableItem: {
415
+ padding: 20,
416
+ backgroundColor: "#1C1C1E",
417
+ borderRadius: 12,
418
+ borderWidth: 1,
419
+ borderColor: "#3A3A3C",
420
+ shadowColor: "#000",
421
+ shadowOffset: { width: 0, height: 2 },
422
+ shadowOpacity: 0.25,
423
+ shadowRadius: 4,
424
+ elevation: 3,
425
+ },
426
+ itemText: {
427
+ color: "#FFFFFF",
428
+ fontSize: 16,
429
+ fontWeight: "600",
430
+ textAlign: "center",
431
+ },
432
+ dropZonesSection: {
433
+ flex: 1,
434
+ paddingVertical: 40,
435
+ },
436
+ droppable: {
437
+ marginBottom: 20,
438
+ overflow: "hidden",
439
+ borderRadius: 16,
440
+ },
441
+ dropZone: {
442
+ height: 140,
443
+ borderWidth: 2,
444
+ borderStyle: "dashed",
445
+ borderRadius: 16,
446
+ justifyContent: "center",
447
+ alignItems: "center",
448
+ padding: 20,
449
+ },
450
+ dropZoneBlue: {
451
+ borderColor: "#58a6ff",
452
+ backgroundColor: "rgba(88, 166, 255, 0.08)",
453
+ },
454
+ dropZoneGreen: {
455
+ borderColor: "#3fb950",
456
+ backgroundColor: "rgba(63, 185, 80, 0.08)",
457
+ },
458
+ dropZoneActive: {
459
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
460
+ borderStyle: "solid",
461
+ transform: [{ scale: 1.02 }],
462
+ },
463
+ dropZoneText: {
464
+ color: "#FFFFFF",
465
+ fontSize: 18,
466
+ fontWeight: "600",
467
+ textAlign: "center",
468
+ marginBottom: 8,
469
+ },
470
+ dropZoneSubtext: {
471
+ color: "#8E8E93",
472
+ fontSize: 14,
473
+ textAlign: "center",
474
+ },
475
+ });
476
+ ```
477
+
478
+ ### Vertical Sortable List
479
+
480
+ ```tsx
481
+ import React, { useCallback, useState } from "react";
482
+ import { StyleSheet, Text, View } from "react-native";
483
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
484
+ import {
485
+ Sortable,
486
+ SortableItem,
487
+ SortableRenderItemProps,
488
+ } from "react-native-reanimated-dnd";
489
+
490
+ interface Task {
491
+ id: string;
492
+ title: string;
493
+ completed: boolean;
494
+ }
495
+
496
+ export default function SortableExample() {
497
+ const [tasks, setTasks] = useState<Task[]>([
498
+ { id: "1", title: "Learn React Native", completed: false },
499
+ { id: "2", title: "Build an app", completed: false },
500
+ { id: "3", title: "Deploy to store", completed: true },
501
+ { id: "4", title: "Celebrate success", completed: false },
502
+ ]);
503
+
504
+ const renderTask = useCallback(
505
+ (props: SortableRenderItemProps<Task>) => {
506
+ const {
507
+ item,
508
+ id,
509
+ positions,
510
+ lowerBound,
511
+ autoScrollDirection,
512
+ itemsCount,
513
+ itemHeight,
514
+ } = props;
515
+ return (
516
+ <SortableItem
517
+ key={id}
518
+ data={item}
519
+ id={id}
520
+ positions={positions}
521
+ lowerBound={lowerBound}
522
+ autoScrollDirection={autoScrollDirection}
523
+ itemsCount={itemsCount}
524
+ itemHeight={itemHeight}
525
+ onMove={(itemId, from, to) => {
526
+ console.log(`Task ${itemId} moved from ${from} to ${to}`);
527
+ // Only log - do NOT update state here
528
+ }}
529
+ onDrop={(itemId, position, allPositions) => {
530
+ if (allPositions) {
531
+ console.log("All positions:", allPositions);
532
+ // Use for tracking, analytics, etc. - NOT for reordering state
533
+ }
534
+ }}
535
+ style={styles.taskItem}
536
+ >
537
+ <View style={styles.taskContent}>
538
+ <View style={styles.taskInfo}>
539
+ <Text style={styles.taskTitle}>{item.title}</Text>
540
+ <Text style={styles.taskStatus}>
541
+ {item.completed ? "✅ Completed" : "⏳ Pending"}
542
+ </Text>
543
+ </View>
544
+
545
+ {/* Drag Handle */}
546
+ <SortableItem.Handle style={styles.dragHandle}>
547
+ <View style={styles.dragIconContainer}>
548
+ <View style={styles.dragColumn}>
549
+ <View style={styles.dragDot} />
550
+ <View style={styles.dragDot} />
551
+ <View style={styles.dragDot} />
552
+ </View>
553
+ <View style={styles.dragColumn}>
554
+ <View style={styles.dragDot} />
555
+ <View style={styles.dragDot} />
556
+ <View style={styles.dragDot} />
557
+ </View>
558
+ </View>
559
+ </SortableItem.Handle>
560
+ </View>
561
+ </SortableItem>
562
+ );
563
+ },
564
+ [tasks]
565
+ );
566
+
567
+ return (
568
+ <GestureHandlerRootView style={styles.container}>
569
+ <View style={styles.header}>
570
+ <Text style={styles.headerTitle}>📋 My Tasks</Text>
571
+ <Text style={styles.headerSubtitle}>Drag to reorder</Text>
572
+ </View>
573
+
574
+ <Sortable
575
+ data={tasks}
576
+ renderItem={renderTask}
577
+ itemHeight={80}
578
+ style={styles.list}
579
+ />
580
+ </GestureHandlerRootView>
581
+ );
582
+ }
583
+
584
+ const styles = StyleSheet.create({
585
+ container: {
586
+ flex: 1,
587
+ backgroundColor: "#000000",
588
+ },
589
+ header: {
590
+ padding: 20,
591
+ paddingBottom: 16,
592
+ borderBottomWidth: 1,
593
+ borderBottomColor: "#2C2C2E",
594
+ },
595
+ headerTitle: {
596
+ color: "#FFFFFF",
597
+ fontSize: 24,
598
+ fontWeight: "700",
599
+ marginBottom: 4,
600
+ },
601
+ headerSubtitle: {
602
+ color: "#8E8E93",
603
+ fontSize: 14,
604
+ },
605
+ list: {
606
+ flex: 1,
607
+ backgroundColor: "#000000",
608
+ marginTop: 20,
609
+ paddingHorizontal: 20,
610
+ borderRadius: 20,
611
+ overflow: "hidden",
612
+ },
613
+ taskItem: {
614
+ height: 80,
615
+
616
+ backgroundColor: "transparent",
617
+ },
618
+ taskContent: {
619
+ flex: 1,
620
+ flexDirection: "row",
621
+ alignItems: "center",
622
+ paddingHorizontal: 20,
623
+ backgroundColor: "#1C1C1E",
624
+
625
+ borderWidth: 1,
626
+ borderColor: "#3A3A3C",
627
+ },
628
+ taskInfo: {
629
+ flex: 1,
630
+ paddingRight: 16,
631
+ },
632
+ taskTitle: {
633
+ color: "#FFFFFF",
634
+ fontSize: 16,
635
+ fontWeight: "600",
636
+ marginBottom: 4,
637
+ },
638
+ taskStatus: {
639
+ color: "#8E8E93",
640
+ fontSize: 14,
641
+ },
642
+ dragHandle: {
643
+ padding: 12,
644
+ borderRadius: 8,
645
+ backgroundColor: "rgba(255, 255, 255, 0.05)",
646
+ },
647
+ dragIconContainer: {
648
+ flexDirection: "row",
649
+ alignItems: "center",
650
+ gap: 3,
651
+ },
652
+ dragColumn: {
653
+ flexDirection: "column",
654
+ gap: 2,
655
+ },
656
+ dragDot: {
657
+ width: 3,
658
+ height: 3,
659
+ borderRadius: 1.5,
660
+ backgroundColor: "#6D6D70",
661
+ },
662
+ });
663
+ ```
664
+
665
+ ### Horizontal Sortable List
666
+
667
+ ```tsx
668
+ import React, { useState } from "react";
669
+ import { StyleSheet, Text, View } from "react-native";
670
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
671
+ import {
672
+ Sortable,
673
+ SortableItem,
674
+ SortableRenderItemProps,
675
+ SortableDirection,
676
+ } from "react-native-reanimated-dnd";
677
+
678
+ interface Tag {
679
+ id: string;
680
+ label: string;
681
+ color: string;
682
+ }
683
+
684
+ export default function HorizontalSortableExample() {
685
+ const [tags, setTags] = useState<Tag[]>([
686
+ { id: "1", label: "React", color: "#61dafb" },
687
+ { id: "2", label: "TypeScript", color: "#3178c6" },
688
+ { id: "3", label: "React Native", color: "#0fa5e9" },
689
+ { id: "4", label: "JavaScript", color: "#f7df1e" },
690
+ { id: "5", label: "Node.js", color: "#339933" },
691
+ ]);
692
+
693
+ const renderTag = (props: SortableRenderItemProps<Tag>) => {
694
+ const {
695
+ item,
696
+ id,
697
+ positions,
698
+ leftBound,
699
+ autoScrollHorizontalDirection,
700
+ itemsCount,
701
+ itemWidth,
702
+ gap,
703
+ paddingHorizontal,
704
+ } = props;
705
+
706
+ return (
707
+ <SortableItem
708
+ key={id}
709
+ data={item}
710
+ id={id}
711
+ positions={positions}
712
+ leftBound={leftBound}
713
+ autoScrollHorizontalDirection={autoScrollHorizontalDirection}
714
+ itemsCount={itemsCount}
715
+ direction={SortableDirection.Horizontal}
716
+ itemWidth={itemWidth}
717
+ gap={gap}
718
+ paddingHorizontal={paddingHorizontal}
719
+ onMove={(itemId, from, to) => {
720
+ console.log(`Tag ${itemId} moved from ${from} to ${to}`);
721
+ // Only log - do NOT update state here
722
+ }}
723
+ onDrop={(itemId, position, allPositions) => {
724
+ if (allPositions) {
725
+ console.log("All positions:", allPositions);
726
+ // Use for tracking, analytics, etc. - NOT for reordering state
727
+ }
728
+ }}
729
+ style={styles.tagItem}
730
+ >
731
+ <View style={[styles.tagContent, { backgroundColor: item.color }]}>
732
+ <Text style={styles.tagText}>{item.label}</Text>
733
+ </View>
734
+ </SortableItem>
735
+ );
736
+ };
737
+
738
+ return (
739
+ <GestureHandlerRootView style={styles.container}>
740
+ <View style={styles.header}>
741
+ <Text style={styles.headerTitle}>🏷️ Tech Tags</Text>
742
+ <Text style={styles.headerSubtitle}>Drag horizontally to reorder</Text>
743
+ </View>
744
+
745
+ <Sortable
746
+ data={tags}
747
+ renderItem={renderTag}
748
+ direction={SortableDirection.Horizontal}
749
+ itemWidth={120}
750
+ gap={12}
751
+ paddingHorizontal={20}
752
+ style={styles.horizontalList}
753
+ />
754
+ </GestureHandlerRootView>
755
+ );
756
+ }
757
+
758
+ const styles = StyleSheet.create({
759
+ container: {
760
+ flex: 1,
761
+ backgroundColor: "#000000",
762
+ },
763
+ header: {
764
+ padding: 20,
765
+ paddingBottom: 16,
766
+ },
767
+ headerTitle: {
768
+ color: "#FFFFFF",
769
+ fontSize: 24,
770
+ fontWeight: "700",
771
+ marginBottom: 4,
772
+ },
773
+ headerSubtitle: {
774
+ color: "#8E8E93",
775
+ fontSize: 14,
776
+ },
777
+ horizontalList: {
778
+ height: 100,
779
+ marginTop: 20,
780
+ },
781
+ tagItem: {
782
+ width: 120,
783
+ height: 60,
784
+ },
785
+ tagContent: {
786
+ flex: 1,
787
+ borderRadius: 30,
788
+ justifyContent: "center",
789
+ alignItems: "center",
790
+ paddingHorizontal: 16,
791
+ },
792
+ tagText: {
793
+ color: "#FFFFFF",
794
+ fontSize: 14,
795
+ fontWeight: "600",
796
+ textAlign: "center",
797
+ },
798
+ });
799
+ ```
800
+
801
+ ## 📚 API Reference
802
+
803
+ ### Components
804
+
805
+ #### `<Draggable>`
806
+
807
+ Makes any component draggable with extensive customization options.
808
+
809
+ ```tsx
810
+ <Draggable
811
+ data={any} // Data associated with the item
812
+ onDragStart={(data) => void} // Called when dragging starts
813
+ onDragEnd={(data) => void} // Called when dragging ends
814
+ onDragging={(position) => void} // Called during dragging
815
+ onStateChange={(state) => void} // Called on state changes
816
+ dragDisabled={boolean} // Disable dragging
817
+ collisionAlgorithm="center|intersect|contain" // Collision detection method
818
+ dragAxis="x|y|both" // Constrain movement axis
819
+ dragBoundsRef={RefObject} // Boundary container reference
820
+ animationFunction={(toValue) => Animation} // Custom animation function
821
+ style={StyleProp<ViewStyle>} // Component styling
822
+ >
823
+ {children}
824
+ </Draggable>
825
+ ```
826
+
827
+ #### `<Droppable>`
828
+
829
+ Creates drop zones with visual feedback and capacity management.
830
+
831
+ ```tsx
832
+ <Droppable
833
+ onDrop={(data) => void} // Called when item is dropped
834
+ onActiveChange={(isActive) => void} // Called on hover state change
835
+ dropDisabled={boolean} // Disable drop functionality
836
+ dropAlignment="top-left|center|bottom-right|..." // Drop positioning
837
+ dropOffset={{ x: number, y: number }} // Position offset
838
+ activeStyle={StyleProp<ViewStyle>} // Style when active
839
+ capacity={number} // Maximum items allowed
840
+ droppableId={string} // Unique identifier
841
+ >
842
+ {children}
843
+ </Droppable>
844
+ ```
845
+
846
+ #### `<Sortable>`
847
+
848
+ High-level component for sortable lists with auto-scrolling. Supports both vertical and horizontal directions.
849
+
850
+ ```tsx
851
+ <Sortable
852
+ data={Array<{ id: string }>} // Array of items to render
853
+ renderItem={(props) => ReactNode} // Render function for items
854
+ direction={SortableDirection} // "vertical" | "horizontal" (default: vertical)
855
+ itemHeight={number} // Height of each item (required for vertical)
856
+ itemWidth={number} // Width of each item (required for horizontal)
857
+ gap={number} // Gap between items (horizontal only)
858
+ paddingHorizontal={number} // Horizontal padding (horizontal only)
859
+ itemKeyExtractor={(item) => string} // Custom key extractor
860
+ useFlatList={boolean} // Use FlatList for performance (default: true)
861
+ style={StyleProp<ViewStyle>} // List container style
862
+ contentContainerStyle={StyleProp<ViewStyle>} // Content container style
863
+ />
864
+ ```
865
+
866
+ #### `<SortableItem>`
867
+
868
+ Individual item within a sortable list with gesture handling.
869
+
870
+ ```tsx
871
+ <SortableItem
872
+ id={string} // Unique identifier
873
+ data={any} // Item data
874
+ positions={SharedValue} // Position tracking
875
+ onMove={(id, from, to) => void} // Called when item moves
876
+ onDragStart={(id, position) => void} // Called when dragging starts
877
+ onDrop={(id, position, allPositions?) => void} // Called when item is dropped
878
+ onDragging={(id, overItemId, y) => void} // Called during dragging
879
+ style={StyleProp<ViewStyle>} // Item styling
880
+ animatedStyle={StyleProp<AnimatedStyle>} // Animated styling
881
+ >
882
+ {children}
883
+ </SortableItem>
884
+ ```
885
+
886
+ ### Hooks
887
+
888
+ #### `useDraggable(options)`
889
+
890
+ Core hook for implementing draggable functionality.
891
+
892
+ #### `useDroppable(options)`
893
+
894
+ Core hook for implementing droppable functionality.
895
+
896
+ #### `useSortable(options)`
897
+
898
+ Hook for individual sortable items with position management.
899
+
900
+ #### `useSortableList(options)`
901
+
902
+ Hook for managing entire vertical sortable lists with auto-scrolling.
903
+
904
+ #### `useHorizontalSortable(options)`
905
+
906
+ Core hook for implementing horizontal sortable functionality for individual items.
907
+
908
+ #### `useHorizontalSortableList(options)`
909
+
910
+ Hook for managing entire horizontal sortable lists with auto-scrolling.
911
+
912
+ ### Context
913
+
914
+ #### `<DropProvider>`
915
+
916
+ Required context provider that manages global drag-and-drop state.
917
+
918
+ ```tsx
919
+ <DropProvider>{/* All draggable and droppable components */}</DropProvider>
920
+ ```
921
+
922
+ ### Types & Enums
923
+
924
+ #### `DraggableState`
925
+
926
+ ```tsx
927
+ enum DraggableState {
928
+ IDLE = "idle",
929
+ DRAGGING = "dragging",
930
+ ANIMATING = "animating",
931
+ }
932
+ ```
933
+
934
+ #### `CollisionAlgorithm`
935
+
936
+ ```tsx
937
+ type CollisionAlgorithm = "center" | "intersect" | "contain";
938
+ ```
939
+
940
+ #### `DropAlignment`
941
+
942
+ ```tsx
943
+ type DropAlignment =
944
+ | "top-left"
945
+ | "top-center"
946
+ | "top-right"
947
+ | "center-left"
948
+ | "center"
949
+ | "center-right"
950
+ | "bottom-left"
951
+ | "bottom-center"
952
+ | "bottom-right";
953
+ ```
954
+
955
+ #### `SortableDirection`
956
+
957
+ ```tsx
958
+ enum SortableDirection {
959
+ Vertical = "vertical",
960
+ Horizontal = "horizontal",
961
+ }
962
+ ```
963
+
964
+ #### `HorizontalScrollDirection`
965
+
966
+ ```tsx
967
+ enum HorizontalScrollDirection {
968
+ None = "none",
969
+ Left = "left",
970
+ Right = "right",
971
+ }
972
+ ```
973
+
974
+ ## 🎨 Advanced Usage
975
+
976
+ ### Custom Animations
977
+
978
+ ```tsx
979
+ import { withTiming, withSpring, Easing } from "react-native-reanimated";
980
+
981
+ // Smooth timing animation
982
+ const smoothAnimation = (toValue) => {
983
+ "worklet";
984
+ return withTiming(toValue, {
985
+ duration: 300,
986
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
987
+ });
988
+ };
989
+
990
+ // Spring animation
991
+ const springAnimation = (toValue) => {
992
+ "worklet";
993
+ return withSpring(toValue, {
994
+ damping: 15,
995
+ stiffness: 150,
996
+ });
997
+ };
998
+
999
+ <Draggable animationFunction={springAnimation}>{/* content */}</Draggable>;
1000
+ ```
1001
+
1002
+ ### Enhanced onDrop Callback with Positions
1003
+
1004
+ The `onDrop` callback now includes an optional third parameter containing all item positions, making it easier to update your state with the new order:
1005
+
1006
+ ```tsx
1007
+ import { SortableItem } from "react-native-reanimated-dnd";
1008
+
1009
+ // Enhanced onDrop with allPositions parameter
1010
+ const handleDrop = (
1011
+ id: string,
1012
+ position: number,
1013
+ allPositions?: { [id: string]: number }
1014
+ ) => {
1015
+ console.log(`Item ${id} dropped at position ${position}`);
1016
+
1017
+ if (allPositions) {
1018
+ // allPositions contains all current item positions
1019
+ console.log("All positions:", allPositions);
1020
+
1021
+ // IMPORTANT: Use this ONLY for read-only tracking
1022
+ // DO NOT update your state arrays directly with this data
1023
+ // Use for: analytics, external state synchronization, logging
1024
+ }
1025
+ };
1026
+
1027
+ <SortableItem id={item.id} onDrop={handleDrop} {...otherProps}>
1028
+ {/* item content */}
1029
+ </SortableItem>;
1030
+ ```
1031
+
1032
+ **Backward Compatibility**: The `allPositions` parameter is optional, so existing code continues to work unchanged. The parameter provides additional position data for advanced use cases where you need complete visibility into all item positions.
1033
+
1034
+ **Important**: The `allPositions` parameter is for **read-only tracking only**. Do not use it to automatically update your external state arrays, as this will break the internal state management. Programmatic list operations (add, update, delete, reorder) will be added in future releases.
1035
+
1036
+ ### Collision Detection Strategies
1037
+
1038
+ ```tsx
1039
+ // Precise center-point collision
1040
+ <Draggable collisionAlgorithm="center">
1041
+ {/* Requires center point to be over drop zone */}
1042
+ </Draggable>
1043
+
1044
+ // Forgiving intersection collision (default)
1045
+ <Draggable collisionAlgorithm="intersect">
1046
+ {/* Any overlap triggers collision */}
1047
+ </Draggable>
1048
+
1049
+ // Strict containment collision
1050
+ <Draggable collisionAlgorithm="contain">
1051
+ {/* Entire draggable must be within drop zone */}
1052
+ </Draggable>
1053
+ ```
1054
+
1055
+ ### Drag Handles
1056
+
1057
+ ```tsx
1058
+ <SortableItem id={item.id} {...props}>
1059
+ <View style={styles.itemContainer}>
1060
+ <Text>{item.title}</Text>
1061
+
1062
+ {/* Only this handle area can initiate dragging */}
1063
+ <SortableItem.Handle style={styles.dragHandle}>
1064
+ <View style={styles.handleIcon}>
1065
+ <View style={styles.dot} />
1066
+ <View style={styles.dot} />
1067
+ <View style={styles.dot} />
1068
+ </View>
1069
+ </SortableItem.Handle>
1070
+ </View>
1071
+ </SortableItem>
1072
+ ```
1073
+
1074
+ ### Bounded Dragging
1075
+
1076
+ ```tsx
1077
+ const containerRef = useRef<View>(null);
1078
+
1079
+ <View ref={containerRef} style={styles.container}>
1080
+ <Draggable
1081
+ data={data}
1082
+ dragBoundsRef={containerRef}
1083
+ dragAxis="x" // Constrain to horizontal movement
1084
+ >
1085
+ {/* content */}
1086
+ </Draggable>
1087
+ </View>;
1088
+ ```
1089
+
1090
+ ### Drop Zone Capacity
1091
+
1092
+ ```tsx
1093
+ <Droppable
1094
+ capacity={3}
1095
+ onDrop={(data) => {
1096
+ if (currentItems.length < 3) {
1097
+ addItem(data);
1098
+ }
1099
+ }}
1100
+ activeStyle={{
1101
+ backgroundColor: currentItems.length < 3 ? "#e8f5e8" : "#ffe8e8",
1102
+ }}
1103
+ >
1104
+ <Text>Drop Zone ({currentItems.length}/3)</Text>
1105
+ </Droppable>
1106
+ ```
1107
+
1108
+ ## 🏃‍♂️ Running the Example App
1109
+
1110
+ 1. Clone the repository:
1111
+
1112
+ ```bash
1113
+ git clone https://github.com/entropyconquers/react-native-reanimated-dnd.git
1114
+ cd react-native-reanimated-dnd
1115
+ ```
1116
+
1117
+ 2. Install dependencies:
1118
+
1119
+ ```bash
1120
+ npm install
1121
+ cd example-app
1122
+ npm install
1123
+ ```
1124
+
1125
+ 3. Run the example app:
1126
+
1127
+ ```bash
1128
+ # iOS
1129
+ npx expo run:ios
1130
+
1131
+ # Android
1132
+ npx expo run:android
1133
+ ```
1134
+
1135
+ The example app includes all 15 interactive examples showcasing every feature of the library.
1136
+
1137
+ ## 🗺️ Project Roadmap
1138
+
1139
+ I am constantly working to improve React Native Reanimated DnD. Here's what's coming next:
1140
+
1141
+ ### 🎯 Next Release (v2.0.0)
1142
+
1143
+ **Focus: Enhanced Functionality & Bug Fixes**
1144
+
1145
+ - 🐛 **Bug Fixes & Issues Resolution**
1146
+ - Address existing reported issues
1147
+ - Performance optimizations
1148
+ - Gesture handling improvements
1149
+ - API Improvements
1150
+
1151
+ - 📐 **Sortable Grids**
1152
+ - 2D grid drag-and-drop support
1153
+ - Flexible grid layouts (2x2, 3x3, custom)
1154
+ - Smart auto-positioning and gap management
1155
+ - Responsive grid behavior
1156
+
1157
+ - ↔️ **Horizontal Sortable Lists**
1158
+ - Full horizontal scrolling support
1159
+ - Auto-scroll for out-of-view items
1160
+ - Customizable scroll behavior
1161
+
1162
+ - 🪆 **Nested Sortable Lists**
1163
+ - Multi-level hierarchy support
1164
+ - Collapse/expand functionality
1165
+ - Parent-child relationship management
1166
+ - Tree-like data structure handling
1167
+
1168
+ - 📋 **Kanban Board Support**
1169
+ - Cross-list dragging capabilities
1170
+ - Multiple column support
1171
+ - Inter-list item transfer
1172
+ - Board-level state management
1173
+
1174
+ ### 💡 Community Requests
1175
+
1176
+ Vote on features you'd like to see by raising an issue.
1177
+
1178
+ **Have an idea?** [Open a feature request](https://github.com/entropyconquers/react-native-reanimated-dnd/issues/new?assignees=&labels=enhancement&template=feature_request.md) and let me know!
1179
+
1180
+ ## 🤝 Contributing
1181
+
1182
+ Contributions are always welcome! We believe in building this library together with the community.
1183
+
1184
+ **Ways to contribute:**
1185
+
1186
+ - 🐛 Report bugs and issues
1187
+ - ✨ Suggest new features
1188
+ - 🔧 Submit pull requests
1189
+ - 📚 Improve documentation
1190
+ - 🧪 Write tests
1191
+ - 💬 Help others in discussions
1192
+
1193
+ Please see our [**Contributing Guide**](CONTRIBUTING.md) for detailed information on:
1194
+
1195
+ - Setting up the development environment
1196
+ - Code style guidelines
1197
+ - Pull request process
1198
+ - Testing requirements
1199
+ - Community guidelines
1200
+
1201
+ ## 📄 License
1202
+
1203
+ MIT © [Vishesh Raheja](https://github.com/entropyconquers)
1204
+
1205
+ ## 🙏 Acknowledgments
1206
+
1207
+ - Built with [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/) for smooth 60fps animations
1208
+ - Gesture handling powered by [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/)
1209
+ - Inspired by the React ecosystem's drag-and-drop libraries
1210
+ - Special thanks to the React Native community for feedback and contributions
1211
+
1212
+ ## ☕ Support the Project
1213
+
1214
+ <div align="center">
1215
+ <body>
1216
+ If this library has helped you build amazing apps, consider supporting its development!
1217
+ </br></br>
1218
+ <a href="https://www.buymeacoffee.com/entropyconquers" target="_blank">
1219
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me a Coffee" style="height: 60px; width: 217px;">
1220
+ </a>
1221
+ </br>
1222
+ </br>
1223
+ Your support helps maintain and improve this library for the entire React Native community! 🚀
1224
+ </body>
1225
+ </html>
1226
+
1227
+ </div>
1228
+ <br/>
1229
+
1230
+ ---
1231
+
1232
+ <div align="center">
1233
+
1234
+ **Made with ❤️ for the React Native community**
1235
+
1236
+ [⭐ Star on GitHub](https://github.com/entropyconquers/react-native-reanimated-dnd) • [📱 Try the Demo](https://github.com/entropyconquers/react-native-reanimated-dnd/tree/main/example-app) • [📖 Documentation](https://github.com/entropyconquers/react-native-reanimated-dnd#readme)
1237
+
1238
+ </div>