-
Notifications
You must be signed in to change notification settings - Fork 17
/
index.tsx
107 lines (92 loc) · 2.88 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
import {Fragment, useEffect, useRef, useState} from 'react';
import {Stage} from '..';
import {insertArray} from '../helpers/insertArray';
import {setAnimationFrameTimeout} from '../helpers/setAnimationFrameTimeout';
type RenderCallback<Item> = (item: Item, stage: Stage) => React.ReactNode;
type ItemWithState<Item> = {
item: Item;
key: number;
stage: Stage;
};
type ItemWithKey<Item> = {
item: Item;
index: number;
};
export function useListTransition<Item>(list: Array<Item>, timeout: number) {
const keyRef = useRef(0);
// change list to our list form with extra information.
const initialList: Array<ItemWithState<Item>> = list.map((item, key) => ({
item,
key: keyRef.current,
stage: 'enter',
}));
const [listState, setListState] = useState(initialList);
useEffect(
function handleListChange() {
const newItemsWithIndex: Array<ItemWithKey<Item>> = [];
list.forEach((item, index) => {
if (listState.every((itemState) => itemState.item !== item)) {
newItemsWithIndex.push({item, index});
}
});
// 1 add new items into list state
if (newItemsWithIndex.length > 0) {
keyRef.current++;
setListState((prevListState) =>
newItemsWithIndex.reduce(
(prev, {item, index}, i) =>
insertArray(prev, index, {
item,
key: keyRef.current,
stage: 'from',
}),
prevListState
)
);
}
// 2 enter those new items immediatly
if (
newItemsWithIndex.length === 0 &&
listState.some((item) => item.stage === 'from')
) {
setAnimationFrameTimeout(() => {
setListState((prev) =>
prev.map((item) => ({
...item,
stage: item.stage === 'from' ? 'enter' : item.stage,
}))
);
});
}
// 3 leave items from list state
const subtractItemStates = listState.filter(
(itemState) =>
!list.includes(itemState.item) && itemState.stage !== 'leave'
);
const subtractItems = subtractItemStates.map((item) => item.item);
if (newItemsWithIndex.length === 0 && subtractItemStates.length > 0) {
setListState((prev) =>
prev.map((itemState) =>
subtractItemStates.includes(itemState)
? {...itemState, stage: 'leave'}
: itemState
)
);
setAnimationFrameTimeout(() => {
setListState((prev) =>
prev.filter((item) => !subtractItems.includes(item.item))
);
}, timeout);
}
},
[list, listState, timeout]
);
function transition(renderCallback: RenderCallback<Item>) {
return listState.map((item) => (
<Fragment key={item.key}>
{renderCallback(item.item, item.stage)}
</Fragment>
));
}
return transition;
}