@react-navigation/bottom-tabs 7.5.0 → 7.7.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 (61) hide show
  1. package/lib/module/navigators/createBottomTabNavigator.js +2 -0
  2. package/lib/module/navigators/createBottomTabNavigator.js.map +1 -1
  3. package/lib/module/unstable/NativeBottomTabView.js +6 -0
  4. package/lib/module/unstable/NativeBottomTabView.js.map +1 -0
  5. package/lib/module/unstable/NativeBottomTabView.native.js +225 -0
  6. package/lib/module/unstable/NativeBottomTabView.native.js.map +1 -0
  7. package/lib/module/unstable/NativeScreen/NativeScreen.js +166 -0
  8. package/lib/module/unstable/NativeScreen/NativeScreen.js.map +1 -0
  9. package/lib/module/unstable/NativeScreen/debounce.js +12 -0
  10. package/lib/module/unstable/NativeScreen/debounce.js.map +1 -0
  11. package/lib/module/unstable/NativeScreen/types.js +4 -0
  12. package/lib/module/unstable/NativeScreen/types.js.map +1 -0
  13. package/lib/module/unstable/NativeScreen/useAnimatedHeaderHeight.js +12 -0
  14. package/lib/module/unstable/NativeScreen/useAnimatedHeaderHeight.js.map +1 -0
  15. package/lib/module/unstable/NativeScreen/useHeaderConfig.js +283 -0
  16. package/lib/module/unstable/NativeScreen/useHeaderConfig.js.map +1 -0
  17. package/lib/module/unstable/createNativeBottomTabNavigator.js +6 -0
  18. package/lib/module/unstable/createNativeBottomTabNavigator.js.map +1 -0
  19. package/lib/module/unstable/createNativeBottomTabNavigator.native.js +65 -0
  20. package/lib/module/unstable/createNativeBottomTabNavigator.native.js.map +1 -0
  21. package/lib/module/unstable/index.js +16 -0
  22. package/lib/module/unstable/index.js.map +1 -0
  23. package/lib/module/unstable/types.js +4 -0
  24. package/lib/module/unstable/types.js.map +1 -0
  25. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +1 -1
  26. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
  27. package/lib/typescript/src/unstable/NativeBottomTabView.d.ts +10 -0
  28. package/lib/typescript/src/unstable/NativeBottomTabView.d.ts.map +1 -0
  29. package/lib/typescript/src/unstable/NativeBottomTabView.native.d.ts +10 -0
  30. package/lib/typescript/src/unstable/NativeBottomTabView.native.d.ts.map +1 -0
  31. package/lib/typescript/src/unstable/NativeScreen/NativeScreen.d.ts +8 -0
  32. package/lib/typescript/src/unstable/NativeScreen/NativeScreen.d.ts.map +1 -0
  33. package/lib/typescript/src/unstable/NativeScreen/debounce.d.ts +2 -0
  34. package/lib/typescript/src/unstable/NativeScreen/debounce.d.ts.map +1 -0
  35. package/lib/typescript/src/unstable/NativeScreen/types.d.ts +467 -0
  36. package/lib/typescript/src/unstable/NativeScreen/types.d.ts.map +1 -0
  37. package/lib/typescript/src/unstable/NativeScreen/useAnimatedHeaderHeight.d.ts +5 -0
  38. package/lib/typescript/src/unstable/NativeScreen/useAnimatedHeaderHeight.d.ts.map +1 -0
  39. package/lib/typescript/src/unstable/NativeScreen/useHeaderConfig.d.ts +11 -0
  40. package/lib/typescript/src/unstable/NativeScreen/useHeaderConfig.d.ts.map +1 -0
  41. package/lib/typescript/src/unstable/createNativeBottomTabNavigator.d.ts +2 -0
  42. package/lib/typescript/src/unstable/createNativeBottomTabNavigator.d.ts.map +1 -0
  43. package/lib/typescript/src/unstable/createNativeBottomTabNavigator.native.d.ts +16 -0
  44. package/lib/typescript/src/unstable/createNativeBottomTabNavigator.native.d.ts.map +1 -0
  45. package/lib/typescript/src/unstable/index.d.ts +13 -0
  46. package/lib/typescript/src/unstable/index.d.ts.map +1 -0
  47. package/lib/typescript/src/unstable/types.d.ts +276 -0
  48. package/lib/typescript/src/unstable/types.d.ts.map +1 -0
  49. package/package.json +12 -6
  50. package/src/navigators/createBottomTabNavigator.tsx +2 -0
  51. package/src/unstable/NativeBottomTabView.native.tsx +303 -0
  52. package/src/unstable/NativeBottomTabView.tsx +20 -0
  53. package/src/unstable/NativeScreen/NativeScreen.tsx +242 -0
  54. package/src/unstable/NativeScreen/debounce.tsx +14 -0
  55. package/src/unstable/NativeScreen/types.ts +517 -0
  56. package/src/unstable/NativeScreen/useAnimatedHeaderHeight.tsx +18 -0
  57. package/src/unstable/NativeScreen/useHeaderConfig.tsx +423 -0
  58. package/src/unstable/createNativeBottomTabNavigator.native.tsx +116 -0
  59. package/src/unstable/createNativeBottomTabNavigator.tsx +4 -0
  60. package/src/unstable/index.tsx +22 -0
  61. package/src/unstable/types.tsx +353 -0
@@ -0,0 +1,242 @@
1
+ import {
2
+ getDefaultHeaderHeight,
3
+ HeaderHeightContext,
4
+ HeaderShownContext,
5
+ useFrameSize,
6
+ } from '@react-navigation/elements';
7
+ import * as React from 'react';
8
+ import {
9
+ Animated,
10
+ Platform,
11
+ StatusBar,
12
+ StyleSheet,
13
+ useAnimatedValue,
14
+ View,
15
+ } from 'react-native';
16
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
17
+ import { ScreenStack, ScreenStackItem } from 'react-native-screens';
18
+
19
+ import type { NativeBottomTabHeaderProps } from '../types';
20
+ import { debounce } from './debounce';
21
+ import { AnimatedHeaderHeightContext } from './useAnimatedHeaderHeight';
22
+ import { useHeaderConfig } from './useHeaderConfig';
23
+
24
+ type Props = NativeBottomTabHeaderProps & {
25
+ children: React.ReactNode;
26
+ };
27
+
28
+ const ANDROID_DEFAULT_HEADER_HEIGHT = 56;
29
+
30
+ export function NativeScreen({ route, navigation, options, children }: Props) {
31
+ const {
32
+ headerShown = true,
33
+ headerTransparent,
34
+ headerBackground,
35
+ header: renderCustomHeader,
36
+ } = options;
37
+
38
+ const isModal = false;
39
+ const insets = useSafeAreaInsets();
40
+
41
+ // Modals are fullscreen in landscape only on iPhone
42
+ const isIPhone = Platform.OS === 'ios' && !(Platform.isPad || Platform.isTV);
43
+
44
+ const isParentHeaderShown = React.useContext(HeaderShownContext);
45
+ const parentHeaderHeight = React.useContext(HeaderHeightContext);
46
+
47
+ const isLandscape = useFrameSize((frame) => frame.width > frame.height);
48
+
49
+ const topInset =
50
+ isParentHeaderShown ||
51
+ (Platform.OS === 'ios' && isModal) ||
52
+ (isIPhone && isLandscape)
53
+ ? 0
54
+ : insets.top;
55
+
56
+ const defaultHeaderHeight = useFrameSize((frame) =>
57
+ Platform.select({
58
+ // FIXME: Currently screens isn't using Material 3
59
+ // So our `getDefaultHeaderHeight` doesn't return the correct value
60
+ // So we hardcode the value here for now until screens is updated
61
+ android: ANDROID_DEFAULT_HEADER_HEIGHT + topInset,
62
+ default: getDefaultHeaderHeight(frame, isModal, topInset),
63
+ })
64
+ );
65
+
66
+ const [headerHeight, setHeaderHeight] = React.useState(defaultHeaderHeight);
67
+
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ const setHeaderHeightDebounced = React.useCallback(
70
+ // Debounce the header height updates to avoid excessive re-renders
71
+ debounce(setHeaderHeight, 100),
72
+ []
73
+ );
74
+
75
+ const hasCustomHeader = renderCustomHeader != null;
76
+
77
+ let headerHeightCorrectionOffset = 0;
78
+
79
+ if (Platform.OS === 'android' && !hasCustomHeader) {
80
+ const statusBarHeight = StatusBar.currentHeight ?? 0;
81
+
82
+ // FIXME: On Android, the native header height is not correctly calculated
83
+ // It includes status bar height even if statusbar is not translucent
84
+ // And the statusbar value itself doesn't match the actual status bar height
85
+ // So we subtract the bogus status bar height and add the actual top inset
86
+ headerHeightCorrectionOffset = -statusBarHeight + topInset;
87
+ }
88
+
89
+ const rawAnimatedHeaderHeight = useAnimatedValue(defaultHeaderHeight);
90
+ const animatedHeaderHeight = React.useMemo(
91
+ () =>
92
+ Animated.add<number>(
93
+ rawAnimatedHeaderHeight,
94
+ headerHeightCorrectionOffset
95
+ ),
96
+ [headerHeightCorrectionOffset, rawAnimatedHeaderHeight]
97
+ );
98
+
99
+ const headerTopInsetEnabled = topInset !== 0;
100
+
101
+ const onHeaderHeightChange = Animated.event(
102
+ [
103
+ {
104
+ nativeEvent: {
105
+ headerHeight: rawAnimatedHeaderHeight,
106
+ },
107
+ },
108
+ ],
109
+ {
110
+ useNativeDriver: true,
111
+ listener: (e) => {
112
+ if (hasCustomHeader) {
113
+ // If we have a custom header, don't use native header height
114
+ return;
115
+ }
116
+
117
+ if (
118
+ Platform.OS === 'android' &&
119
+ (options.headerBackground != null || options.headerTransparent)
120
+ ) {
121
+ // FIXME: On Android, we get 0 if the header is translucent
122
+ // So we set a default height in that case
123
+ setHeaderHeight(ANDROID_DEFAULT_HEADER_HEIGHT + topInset);
124
+ return;
125
+ }
126
+
127
+ if (
128
+ e.nativeEvent &&
129
+ typeof e.nativeEvent === 'object' &&
130
+ 'headerHeight' in e.nativeEvent &&
131
+ typeof e.nativeEvent.headerHeight === 'number'
132
+ ) {
133
+ const headerHeight =
134
+ e.nativeEvent.headerHeight + headerHeightCorrectionOffset;
135
+
136
+ // Only debounce if header has large title or search bar
137
+ // As it's the only case where the header height can change frequently
138
+ const doesHeaderAnimate =
139
+ Platform.OS === 'ios' &&
140
+ (options.headerLargeTitle || options.headerSearchBarOptions);
141
+
142
+ if (doesHeaderAnimate) {
143
+ setHeaderHeightDebounced(headerHeight);
144
+ } else {
145
+ setHeaderHeight(headerHeight);
146
+ }
147
+ }
148
+ },
149
+ }
150
+ );
151
+
152
+ const headerConfig = useHeaderConfig({
153
+ ...options,
154
+ route,
155
+ headerHeight,
156
+ headerShown: hasCustomHeader ? false : headerShown === true,
157
+ headerTopInsetEnabled,
158
+ });
159
+
160
+ return (
161
+ <ScreenStack style={styles.container}>
162
+ <ScreenStackItem
163
+ screenId={route.key}
164
+ headerConfig={headerConfig}
165
+ onHeaderHeightChange={onHeaderHeightChange}
166
+ >
167
+ <AnimatedHeaderHeightContext.Provider value={animatedHeaderHeight}>
168
+ <HeaderHeightContext.Provider
169
+ value={headerShown ? headerHeight : (parentHeaderHeight ?? 0)}
170
+ >
171
+ {headerBackground != null ? (
172
+ /**
173
+ * To show a custom header background, we render it at the top of the screen below the header
174
+ * The header also needs to be positioned absolutely (with `translucent` style)
175
+ */
176
+ <View
177
+ style={[
178
+ styles.background,
179
+ headerTransparent ? styles.translucent : null,
180
+ { height: headerHeight },
181
+ ]}
182
+ >
183
+ {headerBackground()}
184
+ </View>
185
+ ) : null}
186
+ {hasCustomHeader && headerShown ? (
187
+ <View
188
+ onLayout={(e) => {
189
+ const headerHeight = e.nativeEvent.layout.height;
190
+
191
+ setHeaderHeight(headerHeight);
192
+ rawAnimatedHeaderHeight.setValue(headerHeight);
193
+ }}
194
+ style={[
195
+ styles.header,
196
+ headerTransparent ? styles.absolute : null,
197
+ ]}
198
+ >
199
+ {renderCustomHeader?.({
200
+ route,
201
+ navigation,
202
+ options,
203
+ })}
204
+ </View>
205
+ ) : null}
206
+ <HeaderShownContext.Provider
207
+ value={isParentHeaderShown || headerShown}
208
+ >
209
+ {children}
210
+ </HeaderShownContext.Provider>
211
+ </HeaderHeightContext.Provider>
212
+ </AnimatedHeaderHeightContext.Provider>
213
+ </ScreenStackItem>
214
+ </ScreenStack>
215
+ );
216
+ }
217
+
218
+ const styles = StyleSheet.create({
219
+ container: {
220
+ flex: 1,
221
+ },
222
+ header: {
223
+ zIndex: 1,
224
+ },
225
+ absolute: {
226
+ position: 'absolute',
227
+ top: 0,
228
+ start: 0,
229
+ end: 0,
230
+ },
231
+ translucent: {
232
+ position: 'absolute',
233
+ top: 0,
234
+ start: 0,
235
+ end: 0,
236
+ zIndex: 1,
237
+ elevation: 1,
238
+ },
239
+ background: {
240
+ overflow: 'hidden',
241
+ },
242
+ });
@@ -0,0 +1,14 @@
1
+ export function debounce<T extends (...args: any[]) => void>(
2
+ func: T,
3
+ duration: number
4
+ ): T {
5
+ let timeout: ReturnType<typeof setTimeout>;
6
+
7
+ return function (this: unknown, ...args) {
8
+ clearTimeout(timeout);
9
+
10
+ timeout = setTimeout(() => {
11
+ func.apply(this, args);
12
+ }, duration);
13
+ } as T;
14
+ }