-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathreducer.ts
126 lines (117 loc) · 3.81 KB
/
reducer.ts
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
import { type OverlayState, type OverlayItem } from './store';
export type OverlayReducerAction =
| { type: 'ADD'; overlay: OverlayItem }
| { type: 'OPEN'; overlayId: OverlayItem['id'] }
| { type: 'CLOSE'; overlayId: OverlayItem['id'] }
| { type: 'REMOVE'; overlayId: OverlayItem['id'] }
| { type: 'CLOSE_ALL' }
| { type: 'REMOVE_ALL' };
export function overlayReducer(state: OverlayState, action: OverlayReducerAction): OverlayState {
switch (action.type) {
case 'ADD': {
const isExisted = state.orderIds.includes(action.overlay.id);
if (isExisted && state.data[action.overlay.id].isOpen === true) {
throw new Error("You can't open the multiple overlays with the same overlayId. Please set a different id.");
}
return {
currentId: action.overlay.id,
/**
* @description Brings the overlay to the front when reopened after closing without unmounting.
*/
orderIds: [...state.orderIds.filter((orderId) => orderId !== action.overlay.id), action.overlay.id],
data: isExisted
? state.data
: {
...state.data,
[action.overlay.id]: action.overlay,
},
};
}
case 'OPEN': {
return {
...state,
data: {
...state.data,
[action.overlayId]: {
...state.data[action.overlayId],
isOpen: true,
},
},
};
}
case 'CLOSE': {
const openedOrderIds = state.orderIds.filter((orderId) => state.data[orderId].isOpen === true);
const targetOpenedOrderIdIndex = openedOrderIds.findIndex((openedOrderId) => openedOrderId === action.overlayId);
/**
* @description If closing the last overlay, specify the overlay before it.
* @description If closing intermediate overlays, specifies the last overlay.
*
* @example open - [1, 2, 3, 4]
* close 2 => current: 4
* close 4 => current: 3
* close 3 => current: 1
* close 1 => current: null
*/
const currentOverlayId =
targetOpenedOrderIdIndex === openedOrderIds.length - 1
? openedOrderIds[targetOpenedOrderIdIndex - 1] ?? null
: openedOrderIds.at(-1) ?? null;
return {
...state,
currentId: currentOverlayId,
data: {
...state.data,
[action.overlayId]: {
...state.data[action.overlayId],
isOpen: false,
},
},
};
}
case 'REMOVE': {
const remainingOrderIds = state.orderIds.filter((orderId) => orderId !== action.overlayId);
if (state.orderIds.length === remainingOrderIds.length) {
return state;
}
const copiedData = { ...state.data };
delete copiedData[action.overlayId];
const currentId = state.currentId
? remainingOrderIds.includes(state.currentId)
? /**
* @description If `unmount` was executed after `close`
*/
state.currentId
: /**
* @description If you only run `unmount`, there is no `currentId` in `remainingOrderIds`
*/
remainingOrderIds.at(-1) ?? null
: /**
* @description The case where `currentId` is `null`
*/
null;
return {
currentId,
orderIds: remainingOrderIds,
data: copiedData,
};
}
case 'CLOSE_ALL': {
return {
...state,
data: Object.keys(state.data).reduce(
(prev, curr) => ({
...prev,
[curr]: {
...state.data[curr],
isOpen: false,
} satisfies OverlayItem,
}),
{} satisfies Record<string, OverlayItem>
),
};
}
case 'REMOVE_ALL': {
return { currentId: null, orderIds: [], data: {} };
}
}
}