-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
index.tsx
127 lines (116 loc) · 4.86 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import React, {useCallback} from 'react';
import {DragDropContext, Droppable, Draggable, type OnDragEndResponder} from 'react-beautiful-dnd';
import {ScrollView} from 'react-native';
import useDraggableInPortal from './useDraggableInPortal';
import type {DraggableListProps} from './types';
import styles from '../../styles/styles';
type ReorderParams<T> = {
list: T[];
startIndex: number;
endIndex: number;
};
/**
* Reorders a list by moving an item from a start index to an end index.
*/
const reorder = <T,>({list, startIndex, endIndex}: ReorderParams<T>): T[] => {
const result = [...list];
const [removed] = result.splice(startIndex, 1);
if (removed) {
result.splice(endIndex, 0, removed);
}
return result;
};
function DraggableList<T>(
{
data = [],
renderItem,
keyExtractor,
onDragEnd: onDragEndCallback,
renderClone,
shouldUsePortal = false,
// eslint-disable-next-line @typescript-eslint/naming-convention
ListFooterComponent,
}: DraggableListProps<T>,
ref: React.ForwardedRef<ScrollView>,
) {
/**
* Function to be called when the user finishes dragging an item
* It will reorder the list and call the callback function
* to notify the parent component about the change
*/
const onDragEnd: OnDragEndResponder = useCallback(
(result) => {
// If user dropped the item outside of the list
if (!result.destination) {
return;
}
const reorderedItems = reorder({
list: data,
startIndex: result.source.index,
endIndex: result.destination.index,
});
onDragEndCallback?.({data: reorderedItems});
},
[data, onDragEndCallback],
);
/**
* The `react-beautiful-dnd` library uses `position: fixed` to move the dragged item to the top of the screen.
* But when the parent component uses the `transform` property, the `position: fixed` doesn't work as expected.
* Since the TabSelector component uses the `transform` property to animate the tab change
* we have to use portals. It is required when any of the parent components use the `transform` property.
*/
const renderDraggable = useDraggableInPortal({shouldUsePortal});
return (
<ScrollView
ref={ref}
style={styles.flex1}
>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="droppable"
renderClone={renderClone}
>
{(droppableProvided) => (
<div
// eslint-disable-next-line react/jsx-props-no-spreading
{...droppableProvided.droppableProps}
ref={droppableProvided.innerRef}
>
{data.map((item, index) => {
const key = keyExtractor(item, index);
return (
<Draggable
key={key}
draggableId={key}
index={index}
>
{renderDraggable((draggableProvided, snapshot) => (
<div
ref={draggableProvided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.dragHandleProps}
>
{renderItem({
item,
getIndex: () => index,
isActive: snapshot.isDragging,
drag: () => {},
})}
</div>
))}
</Draggable>
);
})}
{droppableProvided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
{ListFooterComponent}
</ScrollView>
);
}
DraggableList.displayName = 'DraggableList';
export default React.forwardRef(DraggableList);