@zoverdoser/react-native-tab-view 1.0.0

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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/lib/module/Pager.android.js +4 -0
  4. package/lib/module/Pager.android.js.map +1 -0
  5. package/lib/module/Pager.ios.js +4 -0
  6. package/lib/module/Pager.ios.js.map +1 -0
  7. package/lib/module/Pager.js +4 -0
  8. package/lib/module/Pager.js.map +1 -0
  9. package/lib/module/PagerViewAdapter.js +131 -0
  10. package/lib/module/PagerViewAdapter.js.map +1 -0
  11. package/lib/module/PanResponderAdapter.js +209 -0
  12. package/lib/module/PanResponderAdapter.js.map +1 -0
  13. package/lib/module/PlatformPressable.js +61 -0
  14. package/lib/module/PlatformPressable.js.map +1 -0
  15. package/lib/module/SceneMap.js +24 -0
  16. package/lib/module/SceneMap.js.map +1 -0
  17. package/lib/module/SceneView.js +72 -0
  18. package/lib/module/SceneView.js.map +1 -0
  19. package/lib/module/TabBar.js +470 -0
  20. package/lib/module/TabBar.js.map +1 -0
  21. package/lib/module/TabBarIndicator.js +122 -0
  22. package/lib/module/TabBarIndicator.js.map +1 -0
  23. package/lib/module/TabBarItem.js +209 -0
  24. package/lib/module/TabBarItem.js.map +1 -0
  25. package/lib/module/TabBarItemLabel.js +34 -0
  26. package/lib/module/TabBarItemLabel.js.map +1 -0
  27. package/lib/module/TabView.js +141 -0
  28. package/lib/module/TabView.js.map +1 -0
  29. package/lib/module/index.js +8 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/package.json +1 -0
  32. package/lib/module/types.js +4 -0
  33. package/lib/module/types.js.map +1 -0
  34. package/lib/module/useAnimatedValue.js +12 -0
  35. package/lib/module/useAnimatedValue.js.map +1 -0
  36. package/lib/typescript/package.json +1 -0
  37. package/lib/typescript/src/Pager.android.d.ts +2 -0
  38. package/lib/typescript/src/Pager.android.d.ts.map +1 -0
  39. package/lib/typescript/src/Pager.d.ts +2 -0
  40. package/lib/typescript/src/Pager.d.ts.map +1 -0
  41. package/lib/typescript/src/Pager.ios.d.ts +2 -0
  42. package/lib/typescript/src/Pager.ios.d.ts.map +1 -0
  43. package/lib/typescript/src/PagerViewAdapter.d.ts +18 -0
  44. package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -0
  45. package/lib/typescript/src/PanResponderAdapter.d.ts +19 -0
  46. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -0
  47. package/lib/typescript/src/PlatformPressable.d.ts +14 -0
  48. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -0
  49. package/lib/typescript/src/SceneMap.d.ts +10 -0
  50. package/lib/typescript/src/SceneMap.d.ts.map +1 -0
  51. package/lib/typescript/src/SceneView.d.ts +16 -0
  52. package/lib/typescript/src/SceneView.d.ts.map +1 -0
  53. package/lib/typescript/src/TabBar.d.ts +31 -0
  54. package/lib/typescript/src/TabBar.d.ts.map +1 -0
  55. package/lib/typescript/src/TabBarIndicator.d.ts +15 -0
  56. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -0
  57. package/lib/typescript/src/TabBarItem.d.ts +18 -0
  58. package/lib/typescript/src/TabBarItem.d.ts.map +1 -0
  59. package/lib/typescript/src/TabBarItemLabel.d.ts +13 -0
  60. package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -0
  61. package/lib/typescript/src/TabView.d.ts +33 -0
  62. package/lib/typescript/src/TabView.d.ts.map +1 -0
  63. package/lib/typescript/src/index.d.ts +11 -0
  64. package/lib/typescript/src/index.d.ts.map +1 -0
  65. package/lib/typescript/src/types.d.ts +76 -0
  66. package/lib/typescript/src/types.d.ts.map +1 -0
  67. package/lib/typescript/src/useAnimatedValue.d.ts +3 -0
  68. package/lib/typescript/src/useAnimatedValue.d.ts.map +1 -0
  69. package/package.json +84 -0
  70. package/src/Pager.android.tsx +1 -0
  71. package/src/Pager.ios.tsx +1 -0
  72. package/src/Pager.tsx +1 -0
  73. package/src/PagerViewAdapter.tsx +186 -0
  74. package/src/PanResponderAdapter.tsx +344 -0
  75. package/src/PlatformPressable.tsx +91 -0
  76. package/src/SceneMap.tsx +30 -0
  77. package/src/SceneView.tsx +106 -0
  78. package/src/TabBar.tsx +725 -0
  79. package/src/TabBarIndicator.tsx +190 -0
  80. package/src/TabBarItem.tsx +301 -0
  81. package/src/TabBarItemLabel.tsx +50 -0
  82. package/src/TabView.tsx +198 -0
  83. package/src/index.tsx +16 -0
  84. package/src/types.tsx +94 -0
  85. package/src/useAnimatedValue.tsx +12 -0
@@ -0,0 +1,344 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ type GestureResponderEvent,
5
+ Keyboard,
6
+ PanResponder,
7
+ type PanResponderGestureState,
8
+ StyleSheet,
9
+ View,
10
+ } from 'react-native';
11
+ import useLatestCallback from 'use-latest-callback';
12
+
13
+ import type {
14
+ EventEmitterProps,
15
+ Layout,
16
+ Listener,
17
+ NavigationState,
18
+ PagerProps,
19
+ Route,
20
+ } from './types';
21
+ import { useAnimatedValue } from './useAnimatedValue';
22
+
23
+ type Props<T extends Route> = PagerProps & {
24
+ layout: Layout;
25
+ onIndexChange: (index: number) => void;
26
+ onTabSelect?: (props: { index: number }) => void;
27
+ navigationState: NavigationState<T>;
28
+ children: (
29
+ props: EventEmitterProps & {
30
+ // Animated value which represents the state of current index
31
+ // It can include fractional digits as it represents the intermediate value
32
+ position: Animated.AnimatedInterpolation<number>;
33
+ // Function to actually render the content of the pager
34
+ // The parent component takes care of rendering
35
+ render: (children: React.ReactNode) => React.ReactNode;
36
+ // Callback to call when switching the tab
37
+ // The tab switch animation is performed even if the index in state is unchanged
38
+ jumpTo: (key: string) => void;
39
+ }
40
+ ) => React.ReactElement;
41
+ };
42
+
43
+ const DEAD_ZONE = 12;
44
+
45
+ const DefaultTransitionSpec = {
46
+ timing: Animated.spring,
47
+ stiffness: 1000,
48
+ damping: 500,
49
+ mass: 3,
50
+ overshootClamping: true,
51
+ };
52
+
53
+ export function PanResponderAdapter<T extends Route>({
54
+ layout,
55
+ keyboardDismissMode = 'auto',
56
+ swipeEnabled = true,
57
+ navigationState,
58
+ onIndexChange,
59
+ onTabSelect,
60
+ onSwipeStart,
61
+ onSwipeEnd,
62
+ children,
63
+ style,
64
+ animationEnabled = false,
65
+ layoutDirection = 'ltr',
66
+ }: Props<T>) {
67
+ const { routes, index } = navigationState;
68
+
69
+ const panX = useAnimatedValue(0);
70
+
71
+ const listenersRef = React.useRef<Listener[]>([]);
72
+
73
+ const navigationStateRef = React.useRef(navigationState);
74
+ const layoutRef = React.useRef(layout);
75
+ const onIndexChangeRef = React.useRef(onIndexChange);
76
+ const onTabSelectRef = React.useRef(onTabSelect);
77
+ const currentIndexRef = React.useRef(index);
78
+ const pendingIndexRef = React.useRef<number>(undefined);
79
+
80
+ const swipeVelocityThreshold = 0.15;
81
+ const swipeDistanceThreshold = layout.width / 1.75;
82
+
83
+ const jumpToIndex = useLatestCallback(
84
+ (index: number, animate = animationEnabled) => {
85
+ const offset = -index * layoutRef.current.width;
86
+
87
+ const { timing, ...transitionConfig } = DefaultTransitionSpec;
88
+
89
+ if (animate) {
90
+ Animated.parallel([
91
+ timing(panX, {
92
+ ...transitionConfig,
93
+ toValue: offset,
94
+ useNativeDriver: false,
95
+ }),
96
+ ]).start(({ finished }) => {
97
+ if (finished) {
98
+ onIndexChangeRef.current(index);
99
+ onTabSelectRef.current?.({ index });
100
+ pendingIndexRef.current = undefined;
101
+ }
102
+ });
103
+ pendingIndexRef.current = index;
104
+ } else {
105
+ panX.setValue(offset);
106
+ onIndexChangeRef.current(index);
107
+ onTabSelectRef.current?.({ index });
108
+ pendingIndexRef.current = undefined;
109
+ }
110
+ }
111
+ );
112
+
113
+ React.useEffect(() => {
114
+ navigationStateRef.current = navigationState;
115
+ layoutRef.current = layout;
116
+ onIndexChangeRef.current = onIndexChange;
117
+ onTabSelectRef.current = onTabSelect;
118
+ });
119
+
120
+ React.useEffect(() => {
121
+ const offset = -navigationStateRef.current.index * layout.width;
122
+
123
+ panX.setValue(offset);
124
+ }, [layout.width, panX]);
125
+
126
+ React.useEffect(() => {
127
+ if (keyboardDismissMode === 'auto') {
128
+ Keyboard.dismiss();
129
+ }
130
+
131
+ if (layout.width && currentIndexRef.current !== index) {
132
+ currentIndexRef.current = index;
133
+ jumpToIndex(index);
134
+ }
135
+ }, [jumpToIndex, keyboardDismissMode, layout.width, index]);
136
+
137
+ const isMovingHorizontally = (
138
+ _: GestureResponderEvent,
139
+ gestureState: PanResponderGestureState
140
+ ) => {
141
+ return (
142
+ Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 2) &&
143
+ Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 2)
144
+ );
145
+ };
146
+
147
+ const canMoveScreen = (
148
+ event: GestureResponderEvent,
149
+ gestureState: PanResponderGestureState
150
+ ) => {
151
+ if (swipeEnabled === false) {
152
+ return false;
153
+ }
154
+
155
+ const diffX =
156
+ layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx;
157
+
158
+ return (
159
+ isMovingHorizontally(event, gestureState) &&
160
+ ((diffX >= DEAD_ZONE && currentIndexRef.current > 0) ||
161
+ (diffX <= -DEAD_ZONE && currentIndexRef.current < routes.length - 1))
162
+ );
163
+ };
164
+
165
+ const startGesture = () => {
166
+ onSwipeStart?.();
167
+
168
+ if (keyboardDismissMode === 'on-drag') {
169
+ Keyboard.dismiss();
170
+ }
171
+
172
+ panX.stopAnimation();
173
+ // @ts-expect-error: _value is private, but docs use it as well
174
+ panX.setOffset(panX._value);
175
+ };
176
+
177
+ const respondToGesture = (
178
+ _: GestureResponderEvent,
179
+ gestureState: PanResponderGestureState
180
+ ) => {
181
+ const diffX =
182
+ layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx;
183
+
184
+ if (
185
+ // swiping left
186
+ (diffX > 0 && index <= 0) ||
187
+ // swiping right
188
+ (diffX < 0 && index >= routes.length - 1)
189
+ ) {
190
+ return;
191
+ }
192
+
193
+ if (layout.width) {
194
+ // @ts-expect-error: _offset is private, but docs use it as well
195
+ const position = (panX._offset + diffX) / -layout.width;
196
+ const next =
197
+ position > index ? Math.ceil(position) : Math.floor(position);
198
+
199
+ if (next !== index) {
200
+ listenersRef.current.forEach((listener) => listener(next));
201
+ }
202
+ }
203
+
204
+ panX.setValue(diffX);
205
+ };
206
+
207
+ const finishGesture = (
208
+ _: GestureResponderEvent,
209
+ gestureState: PanResponderGestureState
210
+ ) => {
211
+ panX.flattenOffset();
212
+
213
+ onSwipeEnd?.();
214
+
215
+ const currentIndex =
216
+ typeof pendingIndexRef.current === 'number'
217
+ ? pendingIndexRef.current
218
+ : currentIndexRef.current;
219
+
220
+ let nextIndex = currentIndex;
221
+
222
+ if (
223
+ Math.abs(gestureState.dx) > Math.abs(gestureState.dy) &&
224
+ Math.abs(gestureState.vx) > Math.abs(gestureState.vy) &&
225
+ (Math.abs(gestureState.dx) > swipeDistanceThreshold ||
226
+ Math.abs(gestureState.vx) > swipeVelocityThreshold)
227
+ ) {
228
+ nextIndex = Math.round(
229
+ Math.min(
230
+ Math.max(
231
+ 0,
232
+ layoutDirection === 'rtl'
233
+ ? currentIndex + gestureState.dx / Math.abs(gestureState.dx)
234
+ : currentIndex - gestureState.dx / Math.abs(gestureState.dx)
235
+ ),
236
+ routes.length - 1
237
+ )
238
+ );
239
+
240
+ currentIndexRef.current = nextIndex;
241
+ }
242
+
243
+ if (!isFinite(nextIndex)) {
244
+ nextIndex = currentIndex;
245
+ }
246
+
247
+ jumpToIndex(nextIndex, true);
248
+ };
249
+
250
+ const addEnterListener = useLatestCallback((listener: Listener) => {
251
+ listenersRef.current.push(listener);
252
+
253
+ return () => {
254
+ const index = listenersRef.current.indexOf(listener);
255
+
256
+ if (index > -1) {
257
+ listenersRef.current.splice(index, 1);
258
+ }
259
+ };
260
+ });
261
+
262
+ const jumpTo = useLatestCallback((key: string) => {
263
+ const index = navigationStateRef.current.routes.findIndex(
264
+ (route: { key: string }) => route.key === key
265
+ );
266
+
267
+ jumpToIndex(index);
268
+ onIndexChange(index);
269
+ });
270
+
271
+ const panResponder = PanResponder.create({
272
+ onMoveShouldSetPanResponder: canMoveScreen,
273
+ onMoveShouldSetPanResponderCapture: canMoveScreen,
274
+ onPanResponderGrant: startGesture,
275
+ onPanResponderMove: respondToGesture,
276
+ onPanResponderTerminate: finishGesture,
277
+ onPanResponderRelease: finishGesture,
278
+ onPanResponderTerminationRequest: () => true,
279
+ });
280
+
281
+ const maxTranslate = layout.width * (routes.length - 1);
282
+ const translateX = Animated.multiply(
283
+ panX.interpolate({
284
+ inputRange: [-maxTranslate, 0],
285
+ outputRange: [-maxTranslate, 0],
286
+ extrapolate: 'clamp',
287
+ }),
288
+ layoutDirection === 'rtl' ? -1 : 1
289
+ );
290
+
291
+ const position = React.useMemo(
292
+ () => (layout.width ? Animated.divide(panX, -layout.width) : null),
293
+ [layout.width, panX]
294
+ );
295
+
296
+ return children({
297
+ position: position ?? new Animated.Value(index),
298
+ addEnterListener,
299
+ jumpTo,
300
+ render: (children) => (
301
+ <Animated.View
302
+ style={[
303
+ styles.sheet,
304
+ layout.width
305
+ ? {
306
+ width: routes.length * layout.width,
307
+ transform: [{ translateX }],
308
+ }
309
+ : null,
310
+ style,
311
+ ]}
312
+ {...panResponder.panHandlers}
313
+ >
314
+ {React.Children.map(children, (child, i) => {
315
+ const route = routes[i];
316
+ const focused = i === index;
317
+
318
+ return (
319
+ <View
320
+ key={route.key}
321
+ style={
322
+ layout.width
323
+ ? { width: layout.width }
324
+ : focused
325
+ ? StyleSheet.absoluteFill
326
+ : null
327
+ }
328
+ >
329
+ {focused || layout.width ? child : null}
330
+ </View>
331
+ );
332
+ })}
333
+ </Animated.View>
334
+ ),
335
+ });
336
+ }
337
+
338
+ const styles = StyleSheet.create({
339
+ sheet: {
340
+ flex: 1,
341
+ flexDirection: 'row',
342
+ alignItems: 'stretch',
343
+ },
344
+ });
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import {
3
+ type GestureResponderEvent,
4
+ Platform,
5
+ Pressable,
6
+ type PressableProps,
7
+ } from 'react-native';
8
+
9
+ export type Props = Omit<PressableProps, 'onPress'> & {
10
+ href?: string;
11
+ pressColor?: string;
12
+ pressOpacity?: number;
13
+ onPress?: (
14
+ e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
15
+ ) => void;
16
+ children: React.ReactNode;
17
+ };
18
+
19
+ const ANDROID_VERSION_LOLLIPOP = 21;
20
+ const ANDROID_SUPPORTS_RIPPLE =
21
+ Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
22
+
23
+ /**
24
+ * PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
25
+ */
26
+ export function PlatformPressable({
27
+ disabled,
28
+ android_ripple,
29
+ pressColor = 'rgba(0, 0, 0, .32)',
30
+ pressOpacity,
31
+ style,
32
+ onPress,
33
+ ...rest
34
+ }: Props) {
35
+ const handlePress = (
36
+ e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
37
+ ) => {
38
+ if (Platform.OS === 'web' && rest.href !== null) {
39
+ // ignore clicks with modifier keys
40
+ const hasModifierKey =
41
+ ('metaKey' in e && e.metaKey) ||
42
+ ('altKey' in e && e.altKey) ||
43
+ ('ctrlKey' in e && e.ctrlKey) ||
44
+ ('shiftKey' in e && e.shiftKey);
45
+
46
+ // only handle left clicks
47
+ const isLeftClick =
48
+ 'button' in e ? e.button == null || e.button === 0 : true;
49
+
50
+ // let browser handle "target=_blank" etc.
51
+ const isSelfTarget =
52
+ e.currentTarget && 'target' in e.currentTarget
53
+ ? [undefined, null, '', 'self'].includes(e.currentTarget.target)
54
+ : true;
55
+
56
+ if (!hasModifierKey && isLeftClick && isSelfTarget) {
57
+ e.preventDefault();
58
+ // call `onPress` only when browser default is prevented
59
+ // this prevents app from handling the click when a link is being opened
60
+ onPress?.(e);
61
+ }
62
+ } else {
63
+ onPress?.(e);
64
+ }
65
+ };
66
+
67
+ return (
68
+ <Pressable
69
+ android_ripple={
70
+ ANDROID_SUPPORTS_RIPPLE && !disabled
71
+ ? { color: pressColor, ...android_ripple }
72
+ : undefined
73
+ }
74
+ style={({ pressed }) => [
75
+ {
76
+ cursor:
77
+ (Platform.OS === 'web' || Platform.OS === 'ios') && !disabled
78
+ ? // Pointer cursor on web
79
+ // Hover effect on iPad and visionOS
80
+ 'pointer'
81
+ : 'auto',
82
+ opacity:
83
+ !ANDROID_SUPPORTS_RIPPLE && pressed && !disabled ? pressOpacity : 1,
84
+ } as const,
85
+ typeof style === 'function' ? style({ pressed }) : style,
86
+ ]}
87
+ onPress={disabled ? undefined : handlePress}
88
+ {...rest}
89
+ />
90
+ );
91
+ }
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ import type { SceneRendererProps } from './types';
4
+
5
+ type SceneProps = {
6
+ route: any;
7
+ } & Omit<SceneRendererProps, 'layout'>;
8
+
9
+ const SceneComponent = React.memo(
10
+ <T extends { component: React.ComponentType<any> } & SceneProps>({
11
+ component,
12
+ ...rest
13
+ }: T) => {
14
+ return React.createElement(component, rest);
15
+ }
16
+ );
17
+
18
+ SceneComponent.displayName = 'SceneComponent';
19
+
20
+ export function SceneMap<T>(scenes: { [key: string]: React.ComponentType<T> }) {
21
+ return ({ route, jumpTo, position }: SceneProps) => (
22
+ <SceneComponent
23
+ key={route.key}
24
+ component={scenes[route.key]}
25
+ route={route}
26
+ jumpTo={jumpTo}
27
+ position={position}
28
+ />
29
+ );
30
+ }
@@ -0,0 +1,106 @@
1
+ import * as React from 'react';
2
+ import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
3
+
4
+ import type {
5
+ EventEmitterProps,
6
+ NavigationState,
7
+ Route,
8
+ SceneRendererProps,
9
+ } from './types';
10
+
11
+ type Props<T extends Route> = SceneRendererProps &
12
+ EventEmitterProps & {
13
+ navigationState: NavigationState<T>;
14
+ lazy: boolean;
15
+ lazyPreloadDistance: number;
16
+ index: number;
17
+ children: (props: { loading: boolean }) => React.ReactNode;
18
+ style?: StyleProp<ViewStyle>;
19
+ };
20
+
21
+ export function SceneView<T extends Route>({
22
+ children,
23
+ navigationState,
24
+ lazy,
25
+ layout,
26
+ index,
27
+ lazyPreloadDistance,
28
+ addEnterListener,
29
+ style,
30
+ }: Props<T>) {
31
+ const [isLoading, setIsLoading] = React.useState(
32
+ Math.abs(navigationState.index - index) > lazyPreloadDistance
33
+ );
34
+
35
+ if (
36
+ isLoading &&
37
+ Math.abs(navigationState.index - index) <= lazyPreloadDistance
38
+ ) {
39
+ // Always render the route when it becomes focused
40
+ setIsLoading(false);
41
+ }
42
+
43
+ React.useEffect(() => {
44
+ const handleEnter = (value: number) => {
45
+ // If we're entering the current route, we need to load it
46
+ if (value === index) {
47
+ setIsLoading((prevState) => {
48
+ if (prevState) {
49
+ return false;
50
+ }
51
+ return prevState;
52
+ });
53
+ }
54
+ };
55
+
56
+ let unsubscribe: (() => void) | undefined;
57
+ let timer: ReturnType<typeof setTimeout> | undefined;
58
+
59
+ if (lazy && isLoading) {
60
+ // If lazy mode is enabled, listen to when we enter screens
61
+ unsubscribe = addEnterListener(handleEnter);
62
+ } else if (isLoading) {
63
+ // If lazy mode is not enabled, render the scene with a delay if not loaded already
64
+ // This improves the initial startup time as the scene is no longer blocking
65
+ timer = setTimeout(() => setIsLoading(false), 0);
66
+ }
67
+
68
+ return () => {
69
+ unsubscribe?.();
70
+ clearTimeout(timer);
71
+ };
72
+ }, [addEnterListener, index, isLoading, lazy]);
73
+
74
+ const focused = navigationState.index === index;
75
+
76
+ return (
77
+ <View
78
+ aria-hidden={!focused}
79
+ style={[
80
+ styles.route,
81
+ // If we don't have the layout yet, make the focused screen fill the container
82
+ // This avoids delay before we are able to render pages side by side
83
+ layout.width
84
+ ? { width: layout.width }
85
+ : focused
86
+ ? StyleSheet.absoluteFill
87
+ : null,
88
+ style,
89
+ ]}
90
+ >
91
+ {
92
+ // Only render the route only if it's either focused or layout is available
93
+ // When layout is not available, we must not render unfocused routes
94
+ // so that the focused route can fill the screen
95
+ focused || layout.width ? children({ loading: isLoading }) : null
96
+ }
97
+ </View>
98
+ );
99
+ }
100
+
101
+ const styles = StyleSheet.create({
102
+ route: {
103
+ flex: 1,
104
+ overflow: 'hidden',
105
+ },
106
+ });