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.
- package/README.md +2 -646
- package/lib/commonjs/PanResponderAdapter.js +6 -5
- package/lib/commonjs/PanResponderAdapter.js.map +1 -1
- package/lib/commonjs/SceneView.js.map +1 -1
- package/lib/commonjs/TabBar.js +79 -32
- package/lib/commonjs/TabBar.js.map +1 -1
- package/lib/commonjs/TabBarIndicator.js +8 -6
- package/lib/commonjs/TabBarIndicator.js.map +1 -1
- package/lib/commonjs/TabBarItem.js +29 -29
- package/lib/commonjs/TabBarItem.js.map +1 -1
- package/lib/commonjs/TabBarItemLabel.js +36 -0
- package/lib/commonjs/TabBarItemLabel.js.map +1 -0
- package/lib/commonjs/TabView.js +6 -1
- package/lib/commonjs/TabView.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +7 -6
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/TabBar.js +79 -32
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +9 -7
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +29 -29
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabBarItemLabel.js +28 -0
- package/lib/module/TabBarItemLabel.js.map +1 -0
- package/lib/module/TabView.js +7 -2
- package/lib/module/TabView.js.map +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
- package/lib/typescript/src/TabBar.d.ts +3 -2
- package/lib/typescript/src/TabBar.d.ts.map +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts +6 -4
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts +11 -0
- package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -0
- package/lib/typescript/src/TabView.d.ts +4 -3
- package/lib/typescript/src/TabView.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/PanResponderAdapter.tsx +7 -5
- package/src/SceneView.tsx +1 -1
- package/src/TabBar.tsx +139 -44
- package/src/TabBarIndicator.tsx +20 -7
- package/src/TabBarItem.tsx +50 -42
- package/src/TabBarItemLabel.tsx +39 -0
- package/src/TabView.tsx +18 -1
- 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
|
-
|
|
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' &&
|
|
170
|
+
Platform.OS === 'android' && direction === 'rtl'
|
|
124
171
|
? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1))
|
|
125
172
|
: scrollAmount,
|
|
126
|
-
|
|
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
|
-
|
|
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' &&
|
|
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
|
-
(
|
|
219
|
-
|
|
220
|
-
: tabWidth + (gap ?? 0))
|
|
300
|
+
(i > 0 ? gap ?? 0 : 0) +
|
|
301
|
+
(navigationState.index === i ? tabWidth / 2 : tabWidth)
|
|
221
302
|
);
|
|
222
|
-
},
|
|
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>(
|
|
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
|
|
354
|
-
|
|
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
|
|
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
|
-
:
|
|
565
|
-
|
|
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
|
},
|
package/src/TabBarIndicator.tsx
CHANGED
|
@@ -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 {
|
|
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,
|
|
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
|
|
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
|
|
package/src/TabBarItem.tsx
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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={
|
|
241
|
-
accessible={
|
|
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 {
|
|
270
|
-
|
|
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
|
+
});
|