@react-navigation/material-top-tabs 8.0.0-alpha.4 → 8.0.0-alpha.42

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 (30) hide show
  1. package/lib/module/index.js.map +1 -1
  2. package/lib/module/navigators/createMaterialTopTabNavigator.js +3 -7
  3. package/lib/module/navigators/createMaterialTopTabNavigator.js.map +1 -1
  4. package/lib/module/utils/useTabAnimation.js +1 -1
  5. package/lib/module/utils/useTabAnimation.js.map +1 -1
  6. package/lib/module/views/MaterialTopTabBar.js +53 -24
  7. package/lib/module/views/MaterialTopTabBar.js.map +1 -1
  8. package/lib/module/views/MaterialTopTabView.js +71 -10
  9. package/lib/module/views/MaterialTopTabView.js.map +1 -1
  10. package/lib/typescript/src/index.d.ts +5 -5
  11. package/lib/typescript/src/index.d.ts.map +1 -1
  12. package/lib/typescript/src/navigators/createMaterialTopTabNavigator.d.ts +9 -15
  13. package/lib/typescript/src/navigators/createMaterialTopTabNavigator.d.ts.map +1 -1
  14. package/lib/typescript/src/types.d.ts +61 -36
  15. package/lib/typescript/src/types.d.ts.map +1 -1
  16. package/lib/typescript/src/utils/TabAnimationContext.d.ts +1 -1
  17. package/lib/typescript/src/utils/TabAnimationContext.d.ts.map +1 -1
  18. package/lib/typescript/src/utils/useTabAnimation.d.ts +1 -1
  19. package/lib/typescript/src/utils/useTabAnimation.d.ts.map +1 -1
  20. package/lib/typescript/src/views/MaterialTopTabBar.d.ts +3 -2
  21. package/lib/typescript/src/views/MaterialTopTabBar.d.ts.map +1 -1
  22. package/lib/typescript/src/views/MaterialTopTabView.d.ts +2 -2
  23. package/lib/typescript/src/views/MaterialTopTabView.d.ts.map +1 -1
  24. package/package.json +19 -20
  25. package/src/index.tsx +1 -0
  26. package/src/navigators/createMaterialTopTabNavigator.tsx +10 -46
  27. package/src/types.tsx +76 -44
  28. package/src/utils/useTabAnimation.tsx +1 -1
  29. package/src/views/MaterialTopTabBar.tsx +76 -34
  30. package/src/views/MaterialTopTabView.tsx +100 -9
package/src/types.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Icon } from '@react-navigation/elements';
1
2
  import type {
2
3
  DefaultNavigatorOptions,
3
4
  Descriptor,
@@ -78,7 +79,7 @@ export type MaterialTopTabNavigationOptions = {
78
79
  /**
79
80
  * Title text for the screen.
80
81
  */
81
- title?: string;
82
+ title?: string | undefined;
82
83
 
83
84
  /**
84
85
  * Title string of a tab displayed in the tab bar
@@ -92,145 +93,163 @@ export type MaterialTopTabNavigationOptions = {
92
93
  focused: boolean;
93
94
  color: ColorValue;
94
95
  children: string;
95
- }) => React.ReactNode);
96
+ }) => React.ReactNode)
97
+ | undefined;
96
98
 
97
99
  /**
98
100
  * Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
99
101
  * It's recommended to set this if you don't have a label for the tab.
100
102
  */
101
- tabBarAccessibilityLabel?: string;
103
+ tabBarAccessibilityLabel?: string | undefined;
102
104
 
103
105
  /**
104
106
  * Whether label font should scale to respect Text Size accessibility settings.
105
107
  */
106
- tabBarAllowFontScaling?: boolean;
108
+ tabBarAllowFontScaling?: boolean | undefined;
107
109
 
108
110
  /**
109
111
  * Whether the tab label should be visible. Defaults to `true`.
110
112
  */
111
- tabBarShowLabel?: boolean;
113
+ tabBarShowLabel?: boolean | undefined;
112
114
 
113
115
  /**
114
- * A function that given { focused: boolean, color: ColorValue } returns a React.Node to display in the tab bar.
116
+ * Icon to display for the tab.
115
117
  */
116
- tabBarIcon?: (props: {
117
- focused: boolean;
118
- color: ColorValue;
119
- }) => React.ReactElement;
118
+ tabBarIcon?:
119
+ | Icon
120
+ | ((props: {
121
+ focused: boolean;
122
+ color: ColorValue;
123
+ size: number;
124
+ }) => Icon | React.ReactNode);
120
125
 
121
126
  /**
122
127
  * Whether the tab icon should be visible. Defaults to `false`.
123
128
  */
124
- tabBarShowIcon?: boolean;
129
+ tabBarShowIcon?: boolean | undefined;
125
130
 
126
131
  /**
127
132
  * Function that returns a React element to use as a badge for the tab.
128
133
  */
129
- tabBarBadge?: () => React.ReactElement;
134
+ tabBarBadge?: (() => React.ReactElement) | undefined;
130
135
 
131
136
  /**
132
137
  * Function that returns a React element as the tab bar indicator.
133
138
  */
134
- tabBarIndicator?: (
135
- props: Omit<
136
- Parameters<
137
- NonNullable<React.ComponentProps<typeof TabBar>['renderIndicator']>
138
- >[0],
139
- 'navigationState'
140
- > & { state: TabNavigationState<ParamListBase> }
141
- ) => React.ReactNode;
139
+ tabBarIndicator?:
140
+ | ((
141
+ props: Omit<
142
+ Parameters<
143
+ NonNullable<React.ComponentProps<typeof TabBar>['renderIndicator']>
144
+ >[0],
145
+ 'navigationState'
146
+ > & { state: TabNavigationState<ParamListBase> }
147
+ ) => React.ReactNode)
148
+ | undefined;
149
+
150
+ /**
151
+ * Variant of the tab bar.
152
+ *
153
+ * The following variants are available:
154
+ *
155
+ * - `primary`: the indicator matches the label & icon width and is centered under the tab.
156
+ * - `secondary`: the indicator spans the full tab width.
157
+ *
158
+ * Defaults to `primary`.
159
+ */
160
+ tabBarVariant?: 'primary' | 'secondary' | undefined;
142
161
 
143
162
  /**
144
163
  * Style object for the tab bar indicator.
145
164
  */
146
- tabBarIndicatorStyle?: StyleProp<ViewStyle>;
165
+ tabBarIndicatorStyle?: StyleProp<ViewStyle> | undefined;
147
166
 
148
167
  /**
149
168
  * Style object for the view containing the tab bar indicator.
150
169
  */
151
- tabBarIndicatorContainerStyle?: StyleProp<ViewStyle>;
170
+ tabBarIndicatorContainerStyle?: StyleProp<ViewStyle> | undefined;
152
171
 
153
172
  /**
154
173
  * ID to locate this tab button in tests.
155
174
  */
156
- tabBarButtonTestID?: string;
175
+ tabBarButtonTestID?: string | undefined;
157
176
 
158
177
  /**
159
178
  * Color for the icon and label in the active tab.
160
179
  */
161
- tabBarActiveTintColor?: ColorValue;
180
+ tabBarActiveTintColor?: ColorValue | undefined;
162
181
 
163
182
  /**
164
183
  * Color for the icon and label in the inactive tabs.
165
184
  */
166
- tabBarInactiveTintColor?: ColorValue;
185
+ tabBarInactiveTintColor?: ColorValue | undefined;
167
186
 
168
187
  /**
169
188
  * Color for material ripple (Android >= 5.0 only).
170
189
  */
171
- tabBarPressColor?: ColorValue;
190
+ tabBarPressColor?: ColorValue | undefined;
172
191
 
173
192
  /**
174
193
  * Opacity for pressed tab (iOS and Android < 5.0 only).
175
194
  */
176
- tabBarPressOpacity?: number;
195
+ tabBarPressOpacity?: number | undefined;
177
196
 
178
197
  /**
179
198
  * Boolean indicating whether the tab bar bounces when overscrolling.
180
199
  */
181
- tabBarBounces?: boolean;
200
+ tabBarBounces?: boolean | undefined;
182
201
 
183
202
  /**
184
203
  * Boolean indicating whether to make the tab bar scrollable.
185
204
  *
186
205
  * If you set this to `true`, you should also specify a width in `tabBarItemStyle` to improve the performance of initial render.
187
206
  */
188
- tabBarScrollEnabled?: boolean;
207
+ tabBarScrollEnabled?: boolean | undefined;
189
208
 
190
209
  /**
191
210
  * Style object for the tab label.
192
211
  */
193
- tabBarLabelStyle?: StyleProp<TextStyle>;
212
+ tabBarLabelStyle?: StyleProp<TextStyle> | undefined;
194
213
 
195
214
  /**
196
215
  * Style object for the individual tab items.
197
216
  */
198
- tabBarItemStyle?: StyleProp<ViewStyle>;
217
+ tabBarItemStyle?: StyleProp<ViewStyle> | undefined;
199
218
 
200
219
  /**
201
220
  * Style object for the view containing the tab items.
202
221
  */
203
- tabBarContentContainerStyle?: StyleProp<ViewStyle>;
222
+ tabBarContentContainerStyle?: StyleProp<ViewStyle> | undefined;
204
223
 
205
224
  /**
206
225
  * Style object for the the tab bar.
207
226
  */
208
- tabBarStyle?: StyleProp<ViewStyle>;
227
+ tabBarStyle?: StyleProp<ViewStyle> | undefined;
209
228
 
210
229
  /**
211
230
  * Gap between tabs
212
231
  */
213
- tabBarGap?: number;
232
+ tabBarGap?: number | undefined;
214
233
 
215
234
  /**
216
235
  * Allows to customize the android ripple effect (Android >= 5.0 only).
217
236
  *
218
- * Default: `{ borderless: true }`
237
+ * Default: `{ borderless: false, foreground: true }`
219
238
  */
220
- tabBarAndroidRipple?: PressableAndroidRippleConfig;
239
+ tabBarAndroidRipple?: PressableAndroidRippleConfig | undefined;
221
240
 
222
241
  /**
223
242
  * Whether to enable swipe gestures when this screen is focused.
224
243
  * Swipe gestures are enabled by default. Passing `false` will disable swipe gestures,
225
244
  * but the user can still switch tabs by pressing the tab bar.
226
245
  */
227
- swipeEnabled?: boolean;
246
+ swipeEnabled?: boolean | undefined;
228
247
 
229
248
  /**
230
249
  * Whether to enable animations when switching between tabs by pressing on the tab bar or programmatically.
231
250
  * Switching tab via swipe gesture will still result in an animation.
232
251
  */
233
- animationEnabled?: boolean;
252
+ animationEnabled?: boolean | undefined;
234
253
 
235
254
  /**
236
255
  * Whether this screen should be lazily rendered. When this is set to `true`,
@@ -243,13 +262,13 @@ export type MaterialTopTabNavigationOptions = {
243
262
  * when they come into the viewport. You can use the `lazyPlaceholder` prop to customize
244
263
  * what the user sees during this short period.
245
264
  */
246
- lazy?: boolean;
265
+ lazy?: boolean | undefined;
247
266
 
248
267
  /**
249
268
  * When `lazy` is enabled, you can specify how many adjacent screens should be preloaded in advance with this prop.
250
269
  * This value defaults to `0` which means lazy pages are loaded as they come into the viewport.
251
270
  */
252
- lazyPreloadDistance?: number;
271
+ lazyPreloadDistance?: number | undefined;
253
272
 
254
273
  /**
255
274
  * Function that returns a React element to render if this screen hasn't been rendered yet.
@@ -259,12 +278,25 @@ export type MaterialTopTabNavigationOptions = {
259
278
  *
260
279
  * By default, this renders `null`.
261
280
  */
262
- lazyPlaceholder?: () => React.ReactNode;
281
+ lazyPlaceholder?: (() => React.ReactNode) | undefined;
263
282
 
264
283
  /**
265
284
  * Style object for the component wrapping the screen content.
266
285
  */
267
- sceneStyle?: StyleProp<ViewStyle>;
286
+ sceneStyle?: StyleProp<ViewStyle> | undefined;
287
+
288
+ /**
289
+ * What should happen when screens become inactive.
290
+ * - `pause`: Effects are cleaned up.
291
+ * - `none`: Screen renders normally
292
+ *
293
+ * Defaults to `pause`.
294
+ *
295
+ * If you set `lazy: false` or preload a screen,
296
+ * It won't be paused until after the first time it becomes focused.
297
+ * This makes sure that effects are run to initialize the screen.
298
+ */
299
+ inactiveBehavior?: ('pause' | 'none') | undefined;
268
300
  };
269
301
 
270
302
  export type MaterialTopTabDescriptor = Descriptor<
@@ -297,11 +329,11 @@ export type MaterialTopTabNavigationConfig = Omit<
297
329
  /**
298
330
  * Function that returns a React element to display as the tab bar.
299
331
  */
300
- tabBar?: (props: MaterialTopTabBarProps) => React.ReactNode;
332
+ tabBar?: ((props: MaterialTopTabBarProps) => React.ReactNode) | undefined;
301
333
  /**
302
334
  * Function that returns a React element to override the underlying adapter.
303
335
  */
304
- adapter?: TabViewProps<Route<string>>['renderAdapter'];
336
+ adapter?: TabViewProps<Route<string>>['renderAdapter'] | undefined;
305
337
  };
306
338
 
307
339
  export type MaterialTopTabBarProps = Pick<
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { TabAnimationContext } from './TabAnimationContext';
4
4
 
5
5
  export function useTabAnimation() {
6
- const animation = React.useContext(TabAnimationContext);
6
+ const animation = React.use(TabAnimationContext);
7
7
 
8
8
  if (animation === undefined) {
9
9
  throw new Error(
@@ -1,19 +1,9 @@
1
- import { Text } from '@react-navigation/elements';
1
+ import { PlatformIcon, Text } from '@react-navigation/elements';
2
2
  import { Color } from '@react-navigation/elements/internal';
3
- import {
4
- type ParamListBase,
5
- type TabNavigationState,
6
- useLinkBuilder,
7
- useLocale,
8
- useTheme,
9
- } from '@react-navigation/native';
3
+ import { useLinkBuilder, useLocale, useTheme } from '@react-navigation/native';
4
+ import * as React from 'react';
10
5
  import { type ColorValue, StyleSheet } from 'react-native';
11
- import {
12
- type Route,
13
- TabBar,
14
- TabBarIndicator,
15
- type TabDescriptor,
16
- } from 'react-native-tab-view';
6
+ import { type Route, TabBar, type TabDescriptor } from 'react-native-tab-view';
17
7
 
18
8
  import type { MaterialTopTabBarProps } from '../types';
19
9
 
@@ -53,18 +43,34 @@ export function MaterialTopTabBar({
53
43
  const { direction } = useLocale();
54
44
  const { buildHref } = useLinkBuilder();
55
45
 
56
- const focusedOptions = descriptors[state.routes[state.index].key].options;
46
+ const focusedRoute = state.routes[state.index];
47
+
48
+ if (focusedRoute == null) {
49
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
50
+ }
51
+
52
+ const focusedOptions = descriptors[focusedRoute.key]?.options ?? {};
53
+ const tabBarVariant = focusedOptions.tabBarVariant ?? 'primary';
57
54
 
58
55
  const activeColor: ColorValue =
59
- focusedOptions.tabBarActiveTintColor ?? colors.text;
56
+ focusedOptions.tabBarActiveTintColor ??
57
+ (tabBarVariant === 'primary' ? colors.primary : colors.text);
58
+
60
59
  const inactiveColor: ColorValue =
61
60
  focusedOptions.tabBarInactiveTintColor ??
62
- Color(activeColor)?.alpha(0.5).string() ??
63
- (dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)');
61
+ Color(colors.text)?.alpha(0.68).string() ??
62
+ (dark ? 'rgba(255, 255, 255, 0.68)' : 'rgba(0, 0, 0, 0.68)');
63
+
64
+ const pressColor: ColorValue =
65
+ focusedOptions.tabBarPressColor ??
66
+ Color(tabBarVariant === 'primary' ? colors.primary : colors.text)
67
+ ?.alpha(0.12)
68
+ .string() ??
69
+ (dark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.12)');
64
70
 
65
71
  const tabBarOptions = Object.fromEntries(
66
72
  state.routes.map((route) => {
67
- const { options } = descriptors[route.key];
73
+ const options = descriptors[route.key]?.options ?? {};
68
74
 
69
75
  const {
70
76
  title,
@@ -79,6 +85,41 @@ export function MaterialTopTabBar({
79
85
  tabBarLabelStyle,
80
86
  } = options;
81
87
 
88
+ let icon;
89
+
90
+ if (tabBarShowIcon === false) {
91
+ icon = undefined;
92
+ } else if (tabBarIcon) {
93
+ icon = ({
94
+ focused,
95
+ color,
96
+ size,
97
+ }: {
98
+ focused: boolean;
99
+ color: ColorValue;
100
+ size: number;
101
+ }) => {
102
+ const iconValue =
103
+ typeof tabBarIcon === 'function'
104
+ ? tabBarIcon({ focused, color, size })
105
+ : tabBarIcon;
106
+
107
+ if (React.isValidElement(iconValue)) {
108
+ return iconValue;
109
+ }
110
+
111
+ if (
112
+ typeof iconValue === 'object' &&
113
+ iconValue != null &&
114
+ 'type' in iconValue
115
+ ) {
116
+ return <PlatformIcon icon={iconValue} size={size} color={color} />;
117
+ }
118
+
119
+ return null;
120
+ };
121
+ }
122
+
82
123
  return [
83
124
  route.key,
84
125
  {
@@ -86,14 +127,14 @@ export function MaterialTopTabBar({
86
127
  testID: tabBarButtonTestID,
87
128
  accessibilityLabel: tabBarAccessibilityLabel,
88
129
  badge: tabBarBadge,
89
- icon: tabBarShowIcon === false ? undefined : tabBarIcon,
130
+ icon,
90
131
  label:
91
132
  tabBarShowLabel === false
92
133
  ? undefined
93
134
  : typeof tabBarLabel === 'function'
94
135
  ? ({ labelText, color }: MaterialLabelProps) =>
95
136
  tabBarLabel({
96
- focused: state.routes[state.index].key === route.key,
137
+ focused: focusedRoute.key === route.key,
97
138
  color,
98
139
  children: labelText ?? route.name,
99
140
  })
@@ -113,6 +154,8 @@ export function MaterialTopTabBar({
113
154
  })
114
155
  );
115
156
 
157
+ const tabBarIndicator = focusedOptions.tabBarIndicator;
158
+
116
159
  return (
117
160
  <TabBar
118
161
  {...rest}
@@ -123,7 +166,7 @@ export function MaterialTopTabBar({
123
166
  bounces={focusedOptions.tabBarBounces}
124
167
  activeColor={activeColor}
125
168
  inactiveColor={inactiveColor}
126
- pressColor={focusedOptions.tabBarPressColor}
169
+ pressColor={pressColor}
127
170
  pressOpacity={focusedOptions.tabBarPressOpacity}
128
171
  tabStyle={focusedOptions.tabBarItemStyle}
129
172
  indicatorStyle={[
@@ -131,10 +174,14 @@ export function MaterialTopTabBar({
131
174
  focusedOptions.tabBarIndicatorStyle,
132
175
  ]}
133
176
  gap={focusedOptions.tabBarGap}
177
+ variant={tabBarVariant}
134
178
  android_ripple={focusedOptions.tabBarAndroidRipple}
135
179
  indicatorContainerStyle={focusedOptions.tabBarIndicatorContainerStyle}
136
180
  contentContainerStyle={focusedOptions.tabBarContentContainerStyle}
137
- style={[{ backgroundColor: colors.card }, focusedOptions.tabBarStyle]}
181
+ style={[
182
+ { backgroundColor: colors.card, borderBottomColor: colors.border },
183
+ focusedOptions.tabBarStyle,
184
+ ]}
138
185
  onTabPress={({ route, preventDefault }) => {
139
186
  const event = navigation.emit({
140
187
  type: 'tabPress',
@@ -152,16 +199,12 @@ export function MaterialTopTabBar({
152
199
  target: route.key,
153
200
  })
154
201
  }
155
- renderIndicator={({ navigationState: state, ...rest }) => {
156
- return focusedOptions.tabBarIndicator ? (
157
- focusedOptions.tabBarIndicator({
158
- state: state as TabNavigationState<ParamListBase>,
159
- ...rest,
160
- })
161
- ) : (
162
- <TabBarIndicator navigationState={state} {...rest} />
163
- );
164
- }}
202
+ renderIndicator={
203
+ tabBarIndicator
204
+ ? ({ navigationState: _, ...rest }) =>
205
+ tabBarIndicator({ ...rest, state })
206
+ : undefined
207
+ }
165
208
  />
166
209
  );
167
210
  }
@@ -170,7 +213,6 @@ const styles = StyleSheet.create({
170
213
  label: {
171
214
  textAlign: 'center',
172
215
  fontSize: 14,
173
- margin: 4,
174
216
  backgroundColor: 'transparent',
175
217
  },
176
218
  });
@@ -1,3 +1,4 @@
1
+ import { ActivityView } from '@react-navigation/elements/internal';
1
2
  import {
2
3
  CommonActions,
3
4
  type ParamListBase,
@@ -6,6 +7,8 @@ import {
6
7
  useLocale,
7
8
  useTheme,
8
9
  } from '@react-navigation/native';
10
+ import { useMemo, useState } from 'react';
11
+ import { type Animated, StyleSheet } from 'react-native';
9
12
  import { TabView } from 'react-native-tab-view';
10
13
 
11
14
  import type {
@@ -13,6 +16,7 @@ import type {
13
16
  MaterialTopTabDescriptorMap,
14
17
  MaterialTopTabNavigationConfig,
15
18
  MaterialTopTabNavigationHelpers,
19
+ MaterialTopTabNavigationOptions,
16
20
  } from '../types';
17
21
  import { TabAnimationContext } from '../utils/TabAnimationContext';
18
22
  import { MaterialTopTabBar } from './MaterialTopTabBar';
@@ -54,7 +58,21 @@ export function MaterialTopTabView({
54
58
  });
55
59
  };
56
60
 
57
- const focusedOptions = descriptors[state.routes[state.index].key].options;
61
+ const focusedRoute = state.routes[state.index];
62
+
63
+ if (focusedRoute == null) {
64
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
65
+ }
66
+
67
+ const focusedDescriptor = descriptors[focusedRoute.key];
68
+
69
+ if (focusedDescriptor == null) {
70
+ throw new Error(
71
+ `Couldn't find a descriptor for route '${focusedRoute.key}'.`
72
+ );
73
+ }
74
+
75
+ const focusedOptions = focusedDescriptor.options;
58
76
 
59
77
  return (
60
78
  <TabView
@@ -62,23 +80,42 @@ export function MaterialTopTabView({
62
80
  onIndexChange={(index) => {
63
81
  const route = state.routes[index];
64
82
 
83
+ if (route == null) {
84
+ throw new Error(`Couldn't find a route at index ${index}.`);
85
+ }
86
+
65
87
  navigation.dispatch({
66
- ...CommonActions.navigate(route),
88
+ ...CommonActions.navigate(route.name, route.params),
67
89
  target: state.key,
68
90
  });
69
91
  }}
70
- renderScene={({ route, position }) => (
71
- <TabAnimationContext.Provider value={{ position }}>
72
- {descriptors[route.key].render()}
73
- </TabAnimationContext.Provider>
74
- )}
92
+ renderScene={({ route, position }) => {
93
+ const descriptor = descriptors[route.key];
94
+
95
+ if (descriptor == null) {
96
+ throw new Error(
97
+ `Couldn't find a descriptor for route '${route.key}'.`
98
+ );
99
+ }
100
+
101
+ return (
102
+ <SceneContent
103
+ focused={route.key === focusedRoute.key}
104
+ preloaded={state.preloadedRouteKeys.includes(route.key)}
105
+ position={position}
106
+ options={descriptor.options}
107
+ >
108
+ {descriptor.render()}
109
+ </SceneContent>
110
+ );
111
+ }}
75
112
  navigationState={state}
76
113
  renderTabBar={renderTabBar}
77
114
  renderLazyPlaceholder={({ route }) =>
78
- descriptors[route.key].options.lazyPlaceholder?.() ?? null
115
+ descriptors[route.key]?.options.lazyPlaceholder?.() ?? null
79
116
  }
80
117
  lazy={({ route }) =>
81
- descriptors[route.key].options.lazy === true &&
118
+ descriptors[route.key]?.options.lazy === true &&
82
119
  !state.preloadedRouteKeys.includes(route.key)
83
120
  }
84
121
  lazyPreloadDistance={focusedOptions.lazyPreloadDistance}
@@ -105,3 +142,57 @@ export function MaterialTopTabView({
105
142
  />
106
143
  );
107
144
  }
145
+
146
+ function SceneContent({
147
+ focused,
148
+ preloaded,
149
+ position,
150
+ options,
151
+ children,
152
+ }: {
153
+ focused: boolean;
154
+ preloaded: boolean;
155
+ position: Animated.AnimatedInterpolation<number>;
156
+ options: MaterialTopTabNavigationOptions;
157
+ children: React.ReactNode;
158
+ }) {
159
+ const { inactiveBehavior = 'pause', lazy } = options;
160
+
161
+ const [loaded, setLoaded] = useState(focused);
162
+
163
+ if (focused && !loaded) {
164
+ setLoaded(true);
165
+ }
166
+
167
+ const animationContext = useMemo(() => ({ position }), [position]);
168
+
169
+ // For preloaded screens and if lazy is false,
170
+ // Keep them active so that the effects can run
171
+ const isActive =
172
+ inactiveBehavior === 'none' ||
173
+ focused ||
174
+ preloaded ||
175
+ (lazy === false && !loaded);
176
+
177
+ return (
178
+ <TabAnimationContext.Provider value={animationContext}>
179
+ <ActivityView
180
+ mode={isActive ? 'normal' : 'paused'}
181
+ visible={
182
+ // Tabs can be swiped quickly
183
+ // So we keep all tabs visible to avoid flash of blank screen
184
+ true
185
+ }
186
+ style={styles.scene}
187
+ >
188
+ {children}
189
+ </ActivityView>
190
+ </TabAnimationContext.Provider>
191
+ );
192
+ }
193
+
194
+ const styles = StyleSheet.create({
195
+ scene: {
196
+ flex: 1,
197
+ },
198
+ });