react-native-drawer-layout 4.0.0-alpha.6 → 4.0.0-alpha.7
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/lib/commonjs/utils/getDrawerWidth.js +42 -0
- package/lib/commonjs/utils/getDrawerWidth.js.map +1 -0
- package/lib/commonjs/utils/useDrawerProgress.js.map +1 -1
- package/lib/commonjs/utils/useFakeSharedValue.js +46 -0
- package/lib/commonjs/utils/useFakeSharedValue.js.map +1 -0
- package/lib/commonjs/views/Drawer.js +55 -279
- package/lib/commonjs/views/Drawer.js.map +1 -1
- package/lib/commonjs/views/Drawer.native.js +324 -0
- package/lib/commonjs/views/Drawer.native.js.map +1 -0
- package/lib/commonjs/views/Overlay.js +22 -39
- package/lib/commonjs/views/Overlay.js.map +1 -1
- package/lib/commonjs/views/Overlay.native.js +58 -0
- package/lib/commonjs/views/Overlay.native.js.map +1 -0
- package/lib/module/utils/getDrawerWidth.js +36 -0
- package/lib/module/utils/getDrawerWidth.js.map +1 -0
- package/lib/module/utils/useDrawerProgress.js.map +1 -1
- package/lib/module/utils/useFakeSharedValue.js +38 -0
- package/lib/module/utils/useFakeSharedValue.js.map +1 -0
- package/lib/module/views/Drawer.js +56 -278
- package/lib/module/views/Drawer.js.map +1 -1
- package/lib/module/views/Drawer.native.js +315 -0
- package/lib/module/views/Drawer.native.js.map +1 -0
- package/lib/module/views/Overlay.js +22 -39
- package/lib/module/views/Overlay.js.map +1 -1
- package/lib/module/views/Overlay.native.js +50 -0
- package/lib/module/views/Overlay.native.js.map +1 -0
- package/lib/typescript/src/types.d.ts +9 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/DrawerProgressContext.d.ts +2 -2
- package/lib/typescript/src/utils/DrawerProgressContext.d.ts.map +1 -1
- package/lib/typescript/src/utils/getDrawerWidth.d.ts +9 -0
- package/lib/typescript/src/utils/getDrawerWidth.d.ts.map +1 -0
- package/lib/typescript/src/utils/useDrawerProgress.d.ts +2 -2
- package/lib/typescript/src/utils/useDrawerProgress.d.ts.map +1 -1
- package/lib/typescript/src/utils/useFakeSharedValue.d.ts +17 -0
- package/lib/typescript/src/utils/useFakeSharedValue.d.ts.map +1 -0
- package/lib/typescript/src/views/Drawer.d.ts +1 -6
- package/lib/typescript/src/views/Drawer.d.ts.map +1 -1
- package/lib/typescript/src/views/Drawer.native.d.ts +4 -0
- package/lib/typescript/src/views/Drawer.native.d.ts.map +1 -0
- package/lib/typescript/src/views/Overlay.d.ts +2 -204
- package/lib/typescript/src/views/Overlay.d.ts.map +1 -1
- package/lib/typescript/src/views/Overlay.native.d.ts +4 -0
- package/lib/typescript/src/views/Overlay.native.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/types.tsx +10 -1
- package/src/utils/DrawerProgressContext.tsx +2 -2
- package/src/utils/getDrawerWidth.tsx +49 -0
- package/src/utils/useDrawerProgress.tsx +2 -2
- package/src/utils/useFakeSharedValue.tsx +49 -0
- package/src/views/Drawer.native.tsx +466 -0
- package/src/views/Drawer.tsx +117 -433
- package/src/views/Overlay.native.tsx +63 -0
- package/src/views/Overlay.tsx +26 -59
package/src/views/Drawer.tsx
CHANGED
|
@@ -1,476 +1,166 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
I18nManager,
|
|
4
|
-
InteractionManager,
|
|
5
|
-
Keyboard,
|
|
6
|
-
Platform,
|
|
7
|
-
StatusBar,
|
|
8
|
-
StyleSheet,
|
|
9
|
-
useWindowDimensions,
|
|
10
|
-
View,
|
|
11
|
-
} from 'react-native';
|
|
12
|
-
import Animated, {
|
|
13
|
-
interpolate,
|
|
14
|
-
runOnJS,
|
|
15
|
-
useAnimatedGestureHandler,
|
|
16
|
-
useAnimatedStyle,
|
|
17
|
-
useDerivedValue,
|
|
18
|
-
useSharedValue,
|
|
19
|
-
withSpring,
|
|
20
|
-
} from 'react-native-reanimated';
|
|
2
|
+
import { StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
21
3
|
import useLatestCallback from 'use-latest-callback';
|
|
22
4
|
|
|
23
5
|
import type { DrawerProps } from '../types';
|
|
24
6
|
import { DrawerProgressContext } from '../utils/DrawerProgressContext';
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
GestureState,
|
|
28
|
-
PanGestureHandler,
|
|
29
|
-
type PanGestureHandlerGestureEvent,
|
|
30
|
-
} from './GestureHandler';
|
|
7
|
+
import { getDrawerWidth } from '../utils/getDrawerWidth';
|
|
8
|
+
import { useFakeSharedValue } from '../utils/useFakeSharedValue';
|
|
31
9
|
import { Overlay } from './Overlay';
|
|
32
10
|
|
|
33
|
-
|
|
34
|
-
export const SWIPE_MIN_OFFSET = 5;
|
|
35
|
-
export const SWIPE_MIN_DISTANCE = 60;
|
|
36
|
-
export const SWIPE_MIN_VELOCITY = 500;
|
|
37
|
-
|
|
38
|
-
export const DRAWER_BORDER_RADIUS = 16;
|
|
39
|
-
|
|
40
|
-
const minmax = (value: number, start: number, end: number) => {
|
|
41
|
-
'worklet';
|
|
42
|
-
|
|
43
|
-
return Math.min(Math.max(value, start), end);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const getDefaultDrawerWidth = ({
|
|
47
|
-
height,
|
|
48
|
-
width,
|
|
49
|
-
}: {
|
|
50
|
-
height: number;
|
|
51
|
-
width: number;
|
|
52
|
-
}) => {
|
|
53
|
-
/*
|
|
54
|
-
* Default drawer width is screen width - header height
|
|
55
|
-
* with a max width of 280 on mobile and 320 on tablet
|
|
56
|
-
* https://material.io/components/navigation-drawer
|
|
57
|
-
*/
|
|
58
|
-
const smallerAxisSize = Math.min(height, width);
|
|
59
|
-
const isLandscape = width > height;
|
|
60
|
-
const isTablet = smallerAxisSize >= 600;
|
|
61
|
-
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
|
|
62
|
-
const maxWidth = isTablet ? 320 : 280;
|
|
63
|
-
|
|
64
|
-
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
|
|
65
|
-
};
|
|
11
|
+
const DRAWER_BORDER_RADIUS = 16;
|
|
66
12
|
|
|
67
13
|
export function Drawer({
|
|
68
14
|
layout: customLayout,
|
|
69
|
-
drawerPosition =
|
|
15
|
+
drawerPosition = 'left',
|
|
70
16
|
drawerStyle,
|
|
71
|
-
drawerType =
|
|
72
|
-
gestureHandlerProps,
|
|
73
|
-
hideStatusBarOnOpen = false,
|
|
74
|
-
keyboardDismissMode = 'on-drag',
|
|
17
|
+
drawerType = 'front',
|
|
75
18
|
onClose,
|
|
76
|
-
onOpen,
|
|
77
|
-
onGestureStart,
|
|
78
|
-
onGestureCancel,
|
|
79
|
-
onGestureEnd,
|
|
80
19
|
onTransitionStart,
|
|
81
20
|
onTransitionEnd,
|
|
82
21
|
open,
|
|
83
22
|
overlayStyle,
|
|
84
23
|
overlayAccessibilityLabel,
|
|
85
|
-
statusBarAnimation = 'slide',
|
|
86
|
-
swipeEnabled = Platform.OS !== 'web' &&
|
|
87
|
-
Platform.OS !== 'windows' &&
|
|
88
|
-
Platform.OS !== 'macos',
|
|
89
|
-
swipeEdgeWidth = SWIPE_EDGE_WIDTH,
|
|
90
|
-
swipeMinDistance = SWIPE_MIN_DISTANCE,
|
|
91
|
-
swipeMinVelocity = SWIPE_MIN_VELOCITY,
|
|
92
24
|
renderDrawerContent,
|
|
93
25
|
children,
|
|
94
26
|
style,
|
|
95
27
|
}: DrawerProps) {
|
|
96
|
-
// FIXME: temporary workaround for useSafeAreaFrame not updating on Web
|
|
97
28
|
const windowDimensions = useWindowDimensions();
|
|
98
|
-
const layout = customLayout ?? windowDimensions;
|
|
99
|
-
|
|
100
|
-
const getDrawerWidth = (): number => {
|
|
101
|
-
const { width = getDefaultDrawerWidth(layout) } =
|
|
102
|
-
StyleSheet.flatten(drawerStyle) || {};
|
|
103
|
-
|
|
104
|
-
if (typeof width === 'string' && width.endsWith('%')) {
|
|
105
|
-
// Try to calculate width if a percentage is given
|
|
106
|
-
const percentage = Number(width.replace(/%$/, ''));
|
|
107
|
-
|
|
108
|
-
if (Number.isFinite(percentage)) {
|
|
109
|
-
return layout.width * (percentage / 100);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return typeof width === 'number' ? width : 0;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const drawerWidth = getDrawerWidth();
|
|
117
|
-
|
|
118
|
-
const isOpen = drawerType === 'permanent' ? true : open;
|
|
119
|
-
const isRight = drawerPosition === 'right';
|
|
120
|
-
|
|
121
|
-
const getDrawerTranslationX = React.useCallback(
|
|
122
|
-
(open: boolean) => {
|
|
123
|
-
'worklet';
|
|
124
29
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return open ? 0 : drawerWidth;
|
|
130
|
-
},
|
|
131
|
-
[drawerPosition, drawerWidth]
|
|
132
|
-
);
|
|
30
|
+
const layout = customLayout ?? windowDimensions;
|
|
31
|
+
const drawerWidth = getDrawerWidth({ layout, drawerStyle });
|
|
133
32
|
|
|
134
|
-
const
|
|
135
|
-
(hide: boolean) => {
|
|
136
|
-
if (hideStatusBarOnOpen) {
|
|
137
|
-
StatusBar.setHidden(hide, statusBarAnimation);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
[hideStatusBarOnOpen, statusBarAnimation]
|
|
141
|
-
);
|
|
33
|
+
const progress = useFakeSharedValue(open ? 1 : 0);
|
|
142
34
|
|
|
143
35
|
React.useEffect(() => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return () => hideStatusBar(false);
|
|
147
|
-
}, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]);
|
|
148
|
-
|
|
149
|
-
const interactionHandleRef = React.useRef<number | null>(null);
|
|
150
|
-
|
|
151
|
-
const startInteraction = () => {
|
|
152
|
-
interactionHandleRef.current = InteractionManager.createInteractionHandle();
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const endInteraction = () => {
|
|
156
|
-
if (interactionHandleRef.current != null) {
|
|
157
|
-
InteractionManager.clearInteractionHandle(interactionHandleRef.current);
|
|
158
|
-
interactionHandleRef.current = null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const hideKeyboard = () => {
|
|
163
|
-
if (keyboardDismissMode === 'on-drag') {
|
|
164
|
-
Keyboard.dismiss();
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const onGestureBegin = () => {
|
|
169
|
-
onGestureStart?.();
|
|
170
|
-
startInteraction();
|
|
171
|
-
hideKeyboard();
|
|
172
|
-
hideStatusBar(true);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const onGestureFinish = () => {
|
|
176
|
-
onGestureEnd?.();
|
|
177
|
-
endInteraction();
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const onGestureAbort = () => {
|
|
181
|
-
onGestureCancel?.();
|
|
182
|
-
endInteraction();
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
|
|
186
|
-
// https://github.com/software-mansion/react-native-gesture-handler/issues/569
|
|
187
|
-
const hitSlop = isRight
|
|
188
|
-
? // Extend hitSlop to the side of the screen when drawer is closed
|
|
189
|
-
// This lets the user drag the drawer from the side of the screen
|
|
190
|
-
{ right: 0, width: isOpen ? undefined : swipeEdgeWidth }
|
|
191
|
-
: { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
|
|
192
|
-
|
|
193
|
-
const borderRadiiStyle = isRight
|
|
194
|
-
? {
|
|
195
|
-
borderTopLeftRadius: DRAWER_BORDER_RADIUS,
|
|
196
|
-
borderBottomLeftRadius: DRAWER_BORDER_RADIUS,
|
|
197
|
-
}
|
|
198
|
-
: {
|
|
199
|
-
borderTopRightRadius: DRAWER_BORDER_RADIUS,
|
|
200
|
-
borderBottomRightRadius: DRAWER_BORDER_RADIUS,
|
|
201
|
-
};
|
|
36
|
+
progress.value = open ? 1 : 0;
|
|
37
|
+
}, [open, progress]);
|
|
202
38
|
|
|
203
|
-
const
|
|
204
|
-
const touchX = useSharedValue(0);
|
|
205
|
-
const translationX = useSharedValue(getDrawerTranslationX(open));
|
|
206
|
-
const gestureState = useSharedValue<GestureState>(GestureState.UNDETERMINED);
|
|
39
|
+
const drawerRef = React.useRef<View>(null);
|
|
207
40
|
|
|
208
|
-
const
|
|
209
|
-
onTransitionStart?.(
|
|
41
|
+
const onTransitionStartLatest = useLatestCallback(() => {
|
|
42
|
+
onTransitionStart?.(open === false);
|
|
210
43
|
});
|
|
211
44
|
|
|
212
|
-
const
|
|
213
|
-
(open
|
|
214
|
-
if (!finished) return;
|
|
215
|
-
onTransitionEnd?.(!open);
|
|
216
|
-
}
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const toggleDrawer = React.useCallback(
|
|
220
|
-
(open: boolean, velocity?: number) => {
|
|
221
|
-
'worklet';
|
|
222
|
-
|
|
223
|
-
const translateX = getDrawerTranslationX(open);
|
|
224
|
-
|
|
225
|
-
if (velocity === undefined) {
|
|
226
|
-
runOnJS(handleAnimationStart)(open);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
touchStartX.value = 0;
|
|
230
|
-
touchX.value = 0;
|
|
231
|
-
translationX.value = withSpring(
|
|
232
|
-
translateX,
|
|
233
|
-
{
|
|
234
|
-
velocity,
|
|
235
|
-
stiffness: 1000,
|
|
236
|
-
damping: 500,
|
|
237
|
-
mass: 3,
|
|
238
|
-
overshootClamping: true,
|
|
239
|
-
restDisplacementThreshold: 0.01,
|
|
240
|
-
restSpeedThreshold: 0.01,
|
|
241
|
-
},
|
|
242
|
-
(finished) => runOnJS(handleAnimationEnd)(open, finished)
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
if (open) {
|
|
246
|
-
runOnJS(onOpen)();
|
|
247
|
-
} else {
|
|
248
|
-
runOnJS(onClose)();
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
[
|
|
252
|
-
getDrawerTranslationX,
|
|
253
|
-
handleAnimationEnd,
|
|
254
|
-
handleAnimationStart,
|
|
255
|
-
onClose,
|
|
256
|
-
onOpen,
|
|
257
|
-
touchStartX,
|
|
258
|
-
touchX,
|
|
259
|
-
translationX,
|
|
260
|
-
]
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
|
|
264
|
-
|
|
265
|
-
const onGestureEvent = useAnimatedGestureHandler<
|
|
266
|
-
PanGestureHandlerGestureEvent,
|
|
267
|
-
{ startX: number; hasCalledOnStart: boolean }
|
|
268
|
-
>({
|
|
269
|
-
onStart: (event, ctx) => {
|
|
270
|
-
ctx.hasCalledOnStart = false;
|
|
271
|
-
ctx.startX = translationX.value;
|
|
272
|
-
gestureState.value = event.state;
|
|
273
|
-
touchStartX.value = event.x;
|
|
274
|
-
},
|
|
275
|
-
onCancel: () => {
|
|
276
|
-
runOnJS(onGestureAbort)();
|
|
277
|
-
},
|
|
278
|
-
onActive: (event, ctx) => {
|
|
279
|
-
touchX.value = event.x;
|
|
280
|
-
translationX.value = ctx.startX + event.translationX;
|
|
281
|
-
gestureState.value = event.state;
|
|
282
|
-
|
|
283
|
-
// onStart will _always_ be called, even when the activation
|
|
284
|
-
// criteria isn't met yet. This makes sure onGestureBegin is only
|
|
285
|
-
// called when the criteria is really met.
|
|
286
|
-
if (!ctx.hasCalledOnStart) {
|
|
287
|
-
ctx.hasCalledOnStart = true;
|
|
288
|
-
runOnJS(onGestureBegin)();
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
onEnd: (event) => {
|
|
292
|
-
gestureState.value = event.state;
|
|
293
|
-
|
|
294
|
-
const nextOpen =
|
|
295
|
-
(Math.abs(event.translationX) > SWIPE_MIN_OFFSET &&
|
|
296
|
-
Math.abs(event.translationX) > swipeMinVelocity) ||
|
|
297
|
-
Math.abs(event.translationX) > swipeMinDistance
|
|
298
|
-
? drawerPosition === 'left'
|
|
299
|
-
? // If swiped to right, open the drawer, otherwise close it
|
|
300
|
-
(event.velocityX === 0 ? event.translationX : event.velocityX) > 0
|
|
301
|
-
: // If swiped to left, open the drawer, otherwise close it
|
|
302
|
-
(event.velocityX === 0 ? event.translationX : event.velocityX) < 0
|
|
303
|
-
: open;
|
|
304
|
-
|
|
305
|
-
toggleDrawer(nextOpen, event.velocityX);
|
|
306
|
-
},
|
|
307
|
-
onFinish: () => {
|
|
308
|
-
runOnJS(onGestureFinish)();
|
|
309
|
-
},
|
|
45
|
+
const onTransitionEndLatest = useLatestCallback(() => {
|
|
46
|
+
onTransitionEnd?.(open === false);
|
|
310
47
|
});
|
|
311
48
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
//
|
|
315
|
-
// While closing the drawer when user starts gesture outside of its area (in greyed
|
|
316
|
-
// out part of the window), we want the drawer to follow only once finger reaches the
|
|
317
|
-
// edge of the drawer.
|
|
318
|
-
// E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
|
|
319
|
-
// dots. The touch gesture starts at '*' and moves left, touch path is indicated by
|
|
320
|
-
// an arrow pointing left
|
|
321
|
-
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
|
|
322
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
323
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
324
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
325
|
-
// |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
|
|
326
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
327
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
328
|
-
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
|
|
329
|
-
// +---------------+ +---------------+ +---------------+ +---------------+
|
|
330
|
-
//
|
|
331
|
-
// For the above to work properly we define animated value that will keep start position
|
|
332
|
-
// of the gesture. Then we use that value to calculate how much we need to subtract from
|
|
333
|
-
// the translationX. If the gesture started on the greyed out area we take the distance from the
|
|
334
|
-
// edge of the drawer to the start position. Otherwise we don't subtract at all and the
|
|
335
|
-
// drawer be pulled back as soon as you start the pan.
|
|
336
|
-
//
|
|
337
|
-
// This is used only when drawerType is "front"
|
|
338
|
-
const touchDistance =
|
|
339
|
-
drawerType === 'front' && gestureState.value === GestureState.ACTIVE
|
|
340
|
-
? minmax(
|
|
341
|
-
drawerPosition === 'left'
|
|
342
|
-
? touchStartX.value - drawerWidth
|
|
343
|
-
: layout.width - drawerWidth - touchStartX.value,
|
|
344
|
-
0,
|
|
345
|
-
layout.width
|
|
346
|
-
)
|
|
347
|
-
: 0;
|
|
348
|
-
|
|
349
|
-
const translateX =
|
|
350
|
-
drawerPosition === 'left'
|
|
351
|
-
? minmax(translationX.value + touchDistance, -drawerWidth, 0)
|
|
352
|
-
: minmax(translationX.value - touchDistance, 0, drawerWidth);
|
|
353
|
-
|
|
354
|
-
return translateX;
|
|
355
|
-
});
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
const element = drawerRef.current as HTMLDivElement | null;
|
|
356
51
|
|
|
357
|
-
|
|
358
|
-
|
|
52
|
+
if (element) {
|
|
53
|
+
element.addEventListener('transitionstart', onTransitionStartLatest);
|
|
54
|
+
element.addEventListener('transitionend', onTransitionEndLatest);
|
|
55
|
+
}
|
|
56
|
+
}, [onTransitionEndLatest, onTransitionStartLatest]);
|
|
359
57
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
drawerType === 'permanent'
|
|
363
|
-
? // Reanimated needs the property to be present, but it results in Browser bug
|
|
364
|
-
// https://bugs.chromium.org/p/chromium/issues/detail?id=20574
|
|
365
|
-
[]
|
|
366
|
-
: [
|
|
367
|
-
{
|
|
368
|
-
translateX:
|
|
369
|
-
// The drawer stays in place when `drawerType` is `back`
|
|
370
|
-
(drawerType === 'back' ? 0 : translateX.value) +
|
|
371
|
-
(drawerPosition === 'left' ? 0 : distanceFromEdge),
|
|
372
|
-
},
|
|
373
|
-
],
|
|
374
|
-
};
|
|
375
|
-
});
|
|
58
|
+
const isOpen = drawerType === 'permanent' ? true : open;
|
|
59
|
+
const isRight = drawerPosition === 'right';
|
|
376
60
|
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
61
|
+
const borderRadiiStyle =
|
|
62
|
+
drawerType !== 'permanent'
|
|
63
|
+
? isRight
|
|
64
|
+
? {
|
|
65
|
+
borderTopLeftRadius: DRAWER_BORDER_RADIUS,
|
|
66
|
+
borderBottomLeftRadius: DRAWER_BORDER_RADIUS,
|
|
67
|
+
}
|
|
68
|
+
: {
|
|
69
|
+
borderTopRightRadius: DRAWER_BORDER_RADIUS,
|
|
70
|
+
borderBottomRightRadius: DRAWER_BORDER_RADIUS,
|
|
71
|
+
}
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
const drawerAnimatedStyle =
|
|
75
|
+
drawerType !== 'permanent'
|
|
76
|
+
? {
|
|
77
|
+
transition: 'transform 0.3s',
|
|
78
|
+
transform: [
|
|
79
|
+
{
|
|
80
|
+
// The drawer stays in place at open position when `drawerType` is `back`
|
|
81
|
+
translateX:
|
|
82
|
+
open || drawerType === 'back'
|
|
83
|
+
? drawerPosition === 'left'
|
|
389
84
|
? 0
|
|
390
|
-
:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
[
|
|
404
|
-
|
|
405
|
-
|
|
85
|
+
: layout.width - drawerWidth
|
|
86
|
+
: drawerPosition === 'left'
|
|
87
|
+
? -drawerWidth
|
|
88
|
+
: layout.width,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
: null;
|
|
93
|
+
|
|
94
|
+
const contentAnimatedStyle =
|
|
95
|
+
drawerType !== 'permanent'
|
|
96
|
+
? {
|
|
97
|
+
transition: 'transform 0.3s',
|
|
98
|
+
transform: [
|
|
99
|
+
{
|
|
100
|
+
translateX: open
|
|
101
|
+
? // The screen content stays in place when `drawerType` is `front`
|
|
102
|
+
drawerType === 'front'
|
|
103
|
+
? 0
|
|
104
|
+
: drawerWidth * (drawerPosition === 'left' ? 1 : -1)
|
|
105
|
+
: 0,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
}
|
|
109
|
+
: null;
|
|
406
110
|
|
|
407
111
|
return (
|
|
408
|
-
<
|
|
112
|
+
<View style={[styles.container, style]}>
|
|
409
113
|
<DrawerProgressContext.Provider value={progress}>
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
styles.main,
|
|
117
|
+
{
|
|
118
|
+
flexDirection:
|
|
119
|
+
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
|
|
120
|
+
},
|
|
121
|
+
]}
|
|
417
122
|
>
|
|
418
|
-
{
|
|
419
|
-
|
|
123
|
+
<View style={[styles.content, contentAnimatedStyle]}>
|
|
124
|
+
<View
|
|
125
|
+
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
|
|
126
|
+
importantForAccessibility={
|
|
127
|
+
isOpen && drawerType !== 'permanent'
|
|
128
|
+
? 'no-hide-descendants'
|
|
129
|
+
: 'auto'
|
|
130
|
+
}
|
|
131
|
+
style={styles.content}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</View>
|
|
135
|
+
{drawerType !== 'permanent' ? (
|
|
136
|
+
<Overlay
|
|
137
|
+
open={open}
|
|
138
|
+
progress={progress}
|
|
139
|
+
onPress={() => onClose()}
|
|
140
|
+
style={overlayStyle}
|
|
141
|
+
accessibilityLabel={overlayAccessibilityLabel}
|
|
142
|
+
/>
|
|
143
|
+
) : null}
|
|
144
|
+
</View>
|
|
145
|
+
<View
|
|
146
|
+
ref={drawerRef}
|
|
420
147
|
style={[
|
|
421
|
-
styles.
|
|
148
|
+
styles.drawer,
|
|
422
149
|
{
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
: 'row',
|
|
150
|
+
width: drawerWidth,
|
|
151
|
+
position: drawerType === 'permanent' ? 'relative' : 'absolute',
|
|
152
|
+
zIndex: drawerType === 'back' ? -1 : 0,
|
|
427
153
|
},
|
|
154
|
+
borderRadiiStyle,
|
|
155
|
+
drawerAnimatedStyle,
|
|
156
|
+
drawerStyle,
|
|
428
157
|
]}
|
|
429
158
|
>
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
isOpen && drawerType !== 'permanent'
|
|
434
|
-
}
|
|
435
|
-
importantForAccessibility={
|
|
436
|
-
isOpen && drawerType !== 'permanent'
|
|
437
|
-
? 'no-hide-descendants'
|
|
438
|
-
: 'auto'
|
|
439
|
-
}
|
|
440
|
-
style={styles.content}
|
|
441
|
-
>
|
|
442
|
-
{children}
|
|
443
|
-
</View>
|
|
444
|
-
{drawerType !== 'permanent' ? (
|
|
445
|
-
<Overlay
|
|
446
|
-
progress={progress}
|
|
447
|
-
onPress={() => toggleDrawer(false)}
|
|
448
|
-
style={overlayStyle}
|
|
449
|
-
accessibilityLabel={overlayAccessibilityLabel}
|
|
450
|
-
/>
|
|
451
|
-
) : null}
|
|
452
|
-
</Animated.View>
|
|
453
|
-
<Animated.View
|
|
454
|
-
removeClippedSubviews={Platform.OS !== 'ios'}
|
|
455
|
-
style={[
|
|
456
|
-
styles.drawer,
|
|
457
|
-
{
|
|
458
|
-
width: drawerWidth,
|
|
459
|
-
position:
|
|
460
|
-
drawerType === 'permanent' ? 'relative' : 'absolute',
|
|
461
|
-
zIndex: drawerType === 'back' ? -1 : 0,
|
|
462
|
-
},
|
|
463
|
-
drawerType === 'permanent' ? null : borderRadiiStyle,
|
|
464
|
-
drawerAnimatedStyle,
|
|
465
|
-
drawerStyle,
|
|
466
|
-
]}
|
|
467
|
-
>
|
|
468
|
-
{renderDrawerContent()}
|
|
469
|
-
</Animated.View>
|
|
470
|
-
</Animated.View>
|
|
471
|
-
</PanGestureHandler>
|
|
159
|
+
{renderDrawerContent()}
|
|
160
|
+
</View>
|
|
161
|
+
</View>
|
|
472
162
|
</DrawerProgressContext.Provider>
|
|
473
|
-
</
|
|
163
|
+
</View>
|
|
474
164
|
);
|
|
475
165
|
}
|
|
476
166
|
|
|
@@ -489,11 +179,5 @@ const styles = StyleSheet.create({
|
|
|
489
179
|
},
|
|
490
180
|
main: {
|
|
491
181
|
flex: 1,
|
|
492
|
-
...Platform.select({
|
|
493
|
-
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
|
|
494
|
-
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
|
|
495
|
-
web: null,
|
|
496
|
-
default: { overflow: 'hidden' },
|
|
497
|
-
}),
|
|
498
182
|
},
|
|
499
183
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Pressable, StyleSheet } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedProps,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
} from 'react-native-reanimated';
|
|
7
|
+
|
|
8
|
+
import type { OverlayProps } from '../types';
|
|
9
|
+
|
|
10
|
+
const PROGRESS_EPSILON = 0.05;
|
|
11
|
+
|
|
12
|
+
export function Overlay({
|
|
13
|
+
progress,
|
|
14
|
+
onPress,
|
|
15
|
+
style,
|
|
16
|
+
accessibilityLabel = 'Close drawer',
|
|
17
|
+
...rest
|
|
18
|
+
}: OverlayProps) {
|
|
19
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
20
|
+
return {
|
|
21
|
+
opacity: progress.value,
|
|
22
|
+
// We don't want the user to be able to press through the overlay when drawer is open
|
|
23
|
+
// We can send the overlay behind the screen to avoid it
|
|
24
|
+
zIndex: progress.value > PROGRESS_EPSILON ? 0 : -1,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const animatedProps = useAnimatedProps(() => {
|
|
29
|
+
const active = progress.value > PROGRESS_EPSILON;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
pointerEvents: active ? 'auto' : 'none',
|
|
33
|
+
accessibilityElementsHidden: !active,
|
|
34
|
+
importantForAccessibility: active ? 'auto' : 'no-hide-descendants',
|
|
35
|
+
} as const;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Animated.View
|
|
40
|
+
{...rest}
|
|
41
|
+
style={[styles.overlay, animatedStyle, style]}
|
|
42
|
+
animatedProps={animatedProps}
|
|
43
|
+
>
|
|
44
|
+
<Pressable
|
|
45
|
+
onPress={onPress}
|
|
46
|
+
style={styles.pressable}
|
|
47
|
+
accessibilityRole="button"
|
|
48
|
+
accessibilityLabel={accessibilityLabel}
|
|
49
|
+
/>
|
|
50
|
+
</Animated.View>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const styles = StyleSheet.create({
|
|
55
|
+
overlay: {
|
|
56
|
+
...StyleSheet.absoluteFillObject,
|
|
57
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
58
|
+
},
|
|
59
|
+
pressable: {
|
|
60
|
+
flex: 1,
|
|
61
|
+
pointerEvents: 'auto',
|
|
62
|
+
},
|
|
63
|
+
});
|