react-native-drawer-layout 4.0.0-alpha.6 → 4.0.0-alpha.8
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 +41 -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 +51 -283
- package/lib/commonjs/views/Drawer.js.map +1 -1
- package/lib/commonjs/views/Drawer.native.js +313 -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 +35 -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 +52 -282
- package/lib/module/views/Drawer.js.map +1 -1
- package/lib/module/views/Drawer.native.js +304 -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 +39 -0
- package/src/utils/useDrawerProgress.tsx +2 -2
- package/src/utils/useFakeSharedValue.tsx +49 -0
- package/src/views/Drawer.native.tsx +450 -0
- package/src/views/Drawer.tsx +102 -434
- package/src/views/Overlay.native.tsx +63 -0
- package/src/views/Overlay.tsx +26 -59
package/src/views/Drawer.tsx
CHANGED
|
@@ -1,476 +1,150 @@
|
|
|
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
|
-
export const SWIPE_EDGE_WIDTH = 32;
|
|
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
|
-
};
|
|
66
|
-
|
|
67
11
|
export function Drawer({
|
|
68
12
|
layout: customLayout,
|
|
69
|
-
drawerPosition =
|
|
13
|
+
drawerPosition = 'left',
|
|
70
14
|
drawerStyle,
|
|
71
|
-
drawerType =
|
|
72
|
-
gestureHandlerProps,
|
|
73
|
-
hideStatusBarOnOpen = false,
|
|
74
|
-
keyboardDismissMode = 'on-drag',
|
|
15
|
+
drawerType = 'front',
|
|
75
16
|
onClose,
|
|
76
|
-
onOpen,
|
|
77
|
-
onGestureStart,
|
|
78
|
-
onGestureCancel,
|
|
79
|
-
onGestureEnd,
|
|
80
17
|
onTransitionStart,
|
|
81
18
|
onTransitionEnd,
|
|
82
19
|
open,
|
|
83
20
|
overlayStyle,
|
|
84
21
|
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
22
|
renderDrawerContent,
|
|
93
23
|
children,
|
|
94
24
|
style,
|
|
95
25
|
}: DrawerProps) {
|
|
96
|
-
// FIXME: temporary workaround for useSafeAreaFrame not updating on Web
|
|
97
26
|
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
27
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return open ? 0 : drawerWidth;
|
|
130
|
-
},
|
|
131
|
-
[drawerPosition, drawerWidth]
|
|
132
|
-
);
|
|
28
|
+
const layout = customLayout ?? windowDimensions;
|
|
29
|
+
const drawerWidth = getDrawerWidth({ layout, drawerStyle });
|
|
133
30
|
|
|
134
|
-
const
|
|
135
|
-
(hide: boolean) => {
|
|
136
|
-
if (hideStatusBarOnOpen) {
|
|
137
|
-
StatusBar.setHidden(hide, statusBarAnimation);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
[hideStatusBarOnOpen, statusBarAnimation]
|
|
141
|
-
);
|
|
31
|
+
const progress = useFakeSharedValue(open ? 1 : 0);
|
|
142
32
|
|
|
143
33
|
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
|
-
};
|
|
34
|
+
progress.value = open ? 1 : 0;
|
|
35
|
+
}, [open, progress]);
|
|
202
36
|
|
|
203
|
-
const
|
|
204
|
-
const touchX = useSharedValue(0);
|
|
205
|
-
const translationX = useSharedValue(getDrawerTranslationX(open));
|
|
206
|
-
const gestureState = useSharedValue<GestureState>(GestureState.UNDETERMINED);
|
|
37
|
+
const drawerRef = React.useRef<View>(null);
|
|
207
38
|
|
|
208
|
-
const
|
|
209
|
-
onTransitionStart?.(
|
|
39
|
+
const onTransitionStartLatest = useLatestCallback(() => {
|
|
40
|
+
onTransitionStart?.(open === false);
|
|
210
41
|
});
|
|
211
42
|
|
|
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
|
-
},
|
|
43
|
+
const onTransitionEndLatest = useLatestCallback(() => {
|
|
44
|
+
onTransitionEnd?.(open === false);
|
|
310
45
|
});
|
|
311
46
|
|
|
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
|
-
});
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
const element = drawerRef.current as HTMLDivElement | null;
|
|
356
49
|
|
|
357
|
-
|
|
358
|
-
|
|
50
|
+
if (element) {
|
|
51
|
+
element.addEventListener('transitionstart', onTransitionStartLatest);
|
|
52
|
+
element.addEventListener('transitionend', onTransitionEndLatest);
|
|
53
|
+
}
|
|
54
|
+
}, [onTransitionEndLatest, onTransitionStartLatest]);
|
|
359
55
|
|
|
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
|
-
});
|
|
56
|
+
const isOpen = drawerType === 'permanent' ? true : open;
|
|
57
|
+
const isRight = drawerPosition === 'right';
|
|
376
58
|
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// The screen content stays in place when `drawerType` is `front`
|
|
388
|
-
drawerType === 'front'
|
|
59
|
+
const drawerAnimatedStyle =
|
|
60
|
+
drawerType !== 'permanent'
|
|
61
|
+
? {
|
|
62
|
+
transition: 'transform 0.3s',
|
|
63
|
+
transform: [
|
|
64
|
+
{
|
|
65
|
+
// The drawer stays in place at open position when `drawerType` is `back`
|
|
66
|
+
translateX:
|
|
67
|
+
open || drawerType === 'back'
|
|
68
|
+
? drawerPosition === 'left'
|
|
389
69
|
? 0
|
|
390
|
-
:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
[
|
|
404
|
-
|
|
405
|
-
|
|
70
|
+
: layout.width - drawerWidth
|
|
71
|
+
: drawerPosition === 'left'
|
|
72
|
+
? -drawerWidth
|
|
73
|
+
: layout.width,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
}
|
|
77
|
+
: null;
|
|
78
|
+
|
|
79
|
+
const contentAnimatedStyle =
|
|
80
|
+
drawerType !== 'permanent'
|
|
81
|
+
? {
|
|
82
|
+
transition: 'transform 0.3s',
|
|
83
|
+
transform: [
|
|
84
|
+
{
|
|
85
|
+
translateX: open
|
|
86
|
+
? // The screen content stays in place when `drawerType` is `front`
|
|
87
|
+
drawerType === 'front'
|
|
88
|
+
? 0
|
|
89
|
+
: drawerWidth * (drawerPosition === 'left' ? 1 : -1)
|
|
90
|
+
: 0,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
: null;
|
|
406
95
|
|
|
407
96
|
return (
|
|
408
|
-
<
|
|
97
|
+
<View style={[styles.container, style]}>
|
|
409
98
|
<DrawerProgressContext.Provider value={progress}>
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
99
|
+
<View
|
|
100
|
+
style={[
|
|
101
|
+
styles.main,
|
|
102
|
+
{
|
|
103
|
+
flexDirection:
|
|
104
|
+
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
|
|
105
|
+
},
|
|
106
|
+
]}
|
|
417
107
|
>
|
|
418
|
-
{
|
|
419
|
-
|
|
108
|
+
<View style={[styles.content, contentAnimatedStyle]}>
|
|
109
|
+
<View
|
|
110
|
+
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
|
|
111
|
+
importantForAccessibility={
|
|
112
|
+
isOpen && drawerType !== 'permanent'
|
|
113
|
+
? 'no-hide-descendants'
|
|
114
|
+
: 'auto'
|
|
115
|
+
}
|
|
116
|
+
style={styles.content}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</View>
|
|
120
|
+
{drawerType !== 'permanent' ? (
|
|
121
|
+
<Overlay
|
|
122
|
+
open={open}
|
|
123
|
+
progress={progress}
|
|
124
|
+
onPress={() => onClose()}
|
|
125
|
+
style={overlayStyle}
|
|
126
|
+
accessibilityLabel={overlayAccessibilityLabel}
|
|
127
|
+
/>
|
|
128
|
+
) : null}
|
|
129
|
+
</View>
|
|
130
|
+
<View
|
|
131
|
+
ref={drawerRef}
|
|
420
132
|
style={[
|
|
421
|
-
styles.
|
|
133
|
+
styles.drawer,
|
|
422
134
|
{
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
: 'row',
|
|
135
|
+
width: drawerWidth,
|
|
136
|
+
position: drawerType === 'permanent' ? 'relative' : 'absolute',
|
|
137
|
+
zIndex: drawerType === 'back' ? -1 : 0,
|
|
427
138
|
},
|
|
139
|
+
drawerAnimatedStyle,
|
|
140
|
+
drawerStyle,
|
|
428
141
|
]}
|
|
429
142
|
>
|
|
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>
|
|
143
|
+
{renderDrawerContent()}
|
|
144
|
+
</View>
|
|
145
|
+
</View>
|
|
472
146
|
</DrawerProgressContext.Provider>
|
|
473
|
-
</
|
|
147
|
+
</View>
|
|
474
148
|
);
|
|
475
149
|
}
|
|
476
150
|
|
|
@@ -489,11 +163,5 @@ const styles = StyleSheet.create({
|
|
|
489
163
|
},
|
|
490
164
|
main: {
|
|
491
165
|
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
166
|
},
|
|
499
167
|
});
|
|
@@ -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
|
+
});
|