react-native-reanimated-dnd 1.0.1

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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +633 -0
  3. package/lib/components/Draggable.d.ts +5 -0
  4. package/lib/components/Draggable.js +265 -0
  5. package/lib/components/Droppable.d.ts +264 -0
  6. package/lib/components/Droppable.js +284 -0
  7. package/lib/components/Sortable.d.ts +184 -0
  8. package/lib/components/Sortable.js +225 -0
  9. package/lib/components/SortableItem.d.ts +158 -0
  10. package/lib/components/SortableItem.js +251 -0
  11. package/lib/components/sortableUtils.d.ts +21 -0
  12. package/lib/components/sortableUtils.js +50 -0
  13. package/lib/context/DropContext.d.ts +118 -0
  14. package/lib/context/DropContext.js +233 -0
  15. package/lib/hooks/index.d.ts +4 -0
  16. package/lib/hooks/index.js +5 -0
  17. package/lib/hooks/useDraggable.d.ts +101 -0
  18. package/lib/hooks/useDraggable.js +567 -0
  19. package/lib/hooks/useDroppable.d.ts +129 -0
  20. package/lib/hooks/useDroppable.js +261 -0
  21. package/lib/hooks/useSortable.d.ts +174 -0
  22. package/lib/hooks/useSortable.js +361 -0
  23. package/lib/hooks/useSortableList.d.ts +182 -0
  24. package/lib/hooks/useSortableList.js +211 -0
  25. package/lib/index.d.ts +11 -0
  26. package/lib/index.js +16 -0
  27. package/lib/types/context.d.ts +166 -0
  28. package/lib/types/context.js +80 -0
  29. package/lib/types/draggable.d.ts +313 -0
  30. package/lib/types/draggable.js +31 -0
  31. package/lib/types/droppable.d.ts +197 -0
  32. package/lib/types/droppable.js +1 -0
  33. package/lib/types/index.d.ts +4 -0
  34. package/lib/types/index.js +8 -0
  35. package/lib/types/sortable.d.ts +432 -0
  36. package/lib/types/sortable.js +6 -0
  37. package/package.json +59 -0
@@ -0,0 +1,284 @@
1
+ // Node Modules
2
+ import React from "react";
3
+ import Animated from "react-native-reanimated";
4
+ import { useDroppable } from "../hooks/useDroppable";
5
+ let _nextDroppableId = 1;
6
+ export const _getUniqueDroppableId = () => {
7
+ return _nextDroppableId++;
8
+ };
9
+ /**
10
+ * A component that creates drop zones for receiving draggable items.
11
+ *
12
+ * The Droppable component provides visual feedback when draggable items hover over it
13
+ * and handles the drop logic when items are released. It integrates seamlessly with
14
+ * the drag-and-drop context to provide collision detection and proper positioning
15
+ * of dropped items.
16
+ *
17
+ * @template TData - The type of data that can be dropped on this droppable
18
+ * @param props - Configuration props for the droppable component
19
+ *
20
+ * @example
21
+ * Basic drop zone:
22
+ * ```typescript
23
+ * import { Droppable } from './components/Droppable';
24
+ *
25
+ * function BasicDropZone() {
26
+ * const handleDrop = (data) => {
27
+ * console.log('Item dropped:', data);
28
+ * // Handle the dropped item
29
+ * addItemToList(data);
30
+ * };
31
+ *
32
+ * return (
33
+ * <Droppable onDrop={handleDrop}>
34
+ * <View style={styles.dropZone}>
35
+ * <Text>Drop items here</Text>
36
+ * </View>
37
+ * </Droppable>
38
+ * );
39
+ * }
40
+ * ```
41
+ *
42
+ * @example
43
+ * Drop zone with visual feedback:
44
+ * ```typescript
45
+ * function VisualDropZone() {
46
+ * const [isHovered, setIsHovered] = useState(false);
47
+ *
48
+ * return (
49
+ * <Droppable
50
+ * onDrop={(data) => {
51
+ * console.log('Dropped:', data.name);
52
+ * processDroppedItem(data);
53
+ * }}
54
+ * onActiveChange={setIsHovered}
55
+ * activeStyle={{
56
+ * backgroundColor: 'rgba(0, 255, 0, 0.2)',
57
+ * borderColor: '#00ff00',
58
+ * borderWidth: 2,
59
+ * transform: [{ scale: 1.05 }]
60
+ * }}
61
+ * style={styles.dropZone}
62
+ * >
63
+ * <View style={[
64
+ * styles.dropContent,
65
+ * isHovered && styles.hoveredContent
66
+ * ]}>
67
+ * <Icon
68
+ * name="cloud-upload"
69
+ * size={32}
70
+ * color={isHovered ? '#00ff00' : '#666'}
71
+ * />
72
+ * <Text style={styles.dropText}>
73
+ * {isHovered ? 'Release to drop' : 'Drag files here'}
74
+ * </Text>
75
+ * </View>
76
+ * </Droppable>
77
+ * );
78
+ * }
79
+ * ```
80
+ *
81
+ * @example
82
+ * Drop zone with custom alignment and capacity:
83
+ * ```typescript
84
+ * function TaskColumn() {
85
+ * const [tasks, setTasks] = useState([]);
86
+ * const maxTasks = 5;
87
+ *
88
+ * return (
89
+ * <Droppable
90
+ * droppableId="todo-column"
91
+ * onDrop={(task) => {
92
+ * if (tasks.length < maxTasks) {
93
+ * setTasks(prev => [...prev, task]);
94
+ * updateTaskStatus(task.id, 'todo');
95
+ * }
96
+ * }}
97
+ * dropAlignment="top-center"
98
+ * dropOffset={{ x: 0, y: 10 }}
99
+ * capacity={maxTasks}
100
+ * activeStyle={{
101
+ * backgroundColor: 'rgba(59, 130, 246, 0.1)',
102
+ * borderColor: '#3b82f6',
103
+ * borderWidth: 2,
104
+ * borderStyle: 'dashed'
105
+ * }}
106
+ * style={styles.column}
107
+ * >
108
+ * <Text style={styles.columnTitle}>
109
+ * To Do ({tasks.length}/{maxTasks})
110
+ * </Text>
111
+ *
112
+ * {tasks.map(task => (
113
+ * <TaskCard key={task.id} task={task} />
114
+ * ))}
115
+ *
116
+ * {tasks.length === 0 && (
117
+ * <Text style={styles.emptyText}>
118
+ * Drop tasks here
119
+ * </Text>
120
+ * )}
121
+ * </Droppable>
122
+ * );
123
+ * }
124
+ * ```
125
+ *
126
+ * @example
127
+ * Conditional drop zone with validation:
128
+ * ```typescript
129
+ * function RestrictedDropZone() {
130
+ * const [canAcceptFiles, setCanAcceptFiles] = useState(true);
131
+ * const [uploadProgress, setUploadProgress] = useState(0);
132
+ *
133
+ * const handleDrop = (fileData) => {
134
+ * // Validate file type and size
135
+ * if (fileData.type !== 'image') {
136
+ * showError('Only image files are allowed');
137
+ * return;
138
+ * }
139
+ *
140
+ * if (fileData.size > 5000000) { // 5MB limit
141
+ * showError('File size must be under 5MB');
142
+ * return;
143
+ * }
144
+ *
145
+ * // Start upload
146
+ * setCanAcceptFiles(false);
147
+ * uploadFile(fileData, setUploadProgress)
148
+ * .then(() => {
149
+ * showSuccess('File uploaded successfully');
150
+ * setCanAcceptFiles(true);
151
+ * setUploadProgress(0);
152
+ * })
153
+ * .catch(() => {
154
+ * showError('Upload failed');
155
+ * setCanAcceptFiles(true);
156
+ * setUploadProgress(0);
157
+ * });
158
+ * };
159
+ *
160
+ * return (
161
+ * <Droppable
162
+ * onDrop={handleDrop}
163
+ * dropDisabled={!canAcceptFiles}
164
+ * onActiveChange={(active) => {
165
+ * if (active && !canAcceptFiles) {
166
+ * showTooltip('Upload in progress...');
167
+ * }
168
+ * }}
169
+ * activeStyle={{
170
+ * backgroundColor: canAcceptFiles
171
+ * ? 'rgba(34, 197, 94, 0.1)'
172
+ * : 'rgba(239, 68, 68, 0.1)',
173
+ * borderColor: canAcceptFiles ? '#22c55e' : '#ef4444'
174
+ * }}
175
+ * style={[
176
+ * styles.uploadZone,
177
+ * !canAcceptFiles && styles.disabled
178
+ * ]}
179
+ * >
180
+ * <View style={styles.uploadContent}>
181
+ * {uploadProgress > 0 ? (
182
+ * <>
183
+ * <ProgressBar progress={uploadProgress} />
184
+ * <Text>Uploading... {Math.round(uploadProgress * 100)}%</Text>
185
+ * </>
186
+ * ) : (
187
+ * <>
188
+ * <Icon
189
+ * name="image"
190
+ * size={48}
191
+ * color={canAcceptFiles ? '#22c55e' : '#ef4444'}
192
+ * />
193
+ * <Text style={styles.uploadText}>
194
+ * {canAcceptFiles
195
+ * ? 'Drop images here (max 5MB)'
196
+ * : 'Upload in progress...'}
197
+ * </Text>
198
+ * </>
199
+ * )}
200
+ * </View>
201
+ * </Droppable>
202
+ * );
203
+ * }
204
+ * ```
205
+ *
206
+ * @example
207
+ * Multiple drop zones with different behaviors:
208
+ * ```typescript
209
+ * function MultiDropZoneExample() {
210
+ * const [items, setItems] = useState([]);
211
+ * const [trash, setTrash] = useState([]);
212
+ *
213
+ * return (
214
+ * <DropProvider>
215
+ * <View style={styles.container}>
216
+ * {/* Source items *\/}
217
+ * {items.map(item => (
218
+ * <Draggable key={item.id} data={item}>
219
+ * <ItemCard item={item} />
220
+ * </Draggable>
221
+ * ))}
222
+ *
223
+ * {/* Archive drop zone *\/}
224
+ * <Droppable
225
+ * droppableId="archive"
226
+ * onDrop={(item) => {
227
+ * archiveItem(item.id);
228
+ * setItems(prev => prev.filter(i => i.id !== item.id));
229
+ * }}
230
+ * dropAlignment="center"
231
+ * activeStyle={styles.archiveActive}
232
+ * >
233
+ * <View style={styles.archiveZone}>
234
+ * <Icon name="archive" size={24} />
235
+ * <Text>Archive</Text>
236
+ * </View>
237
+ * </Droppable>
238
+ *
239
+ * {/* Trash drop zone *\/}
240
+ * <Droppable
241
+ * droppableId="trash"
242
+ * onDrop={(item) => {
243
+ * setTrash(prev => [...prev, item]);
244
+ * setItems(prev => prev.filter(i => i.id !== item.id));
245
+ * }}
246
+ * dropAlignment="center"
247
+ * activeStyle={styles.trashActive}
248
+ * capacity={10} // Max 10 items in trash
249
+ * >
250
+ * <View style={styles.trashZone}>
251
+ * <Icon name="trash" size={24} />
252
+ * <Text>Trash ({trash.length}/10)</Text>
253
+ * </View>
254
+ * </Droppable>
255
+ * </View>
256
+ * </DropProvider>
257
+ * );
258
+ * }
259
+ * ```
260
+ *
261
+ * @see {@link useDroppable} for the underlying hook
262
+ * @see {@link Draggable} for draggable components
263
+ * @see {@link DropAlignment} for alignment options
264
+ * @see {@link DropOffset} for offset configuration
265
+ * @see {@link UseDroppableOptions} for configuration options
266
+ * @see {@link UseDroppableReturn} for hook return details
267
+ * @see {@link DropProvider} for drag-and-drop context setup
268
+ */
269
+ export const Droppable = ({ onDrop, dropDisabled, onActiveChange, dropAlignment, dropOffset, activeStyle, droppableId, capacity, style, children, }) => {
270
+ const { viewProps, animatedViewRef } = useDroppable({
271
+ onDrop,
272
+ dropDisabled,
273
+ onActiveChange,
274
+ dropAlignment,
275
+ dropOffset,
276
+ activeStyle,
277
+ droppableId,
278
+ capacity,
279
+ });
280
+ // The style is now fully handled in the hook and returned via viewProps.style
281
+ return (<Animated.View ref={animatedViewRef} {...viewProps} style={[style, viewProps.style]} collapsable={false}>
282
+ {children}
283
+ </Animated.View>);
284
+ };
@@ -0,0 +1,184 @@
1
+ import React from "react";
2
+ import { SortableProps } from "../types/sortable";
3
+ /**
4
+ * A high-level component for creating sortable lists with smooth reordering animations.
5
+ *
6
+ * The Sortable component provides a complete solution for sortable lists, handling
7
+ * all the complex state management, gesture handling, and animations internally.
8
+ * It renders a scrollable list where items can be dragged to reorder them with
9
+ * smooth animations and auto-scrolling support.
10
+ *
11
+ * @template TData - The type of data items in the list (must extend `{ id: string }`)
12
+ * @param props - Configuration props for the sortable list
13
+ *
14
+ * @example
15
+ * Basic sortable list:
16
+ * ```typescript
17
+ * import { Sortable } from './components/Sortable';
18
+ *
19
+ * interface Task {
20
+ * id: string;
21
+ * title: string;
22
+ * completed: boolean;
23
+ * }
24
+ *
25
+ * function TaskList() {
26
+ * const [tasks, setTasks] = useState<Task[]>([
27
+ * { id: '1', title: 'Learn React Native', completed: false },
28
+ * { id: '2', title: 'Build an app', completed: false },
29
+ * { id: '3', title: 'Deploy to store', completed: false }
30
+ * ]);
31
+ *
32
+ * const renderTask = ({ item, id, positions, ...props }) => (
33
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
34
+ * <View style={styles.taskItem}>
35
+ * <Text>{item.title}</Text>
36
+ * <Text>{item.completed ? '✓' : '○'}</Text>
37
+ * </View>
38
+ * </SortableItem>
39
+ * );
40
+ *
41
+ * return (
42
+ * <Sortable
43
+ * data={tasks}
44
+ * renderItem={renderTask}
45
+ * itemHeight={60}
46
+ * style={styles.list}
47
+ * />
48
+ * );
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * Sortable list with custom styling and callbacks:
54
+ * ```typescript
55
+ * function AdvancedTaskList() {
56
+ * const [tasks, setTasks] = useState(initialTasks);
57
+ *
58
+ * const renderTask = ({ item, id, positions, ...props }) => (
59
+ * <SortableItem
60
+ * key={id}
61
+ * id={id}
62
+ * positions={positions}
63
+ * {...props}
64
+ * onMove={(itemId, from, to) => {
65
+ * // Update data when items are reordered
66
+ * const newTasks = [...tasks];
67
+ * const [movedTask] = newTasks.splice(from, 1);
68
+ * newTasks.splice(to, 0, movedTask);
69
+ * setTasks(newTasks);
70
+ *
71
+ * // Analytics
72
+ * analytics.track('task_reordered', { taskId: itemId, from, to });
73
+ * }}
74
+ * onDragStart={(itemId) => {
75
+ * hapticFeedback();
76
+ * setDraggingTask(itemId);
77
+ * }}
78
+ * onDrop={(itemId) => {
79
+ * setDraggingTask(null);
80
+ * }}
81
+ * >
82
+ * <Animated.View style={[styles.taskItem, item.priority === 'high' && styles.highPriority]}>
83
+ * <Text style={styles.taskTitle}>{item.title}</Text>
84
+ * <Text style={styles.taskDue}>{item.dueDate}</Text>
85
+ * <View style={styles.dragHandle}>
86
+ * <Icon name="drag-handle" size={20} color="#666" />
87
+ * </View>
88
+ * </Animated.View>
89
+ * </SortableItem>
90
+ * );
91
+ *
92
+ * return (
93
+ * <View style={styles.container}>
94
+ * <Text style={styles.header}>My Tasks ({tasks.length})</Text>
95
+ * <Sortable
96
+ * data={tasks}
97
+ * renderItem={renderTask}
98
+ * itemHeight={80}
99
+ * style={styles.sortableList}
100
+ * contentContainerStyle={styles.listContent}
101
+ * />
102
+ * </View>
103
+ * );
104
+ * }
105
+ * ```
106
+ *
107
+ * @example
108
+ * Sortable list with drag handles:
109
+ * ```typescript
110
+ * function SortableWithHandles() {
111
+ * const [items, setItems] = useState(data);
112
+ *
113
+ * const renderItem = ({ item, id, positions, ...props }) => (
114
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
115
+ * <View style={styles.itemContainer}>
116
+ * <View style={styles.itemContent}>
117
+ * <Text style={styles.itemTitle}>{item.title}</Text>
118
+ * <Text style={styles.itemSubtitle}>{item.subtitle}</Text>
119
+ * </View>
120
+ *
121
+ * {/* Only this handle area can initiate dragging *\/}
122
+ * <SortableItem.Handle style={styles.dragHandle}>
123
+ * <View style={styles.handleIcon}>
124
+ * <View style={styles.handleDot} />
125
+ * <View style={styles.handleDot} />
126
+ * <View style={styles.handleDot} />
127
+ * </View>
128
+ * </SortableItem.Handle>
129
+ * </View>
130
+ * </SortableItem>
131
+ * );
132
+ *
133
+ * return (
134
+ * <Sortable
135
+ * data={items}
136
+ * renderItem={renderItem}
137
+ * itemHeight={70}
138
+ * />
139
+ * );
140
+ * }
141
+ * ```
142
+ *
143
+ * @example
144
+ * Sortable list with custom key extractor:
145
+ * ```typescript
146
+ * interface CustomItem {
147
+ * uuid: string;
148
+ * name: string;
149
+ * order: number;
150
+ * }
151
+ *
152
+ * function CustomSortableList() {
153
+ * const [items, setItems] = useState<CustomItem[]>(data);
154
+ *
155
+ * const renderItem = ({ item, id, positions, ...props }) => (
156
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
157
+ * <View style={styles.customItem}>
158
+ * <Text>{item.name}</Text>
159
+ * <Text>Order: {item.order}</Text>
160
+ * </View>
161
+ * </SortableItem>
162
+ * );
163
+ *
164
+ * return (
165
+ * <Sortable
166
+ * data={items}
167
+ * renderItem={renderItem}
168
+ * itemHeight={50}
169
+ * itemKeyExtractor={(item) => item.uuid} // Use uuid instead of id
170
+ * />
171
+ * );
172
+ * }
173
+ * ```
174
+ *
175
+ * @see {@link SortableItem} for individual item component
176
+ * @see {@link useSortableList} for the underlying hook
177
+ * @see {@link SortableRenderItemProps} for render function props
178
+ * @see {@link UseSortableListOptions} for configuration options
179
+ * @see {@link UseSortableListReturn} for hook return details
180
+ * @see {@link DropProvider} for drag-and-drop context
181
+ */
182
+ export declare function Sortable<TData extends {
183
+ id: string;
184
+ }>({ data, renderItem, itemHeight, style, contentContainerStyle, itemKeyExtractor, }: SortableProps<TData>): React.JSX.Element;
@@ -0,0 +1,225 @@
1
+ import React from "react";
2
+ import { StyleSheet } from "react-native";
3
+ import Animated from "react-native-reanimated";
4
+ import { GestureHandlerRootView, ScrollView, } from "react-native-gesture-handler";
5
+ import { DropProvider } from "../context/DropContext";
6
+ import { useSortableList, } from "../hooks/useSortableList";
7
+ // Create an animated version of the ScrollView
8
+ const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
9
+ /**
10
+ * A high-level component for creating sortable lists with smooth reordering animations.
11
+ *
12
+ * The Sortable component provides a complete solution for sortable lists, handling
13
+ * all the complex state management, gesture handling, and animations internally.
14
+ * It renders a scrollable list where items can be dragged to reorder them with
15
+ * smooth animations and auto-scrolling support.
16
+ *
17
+ * @template TData - The type of data items in the list (must extend `{ id: string }`)
18
+ * @param props - Configuration props for the sortable list
19
+ *
20
+ * @example
21
+ * Basic sortable list:
22
+ * ```typescript
23
+ * import { Sortable } from './components/Sortable';
24
+ *
25
+ * interface Task {
26
+ * id: string;
27
+ * title: string;
28
+ * completed: boolean;
29
+ * }
30
+ *
31
+ * function TaskList() {
32
+ * const [tasks, setTasks] = useState<Task[]>([
33
+ * { id: '1', title: 'Learn React Native', completed: false },
34
+ * { id: '2', title: 'Build an app', completed: false },
35
+ * { id: '3', title: 'Deploy to store', completed: false }
36
+ * ]);
37
+ *
38
+ * const renderTask = ({ item, id, positions, ...props }) => (
39
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
40
+ * <View style={styles.taskItem}>
41
+ * <Text>{item.title}</Text>
42
+ * <Text>{item.completed ? '✓' : '○'}</Text>
43
+ * </View>
44
+ * </SortableItem>
45
+ * );
46
+ *
47
+ * return (
48
+ * <Sortable
49
+ * data={tasks}
50
+ * renderItem={renderTask}
51
+ * itemHeight={60}
52
+ * style={styles.list}
53
+ * />
54
+ * );
55
+ * }
56
+ * ```
57
+ *
58
+ * @example
59
+ * Sortable list with custom styling and callbacks:
60
+ * ```typescript
61
+ * function AdvancedTaskList() {
62
+ * const [tasks, setTasks] = useState(initialTasks);
63
+ *
64
+ * const renderTask = ({ item, id, positions, ...props }) => (
65
+ * <SortableItem
66
+ * key={id}
67
+ * id={id}
68
+ * positions={positions}
69
+ * {...props}
70
+ * onMove={(itemId, from, to) => {
71
+ * // Update data when items are reordered
72
+ * const newTasks = [...tasks];
73
+ * const [movedTask] = newTasks.splice(from, 1);
74
+ * newTasks.splice(to, 0, movedTask);
75
+ * setTasks(newTasks);
76
+ *
77
+ * // Analytics
78
+ * analytics.track('task_reordered', { taskId: itemId, from, to });
79
+ * }}
80
+ * onDragStart={(itemId) => {
81
+ * hapticFeedback();
82
+ * setDraggingTask(itemId);
83
+ * }}
84
+ * onDrop={(itemId) => {
85
+ * setDraggingTask(null);
86
+ * }}
87
+ * >
88
+ * <Animated.View style={[styles.taskItem, item.priority === 'high' && styles.highPriority]}>
89
+ * <Text style={styles.taskTitle}>{item.title}</Text>
90
+ * <Text style={styles.taskDue}>{item.dueDate}</Text>
91
+ * <View style={styles.dragHandle}>
92
+ * <Icon name="drag-handle" size={20} color="#666" />
93
+ * </View>
94
+ * </Animated.View>
95
+ * </SortableItem>
96
+ * );
97
+ *
98
+ * return (
99
+ * <View style={styles.container}>
100
+ * <Text style={styles.header}>My Tasks ({tasks.length})</Text>
101
+ * <Sortable
102
+ * data={tasks}
103
+ * renderItem={renderTask}
104
+ * itemHeight={80}
105
+ * style={styles.sortableList}
106
+ * contentContainerStyle={styles.listContent}
107
+ * />
108
+ * </View>
109
+ * );
110
+ * }
111
+ * ```
112
+ *
113
+ * @example
114
+ * Sortable list with drag handles:
115
+ * ```typescript
116
+ * function SortableWithHandles() {
117
+ * const [items, setItems] = useState(data);
118
+ *
119
+ * const renderItem = ({ item, id, positions, ...props }) => (
120
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
121
+ * <View style={styles.itemContainer}>
122
+ * <View style={styles.itemContent}>
123
+ * <Text style={styles.itemTitle}>{item.title}</Text>
124
+ * <Text style={styles.itemSubtitle}>{item.subtitle}</Text>
125
+ * </View>
126
+ *
127
+ * {/* Only this handle area can initiate dragging *\/}
128
+ * <SortableItem.Handle style={styles.dragHandle}>
129
+ * <View style={styles.handleIcon}>
130
+ * <View style={styles.handleDot} />
131
+ * <View style={styles.handleDot} />
132
+ * <View style={styles.handleDot} />
133
+ * </View>
134
+ * </SortableItem.Handle>
135
+ * </View>
136
+ * </SortableItem>
137
+ * );
138
+ *
139
+ * return (
140
+ * <Sortable
141
+ * data={items}
142
+ * renderItem={renderItem}
143
+ * itemHeight={70}
144
+ * />
145
+ * );
146
+ * }
147
+ * ```
148
+ *
149
+ * @example
150
+ * Sortable list with custom key extractor:
151
+ * ```typescript
152
+ * interface CustomItem {
153
+ * uuid: string;
154
+ * name: string;
155
+ * order: number;
156
+ * }
157
+ *
158
+ * function CustomSortableList() {
159
+ * const [items, setItems] = useState<CustomItem[]>(data);
160
+ *
161
+ * const renderItem = ({ item, id, positions, ...props }) => (
162
+ * <SortableItem key={id} id={id} positions={positions} {...props}>
163
+ * <View style={styles.customItem}>
164
+ * <Text>{item.name}</Text>
165
+ * <Text>Order: {item.order}</Text>
166
+ * </View>
167
+ * </SortableItem>
168
+ * );
169
+ *
170
+ * return (
171
+ * <Sortable
172
+ * data={items}
173
+ * renderItem={renderItem}
174
+ * itemHeight={50}
175
+ * itemKeyExtractor={(item) => item.uuid} // Use uuid instead of id
176
+ * />
177
+ * );
178
+ * }
179
+ * ```
180
+ *
181
+ * @see {@link SortableItem} for individual item component
182
+ * @see {@link useSortableList} for the underlying hook
183
+ * @see {@link SortableRenderItemProps} for render function props
184
+ * @see {@link UseSortableListOptions} for configuration options
185
+ * @see {@link UseSortableListReturn} for hook return details
186
+ * @see {@link DropProvider} for drag-and-drop context
187
+ */
188
+ export function Sortable({ data, renderItem, itemHeight, style, contentContainerStyle, itemKeyExtractor = (item) => item.id, }) {
189
+ const sortableOptions = {
190
+ data,
191
+ itemHeight,
192
+ itemKeyExtractor,
193
+ };
194
+ const { scrollViewRef, dropProviderRef, handleScroll, handleScrollEnd, contentHeight, getItemProps, } = useSortableList(sortableOptions);
195
+ return (<GestureHandlerRootView style={styles.flex}>
196
+ <DropProvider ref={dropProviderRef}>
197
+ <AnimatedScrollView ref={scrollViewRef} onScroll={handleScroll} scrollEventThrottle={16} style={[styles.scrollView, style]} contentContainerStyle={[
198
+ { height: contentHeight },
199
+ contentContainerStyle,
200
+ ]} onScrollEndDrag={handleScrollEnd} onMomentumScrollEnd={handleScrollEnd} simultaneousHandlers={dropProviderRef}>
201
+ {data.map((item, index) => {
202
+ // Get the item props from our hook
203
+ const itemProps = getItemProps(item, index);
204
+ // Create the complete props with the item and index
205
+ const sortableItemProps = {
206
+ item,
207
+ index,
208
+ ...itemProps,
209
+ };
210
+ return renderItem(sortableItemProps);
211
+ })}
212
+ </AnimatedScrollView>
213
+ </DropProvider>
214
+ </GestureHandlerRootView>);
215
+ }
216
+ const styles = StyleSheet.create({
217
+ flex: {
218
+ flex: 1,
219
+ },
220
+ scrollView: {
221
+ flex: 1,
222
+ position: "relative",
223
+ backgroundColor: "white",
224
+ },
225
+ });