react-native-tab-view 3.5.2 → 4.0.0-alpha.1

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