react-native-tab-view 3.2.0 → 3.3.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 (54) hide show
  1. package/README.md +4 -0
  2. package/lib/commonjs/PagerViewAdapter.js +25 -8
  3. package/lib/commonjs/PagerViewAdapter.js.map +1 -1
  4. package/lib/commonjs/PanResponderAdapter.js +29 -20
  5. package/lib/commonjs/PanResponderAdapter.js.map +1 -1
  6. package/lib/commonjs/SceneMap.js +9 -12
  7. package/lib/commonjs/SceneMap.js.map +1 -1
  8. package/lib/commonjs/SceneView.js +54 -101
  9. package/lib/commonjs/SceneView.js.map +1 -1
  10. package/lib/commonjs/TabBar.js +327 -327
  11. package/lib/commonjs/TabBar.js.map +1 -1
  12. package/lib/commonjs/TabBarIndicator.js +81 -99
  13. package/lib/commonjs/TabBarIndicator.js.map +1 -1
  14. package/lib/commonjs/TabBarItem.js +184 -161
  15. package/lib/commonjs/TabBarItem.js.map +1 -1
  16. package/lib/commonjs/TabView.js +3 -1
  17. package/lib/commonjs/TabView.js.map +1 -1
  18. package/lib/commonjs/types.js.map +1 -1
  19. package/lib/module/PagerViewAdapter.js +25 -8
  20. package/lib/module/PagerViewAdapter.js.map +1 -1
  21. package/lib/module/PanResponderAdapter.js +29 -19
  22. package/lib/module/PanResponderAdapter.js.map +1 -1
  23. package/lib/module/SceneMap.js +9 -14
  24. package/lib/module/SceneMap.js.map +1 -1
  25. package/lib/module/SceneView.js +53 -98
  26. package/lib/module/SceneView.js.map +1 -1
  27. package/lib/module/TabBar.js +323 -323
  28. package/lib/module/TabBar.js.map +1 -1
  29. package/lib/module/TabBarIndicator.js +74 -92
  30. package/lib/module/TabBarIndicator.js.map +1 -1
  31. package/lib/module/TabBarItem.js +178 -154
  32. package/lib/module/TabBarItem.js.map +1 -1
  33. package/lib/module/TabView.js +3 -1
  34. package/lib/module/TabView.js.map +1 -1
  35. package/lib/module/types.js.map +1 -1
  36. package/lib/typescript/PagerViewAdapter.d.ts +1 -1
  37. package/lib/typescript/PanResponderAdapter.d.ts +1 -1
  38. package/lib/typescript/SceneMap.d.ts +5 -3
  39. package/lib/typescript/SceneView.d.ts +1 -18
  40. package/lib/typescript/TabBar.d.ts +7 -38
  41. package/lib/typescript/TabBarIndicator.d.ts +2 -10
  42. package/lib/typescript/TabBarItem.d.ts +3 -5
  43. package/lib/typescript/TabView.d.ts +1 -1
  44. package/lib/typescript/types.d.ts +1 -0
  45. package/package.json +4 -1
  46. package/src/PagerViewAdapter.tsx +29 -10
  47. package/src/PanResponderAdapter.tsx +29 -20
  48. package/src/SceneMap.tsx +11 -7
  49. package/src/SceneView.tsx +70 -106
  50. package/src/TabBar.tsx +451 -391
  51. package/src/TabBarIndicator.tsx +108 -114
  52. package/src/TabBarItem.tsx +214 -185
  53. package/src/TabView.tsx +2 -0
  54. package/src/types.tsx +1 -0
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react-native';
11
11
 
12
12
  import type { Route, SceneRendererProps, NavigationState } from './types';
13
+ import useAnimatedValue from './useAnimatedValue';
13
14
 
14
15
  export type GetTabWidth = (index: number) => number;
15
16
 
@@ -21,126 +22,119 @@ export type Props<T extends Route> = SceneRendererProps & {
21
22
  gap?: number;
22
23
  };
23
24
 
24
- export default class TabBarIndicator<T extends Route> extends React.Component<
25
- Props<T>
26
- > {
27
- componentDidMount() {
28
- this.fadeInIndicator();
29
- }
25
+ const getTranslateX = (
26
+ position: Animated.AnimatedInterpolation,
27
+ routes: Route[],
28
+ getTabWidth: GetTabWidth,
29
+ gap?: number
30
+ ) => {
31
+ const inputRange = routes.map((_, i) => i);
32
+
33
+ // every index contains widths at all previous indices
34
+ const outputRange = routes.reduce<number[]>((acc, _, i) => {
35
+ if (i === 0) return [0];
36
+ return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
37
+ }, []);
38
+
39
+ const translateX = position.interpolate({
40
+ inputRange,
41
+ outputRange,
42
+ extrapolate: 'clamp',
43
+ });
44
+
45
+ return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
46
+ };
30
47
 
31
- componentDidUpdate() {
32
- this.fadeInIndicator();
48
+ export default function TabBarIndicator<T extends Route>({
49
+ getTabWidth,
50
+ layout,
51
+ navigationState,
52
+ position,
53
+ width,
54
+ gap,
55
+ style,
56
+ }: Props<T>) {
57
+ const isIndicatorShown = React.useRef(false);
58
+ const isWidthDynamic = width === 'auto';
59
+
60
+ const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
61
+
62
+ const hasMeasuredTabWidths =
63
+ Boolean(layout.width) &&
64
+ navigationState.routes.every((_, i) => getTabWidth(i));
65
+
66
+ React.useEffect(() => {
67
+ const fadeInIndicator = () => {
68
+ if (
69
+ !isIndicatorShown.current &&
70
+ isWidthDynamic &&
71
+ // We should fade-in the indicator when we have widths for all the tab items
72
+ hasMeasuredTabWidths
73
+ ) {
74
+ isIndicatorShown.current = true;
75
+
76
+ Animated.timing(opacity, {
77
+ toValue: 1,
78
+ duration: 150,
79
+ easing: Easing.in(Easing.linear),
80
+ useNativeDriver: true,
81
+ }).start();
82
+ }
83
+ };
84
+
85
+ fadeInIndicator();
86
+
87
+ return () => opacity.stopAnimation();
88
+ }, [hasMeasuredTabWidths, isWidthDynamic, opacity]);
89
+
90
+ const { routes } = navigationState;
91
+
92
+ const transform = [];
93
+
94
+ if (layout.width) {
95
+ const translateX =
96
+ routes.length > 1 ? getTranslateX(position, routes, getTabWidth, gap) : 0;
97
+
98
+ transform.push({ translateX });
33
99
  }
34
100
 
35
- private fadeInIndicator = () => {
36
- const { navigationState, layout, width, getTabWidth } = this.props;
37
-
38
- if (
39
- !this.isIndicatorShown &&
40
- width === 'auto' &&
41
- layout.width &&
42
- // We should fade-in the indicator when we have widths for all the tab items
43
- navigationState.routes.every((_, i) => getTabWidth(i))
44
- ) {
45
- this.isIndicatorShown = true;
46
-
47
- Animated.timing(this.opacity, {
48
- toValue: 1,
49
- duration: 150,
50
- easing: Easing.in(Easing.linear),
51
- useNativeDriver: true,
52
- }).start();
53
- }
54
- };
55
-
56
- private isIndicatorShown = false;
57
-
58
- private opacity = new Animated.Value(this.props.width === 'auto' ? 0 : 1);
59
-
60
- private getTranslateX = (
61
- position: Animated.AnimatedInterpolation,
62
- routes: Route[],
63
- getTabWidth: GetTabWidth,
64
- gap?: number
65
- ) => {
101
+ if (width === 'auto') {
66
102
  const inputRange = routes.map((_, i) => i);
67
-
68
- // every index contains widths at all previous indices
69
- const outputRange = routes.reduce<number[]>((acc, _, i) => {
70
- if (i === 0) return [0];
71
- return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
72
- }, []);
73
-
74
- const translateX = position.interpolate({
75
- inputRange,
76
- outputRange,
77
- extrapolate: 'clamp',
78
- });
79
-
80
- return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
81
- };
82
-
83
- render() {
84
- const {
85
- position,
86
- navigationState,
87
- getTabWidth,
88
- width,
89
- style,
90
- layout,
91
- gap,
92
- } = this.props;
93
- const { routes } = navigationState;
94
-
95
- const transform = [];
96
-
97
- if (layout.width) {
98
- const translateX =
99
- routes.length > 1
100
- ? this.getTranslateX(position, routes, getTabWidth, gap)
101
- : 0;
102
-
103
- transform.push({ translateX });
104
- }
105
-
106
- if (width === 'auto') {
107
- const inputRange = routes.map((_, i) => i);
108
- const outputRange = inputRange.map(getTabWidth);
109
-
110
- transform.push(
111
- {
112
- scaleX:
113
- routes.length > 1
114
- ? position.interpolate({
115
- inputRange,
116
- outputRange,
117
- extrapolate: 'clamp',
118
- })
119
- : outputRange[0],
120
- },
121
- { translateX: 0.5 }
122
- );
123
- }
124
-
125
- return (
126
- <Animated.View
127
- style={[
128
- styles.indicator,
129
- { width: width === 'auto' ? 1 : width },
130
- // If layout is not available, use `left` property for positioning the indicator
131
- // This avoids rendering delay until we are able to calculate translateX
132
- // If platform is macos use `left` property as `transform` is broken at the moment.
133
- // See: https://github.com/microsoft/react-native-macos/issues/280
134
- layout.width && Platform.OS !== 'macos'
135
- ? { left: 0 }
136
- : { left: `${(100 / routes.length) * navigationState.index}%` },
137
- { transform },
138
- width === 'auto' ? { opacity: this.opacity } : null,
139
- style,
140
- ]}
141
- />
103
+ const outputRange = inputRange.map(getTabWidth);
104
+
105
+ transform.push(
106
+ {
107
+ scaleX:
108
+ routes.length > 1
109
+ ? position.interpolate({
110
+ inputRange,
111
+ outputRange,
112
+ extrapolate: 'clamp',
113
+ })
114
+ : outputRange[0],
115
+ },
116
+ { translateX: 0.5 }
142
117
  );
143
118
  }
119
+
120
+ return (
121
+ <Animated.View
122
+ style={[
123
+ styles.indicator,
124
+ { width: width === 'auto' ? 1 : width },
125
+ // If layout is not available, use `left` property for positioning the indicator
126
+ // This avoids rendering delay until we are able to calculate translateX
127
+ // If platform is macos use `left` property as `transform` is broken at the moment.
128
+ // See: https://github.com/microsoft/react-native-macos/issues/280
129
+ layout.width && Platform.OS !== 'macos'
130
+ ? { left: 0 }
131
+ : { left: `${(100 / routes.length) * navigationState.index}%` },
132
+ { transform },
133
+ width === 'auto' ? { opacity: opacity } : null,
134
+ style,
135
+ ]}
136
+ />
137
+ );
144
138
  }
145
139
 
146
140
  const styles = StyleSheet.create({
@@ -8,6 +8,7 @@ import {
8
8
  TextStyle,
9
9
  ViewStyle,
10
10
  } from 'react-native';
11
+ import useLatestCallback from 'use-latest-callback';
11
12
  import PlatformPressable from './PlatformPressable';
12
13
  import type { Scene, Route, NavigationState } from './types';
13
14
 
@@ -37,6 +38,7 @@ export type Props<T extends Route> = {
37
38
  onLayout?: (event: LayoutChangeEvent) => void;
38
39
  onPress: () => void;
39
40
  onLongPress: () => void;
41
+ defaultTabWidth?: number;
40
42
  labelStyle?: StyleProp<TextStyle>;
41
43
  style: StyleProp<ViewStyle>;
42
44
  };
@@ -44,219 +46,246 @@ export type Props<T extends Route> = {
44
46
  const DEFAULT_ACTIVE_COLOR = 'rgba(255, 255, 255, 1)';
45
47
  const DEFAULT_INACTIVE_COLOR = 'rgba(255, 255, 255, 0.7)';
46
48
 
47
- export default class TabBarItem<T extends Route> extends React.Component<
48
- Props<T>
49
- > {
50
- private getActiveOpacity = (
51
- position: Animated.AnimatedInterpolation,
52
- routes: Route[],
53
- tabIndex: number
54
- ) => {
55
- if (routes.length > 1) {
56
- const inputRange = routes.map((_, i) => i);
49
+ const getActiveOpacity = (
50
+ position: Animated.AnimatedInterpolation,
51
+ routesLength: number,
52
+ tabIndex: number
53
+ ) => {
54
+ if (routesLength > 1) {
55
+ const inputRange = Array.from({ length: routesLength }, (_, i) => i);
57
56
 
58
- return position.interpolate({
59
- inputRange,
60
- outputRange: inputRange.map((i) => (i === tabIndex ? 1 : 0)),
61
- });
62
- } else {
63
- return 1;
64
- }
65
- };
66
-
67
- private getInactiveOpacity = (
68
- position: Animated.AnimatedInterpolation,
69
- routes: Route[],
70
- tabIndex: number
71
- ) => {
72
- if (routes.length > 1) {
73
- const inputRange = routes.map((_: Route, i: number) => i);
74
-
75
- return position.interpolate({
76
- inputRange,
77
- outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
78
- });
79
- } else {
80
- return 0;
81
- }
82
- };
83
-
84
- render() {
85
- const {
86
- route,
87
- position,
88
- navigationState,
89
- renderLabel: renderLabelCustom,
90
- renderIcon,
91
- renderBadge,
92
- getLabelText,
93
- getTestID,
94
- getAccessibilityLabel,
95
- getAccessible,
96
- activeColor: activeColorCustom,
97
- inactiveColor: inactiveColorCustom,
98
- pressColor,
99
- pressOpacity,
100
- labelStyle,
101
- style,
102
- onLayout,
103
- onPress,
104
- onLongPress,
105
- } = this.props;
106
-
107
- const tabIndex = navigationState.routes.indexOf(route);
108
- const isFocused = navigationState.index === tabIndex;
109
-
110
- const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
111
-
112
- const activeColor =
113
- activeColorCustom !== undefined
114
- ? activeColorCustom
115
- : typeof labelColorFromStyle === 'string'
116
- ? labelColorFromStyle
117
- : DEFAULT_ACTIVE_COLOR;
118
- const inactiveColor =
119
- inactiveColorCustom !== undefined
120
- ? inactiveColorCustom
121
- : typeof labelColorFromStyle === 'string'
122
- ? labelColorFromStyle
123
- : DEFAULT_INACTIVE_COLOR;
57
+ return position.interpolate({
58
+ inputRange,
59
+ outputRange: inputRange.map((i) => (i === tabIndex ? 1 : 0)),
60
+ });
61
+ } else {
62
+ return 1;
63
+ }
64
+ };
124
65
 
125
- const activeOpacity = this.getActiveOpacity(
126
- position,
127
- navigationState.routes,
128
- tabIndex
129
- );
130
- const inactiveOpacity = this.getInactiveOpacity(
131
- position,
132
- navigationState.routes,
133
- tabIndex
134
- );
66
+ const getInactiveOpacity = (
67
+ position: Animated.AnimatedInterpolation,
68
+ routesLength: number,
69
+ tabIndex: number
70
+ ) => {
71
+ if (routesLength > 1) {
72
+ const inputRange = Array.from({ length: routesLength }, (_, i) => i);
135
73
 
136
- let icon: React.ReactNode | null = null;
137
- let label: React.ReactNode | null = null;
74
+ return position.interpolate({
75
+ inputRange,
76
+ outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
77
+ });
78
+ } else {
79
+ return 0;
80
+ }
81
+ };
138
82
 
139
- if (renderIcon) {
140
- const activeIcon = renderIcon({
141
- route,
142
- focused: true,
143
- color: activeColor,
144
- });
145
- const inactiveIcon = renderIcon({
146
- route,
147
- focused: false,
148
- color: inactiveColor,
149
- });
83
+ type TabBarItemInternalProps<T extends Route> = Omit<
84
+ Props<T>,
85
+ 'navigationState'
86
+ > & {
87
+ isFocused: boolean;
88
+ index: number;
89
+ routesLength: number;
90
+ };
150
91
 
151
- if (inactiveIcon != null && activeIcon != null) {
152
- icon = (
153
- <View style={styles.icon}>
154
- <Animated.View style={{ opacity: inactiveOpacity }}>
155
- {inactiveIcon}
156
- </Animated.View>
157
- <Animated.View
158
- style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
159
- >
160
- {activeIcon}
161
- </Animated.View>
162
- </View>
163
- );
164
- }
165
- }
92
+ const TabBarItemInternal = <T extends Route>({
93
+ getAccessibilityLabel,
94
+ getAccessible,
95
+ getLabelText,
96
+ getTestID,
97
+ onLongPress,
98
+ onPress,
99
+ isFocused,
100
+ position,
101
+ route,
102
+ style,
103
+ inactiveColor: inactiveColorCustom,
104
+ activeColor: activeColorCustom,
105
+ labelStyle,
106
+ onLayout,
107
+ index: tabIndex,
108
+ pressColor,
109
+ pressOpacity,
110
+ renderBadge,
111
+ renderIcon,
112
+ defaultTabWidth,
113
+ routesLength,
114
+ renderLabel: renderLabelCustom,
115
+ }: TabBarItemInternalProps<T>) => {
116
+ const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
166
117
 
167
- const renderLabel =
168
- renderLabelCustom !== undefined
169
- ? renderLabelCustom
170
- : ({ route, color }: { route: T; color: string }) => {
171
- const labelText = getLabelText({ route });
118
+ const activeColor =
119
+ activeColorCustom !== undefined
120
+ ? activeColorCustom
121
+ : typeof labelColorFromStyle === 'string'
122
+ ? labelColorFromStyle
123
+ : DEFAULT_ACTIVE_COLOR;
124
+ const inactiveColor =
125
+ inactiveColorCustom !== undefined
126
+ ? inactiveColorCustom
127
+ : typeof labelColorFromStyle === 'string'
128
+ ? labelColorFromStyle
129
+ : DEFAULT_INACTIVE_COLOR;
172
130
 
173
- if (typeof labelText === 'string') {
174
- return (
175
- <Animated.Text
176
- style={[
177
- styles.label,
178
- icon ? { marginTop: 0 } : null,
179
- labelStyle,
180
- { color },
181
- ]}
182
- >
183
- {labelText}
184
- </Animated.Text>
185
- );
186
- }
131
+ const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
132
+ const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
187
133
 
188
- return labelText;
189
- };
134
+ let icon: React.ReactNode | null = null;
135
+ let label: React.ReactNode | null = null;
190
136
 
191
- if (renderLabel) {
192
- const activeLabel = renderLabel({
193
- route,
194
- focused: true,
195
- color: activeColor,
196
- });
197
- const inactiveLabel = renderLabel({
198
- route,
199
- focused: false,
200
- color: inactiveColor,
201
- });
137
+ if (renderIcon) {
138
+ const activeIcon = renderIcon({
139
+ route,
140
+ focused: true,
141
+ color: activeColor,
142
+ });
143
+ const inactiveIcon = renderIcon({
144
+ route,
145
+ focused: false,
146
+ color: inactiveColor,
147
+ });
202
148
 
203
- label = (
204
- <View>
149
+ if (inactiveIcon != null && activeIcon != null) {
150
+ icon = (
151
+ <View style={styles.icon}>
205
152
  <Animated.View style={{ opacity: inactiveOpacity }}>
206
- {inactiveLabel}
153
+ {inactiveIcon}
207
154
  </Animated.View>
208
155
  <Animated.View
209
156
  style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
210
157
  >
211
- {activeLabel}
158
+ {activeIcon}
212
159
  </Animated.View>
213
160
  </View>
214
161
  );
215
162
  }
163
+ }
216
164
 
217
- const tabStyle = StyleSheet.flatten(style);
218
- const isWidthSet = tabStyle?.width !== undefined;
219
- const tabContainerStyle: ViewStyle | null = isWidthSet ? null : { flex: 1 };
220
-
221
- const scene = { route };
165
+ const renderLabel =
166
+ renderLabelCustom !== undefined
167
+ ? renderLabelCustom
168
+ : (labelProps: { route: T; color: string }) => {
169
+ const labelText = getLabelText({ route: labelProps.route });
222
170
 
223
- let accessibilityLabel = getAccessibilityLabel(scene);
171
+ if (typeof labelText === 'string') {
172
+ return (
173
+ <Animated.Text
174
+ style={[
175
+ styles.label,
176
+ icon ? { marginTop: 0 } : null,
177
+ labelStyle,
178
+ { color: labelProps.color },
179
+ ]}
180
+ >
181
+ {labelText}
182
+ </Animated.Text>
183
+ );
184
+ }
224
185
 
225
- accessibilityLabel =
226
- typeof accessibilityLabel !== 'undefined'
227
- ? accessibilityLabel
228
- : getLabelText(scene);
186
+ return labelText;
187
+ };
229
188
 
230
- const badge = renderBadge ? renderBadge(scene) : null;
189
+ if (renderLabel) {
190
+ const activeLabel = renderLabel({
191
+ route,
192
+ focused: true,
193
+ color: activeColor,
194
+ });
195
+ const inactiveLabel = renderLabel({
196
+ route,
197
+ focused: false,
198
+ color: inactiveColor,
199
+ });
231
200
 
232
- return (
233
- <PlatformPressable
234
- android_ripple={{ borderless: true }}
235
- testID={getTestID(scene)}
236
- accessible={getAccessible(scene)}
237
- accessibilityLabel={accessibilityLabel}
238
- accessibilityRole="tab"
239
- accessibilityState={{ selected: isFocused }}
240
- // @ts-ignore: this is to support older React Native versions
241
- accessibilityStates={isFocused ? ['selected'] : []}
242
- pressColor={pressColor}
243
- pressOpacity={pressOpacity}
244
- delayPressIn={0}
245
- onLayout={onLayout}
246
- onPress={onPress}
247
- onLongPress={onLongPress}
248
- style={[styles.pressable, tabContainerStyle]}
249
- >
250
- <View pointerEvents="none" style={[styles.item, tabStyle]}>
251
- {icon}
252
- {label}
253
- {badge != null ? <View style={styles.badge}>{badge}</View> : null}
254
- </View>
255
- </PlatformPressable>
201
+ label = (
202
+ <View>
203
+ <Animated.View style={{ opacity: inactiveOpacity }}>
204
+ {inactiveLabel}
205
+ </Animated.View>
206
+ <Animated.View
207
+ style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
208
+ >
209
+ {activeLabel}
210
+ </Animated.View>
211
+ </View>
256
212
  );
257
213
  }
214
+
215
+ const tabStyle = StyleSheet.flatten(style);
216
+ const isWidthSet = tabStyle?.width !== undefined;
217
+
218
+ const tabContainerStyle: ViewStyle | null = isWidthSet
219
+ ? null
220
+ : { width: defaultTabWidth };
221
+
222
+ const scene = { route };
223
+
224
+ let accessibilityLabel = getAccessibilityLabel(scene);
225
+
226
+ accessibilityLabel =
227
+ typeof accessibilityLabel !== 'undefined'
228
+ ? accessibilityLabel
229
+ : getLabelText(scene);
230
+
231
+ const badge = renderBadge ? renderBadge(scene) : null;
232
+
233
+ return (
234
+ <PlatformPressable
235
+ android_ripple={{ borderless: true }}
236
+ testID={getTestID(scene)}
237
+ accessible={getAccessible(scene)}
238
+ accessibilityLabel={accessibilityLabel}
239
+ accessibilityRole="tab"
240
+ accessibilityState={{ selected: isFocused }}
241
+ // @ts-ignore: this is to support older React Native versions
242
+ accessibilityStates={isFocused ? ['selected'] : []}
243
+ pressColor={pressColor}
244
+ pressOpacity={pressOpacity}
245
+ delayPressIn={0}
246
+ onLayout={onLayout}
247
+ onPress={onPress}
248
+ onLongPress={onLongPress}
249
+ style={[styles.pressable, tabContainerStyle]}
250
+ >
251
+ <View pointerEvents="none" style={[styles.item, tabStyle]}>
252
+ {icon}
253
+ {label}
254
+ {badge != null ? <View style={styles.badge}>{badge}</View> : null}
255
+ </View>
256
+ </PlatformPressable>
257
+ );
258
+ };
259
+
260
+ const MemoizedTabBarItemInternal = React.memo(
261
+ TabBarItemInternal
262
+ ) as typeof TabBarItemInternal;
263
+
264
+ function TabBarItem<T extends Route>(props: Props<T>) {
265
+ const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
266
+ props;
267
+ const onPressLatest = useLatestCallback(onPress);
268
+ const onLongPressLatest = useLatestCallback(onLongPress);
269
+ const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
270
+
271
+ const tabIndex = navigationState.routes.indexOf(route);
272
+
273
+ return (
274
+ <MemoizedTabBarItemInternal
275
+ {...rest}
276
+ onPress={onPressLatest}
277
+ onLayout={onLayoutLatest}
278
+ onLongPress={onLongPressLatest}
279
+ isFocused={navigationState.index === tabIndex}
280
+ route={route}
281
+ index={tabIndex}
282
+ routesLength={navigationState.routes.length}
283
+ />
284
+ );
258
285
  }
259
286
 
287
+ export default TabBarItem;
288
+
260
289
  const styles = StyleSheet.create({
261
290
  label: {
262
291
  margin: 4,