@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.
- package/LICENSE +21 -0
- package/README.md +1238 -0
- package/documentation/web-docs/README.md +41 -0
- package/example-app/.expo/README.md +8 -0
- package/example-app/README.md +515 -0
- package/lib/components/Draggable.d.ts +5 -0
- package/lib/components/Draggable.js +1 -0
- package/lib/components/Droppable.d.ts +3 -0
- package/lib/components/Droppable.js +1 -0
- package/lib/components/Sortable.d.ts +3 -0
- package/lib/components/Sortable.js +1 -0
- package/lib/components/SortableItem.d.ts +6 -0
- package/lib/components/SortableItem.js +1 -0
- package/lib/components/sortableUtils.d.ts +33 -0
- package/lib/components/sortableUtils.js +1 -0
- package/lib/context/DropContext.d.ts +3 -0
- package/lib/context/DropContext.js +1 -0
- package/lib/hooks/index.d.ts +6 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useDraggable.d.ts +2 -0
- package/lib/hooks/useDraggable.js +1 -0
- package/lib/hooks/useDroppable.d.ts +2 -0
- package/lib/hooks/useDroppable.js +1 -0
- package/lib/hooks/useHorizontalSortable.d.ts +2 -0
- package/lib/hooks/useHorizontalSortable.js +1 -0
- package/lib/hooks/useHorizontalSortableList.d.ts +4 -0
- package/lib/hooks/useHorizontalSortableList.js +1 -0
- package/lib/hooks/useSortable.d.ts +50 -0
- package/lib/hooks/useSortable.js +1 -0
- package/lib/hooks/useSortableList.d.ts +27 -0
- package/lib/hooks/useSortableList.js +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +1 -0
- package/lib/types/context.d.ts +67 -0
- package/lib/types/context.js +1 -0
- package/lib/types/draggable.d.ts +54 -0
- package/lib/types/draggable.js +1 -0
- package/lib/types/droppable.d.ts +26 -0
- package/lib/types/droppable.js +1 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.js +1 -0
- package/lib/types/sortable.d.ts +233 -0
- package/lib/types/sortable.js +1 -0
- 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
|
+
[](https://badge.fury.io/js/react-native-reanimated-dnd)
|
|
13
|
+
[](https://opensource.org/licenses/MIT)
|
|
14
|
+
[](https://www.typescriptlang.org/)
|
|
15
|
+
[](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>
|