@momo-kits/tab-view 0.0.55-alpha.30
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/Pager.android.tsx +1 -0
- package/Pager.ios.tsx +1 -0
- package/Pager.tsx +1 -0
- package/PagerViewAdapter.tsx +160 -0
- package/PanResponderAdapter.tsx +331 -0
- package/PlatformPressable.tsx +45 -0
- package/SceneMap.tsx +25 -0
- package/SceneView.tsx +142 -0
- package/TabBar.tsx +559 -0
- package/TabBarIndicator.tsx +160 -0
- package/TabBarItem.tsx +296 -0
- package/TabView.tsx +156 -0
- package/index.tsx +15 -0
- package/package.json +19 -0
- package/publish.sh +29 -0
- package/types.tsx +58 -0
- package/useAnimatedValue.tsx +12 -0
package/TabBar.tsx
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
View,
|
|
6
|
+
StyleProp,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
TextStyle,
|
|
9
|
+
LayoutChangeEvent,
|
|
10
|
+
I18nManager,
|
|
11
|
+
Platform,
|
|
12
|
+
FlatList,
|
|
13
|
+
ListRenderItemInfo,
|
|
14
|
+
} from 'react-native';
|
|
15
|
+
import TabBarItem, { Props as TabBarItemProps } from './TabBarItem';
|
|
16
|
+
import TabBarIndicator, { Props as IndicatorProps } from './TabBarIndicator';
|
|
17
|
+
import type {
|
|
18
|
+
Route,
|
|
19
|
+
Scene,
|
|
20
|
+
SceneRendererProps,
|
|
21
|
+
NavigationState,
|
|
22
|
+
Layout,
|
|
23
|
+
Event,
|
|
24
|
+
} from './types';
|
|
25
|
+
import { Colors, Spacing } from '@momo-kits/v2-core';
|
|
26
|
+
|
|
27
|
+
export type Props<T extends Route> = SceneRendererProps & {
|
|
28
|
+
navigationState: NavigationState<T>;
|
|
29
|
+
scrollEnabled?: boolean;
|
|
30
|
+
bounces?: boolean;
|
|
31
|
+
activeColor?: string;
|
|
32
|
+
inactiveColor?: string;
|
|
33
|
+
activeStyle?: StyleProp<TextStyle>;
|
|
34
|
+
inactiveStyle?: StyleProp<TextStyle>;
|
|
35
|
+
pressColor?: string;
|
|
36
|
+
pressOpacity?: number;
|
|
37
|
+
getLabelText: (scene: Scene<T>) => string | undefined;
|
|
38
|
+
getAccessible: (scene: Scene<T>) => boolean | undefined;
|
|
39
|
+
getAccessibilityLabel: (scene: Scene<T>) => string | undefined;
|
|
40
|
+
getTestID: (scene: Scene<T>) => string | undefined;
|
|
41
|
+
renderLabel?: (
|
|
42
|
+
scene: Scene<T> & {
|
|
43
|
+
focused: boolean;
|
|
44
|
+
color: string;
|
|
45
|
+
},
|
|
46
|
+
) => React.ReactNode;
|
|
47
|
+
renderIcon?: (
|
|
48
|
+
scene: Scene<T> & {
|
|
49
|
+
focused: boolean;
|
|
50
|
+
color: string;
|
|
51
|
+
},
|
|
52
|
+
) => React.ReactNode;
|
|
53
|
+
renderBadge?: (scene: Scene<T>) => React.ReactNode;
|
|
54
|
+
renderIndicator: (props: IndicatorProps<T>) => React.ReactNode;
|
|
55
|
+
renderTabBarItem?: (
|
|
56
|
+
props: TabBarItemProps<T> & { key: string },
|
|
57
|
+
) => React.ReactElement;
|
|
58
|
+
onTabPress?: (scene: Scene<T> & Event) => void;
|
|
59
|
+
onTabLongPress?: (scene: Scene<T>) => void;
|
|
60
|
+
tabStyle?: StyleProp<ViewStyle>;
|
|
61
|
+
indicatorStyle?: StyleProp<ViewStyle>;
|
|
62
|
+
indicatorContainerStyle?: StyleProp<ViewStyle>;
|
|
63
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
64
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
65
|
+
style?: StyleProp<ViewStyle>;
|
|
66
|
+
gap?: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type State = {
|
|
70
|
+
layout: Layout;
|
|
71
|
+
tabWidths: { [key: string]: number };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const Separator = ({ width }: { width: number }) => {
|
|
75
|
+
return <View style={{ width }} />;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default class TabBar<T extends Route> extends React.Component<
|
|
79
|
+
Props<T>,
|
|
80
|
+
State
|
|
81
|
+
> {
|
|
82
|
+
static defaultProps = {
|
|
83
|
+
getLabelText: ({ route }: Scene<Route>) => route.title,
|
|
84
|
+
getAccessible: ({ route }: Scene<Route>) =>
|
|
85
|
+
typeof route.accessible !== 'undefined' ? route.accessible : true,
|
|
86
|
+
getAccessibilityLabel: ({ route }: Scene<Route>) =>
|
|
87
|
+
typeof route.accessibilityLabel === 'string'
|
|
88
|
+
? route.accessibilityLabel
|
|
89
|
+
: typeof route.title === 'string'
|
|
90
|
+
? route.title
|
|
91
|
+
: undefined,
|
|
92
|
+
getTestID: ({ route }: Scene<Route>) => route.testID,
|
|
93
|
+
renderIndicator: (props: IndicatorProps<Route>) => (
|
|
94
|
+
<TabBarIndicator {...props} />
|
|
95
|
+
),
|
|
96
|
+
gap: 0,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
state: State = {
|
|
100
|
+
layout: { width: 0, height: 0 },
|
|
101
|
+
tabWidths: {},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
componentDidUpdate(prevProps: Props<T>, prevState: State) {
|
|
105
|
+
const { navigationState } = this.props;
|
|
106
|
+
const { layout, tabWidths } = this.state;
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
prevProps.navigationState.routes.length !==
|
|
110
|
+
navigationState.routes.length ||
|
|
111
|
+
prevProps.navigationState.index !== navigationState.index ||
|
|
112
|
+
prevState.layout.width !== layout.width ||
|
|
113
|
+
prevState.tabWidths !== tabWidths
|
|
114
|
+
) {
|
|
115
|
+
if (
|
|
116
|
+
this.getFlattenedTabWidth(this.props.tabStyle) === 'auto' &&
|
|
117
|
+
!(
|
|
118
|
+
layout.width &&
|
|
119
|
+
navigationState.routes.every(
|
|
120
|
+
(r) => typeof tabWidths[r.key] === 'number',
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
) {
|
|
124
|
+
// When tab width is dynamic, only adjust the scroll once we have all tab widths and layout
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.resetScroll(navigationState.index);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// to store the layout.width of each tab
|
|
133
|
+
// when all onLayout's are fired, this would be set in state
|
|
134
|
+
private measuredTabWidths: { [key: string]: number } = {};
|
|
135
|
+
|
|
136
|
+
private scrollAmount = new Animated.Value(0);
|
|
137
|
+
|
|
138
|
+
private flatListRef = React.createRef<FlatList>();
|
|
139
|
+
|
|
140
|
+
private getFlattenedTabWidth = (style: StyleProp<ViewStyle>) => {
|
|
141
|
+
const tabStyle = StyleSheet.flatten(style);
|
|
142
|
+
|
|
143
|
+
return tabStyle ? tabStyle.width : undefined;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
private getComputedTabWidth = (
|
|
147
|
+
index: number,
|
|
148
|
+
layout: Layout,
|
|
149
|
+
routes: Route[],
|
|
150
|
+
scrollEnabled: boolean | undefined,
|
|
151
|
+
tabWidths: { [key: string]: number },
|
|
152
|
+
flattenedWidth: string | number | undefined,
|
|
153
|
+
) => {
|
|
154
|
+
if (flattenedWidth === 'auto') {
|
|
155
|
+
return tabWidths[routes[index].key] || 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
switch (typeof flattenedWidth) {
|
|
159
|
+
case 'number':
|
|
160
|
+
return flattenedWidth;
|
|
161
|
+
case 'string':
|
|
162
|
+
if (flattenedWidth.endsWith('%')) {
|
|
163
|
+
const width = parseFloat(flattenedWidth);
|
|
164
|
+
if (Number.isFinite(width)) {
|
|
165
|
+
return layout.width * (width / 100);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (scrollEnabled) {
|
|
171
|
+
return (layout.width / 5) * 2;
|
|
172
|
+
}
|
|
173
|
+
return layout.width / routes.length;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
private getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
|
|
177
|
+
tabBarWidth - layoutWidth;
|
|
178
|
+
|
|
179
|
+
private getTabBarWidth = (props: Props<T>, state: State) => {
|
|
180
|
+
const { layout, tabWidths } = state;
|
|
181
|
+
const { scrollEnabled, tabStyle } = props;
|
|
182
|
+
const { routes } = props.navigationState;
|
|
183
|
+
|
|
184
|
+
return routes.reduce<number>(
|
|
185
|
+
(acc, _, i) =>
|
|
186
|
+
acc +
|
|
187
|
+
(i > 0 ? props.gap ?? 0 : 0) +
|
|
188
|
+
this.getComputedTabWidth(
|
|
189
|
+
i,
|
|
190
|
+
layout,
|
|
191
|
+
routes,
|
|
192
|
+
scrollEnabled,
|
|
193
|
+
tabWidths,
|
|
194
|
+
this.getFlattenedTabWidth(tabStyle),
|
|
195
|
+
),
|
|
196
|
+
0,
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
private normalizeScrollValue = (
|
|
201
|
+
props: Props<T>,
|
|
202
|
+
state: State,
|
|
203
|
+
value: number,
|
|
204
|
+
) => {
|
|
205
|
+
const { layout } = state;
|
|
206
|
+
const tabBarWidth = this.getTabBarWidth(props, state);
|
|
207
|
+
const maxDistance = this.getMaxScrollDistance(
|
|
208
|
+
tabBarWidth,
|
|
209
|
+
layout.width,
|
|
210
|
+
);
|
|
211
|
+
const scrollValue = Math.max(Math.min(value, maxDistance), 0);
|
|
212
|
+
|
|
213
|
+
if (Platform.OS === 'android' && I18nManager.isRTL) {
|
|
214
|
+
// On Android, scroll value is not applied in reverse in RTL
|
|
215
|
+
// so we need to manually adjust it to apply correct value
|
|
216
|
+
return maxDistance - scrollValue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return scrollValue;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
private getScrollAmount = (
|
|
223
|
+
props: Props<T>,
|
|
224
|
+
state: State,
|
|
225
|
+
index: number,
|
|
226
|
+
) => {
|
|
227
|
+
const { layout, tabWidths } = state;
|
|
228
|
+
const { scrollEnabled, tabStyle } = props;
|
|
229
|
+
const { routes } = props.navigationState;
|
|
230
|
+
|
|
231
|
+
const centerDistance = Array.from({ length: index + 1 }).reduce<number>(
|
|
232
|
+
(total, _, i) => {
|
|
233
|
+
const tabWidth = this.getComputedTabWidth(
|
|
234
|
+
i,
|
|
235
|
+
layout,
|
|
236
|
+
routes,
|
|
237
|
+
scrollEnabled,
|
|
238
|
+
tabWidths,
|
|
239
|
+
this.getFlattenedTabWidth(tabStyle),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// To get the current index centered we adjust scroll amount by width of indexes
|
|
243
|
+
// 0 through (i - 1) and add half the width of current index i
|
|
244
|
+
return (
|
|
245
|
+
total +
|
|
246
|
+
(index === i
|
|
247
|
+
? (tabWidth + (props.gap ?? 0)) / 2
|
|
248
|
+
: tabWidth + (props.gap ?? 0))
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
0,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const scrollAmount = centerDistance - layout.width / 2;
|
|
255
|
+
|
|
256
|
+
return this.normalizeScrollValue(props, state, scrollAmount);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
private resetScroll = (index: number) => {
|
|
260
|
+
if (this.props.scrollEnabled) {
|
|
261
|
+
this.flatListRef.current?.scrollToOffset({
|
|
262
|
+
offset: this.getScrollAmount(this.props, this.state, index),
|
|
263
|
+
animated: true,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
private handleLayout = (e: LayoutChangeEvent) => {
|
|
269
|
+
const { height, width } = e.nativeEvent.layout;
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
this.state.layout.width === width &&
|
|
273
|
+
this.state.layout.height === height
|
|
274
|
+
) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.setState({
|
|
279
|
+
layout: {
|
|
280
|
+
height,
|
|
281
|
+
width,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
private getTranslateX = (
|
|
287
|
+
scrollAmount: Animated.Value,
|
|
288
|
+
maxScrollDistance: number,
|
|
289
|
+
) =>
|
|
290
|
+
Animated.multiply(
|
|
291
|
+
Platform.OS === 'android' && I18nManager.isRTL
|
|
292
|
+
? Animated.add(
|
|
293
|
+
maxScrollDistance,
|
|
294
|
+
Animated.multiply(scrollAmount, -1),
|
|
295
|
+
)
|
|
296
|
+
: scrollAmount,
|
|
297
|
+
I18nManager.isRTL ? 1 : -1,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
render() {
|
|
301
|
+
const {
|
|
302
|
+
position,
|
|
303
|
+
navigationState,
|
|
304
|
+
jumpTo,
|
|
305
|
+
scrollEnabled,
|
|
306
|
+
bounces,
|
|
307
|
+
getAccessibilityLabel,
|
|
308
|
+
getAccessible,
|
|
309
|
+
getLabelText,
|
|
310
|
+
getTestID,
|
|
311
|
+
renderBadge,
|
|
312
|
+
renderIcon,
|
|
313
|
+
renderLabel,
|
|
314
|
+
renderTabBarItem,
|
|
315
|
+
activeColor = Colors.black_21,
|
|
316
|
+
inactiveColor = Colors.black_09,
|
|
317
|
+
pressColor,
|
|
318
|
+
pressOpacity,
|
|
319
|
+
onTabPress,
|
|
320
|
+
onTabLongPress,
|
|
321
|
+
tabStyle,
|
|
322
|
+
labelStyle,
|
|
323
|
+
indicatorStyle,
|
|
324
|
+
contentContainerStyle,
|
|
325
|
+
style,
|
|
326
|
+
indicatorContainerStyle,
|
|
327
|
+
gap = 0,
|
|
328
|
+
} = this.props;
|
|
329
|
+
const { layout, tabWidths } = this.state;
|
|
330
|
+
const { routes } = navigationState;
|
|
331
|
+
|
|
332
|
+
const isWidthDynamic = this.getFlattenedTabWidth(tabStyle) === 'auto';
|
|
333
|
+
const tabBarWidth = this.getTabBarWidth(this.props, this.state);
|
|
334
|
+
const separatorsWidth = Math.max(0, routes.length - 1) * gap;
|
|
335
|
+
const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
|
|
336
|
+
|
|
337
|
+
const tabBarWidthPercent = `${routes.length * 40}%`;
|
|
338
|
+
const translateX = this.getTranslateX(
|
|
339
|
+
this.scrollAmount,
|
|
340
|
+
this.getMaxScrollDistance(tabBarWidth, layout.width),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<Animated.View
|
|
345
|
+
onLayout={this.handleLayout}
|
|
346
|
+
style={[styles.tabBar, style]}>
|
|
347
|
+
<Animated.View
|
|
348
|
+
pointerEvents="none"
|
|
349
|
+
style={[
|
|
350
|
+
styles.indicatorContainer,
|
|
351
|
+
scrollEnabled
|
|
352
|
+
? { transform: [{ translateX }] as any }
|
|
353
|
+
: null,
|
|
354
|
+
tabBarWidth > separatorsWidth
|
|
355
|
+
? { width: tabBarWidth - separatorsWidth }
|
|
356
|
+
: scrollEnabled
|
|
357
|
+
? { width: tabBarWidthPercent }
|
|
358
|
+
: null,
|
|
359
|
+
indicatorContainerStyle,
|
|
360
|
+
]}>
|
|
361
|
+
{this.props.renderIndicator({
|
|
362
|
+
position,
|
|
363
|
+
layout,
|
|
364
|
+
navigationState,
|
|
365
|
+
jumpTo,
|
|
366
|
+
width: isWidthDynamic
|
|
367
|
+
? 'auto'
|
|
368
|
+
: `${(100 - separatorPercent) / routes.length}%`,
|
|
369
|
+
style: indicatorStyle,
|
|
370
|
+
getTabWidth: (i: number) =>
|
|
371
|
+
this.getComputedTabWidth(
|
|
372
|
+
i,
|
|
373
|
+
layout,
|
|
374
|
+
routes,
|
|
375
|
+
scrollEnabled,
|
|
376
|
+
tabWidths,
|
|
377
|
+
this.getFlattenedTabWidth(tabStyle),
|
|
378
|
+
),
|
|
379
|
+
gap,
|
|
380
|
+
})}
|
|
381
|
+
</Animated.View>
|
|
382
|
+
<View style={styles.scroll}>
|
|
383
|
+
<Animated.FlatList
|
|
384
|
+
data={routes as Animated.WithAnimatedValue<T>[]}
|
|
385
|
+
keyExtractor={(item) => item.key}
|
|
386
|
+
horizontal
|
|
387
|
+
accessibilityRole="tablist"
|
|
388
|
+
keyboardShouldPersistTaps="handled"
|
|
389
|
+
scrollEnabled={scrollEnabled}
|
|
390
|
+
bounces={bounces}
|
|
391
|
+
alwaysBounceHorizontal={false}
|
|
392
|
+
scrollsToTop={false}
|
|
393
|
+
showsHorizontalScrollIndicator={false}
|
|
394
|
+
showsVerticalScrollIndicator={false}
|
|
395
|
+
automaticallyAdjustContentInsets={false}
|
|
396
|
+
overScrollMode="never"
|
|
397
|
+
contentContainerStyle={[
|
|
398
|
+
styles.tabContent,
|
|
399
|
+
scrollEnabled
|
|
400
|
+
? {
|
|
401
|
+
width:
|
|
402
|
+
tabBarWidth > separatorsWidth
|
|
403
|
+
? tabBarWidth
|
|
404
|
+
: tabBarWidthPercent,
|
|
405
|
+
}
|
|
406
|
+
: styles.container,
|
|
407
|
+
contentContainerStyle,
|
|
408
|
+
]}
|
|
409
|
+
scrollEventThrottle={16}
|
|
410
|
+
renderItem={({
|
|
411
|
+
item: route,
|
|
412
|
+
index,
|
|
413
|
+
}: ListRenderItemInfo<T>) => {
|
|
414
|
+
const props: TabBarItemProps<T> & { key: string } =
|
|
415
|
+
{
|
|
416
|
+
key: route.key,
|
|
417
|
+
position: position,
|
|
418
|
+
route: route,
|
|
419
|
+
navigationState: navigationState,
|
|
420
|
+
getAccessibilityLabel:
|
|
421
|
+
getAccessibilityLabel,
|
|
422
|
+
getAccessible: getAccessible,
|
|
423
|
+
getLabelText: getLabelText,
|
|
424
|
+
getTestID: getTestID,
|
|
425
|
+
renderBadge: renderBadge,
|
|
426
|
+
renderIcon: renderIcon,
|
|
427
|
+
renderLabel: renderLabel,
|
|
428
|
+
activeColor: activeColor,
|
|
429
|
+
inactiveColor: inactiveColor,
|
|
430
|
+
|
|
431
|
+
pressColor: pressColor,
|
|
432
|
+
pressOpacity: pressOpacity,
|
|
433
|
+
onLayout: isWidthDynamic
|
|
434
|
+
? (e) => {
|
|
435
|
+
this.measuredTabWidths[
|
|
436
|
+
route.key
|
|
437
|
+
] = e.nativeEvent.layout.width;
|
|
438
|
+
|
|
439
|
+
// When we have measured widths for all of the tabs, we should updates the state
|
|
440
|
+
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
|
|
441
|
+
if (
|
|
442
|
+
routes.every(
|
|
443
|
+
(r) =>
|
|
444
|
+
typeof this
|
|
445
|
+
.measuredTabWidths[
|
|
446
|
+
r.key
|
|
447
|
+
] === 'number',
|
|
448
|
+
)
|
|
449
|
+
) {
|
|
450
|
+
this.setState({
|
|
451
|
+
tabWidths: {
|
|
452
|
+
...this
|
|
453
|
+
.measuredTabWidths,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
: undefined,
|
|
459
|
+
onPress: () => {
|
|
460
|
+
const event: Scene<T> & Event = {
|
|
461
|
+
route,
|
|
462
|
+
defaultPrevented: false,
|
|
463
|
+
preventDefault: () => {
|
|
464
|
+
event.defaultPrevented = true;
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
onTabPress?.(event);
|
|
469
|
+
|
|
470
|
+
if (event.defaultPrevented) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.props.jumpTo(route.key);
|
|
475
|
+
},
|
|
476
|
+
onLongPress: () =>
|
|
477
|
+
onTabLongPress?.({ route }),
|
|
478
|
+
labelStyle: labelStyle,
|
|
479
|
+
style: [
|
|
480
|
+
tabStyle,
|
|
481
|
+
// Calculate the deafult width for tab for FlatList to work.
|
|
482
|
+
this.getFlattenedTabWidth(tabStyle) ===
|
|
483
|
+
undefined && {
|
|
484
|
+
width: this.getComputedTabWidth(
|
|
485
|
+
index,
|
|
486
|
+
layout,
|
|
487
|
+
routes,
|
|
488
|
+
scrollEnabled,
|
|
489
|
+
tabWidths,
|
|
490
|
+
this.getFlattenedTabWidth(
|
|
491
|
+
tabStyle,
|
|
492
|
+
),
|
|
493
|
+
),
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<React.Fragment key={route.key}>
|
|
500
|
+
{gap > 0 && index > 0 ? (
|
|
501
|
+
<Separator width={gap} />
|
|
502
|
+
) : null}
|
|
503
|
+
{renderTabBarItem ? (
|
|
504
|
+
renderTabBarItem(props)
|
|
505
|
+
) : (
|
|
506
|
+
<TabBarItem {...props} />
|
|
507
|
+
)}
|
|
508
|
+
</React.Fragment>
|
|
509
|
+
);
|
|
510
|
+
}}
|
|
511
|
+
onScroll={Animated.event(
|
|
512
|
+
[
|
|
513
|
+
{
|
|
514
|
+
nativeEvent: {
|
|
515
|
+
contentOffset: { x: this.scrollAmount },
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
{ useNativeDriver: true },
|
|
520
|
+
)}
|
|
521
|
+
ref={this.flatListRef}
|
|
522
|
+
/>
|
|
523
|
+
</View>
|
|
524
|
+
</Animated.View>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const styles = StyleSheet.create({
|
|
530
|
+
container: {
|
|
531
|
+
flex: 1,
|
|
532
|
+
},
|
|
533
|
+
scroll: {
|
|
534
|
+
overflow: Platform.select({ default: 'scroll', web: undefined }),
|
|
535
|
+
},
|
|
536
|
+
tabBar: {
|
|
537
|
+
backgroundColor: Colors.black_01,
|
|
538
|
+
elevation: 4,
|
|
539
|
+
shadowColor: 'black',
|
|
540
|
+
shadowOpacity: 0.1,
|
|
541
|
+
shadowRadius: StyleSheet.hairlineWidth,
|
|
542
|
+
shadowOffset: {
|
|
543
|
+
height: StyleSheet.hairlineWidth,
|
|
544
|
+
width: 0,
|
|
545
|
+
},
|
|
546
|
+
zIndex: 1,
|
|
547
|
+
},
|
|
548
|
+
tabContent: {
|
|
549
|
+
flexDirection: 'row',
|
|
550
|
+
flexWrap: 'nowrap',
|
|
551
|
+
},
|
|
552
|
+
indicatorContainer: {
|
|
553
|
+
position: 'absolute',
|
|
554
|
+
top: 0,
|
|
555
|
+
left: 0,
|
|
556
|
+
right: 0,
|
|
557
|
+
bottom: 0,
|
|
558
|
+
},
|
|
559
|
+
});
|