react-native-tab-view 3.5.1 → 4.0.0-alpha.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 (49) hide show
  1. package/README.md +2 -646
  2. package/lib/commonjs/PanResponderAdapter.js +6 -5
  3. package/lib/commonjs/PanResponderAdapter.js.map +1 -1
  4. package/lib/commonjs/SceneView.js.map +1 -1
  5. package/lib/commonjs/TabBar.js +79 -32
  6. package/lib/commonjs/TabBar.js.map +1 -1
  7. package/lib/commonjs/TabBarIndicator.js +8 -6
  8. package/lib/commonjs/TabBarIndicator.js.map +1 -1
  9. package/lib/commonjs/TabBarItem.js +29 -29
  10. package/lib/commonjs/TabBarItem.js.map +1 -1
  11. package/lib/commonjs/TabBarItemLabel.js +36 -0
  12. package/lib/commonjs/TabBarItemLabel.js.map +1 -0
  13. package/lib/commonjs/TabView.js +6 -1
  14. package/lib/commonjs/TabView.js.map +1 -1
  15. package/lib/module/PanResponderAdapter.js +7 -6
  16. package/lib/module/PanResponderAdapter.js.map +1 -1
  17. package/lib/module/SceneView.js.map +1 -1
  18. package/lib/module/TabBar.js +79 -32
  19. package/lib/module/TabBar.js.map +1 -1
  20. package/lib/module/TabBarIndicator.js +9 -7
  21. package/lib/module/TabBarIndicator.js.map +1 -1
  22. package/lib/module/TabBarItem.js +29 -29
  23. package/lib/module/TabBarItem.js.map +1 -1
  24. package/lib/module/TabBarItemLabel.js +28 -0
  25. package/lib/module/TabBarItemLabel.js.map +1 -0
  26. package/lib/module/TabView.js +7 -2
  27. package/lib/module/TabView.js.map +1 -1
  28. package/lib/typescript/src/PanResponderAdapter.d.ts +1 -1
  29. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
  30. package/lib/typescript/src/TabBar.d.ts +3 -2
  31. package/lib/typescript/src/TabBar.d.ts.map +1 -1
  32. package/lib/typescript/src/TabBarIndicator.d.ts +6 -4
  33. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
  34. package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
  35. package/lib/typescript/src/TabBarItemLabel.d.ts +11 -0
  36. package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -0
  37. package/lib/typescript/src/TabView.d.ts +4 -3
  38. package/lib/typescript/src/TabView.d.ts.map +1 -1
  39. package/lib/typescript/src/types.d.ts +1 -0
  40. package/lib/typescript/src/types.d.ts.map +1 -1
  41. package/package.json +7 -7
  42. package/src/PanResponderAdapter.tsx +7 -5
  43. package/src/SceneView.tsx +1 -1
  44. package/src/TabBar.tsx +139 -44
  45. package/src/TabBarIndicator.tsx +20 -7
  46. package/src/TabBarItem.tsx +50 -42
  47. package/src/TabBarItemLabel.tsx +39 -0
  48. package/src/TabView.tsx +18 -1
  49. package/src/types.tsx +2 -0
package/src/TabBar.tsx CHANGED
@@ -21,6 +21,7 @@ import { Props as TabBarItemProps, TabBarItem } from './TabBarItem';
21
21
  import type {
22
22
  Event,
23
23
  Layout,
24
+ LocaleDirection,
24
25
  NavigationState,
25
26
  Route,
26
27
  Scene,
@@ -65,12 +66,14 @@ export type Props<T extends Route> = SceneRendererProps & {
65
66
  labelStyle?: StyleProp<TextStyle>;
66
67
  contentContainerStyle?: StyleProp<ViewStyle>;
67
68
  style?: StyleProp<ViewStyle>;
69
+ direction?: LocaleDirection;
68
70
  gap?: number;
69
71
  testID?: string;
70
72
  android_ripple?: PressableAndroidRippleConfig;
71
73
  };
72
74
 
73
75
  type FlattenedTabWidth = string | number | undefined;
76
+ type FlattenedTabPadding = string | number | undefined;
74
77
 
75
78
  const Separator = ({ width }: { width: number }) => {
76
79
  return <View style={{ width }} />;
@@ -82,13 +85,50 @@ const getFlattenedTabWidth = (style: StyleProp<ViewStyle>) => {
82
85
  return tabStyle?.width;
83
86
  };
84
87
 
88
+ const getFlattenedPaddingLeft = (style: StyleProp<ViewStyle>) => {
89
+ const flattenStyle = StyleSheet.flatten(style);
90
+
91
+ return flattenStyle
92
+ ? flattenStyle.paddingLeft || flattenStyle.paddingHorizontal || 0
93
+ : 0;
94
+ };
95
+
96
+ const getFlattenedPaddingRight = (style: StyleProp<ViewStyle>) => {
97
+ const flattenStyle = StyleSheet.flatten(style);
98
+
99
+ return flattenStyle
100
+ ? flattenStyle.paddingRight || flattenStyle.paddingHorizontal || 0
101
+ : 0;
102
+ };
103
+
104
+ const convertPaddingPercentToSize = (
105
+ value: FlattenedTabPadding,
106
+ layout: Layout
107
+ ): number => {
108
+ switch (typeof value) {
109
+ case 'number':
110
+ return value;
111
+ case 'string':
112
+ if (value.endsWith('%')) {
113
+ const width = parseFloat(value);
114
+ if (Number.isFinite(width)) {
115
+ return layout.width * (width / 100);
116
+ }
117
+ }
118
+ }
119
+ return 0;
120
+ };
121
+
85
122
  const getComputedTabWidth = (
86
123
  index: number,
87
124
  layout: Layout,
88
125
  routes: Route[],
89
126
  scrollEnabled: boolean | undefined,
90
127
  tabWidths: { [key: string]: number },
91
- flattenedWidth: FlattenedTabWidth
128
+ flattenedWidth: FlattenedTabWidth,
129
+ flattenedPaddingLeft: FlattenedTabPadding,
130
+ flattenedPaddingRight: FlattenedTabPadding,
131
+ gap?: number
92
132
  ) => {
93
133
  if (flattenedWidth === 'auto') {
94
134
  return tabWidths[routes[index].key] || 0;
@@ -109,7 +149,13 @@ const getComputedTabWidth = (
109
149
  if (scrollEnabled) {
110
150
  return (layout.width / 5) * 2;
111
151
  }
112
- return layout.width / routes.length;
152
+
153
+ const gapTotalWidth = (gap ?? 0) * (routes.length - 1);
154
+ const paddingTotalWidth =
155
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
156
+ convertPaddingPercentToSize(flattenedPaddingRight, layout);
157
+
158
+ return (layout.width - gapTotalWidth - paddingTotalWidth) / routes.length;
113
159
  };
114
160
 
115
161
  const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
@@ -117,13 +163,14 @@ const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
117
163
 
118
164
  const getTranslateX = (
119
165
  scrollAmount: Animated.Value,
120
- maxScrollDistance: number
166
+ maxScrollDistance: number,
167
+ direction: LocaleDirection
121
168
  ) =>
122
169
  Animated.multiply(
123
- Platform.OS === 'android' && I18nManager.isRTL
170
+ Platform.OS === 'android' && direction === 'rtl'
124
171
  ? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1))
125
172
  : scrollAmount,
126
- I18nManager.isRTL ? 1 : -1
173
+ direction === 'rtl' ? 1 : -1
127
174
  );
128
175
 
129
176
  const getTabBarWidth = <T extends Route>({
@@ -132,13 +179,23 @@ const getTabBarWidth = <T extends Route>({
132
179
  gap,
133
180
  scrollEnabled,
134
181
  flattenedTabWidth,
182
+ flattenedPaddingLeft,
183
+ flattenedPaddingRight,
135
184
  tabWidths,
136
185
  }: Pick<Props<T>, 'navigationState' | 'gap' | 'layout' | 'scrollEnabled'> & {
137
186
  tabWidths: Record<string, number>;
187
+ flattenedPaddingLeft: FlattenedTabPadding;
188
+ flattenedPaddingRight: FlattenedTabPadding;
138
189
  flattenedTabWidth: FlattenedTabWidth;
139
190
  }) => {
140
191
  const { routes } = navigationState;
141
192
 
193
+ const paddingsWidth = Math.max(
194
+ 0,
195
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
196
+ convertPaddingPercentToSize(flattenedPaddingRight, layout)
197
+ );
198
+
142
199
  return routes.reduce<number>(
143
200
  (acc, _, i) =>
144
201
  acc +
@@ -149,9 +206,12 @@ const getTabBarWidth = <T extends Route>({
149
206
  routes,
150
207
  scrollEnabled,
151
208
  tabWidths,
152
- flattenedTabWidth
209
+ flattenedTabWidth,
210
+ flattenedPaddingLeft,
211
+ flattenedPaddingRight,
212
+ gap
153
213
  ),
154
- 0
214
+ paddingsWidth
155
215
  );
156
216
  };
157
217
 
@@ -163,10 +223,16 @@ const normalizeScrollValue = <T extends Route>({
163
223
  tabWidths,
164
224
  value,
165
225
  flattenedTabWidth,
226
+ flattenedPaddingLeft,
227
+ flattenedPaddingRight,
228
+ direction,
166
229
  }: Pick<Props<T>, 'layout' | 'navigationState' | 'gap' | 'scrollEnabled'> & {
167
230
  tabWidths: Record<string, number>;
168
231
  value: number;
169
232
  flattenedTabWidth: FlattenedTabWidth;
233
+ flattenedPaddingLeft: FlattenedTabPadding;
234
+ flattenedPaddingRight: FlattenedTabPadding;
235
+ direction: LocaleDirection;
170
236
  }) => {
171
237
  const tabBarWidth = getTabBarWidth({
172
238
  layout,
@@ -175,11 +241,13 @@ const normalizeScrollValue = <T extends Route>({
175
241
  gap,
176
242
  scrollEnabled,
177
243
  flattenedTabWidth,
244
+ flattenedPaddingLeft,
245
+ flattenedPaddingRight,
178
246
  });
179
247
  const maxDistance = getMaxScrollDistance(tabBarWidth, layout.width);
180
248
  const scrollValue = Math.max(Math.min(value, maxDistance), 0);
181
249
 
182
- if (Platform.OS === 'android' && I18nManager.isRTL) {
250
+ if (Platform.OS === 'android' && direction === 'rtl') {
183
251
  // On Android, scroll value is not applied in reverse in RTL
184
252
  // so we need to manually adjust it to apply correct value
185
253
  return maxDistance - scrollValue;
@@ -195,10 +263,21 @@ const getScrollAmount = <T extends Route>({
195
263
  scrollEnabled,
196
264
  flattenedTabWidth,
197
265
  tabWidths,
266
+ flattenedPaddingLeft,
267
+ flattenedPaddingRight,
268
+ direction,
198
269
  }: Pick<Props<T>, 'layout' | 'navigationState' | 'scrollEnabled' | 'gap'> & {
199
270
  tabWidths: Record<string, number>;
200
271
  flattenedTabWidth: FlattenedTabWidth;
272
+ flattenedPaddingLeft: FlattenedTabPadding;
273
+ flattenedPaddingRight: FlattenedTabPadding;
274
+ direction: LocaleDirection;
201
275
  }) => {
276
+ const paddingInitial =
277
+ direction === 'rtl'
278
+ ? convertPaddingPercentToSize(flattenedPaddingRight, layout)
279
+ : convertPaddingPercentToSize(flattenedPaddingLeft, layout);
280
+
202
281
  const centerDistance = Array.from({
203
282
  length: navigationState.index + 1,
204
283
  }).reduce<number>((total, _, i) => {
@@ -208,18 +287,20 @@ const getScrollAmount = <T extends Route>({
208
287
  navigationState.routes,
209
288
  scrollEnabled,
210
289
  tabWidths,
211
- flattenedTabWidth
290
+ flattenedTabWidth,
291
+ flattenedPaddingLeft,
292
+ flattenedPaddingRight,
293
+ gap
212
294
  );
213
295
 
214
296
  // To get the current index centered we adjust scroll amount by width of indexes
215
297
  // 0 through (i - 1) and add half the width of current index i
216
298
  return (
217
299
  total +
218
- (navigationState.index === i
219
- ? (tabWidth + (gap ?? 0)) / 2
220
- : tabWidth + (gap ?? 0))
300
+ (i > 0 ? gap ?? 0 : 0) +
301
+ (navigationState.index === i ? tabWidth / 2 : tabWidth)
221
302
  );
222
- }, 0);
303
+ }, paddingInitial);
223
304
 
224
305
  const scrollAmount = centerDistance - layout.width / 2;
225
306
 
@@ -231,6 +312,9 @@ const getScrollAmount = <T extends Route>({
231
312
  gap,
232
313
  scrollEnabled,
233
314
  flattenedTabWidth,
315
+ flattenedPaddingLeft,
316
+ flattenedPaddingRight,
317
+ direction,
234
318
  });
235
319
  };
236
320
 
@@ -281,22 +365,27 @@ export function TabBar<T extends Route>({
281
365
  renderBadge,
282
366
  renderIcon,
283
367
  renderLabel,
368
+ direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
284
369
  renderTabBarItem,
285
370
  style,
286
371
  tabStyle,
372
+ layout: propLayout,
287
373
  testID,
288
374
  android_ripple,
289
375
  }: Props<T>) {
290
- const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
376
+ const [layout, setLayout] = React.useState<Layout>(
377
+ propLayout ?? { width: 0, height: 0 }
378
+ );
291
379
  const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
292
380
  const flatListRef = React.useRef<FlatList | null>(null);
293
381
  const isFirst = React.useRef(true);
294
382
  const scrollAmount = useAnimatedValue(0);
295
383
  const measuredTabWidths = React.useRef<Record<string, number>>({});
296
-
297
384
  const { routes } = navigationState;
298
385
  const flattenedTabWidth = getFlattenedTabWidth(tabStyle);
299
386
  const isWidthDynamic = flattenedTabWidth === 'auto';
387
+ const flattenedPaddingRight = getFlattenedPaddingRight(contentContainerStyle);
388
+ const flattenedPaddingLeft = getFlattenedPaddingLeft(contentContainerStyle);
300
389
  const scrollOffset = getScrollAmount({
301
390
  layout,
302
391
  navigationState,
@@ -304,6 +393,9 @@ export function TabBar<T extends Route>({
304
393
  gap,
305
394
  scrollEnabled,
306
395
  flattenedTabWidth,
396
+ flattenedPaddingLeft,
397
+ flattenedPaddingRight,
398
+ direction,
307
399
  });
308
400
 
309
401
  const hasMeasuredTabWidths =
@@ -347,19 +439,25 @@ export function TabBar<T extends Route>({
347
439
  gap,
348
440
  scrollEnabled,
349
441
  flattenedTabWidth,
442
+ flattenedPaddingLeft,
443
+ flattenedPaddingRight,
350
444
  });
351
445
 
352
446
  const separatorsWidth = Math.max(0, routes.length - 1) * gap;
353
- const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
354
- const tabBarWidthPercent = `${routes.length * 40}%`;
447
+ const paddingsWidth = Math.max(
448
+ 0,
449
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
450
+ convertPaddingPercentToSize(flattenedPaddingRight, layout)
451
+ );
355
452
 
356
453
  const translateX = React.useMemo(
357
454
  () =>
358
455
  getTranslateX(
359
456
  scrollAmount,
360
- getMaxScrollDistance(tabBarWidth, layout.width)
457
+ getMaxScrollDistance(tabBarWidth, layout.width),
458
+ direction
361
459
  ),
362
- [layout.width, scrollAmount, tabBarWidth]
460
+ [direction, layout.width, scrollAmount, tabBarWidth]
363
461
  );
364
462
 
365
463
  const renderItem = React.useCallback(
@@ -436,7 +534,10 @@ export function TabBar<T extends Route>({
436
534
  routes,
437
535
  scrollEnabled,
438
536
  tabWidths,
439
- getFlattenedTabWidth(tabStyle)
537
+ getFlattenedTabWidth(tabStyle),
538
+ getFlattenedPaddingRight(contentContainerStyle),
539
+ getFlattenedPaddingLeft(contentContainerStyle),
540
+ gap
440
541
  )
441
542
  : undefined,
442
543
  android_ripple,
@@ -479,6 +580,7 @@ export function TabBar<T extends Route>({
479
580
  routes,
480
581
  scrollEnabled,
481
582
  tabStyle,
583
+ contentContainerStyle,
482
584
  tabWidths,
483
585
  ]
484
586
  );
@@ -488,21 +590,10 @@ export function TabBar<T extends Route>({
488
590
  const contentContainerStyleMemoized = React.useMemo(
489
591
  () => [
490
592
  styles.tabContent,
491
- scrollEnabled
492
- ? {
493
- width:
494
- tabBarWidth > separatorsWidth ? tabBarWidth : tabBarWidthPercent,
495
- }
496
- : styles.container,
593
+ scrollEnabled ? { width: tabBarWidth } : null,
497
594
  contentContainerStyle,
498
595
  ],
499
- [
500
- contentContainerStyle,
501
- scrollEnabled,
502
- separatorsWidth,
503
- tabBarWidth,
504
- tabBarWidthPercent,
505
- ]
596
+ [contentContainerStyle, scrollEnabled, tabBarWidth]
506
597
  );
507
598
 
508
599
  const handleScroll = React.useMemo(
@@ -546,11 +637,7 @@ export function TabBar<T extends Route>({
546
637
  style={[
547
638
  styles.indicatorContainer,
548
639
  scrollEnabled ? { transform: [{ translateX }] as any } : null,
549
- tabBarWidth > separatorsWidth
550
- ? { width: tabBarWidth - separatorsWidth }
551
- : scrollEnabled
552
- ? { width: tabBarWidthPercent }
553
- : null,
640
+ scrollEnabled ? { width: tabBarWidth } : null,
554
641
  indicatorContainerStyle,
555
642
  ]}
556
643
  >
@@ -559,10 +646,17 @@ export function TabBar<T extends Route>({
559
646
  layout,
560
647
  navigationState,
561
648
  jumpTo,
649
+ direction,
562
650
  width: isWidthDynamic
563
651
  ? 'auto'
564
- : `${(100 - separatorPercent) / routes.length}%`,
565
- style: indicatorStyle,
652
+ : Math.max(
653
+ 0,
654
+ (tabBarWidth - separatorsWidth - paddingsWidth) / routes.length
655
+ ),
656
+ style: [
657
+ indicatorStyle,
658
+ { left: flattenedPaddingLeft, right: flattenedPaddingRight },
659
+ ],
566
660
  getTabWidth: (i: number) =>
567
661
  getComputedTabWidth(
568
662
  i,
@@ -570,7 +664,10 @@ export function TabBar<T extends Route>({
570
664
  routes,
571
665
  scrollEnabled,
572
666
  tabWidths,
573
- flattenedTabWidth
667
+ flattenedTabWidth,
668
+ flattenedPaddingRight,
669
+ flattenedPaddingLeft,
670
+ gap
574
671
  ),
575
672
  gap,
576
673
  })}
@@ -605,9 +702,6 @@ export function TabBar<T extends Route>({
605
702
  }
606
703
 
607
704
  const styles = StyleSheet.create({
608
- container: {
609
- flex: 1,
610
- },
611
705
  scroll: {
612
706
  overflow: Platform.select({ default: 'scroll', web: undefined }),
613
707
  },
@@ -624,6 +718,7 @@ const styles = StyleSheet.create({
624
718
  zIndex: 1,
625
719
  },
626
720
  tabContent: {
721
+ flexGrow: 1,
627
722
  flexDirection: 'row',
628
723
  flexWrap: 'nowrap',
629
724
  },
@@ -2,14 +2,18 @@ import * as React from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
5
- I18nManager,
6
5
  Platform,
7
6
  StyleProp,
8
7
  StyleSheet,
9
8
  ViewStyle,
10
9
  } from 'react-native';
11
10
 
12
- import type { NavigationState, Route, SceneRendererProps } from './types';
11
+ import type {
12
+ LocaleDirection,
13
+ NavigationState,
14
+ Route,
15
+ SceneRendererProps,
16
+ } from './types';
13
17
  import { useAnimatedValue } from './useAnimatedValue';
14
18
 
15
19
  export type GetTabWidth = (index: number) => number;
@@ -17,15 +21,18 @@ export type GetTabWidth = (index: number) => number;
17
21
  export type Props<T extends Route> = SceneRendererProps & {
18
22
  navigationState: NavigationState<T>;
19
23
  width: string | number;
20
- style?: StyleProp<ViewStyle>;
21
24
  getTabWidth: GetTabWidth;
25
+ direction: LocaleDirection;
26
+ style?: StyleProp<ViewStyle>;
22
27
  gap?: number;
28
+ children?: React.ReactNode;
23
29
  };
24
30
 
25
31
  const getTranslateX = (
26
32
  position: Animated.AnimatedInterpolation<number>,
27
33
  routes: Route[],
28
34
  getTabWidth: GetTabWidth,
35
+ direction: LocaleDirection,
29
36
  gap?: number
30
37
  ) => {
31
38
  const inputRange = routes.map((_, i) => i);
@@ -42,7 +49,7 @@ const getTranslateX = (
42
49
  extrapolate: 'clamp',
43
50
  });
44
51
 
45
- return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
52
+ return Animated.multiply(translateX, direction === 'rtl' ? -1 : 1);
46
53
  };
47
54
 
48
55
  export function TabBarIndicator<T extends Route>({
@@ -51,8 +58,10 @@ export function TabBarIndicator<T extends Route>({
51
58
  navigationState,
52
59
  position,
53
60
  width,
61
+ direction,
54
62
  gap,
55
63
  style,
64
+ children,
56
65
  }: Props<T>) {
57
66
  const isIndicatorShown = React.useRef(false);
58
67
  const isWidthDynamic = width === 'auto';
@@ -96,7 +105,9 @@ export function TabBarIndicator<T extends Route>({
96
105
 
97
106
  if (layout.width) {
98
107
  const translateX =
99
- routes.length > 1 ? getTranslateX(position, routes, getTabWidth, gap) : 0;
108
+ routes.length > 1
109
+ ? getTranslateX(position, routes, getTabWidth, direction, gap)
110
+ : 0;
100
111
 
101
112
  transform.push({ translateX });
102
113
  }
@@ -116,7 +127,7 @@ export function TabBarIndicator<T extends Route>({
116
127
  })
117
128
  : outputRange[0],
118
129
  },
119
- { translateX: 0.5 }
130
+ { translateX: direction === 'rtl' ? -0.5 : 0.5 }
120
131
  );
121
132
  }
122
133
 
@@ -136,7 +147,9 @@ export function TabBarIndicator<T extends Route>({
136
147
  width === 'auto' ? { opacity: opacity } : null,
137
148
  style,
138
149
  ]}
139
- />
150
+ >
151
+ {children}
152
+ </Animated.View>
140
153
  );
141
154
  }
142
155
 
@@ -12,6 +12,7 @@ import {
12
12
  import useLatestCallback from 'use-latest-callback';
13
13
 
14
14
  import { PlatformPressable } from './PlatformPressable';
15
+ import { TabBarItemLabel } from './TabBarItemLabel';
15
16
  import type { NavigationState, Route, Scene } from './types';
16
17
 
17
18
  export type Props<T extends Route> = {
@@ -85,18 +86,26 @@ const getInactiveOpacity = (
85
86
 
86
87
  type TabBarItemInternalProps<T extends Route> = Omit<
87
88
  Props<T>,
88
- 'navigationState'
89
+ | 'navigationState'
90
+ | 'getAccessibilityLabel'
91
+ | 'getLabelText'
92
+ | 'getTestID'
93
+ | 'getAccessible'
89
94
  > & {
90
95
  isFocused: boolean;
91
96
  index: number;
92
97
  routesLength: number;
98
+ accessibilityLabel?: string;
99
+ label?: string;
100
+ testID?: string;
101
+ accessible?: boolean;
93
102
  };
94
103
 
95
104
  const TabBarItemInternal = <T extends Route>({
96
- getAccessibilityLabel,
97
- getAccessible,
98
- getLabelText,
99
- getTestID,
105
+ accessibilityLabel,
106
+ accessible,
107
+ label: labelText,
108
+ testID,
100
109
  onLongPress,
101
110
  onPress,
102
111
  isFocused,
@@ -166,29 +175,16 @@ const TabBarItemInternal = <T extends Route>({
166
175
  }
167
176
  }
168
177
 
169
- const renderLabel =
170
- renderLabelCustom !== undefined
171
- ? renderLabelCustom
172
- : (labelProps: { route: T; color: string }) => {
173
- const labelText = getLabelText({ route: labelProps.route });
174
-
175
- if (typeof labelText === 'string') {
176
- return (
177
- <Animated.Text
178
- style={[
179
- styles.label,
180
- icon ? { marginTop: 0 } : null,
181
- labelStyle,
182
- { color: labelProps.color },
183
- ]}
184
- >
185
- {labelText}
186
- </Animated.Text>
187
- );
188
- }
189
-
190
- return labelText;
191
- };
178
+ const renderLabel = renderLabelCustom
179
+ ? renderLabelCustom
180
+ : (labelProps: { color: string }) => (
181
+ <TabBarItemLabel
182
+ {...labelProps}
183
+ icon={icon}
184
+ label={labelText}
185
+ labelStyle={labelStyle}
186
+ />
187
+ );
192
188
 
193
189
  if (renderLabel) {
194
190
  const activeLabel = renderLabel({
@@ -225,20 +221,16 @@ const TabBarItemInternal = <T extends Route>({
225
221
 
226
222
  const scene = { route };
227
223
 
228
- let accessibilityLabel = getAccessibilityLabel(scene);
229
-
230
224
  accessibilityLabel =
231
- typeof accessibilityLabel !== 'undefined'
232
- ? accessibilityLabel
233
- : getLabelText(scene);
225
+ typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText;
234
226
 
235
227
  const badge = renderBadge ? renderBadge(scene) : null;
236
228
 
237
229
  return (
238
230
  <PlatformPressable
239
231
  android_ripple={android_ripple}
240
- testID={getTestID(scene)}
241
- accessible={getAccessible(scene)}
232
+ testID={testID}
233
+ accessible={accessible}
242
234
  accessibilityLabel={accessibilityLabel}
243
235
  accessibilityRole="tab"
244
236
  accessibilityState={{ selected: isFocused }}
@@ -266,14 +258,31 @@ const MemoizedTabBarItemInternal = React.memo(
266
258
  ) as typeof TabBarItemInternal;
267
259
 
268
260
  export function TabBarItem<T extends Route>(props: Props<T>) {
269
- const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
270
- props;
261
+ const {
262
+ onPress,
263
+ onLongPress,
264
+ onLayout,
265
+ navigationState,
266
+ route,
267
+ getAccessibilityLabel,
268
+ getLabelText,
269
+ getTestID,
270
+ getAccessible,
271
+ ...rest
272
+ } = props;
271
273
  const onPressLatest = useLatestCallback(onPress);
272
274
  const onLongPressLatest = useLatestCallback(onLongPress);
273
275
  const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
274
276
 
275
277
  const tabIndex = navigationState.routes.indexOf(route);
276
278
 
279
+ const scene = { route };
280
+
281
+ const accessibilityLabel = getAccessibilityLabel(scene);
282
+ const label = getLabelText(scene);
283
+ const testID = getTestID(scene);
284
+ const accessible = getAccessible(scene);
285
+
277
286
  return (
278
287
  <MemoizedTabBarItemInternal
279
288
  {...rest}
@@ -284,16 +293,15 @@ export function TabBarItem<T extends Route>(props: Props<T>) {
284
293
  route={route}
285
294
  index={tabIndex}
286
295
  routesLength={navigationState.routes.length}
296
+ accessibilityLabel={accessibilityLabel}
297
+ label={label}
298
+ testID={testID}
299
+ accessible={accessible}
287
300
  />
288
301
  );
289
302
  }
290
303
 
291
304
  const styles = StyleSheet.create({
292
- label: {
293
- margin: 4,
294
- backgroundColor: 'transparent',
295
- textTransform: 'uppercase',
296
- },
297
305
  icon: {
298
306
  margin: 2,
299
307
  },
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import { Animated, StyleSheet } from 'react-native';
4
+
5
+ interface TabBarItemLabelProps {
6
+ color: string;
7
+ label?: string;
8
+ labelStyle: StyleProp<ViewStyle>;
9
+ icon: React.ReactNode;
10
+ }
11
+
12
+ export const TabBarItemLabel = React.memo(
13
+ ({ color, label, labelStyle, icon }: TabBarItemLabelProps) => {
14
+ if (!label) {
15
+ return null;
16
+ }
17
+
18
+ return (
19
+ <Animated.Text
20
+ style={[
21
+ styles.label,
22
+ icon ? { marginTop: 0 } : null,
23
+ labelStyle,
24
+ { color: color },
25
+ ]}
26
+ >
27
+ {label}
28
+ </Animated.Text>
29
+ );
30
+ }
31
+ );
32
+
33
+ const styles = StyleSheet.create({
34
+ label: {
35
+ margin: 4,
36
+ backgroundColor: 'transparent',
37
+ textTransform: 'uppercase',
38
+ },
39
+ });