@amazon-devices/react-navigation__react-native-tab-view 2.0.1758683737

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 (116) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +649 -0
  3. package/SystemBundles/c5889b273a6efc2a3c30241851055c37630fbf3381df86d403967369775f0536.bundle +14497 -0
  4. package/SystemBundles/c5889b273a6efc2a3c30241851055c37630fbf3381df86d403967369775f0536.map +1 -0
  5. package/SystemModules/amzn__react-navigation__react-native-tab-view-2.modules.txt +41 -0
  6. package/SystemModules/amzn__react-navigation__react-native-tab-view-2.private.modules.txt +98 -0
  7. package/SystemModules/keplerscript-system-bundle-config.json +7 -0
  8. package/THIRD_PARTY_NOTICES.txt +24 -0
  9. package/kepler-compatibility.json +8 -0
  10. package/kepler-transformed-package-info.json +3 -0
  11. package/lib/commonjs/Pager.android.js +14 -0
  12. package/lib/commonjs/Pager.android.js.map +1 -0
  13. package/lib/commonjs/Pager.ios.js +14 -0
  14. package/lib/commonjs/Pager.ios.js.map +1 -0
  15. package/lib/commonjs/Pager.js +14 -0
  16. package/lib/commonjs/Pager.js.map +1 -0
  17. package/lib/commonjs/PagerViewAdapter.js +132 -0
  18. package/lib/commonjs/PagerViewAdapter.js.map +1 -0
  19. package/lib/commonjs/PanResponderAdapter.js +204 -0
  20. package/lib/commonjs/PanResponderAdapter.js.map +1 -0
  21. package/lib/commonjs/PlatformPressable.js +43 -0
  22. package/lib/commonjs/PlatformPressable.js.map +1 -0
  23. package/lib/commonjs/SceneMap.js +29 -0
  24. package/lib/commonjs/SceneMap.js.map +1 -0
  25. package/lib/commonjs/SceneView.js +78 -0
  26. package/lib/commonjs/SceneView.js.map +1 -0
  27. package/lib/commonjs/TabBar.js +405 -0
  28. package/lib/commonjs/TabBar.js.map +1 -0
  29. package/lib/commonjs/TabBarIndicator.js +110 -0
  30. package/lib/commonjs/TabBarIndicator.js.map +1 -0
  31. package/lib/commonjs/TabBarItem.js +227 -0
  32. package/lib/commonjs/TabBarItem.js.map +1 -0
  33. package/lib/commonjs/TabView.js +122 -0
  34. package/lib/commonjs/TabView.js.map +1 -0
  35. package/lib/commonjs/index.js +42 -0
  36. package/lib/commonjs/index.js.map +1 -0
  37. package/lib/commonjs/types.js +6 -0
  38. package/lib/commonjs/types.js.map +1 -0
  39. package/lib/commonjs/useAnimatedValue.js +18 -0
  40. package/lib/commonjs/useAnimatedValue.js.map +1 -0
  41. package/lib/module/Pager.android.js +2 -0
  42. package/lib/module/Pager.android.js.map +1 -0
  43. package/lib/module/Pager.ios.js +2 -0
  44. package/lib/module/Pager.ios.js.map +1 -0
  45. package/lib/module/Pager.js +2 -0
  46. package/lib/module/Pager.js.map +1 -0
  47. package/lib/module/PagerViewAdapter.js +123 -0
  48. package/lib/module/PagerViewAdapter.js.map +1 -0
  49. package/lib/module/PanResponderAdapter.js +195 -0
  50. package/lib/module/PanResponderAdapter.js.map +1 -0
  51. package/lib/module/PlatformPressable.js +35 -0
  52. package/lib/module/PlatformPressable.js.map +1 -0
  53. package/lib/module/SceneMap.js +21 -0
  54. package/lib/module/SceneMap.js.map +1 -0
  55. package/lib/module/SceneView.js +70 -0
  56. package/lib/module/SceneView.js.map +1 -0
  57. package/lib/module/TabBar.js +396 -0
  58. package/lib/module/TabBar.js.map +1 -0
  59. package/lib/module/TabBarIndicator.js +101 -0
  60. package/lib/module/TabBarIndicator.js.map +1 -0
  61. package/lib/module/TabBarItem.js +218 -0
  62. package/lib/module/TabBarItem.js.map +1 -0
  63. package/lib/module/TabView.js +113 -0
  64. package/lib/module/TabView.js.map +1 -0
  65. package/lib/module/index.js +6 -0
  66. package/lib/module/index.js.map +1 -0
  67. package/lib/module/types.js +2 -0
  68. package/lib/module/types.js.map +1 -0
  69. package/lib/module/useAnimatedValue.js +10 -0
  70. package/lib/module/useAnimatedValue.js.map +1 -0
  71. package/lib/typescript/src/Pager.android.d.ts +2 -0
  72. package/lib/typescript/src/Pager.android.d.ts.map +1 -0
  73. package/lib/typescript/src/Pager.d.ts +2 -0
  74. package/lib/typescript/src/Pager.d.ts.map +1 -0
  75. package/lib/typescript/src/Pager.ios.d.ts +2 -0
  76. package/lib/typescript/src/Pager.ios.d.ts.map +1 -0
  77. package/lib/typescript/src/PagerViewAdapter.d.ts +15 -0
  78. package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -0
  79. package/lib/typescript/src/PanResponderAdapter.d.ts +16 -0
  80. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -0
  81. package/lib/typescript/src/PlatformPressable.d.ts +16 -0
  82. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -0
  83. package/lib/typescript/src/SceneMap.d.ts +10 -0
  84. package/lib/typescript/src/SceneMap.d.ts.map +1 -0
  85. package/lib/typescript/src/SceneView.d.ts +16 -0
  86. package/lib/typescript/src/SceneView.d.ts.map +1 -0
  87. package/lib/typescript/src/TabBar.d.ts +44 -0
  88. package/lib/typescript/src/TabBar.d.ts.map +1 -0
  89. package/lib/typescript/src/TabBarIndicator.d.ts +13 -0
  90. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -0
  91. package/lib/typescript/src/TabBarItem.d.ts +37 -0
  92. package/lib/typescript/src/TabBarItem.d.ts.map +1 -0
  93. package/lib/typescript/src/TabView.d.ts +27 -0
  94. package/lib/typescript/src/TabView.d.ts.map +1 -0
  95. package/lib/typescript/src/index.d.ts +11 -0
  96. package/lib/typescript/src/index.d.ts.map +1 -0
  97. package/lib/typescript/src/types.d.ts +42 -0
  98. package/lib/typescript/src/types.d.ts.map +1 -0
  99. package/lib/typescript/src/useAnimatedValue.d.ts +3 -0
  100. package/lib/typescript/src/useAnimatedValue.d.ts.map +1 -0
  101. package/package.json +1 -0
  102. package/src/Pager.android.tsx +1 -0
  103. package/src/Pager.ios.tsx +1 -0
  104. package/src/Pager.tsx +1 -0
  105. package/src/PagerViewAdapter.tsx +180 -0
  106. package/src/PanResponderAdapter.tsx +340 -0
  107. package/src/PlatformPressable.tsx +42 -0
  108. package/src/SceneMap.tsx +30 -0
  109. package/src/SceneView.tsx +107 -0
  110. package/src/TabBar.tsx +639 -0
  111. package/src/TabBarIndicator.tsx +153 -0
  112. package/src/TabBarItem.tsx +319 -0
  113. package/src/TabView.tsx +155 -0
  114. package/src/index.tsx +10 -0
  115. package/src/types.tsx +59 -0
  116. package/src/useAnimatedValue.tsx +12 -0
package/src/TabBar.tsx ADDED
@@ -0,0 +1,639 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ DimensionValue,
5
+ FlatList,
6
+ I18nManager,
7
+ LayoutChangeEvent,
8
+ ListRenderItemInfo,
9
+ Platform,
10
+ PressableAndroidRippleConfig,
11
+ StyleProp,
12
+ StyleSheet,
13
+ TextStyle,
14
+ View,
15
+ ViewStyle,
16
+ ViewToken,
17
+ } from 'react-native';
18
+ import useLatestCallback from 'use-latest-callback';
19
+
20
+ import TabBarIndicator, { Props as IndicatorProps } from './TabBarIndicator';
21
+ import TabBarItem, { Props as TabBarItemProps } from './TabBarItem';
22
+ import type {
23
+ Event,
24
+ Layout,
25
+ NavigationState,
26
+ Route,
27
+ Scene,
28
+ SceneRendererProps,
29
+ } from './types';
30
+ import useAnimatedValue from './useAnimatedValue';
31
+
32
+ export type Props<T extends Route> = SceneRendererProps & {
33
+ navigationState: NavigationState<T>;
34
+ scrollEnabled?: boolean;
35
+ bounces?: boolean;
36
+ activeColor?: string;
37
+ inactiveColor?: string;
38
+ pressColor?: string;
39
+ pressOpacity?: number;
40
+ getLabelText?: (scene: Scene<T>) => string | undefined;
41
+ getAccessible?: (scene: Scene<T>) => boolean | undefined;
42
+ getAccessibilityLabel?: (scene: Scene<T>) => string | undefined;
43
+ getTestID?: (scene: Scene<T>) => string | undefined;
44
+ renderLabel?: (
45
+ scene: Scene<T> & {
46
+ focused: boolean;
47
+ color: string;
48
+ }
49
+ ) => React.ReactNode;
50
+ renderIcon?: (
51
+ scene: Scene<T> & {
52
+ focused: boolean;
53
+ color: string;
54
+ }
55
+ ) => React.ReactNode;
56
+ renderBadge?: (scene: Scene<T>) => React.ReactNode;
57
+ renderIndicator?: (props: IndicatorProps<T>) => React.ReactNode;
58
+ renderTabBarItem?: (
59
+ props: TabBarItemProps<T> & { key: string }
60
+ ) => React.ReactElement;
61
+ onTabPress?: (scene: Scene<T> & Event) => void;
62
+ onTabLongPress?: (scene: Scene<T>) => void;
63
+ tabStyle?: StyleProp<ViewStyle>;
64
+ indicatorStyle?: StyleProp<ViewStyle>;
65
+ indicatorContainerStyle?: StyleProp<ViewStyle>;
66
+ labelStyle?: StyleProp<TextStyle>;
67
+ contentContainerStyle?: StyleProp<ViewStyle>;
68
+ style?: StyleProp<ViewStyle>;
69
+ gap?: number;
70
+ testID?: string;
71
+ android_ripple?: PressableAndroidRippleConfig;
72
+ };
73
+
74
+ type FlattenedTabWidth = DimensionValue | undefined;
75
+
76
+ const Separator = ({ width }: { width: number }) => {
77
+ return <View style={{ width }} />;
78
+ };
79
+
80
+ const getFlattenedTabWidth = (style: StyleProp<ViewStyle>) => {
81
+ const tabStyle = StyleSheet.flatten(style);
82
+
83
+ return tabStyle?.width;
84
+ };
85
+
86
+ const getComputedTabWidth = (
87
+ index: number,
88
+ layout: Layout,
89
+ routes: Route[],
90
+ scrollEnabled: boolean | undefined,
91
+ tabWidths: { [key: string]: number },
92
+ flattenedWidth: FlattenedTabWidth
93
+ ) => {
94
+ if (flattenedWidth === 'auto') {
95
+ return tabWidths[routes[index].key] || 0;
96
+ }
97
+
98
+ switch (typeof flattenedWidth) {
99
+ case 'number':
100
+ return flattenedWidth;
101
+ case 'string':
102
+ if (flattenedWidth.endsWith('%')) {
103
+ const width = parseFloat(flattenedWidth);
104
+ if (Number.isFinite(width)) {
105
+ return layout.width * (width / 100);
106
+ }
107
+ }
108
+ }
109
+
110
+ if (scrollEnabled) {
111
+ return (layout.width / 5) * 2;
112
+ }
113
+ return layout.width / routes.length;
114
+ };
115
+
116
+ const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
117
+ tabBarWidth - layoutWidth;
118
+
119
+ const getTranslateX = (
120
+ scrollAmount: Animated.Value,
121
+ maxScrollDistance: number
122
+ ) =>
123
+ Animated.multiply(
124
+ Platform.OS === 'android' && I18nManager.isRTL
125
+ ? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1))
126
+ : scrollAmount,
127
+ I18nManager.isRTL ? 1 : -1
128
+ );
129
+
130
+ const getTabBarWidth = <T extends Route>({
131
+ navigationState,
132
+ layout,
133
+ gap,
134
+ scrollEnabled,
135
+ flattenedTabWidth,
136
+ tabWidths,
137
+ }: Pick<Props<T>, 'navigationState' | 'gap' | 'layout' | 'scrollEnabled'> & {
138
+ tabWidths: Record<string, number>;
139
+ flattenedTabWidth: FlattenedTabWidth;
140
+ }) => {
141
+ const { routes } = navigationState;
142
+
143
+ return routes.reduce<number>(
144
+ (acc, _, i) =>
145
+ acc +
146
+ (i > 0 ? gap ?? 0 : 0) +
147
+ getComputedTabWidth(
148
+ i,
149
+ layout,
150
+ routes,
151
+ scrollEnabled,
152
+ tabWidths,
153
+ flattenedTabWidth
154
+ ),
155
+ 0
156
+ );
157
+ };
158
+
159
+ const normalizeScrollValue = <T extends Route>({
160
+ layout,
161
+ navigationState,
162
+ gap,
163
+ scrollEnabled,
164
+ tabWidths,
165
+ value,
166
+ flattenedTabWidth,
167
+ }: Pick<Props<T>, 'layout' | 'navigationState' | 'gap' | 'scrollEnabled'> & {
168
+ tabWidths: Record<string, number>;
169
+ value: number;
170
+ flattenedTabWidth: FlattenedTabWidth;
171
+ }) => {
172
+ const tabBarWidth = getTabBarWidth({
173
+ layout,
174
+ navigationState,
175
+ tabWidths,
176
+ gap,
177
+ scrollEnabled,
178
+ flattenedTabWidth,
179
+ });
180
+ const maxDistance = getMaxScrollDistance(tabBarWidth, layout.width);
181
+ const scrollValue = Math.max(Math.min(value, maxDistance), 0);
182
+
183
+ if (Platform.OS === 'android' && I18nManager.isRTL) {
184
+ // On Android, scroll value is not applied in reverse in RTL
185
+ // so we need to manually adjust it to apply correct value
186
+ return maxDistance - scrollValue;
187
+ }
188
+
189
+ return scrollValue;
190
+ };
191
+
192
+ const getScrollAmount = <T extends Route>({
193
+ layout,
194
+ navigationState,
195
+ gap,
196
+ scrollEnabled,
197
+ flattenedTabWidth,
198
+ tabWidths,
199
+ }: Pick<Props<T>, 'layout' | 'navigationState' | 'scrollEnabled' | 'gap'> & {
200
+ tabWidths: Record<string, number>;
201
+ flattenedTabWidth: FlattenedTabWidth;
202
+ }) => {
203
+ const centerDistance = Array.from({
204
+ length: navigationState.index + 1,
205
+ }).reduce<number>((total, _, i) => {
206
+ const tabWidth = getComputedTabWidth(
207
+ i,
208
+ layout,
209
+ navigationState.routes,
210
+ scrollEnabled,
211
+ tabWidths,
212
+ flattenedTabWidth
213
+ );
214
+
215
+ // To get the current index centered we adjust scroll amount by width of indexes
216
+ // 0 through (i - 1) and add half the width of current index i
217
+ return (
218
+ total +
219
+ (navigationState.index === i
220
+ ? (tabWidth + (gap ?? 0)) / 2
221
+ : tabWidth + (gap ?? 0))
222
+ );
223
+ }, 0);
224
+
225
+ const scrollAmount = centerDistance - layout.width / 2;
226
+
227
+ return normalizeScrollValue({
228
+ layout,
229
+ navigationState,
230
+ tabWidths,
231
+ value: scrollAmount,
232
+ gap,
233
+ scrollEnabled,
234
+ flattenedTabWidth,
235
+ });
236
+ };
237
+
238
+ const getLabelTextDefault = ({ route }: Scene<Route>) => route.title;
239
+
240
+ const getAccessibleDefault = ({ route }: Scene<Route>) =>
241
+ typeof route.accessible !== 'undefined' ? route.accessible : true;
242
+
243
+ const getAccessibilityLabelDefault = ({ route }: Scene<Route>) =>
244
+ typeof route.accessibilityLabel === 'string'
245
+ ? route.accessibilityLabel
246
+ : typeof route.title === 'string'
247
+ ? route.title
248
+ : undefined;
249
+
250
+ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
251
+ <TabBarIndicator {...props} />
252
+ );
253
+
254
+ const getTestIdDefault = ({ route }: Scene<Route>) => route.testID;
255
+
256
+ // How many items measurements should we update per batch.
257
+ // Defaults to 10, since that's whats FlatList is using in initialNumToRender.
258
+ const MEASURE_PER_BATCH = 10;
259
+
260
+ export default function TabBar<T extends Route>({
261
+ getLabelText = getLabelTextDefault,
262
+ getAccessible = getAccessibleDefault,
263
+ getAccessibilityLabel = getAccessibilityLabelDefault,
264
+ getTestID = getTestIdDefault,
265
+ renderIndicator = renderIndicatorDefault,
266
+ gap = 0,
267
+ scrollEnabled,
268
+ jumpTo,
269
+ navigationState,
270
+ position,
271
+ activeColor,
272
+ bounces,
273
+ contentContainerStyle,
274
+ inactiveColor,
275
+ indicatorContainerStyle,
276
+ indicatorStyle,
277
+ labelStyle,
278
+ onTabLongPress,
279
+ onTabPress,
280
+ pressColor,
281
+ pressOpacity,
282
+ renderBadge,
283
+ renderIcon,
284
+ renderLabel,
285
+ renderTabBarItem,
286
+ style,
287
+ tabStyle,
288
+ testID,
289
+ android_ripple,
290
+ }: Props<T>) {
291
+ const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
292
+ const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
293
+ const flatListRef = React.useRef<FlatList | null>(null);
294
+ const isFirst = React.useRef(true);
295
+ const scrollAmount = useAnimatedValue(0);
296
+ const measuredTabWidths = React.useRef<Record<string, number>>({});
297
+
298
+ const { routes } = navigationState;
299
+ const flattenedTabWidth = getFlattenedTabWidth(tabStyle);
300
+ const isWidthDynamic = flattenedTabWidth === 'auto';
301
+ const scrollOffset = getScrollAmount({
302
+ layout,
303
+ navigationState,
304
+ tabWidths,
305
+ gap,
306
+ scrollEnabled,
307
+ flattenedTabWidth,
308
+ });
309
+
310
+ const hasMeasuredTabWidths =
311
+ Boolean(layout.width) &&
312
+ routes
313
+ .slice(0, navigationState.index)
314
+ .every((r) => typeof tabWidths[r.key] === 'number');
315
+
316
+ React.useEffect(() => {
317
+ if (isFirst.current) {
318
+ isFirst.current = false;
319
+ return;
320
+ }
321
+
322
+ if (isWidthDynamic && !hasMeasuredTabWidths) {
323
+ return;
324
+ }
325
+
326
+ if (scrollEnabled) {
327
+ flatListRef.current?.scrollToOffset({
328
+ offset: scrollOffset,
329
+ animated: true,
330
+ });
331
+ }
332
+ }, [hasMeasuredTabWidths, isWidthDynamic, scrollEnabled, scrollOffset]);
333
+
334
+ const handleLayout = (e: LayoutChangeEvent) => {
335
+ const { height, width } = e.nativeEvent.layout;
336
+
337
+ setLayout((layout) =>
338
+ layout.width === width && layout.height === height
339
+ ? layout
340
+ : { width, height }
341
+ );
342
+ };
343
+
344
+ const tabBarWidth = getTabBarWidth({
345
+ layout,
346
+ navigationState,
347
+ tabWidths,
348
+ gap,
349
+ scrollEnabled,
350
+ flattenedTabWidth,
351
+ });
352
+
353
+ const separatorsWidth = Math.max(0, routes.length - 1) * gap;
354
+ const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
355
+ const tabBarWidthPercent = `${routes.length * 40}%`;
356
+
357
+ const translateX = React.useMemo(
358
+ () =>
359
+ getTranslateX(
360
+ scrollAmount,
361
+ getMaxScrollDistance(tabBarWidth, layout.width)
362
+ ),
363
+ [layout.width, scrollAmount, tabBarWidth]
364
+ );
365
+
366
+ const renderItem = React.useCallback(
367
+ ({ item: route, index }: ListRenderItemInfo<T>) => {
368
+ const props: TabBarItemProps<T> & { key: string } = {
369
+ key: route.key,
370
+ position: position,
371
+ route: route,
372
+ navigationState: navigationState,
373
+ getAccessibilityLabel: getAccessibilityLabel,
374
+ getAccessible: getAccessible,
375
+ getLabelText: getLabelText,
376
+ getTestID: getTestID,
377
+ renderBadge: renderBadge,
378
+ renderIcon: renderIcon,
379
+ renderLabel: renderLabel,
380
+ activeColor: activeColor,
381
+ inactiveColor: inactiveColor,
382
+ pressColor: pressColor,
383
+ pressOpacity: pressOpacity,
384
+ onLayout: isWidthDynamic
385
+ ? (e: LayoutChangeEvent) => {
386
+ measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;
387
+
388
+ // When we have measured widths for all of the tabs, we should updates the state
389
+ // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
390
+ // If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
391
+ if (
392
+ routes.length > MEASURE_PER_BATCH &&
393
+ index === MEASURE_PER_BATCH &&
394
+ routes
395
+ .slice(0, MEASURE_PER_BATCH)
396
+ .every(
397
+ (r) => typeof measuredTabWidths.current[r.key] === 'number'
398
+ )
399
+ ) {
400
+ setTabWidths({ ...measuredTabWidths.current });
401
+ } else if (
402
+ routes.every(
403
+ (r) => typeof measuredTabWidths.current[r.key] === 'number'
404
+ )
405
+ ) {
406
+ // When we have measured widths for all of the tabs, we should updates the state
407
+ // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
408
+ setTabWidths({ ...measuredTabWidths.current });
409
+ }
410
+ }
411
+ : undefined,
412
+ onPress: () => {
413
+ const event: Scene<T> & Event = {
414
+ route,
415
+ defaultPrevented: false,
416
+ preventDefault: () => {
417
+ event.defaultPrevented = true;
418
+ },
419
+ };
420
+
421
+ onTabPress?.(event);
422
+
423
+ if (event.defaultPrevented) {
424
+ return;
425
+ }
426
+
427
+ jumpTo(route.key);
428
+ },
429
+ onLongPress: () => onTabLongPress?.({ route }),
430
+ labelStyle: labelStyle,
431
+ style: tabStyle,
432
+ // Calculate the deafult width for tab for FlatList to work
433
+ defaultTabWidth: !isWidthDynamic
434
+ ? getComputedTabWidth(
435
+ index,
436
+ layout,
437
+ routes,
438
+ scrollEnabled,
439
+ tabWidths,
440
+ getFlattenedTabWidth(tabStyle)
441
+ )
442
+ : undefined,
443
+ android_ripple,
444
+ };
445
+
446
+ return (
447
+ <>
448
+ {gap > 0 && index > 0 ? <Separator width={gap} /> : null}
449
+ {renderTabBarItem ? (
450
+ renderTabBarItem(props)
451
+ ) : (
452
+ <TabBarItem {...props} />
453
+ )}
454
+ </>
455
+ );
456
+ },
457
+ [
458
+ activeColor,
459
+ android_ripple,
460
+ gap,
461
+ getAccessibilityLabel,
462
+ getAccessible,
463
+ getLabelText,
464
+ getTestID,
465
+ inactiveColor,
466
+ isWidthDynamic,
467
+ jumpTo,
468
+ labelStyle,
469
+ layout,
470
+ navigationState,
471
+ onTabLongPress,
472
+ onTabPress,
473
+ position,
474
+ pressColor,
475
+ pressOpacity,
476
+ renderBadge,
477
+ renderIcon,
478
+ renderLabel,
479
+ renderTabBarItem,
480
+ routes,
481
+ scrollEnabled,
482
+ tabStyle,
483
+ tabWidths,
484
+ ]
485
+ );
486
+
487
+ const keyExtractor = React.useCallback((item: T) => item.key, []);
488
+
489
+ const contentContainerStyleMemoized = React.useMemo(
490
+ () => [
491
+ styles.tabContent,
492
+ scrollEnabled
493
+ ? {
494
+ width:
495
+ tabBarWidth > separatorsWidth ? tabBarWidth : tabBarWidthPercent,
496
+ }
497
+ : styles.container,
498
+ contentContainerStyle,
499
+ ],
500
+ [
501
+ contentContainerStyle,
502
+ scrollEnabled,
503
+ separatorsWidth,
504
+ tabBarWidth,
505
+ tabBarWidthPercent,
506
+ ]
507
+ );
508
+
509
+ const handleScroll = React.useMemo(
510
+ () =>
511
+ Animated.event(
512
+ [
513
+ {
514
+ nativeEvent: {
515
+ contentOffset: { x: scrollAmount },
516
+ },
517
+ },
518
+ ],
519
+ { useNativeDriver: true }
520
+ ),
521
+ [scrollAmount]
522
+ );
523
+
524
+ const handleViewableItemsChanged = useLatestCallback(
525
+ ({ changed }: { changed: ViewToken[] }) => {
526
+ if (routes.length <= MEASURE_PER_BATCH) {
527
+ return;
528
+ }
529
+ // Get next vievable item
530
+ const item = changed[changed.length - 1];
531
+ const index = item?.index || 0;
532
+ if (
533
+ item.isViewable &&
534
+ (index % 10 === 0 ||
535
+ index === navigationState.index ||
536
+ index === routes.length - 1)
537
+ ) {
538
+ setTabWidths({ ...measuredTabWidths.current });
539
+ }
540
+ }
541
+ );
542
+
543
+ // FIXME: Not sure why i have to as-any here (x3) ..
544
+ return (
545
+ <Animated.View onLayout={handleLayout} style={[styles.tabBar, style]}>
546
+ <Animated.View
547
+ pointerEvents="none"
548
+ style={[
549
+ styles.indicatorContainer,
550
+ scrollEnabled ? { transform: [{ translateX }] as any } : null,
551
+ tabBarWidth > separatorsWidth
552
+ ? { width: tabBarWidth - separatorsWidth as any }
553
+ : scrollEnabled
554
+ ? { width: tabBarWidthPercent as any }
555
+ : null,
556
+ indicatorContainerStyle,
557
+ ]}
558
+ >
559
+ {renderIndicator({
560
+ position,
561
+ layout,
562
+ navigationState,
563
+ jumpTo,
564
+ width: isWidthDynamic
565
+ ? 'auto'
566
+ : `${(100 - separatorPercent) / routes.length}%`,
567
+ style: indicatorStyle,
568
+ getTabWidth: (i: number) =>
569
+ getComputedTabWidth(
570
+ i,
571
+ layout,
572
+ routes,
573
+ scrollEnabled,
574
+ tabWidths,
575
+ flattenedTabWidth
576
+ ),
577
+ gap,
578
+ })}
579
+ </Animated.View>
580
+ <View style={styles.scroll}>
581
+ <Animated.FlatList
582
+ data={routes as Animated.WithAnimatedValue<T>[]}
583
+ keyExtractor={keyExtractor}
584
+ horizontal
585
+ accessibilityRole="tablist"
586
+ keyboardShouldPersistTaps="handled"
587
+ scrollEnabled={scrollEnabled}
588
+ bounces={bounces}
589
+ initialNumToRender={MEASURE_PER_BATCH}
590
+ onViewableItemsChanged={handleViewableItemsChanged}
591
+ alwaysBounceHorizontal={false}
592
+ scrollsToTop={false}
593
+ showsHorizontalScrollIndicator={false}
594
+ showsVerticalScrollIndicator={false}
595
+ automaticallyAdjustContentInsets={false}
596
+ overScrollMode="never"
597
+ contentContainerStyle={contentContainerStyleMemoized as any}
598
+ scrollEventThrottle={16}
599
+ renderItem={renderItem}
600
+ onScroll={handleScroll}
601
+ ref={flatListRef}
602
+ testID={testID}
603
+ />
604
+ </View>
605
+ </Animated.View>
606
+ );
607
+ }
608
+
609
+ const styles = StyleSheet.create({
610
+ container: {
611
+ flex: 1,
612
+ },
613
+ scroll: {
614
+ overflow: Platform.select({ default: 'scroll', web: undefined }),
615
+ },
616
+ tabBar: {
617
+ backgroundColor: '#2196f3',
618
+ elevation: 4,
619
+ shadowColor: 'black',
620
+ shadowOpacity: 0.1,
621
+ shadowRadius: StyleSheet.hairlineWidth,
622
+ shadowOffset: {
623
+ height: StyleSheet.hairlineWidth,
624
+ width: 0,
625
+ },
626
+ zIndex: 1,
627
+ },
628
+ tabContent: {
629
+ flexDirection: 'row',
630
+ flexWrap: 'nowrap',
631
+ },
632
+ indicatorContainer: {
633
+ position: 'absolute',
634
+ top: 0,
635
+ left: 0,
636
+ right: 0,
637
+ bottom: 0,
638
+ },
639
+ });