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