@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
@@ -0,0 +1,153 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ DimensionValue,
5
+ Easing,
6
+ I18nManager,
7
+ Platform,
8
+ StyleProp,
9
+ StyleSheet,
10
+ ViewStyle,
11
+ } from 'react-native';
12
+
13
+ import type { NavigationState, Route, SceneRendererProps } from './types';
14
+ import useAnimatedValue from './useAnimatedValue';
15
+
16
+ export type GetTabWidth = (index: number) => number;
17
+
18
+ export type Props<T extends Route> = SceneRendererProps & {
19
+ navigationState: NavigationState<T>;
20
+ width: string | number;
21
+ style?: StyleProp<ViewStyle>;
22
+ getTabWidth: GetTabWidth;
23
+ gap?: number;
24
+ };
25
+
26
+ const getTranslateX = (
27
+ position: Animated.AnimatedInterpolation<number>,
28
+ routes: Route[],
29
+ getTabWidth: GetTabWidth,
30
+ gap?: number
31
+ ) => {
32
+ const inputRange = routes.map((_, i) => i);
33
+
34
+ // every index contains widths at all previous indices
35
+ const outputRange = routes.reduce<number[]>((acc, _, i) => {
36
+ if (i === 0) return [0];
37
+ return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
38
+ }, []);
39
+
40
+ const translateX = position.interpolate({
41
+ inputRange,
42
+ outputRange,
43
+ extrapolate: 'clamp',
44
+ });
45
+
46
+ return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
47
+ };
48
+
49
+ export default function TabBarIndicator<T extends Route>({
50
+ getTabWidth,
51
+ layout,
52
+ navigationState,
53
+ position,
54
+ width,
55
+ gap,
56
+ style,
57
+ }: Props<T>) {
58
+ const isIndicatorShown = React.useRef(false);
59
+ const isWidthDynamic = width === 'auto';
60
+
61
+ const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
62
+
63
+ const indicatorVisible = isWidthDynamic
64
+ ? layout.width &&
65
+ navigationState.routes
66
+ .slice(0, navigationState.index)
67
+ .every((_, r) => getTabWidth(r))
68
+ : true;
69
+
70
+ React.useEffect(() => {
71
+ const fadeInIndicator = () => {
72
+ if (
73
+ !isIndicatorShown.current &&
74
+ isWidthDynamic &&
75
+ // We should fade-in the indicator when we have widths for all the tab items
76
+ indicatorVisible
77
+ ) {
78
+ isIndicatorShown.current = true;
79
+
80
+ Animated.timing(opacity, {
81
+ toValue: 1,
82
+ duration: 150,
83
+ easing: Easing.in(Easing.linear),
84
+ useNativeDriver: true,
85
+ }).start();
86
+ }
87
+ };
88
+
89
+ fadeInIndicator();
90
+
91
+ return () => opacity.stopAnimation();
92
+ }, [indicatorVisible, isWidthDynamic, opacity]);
93
+
94
+ const { routes } = navigationState;
95
+
96
+ const transform = [];
97
+
98
+ if (layout.width) {
99
+ const translateX =
100
+ routes.length > 1 ? getTranslateX(position, routes, getTabWidth, gap) : 0;
101
+
102
+ transform.push({ translateX });
103
+ }
104
+
105
+ if (width === 'auto') {
106
+ const inputRange = routes.map((_, i) => i);
107
+ const outputRange = inputRange.map(getTabWidth);
108
+
109
+ transform.push(
110
+ {
111
+ scaleX:
112
+ routes.length > 1
113
+ ? position.interpolate({
114
+ inputRange,
115
+ outputRange,
116
+ extrapolate: 'clamp',
117
+ })
118
+ : outputRange[0],
119
+ },
120
+ { translateX: 0.5 }
121
+ );
122
+ }
123
+
124
+ return (
125
+ <Animated.View
126
+ style={[
127
+ styles.indicator,
128
+ { width: (width === 'auto' ? 1 : width) as DimensionValue | undefined },
129
+ // If layout is not available, use `left` property for positioning the indicator
130
+ // This avoids rendering delay until we are able to calculate translateX
131
+ // If platform is macos use `left` property as `transform` is broken at the moment.
132
+ // See: https://github.com/microsoft/react-native-macos/issues/280
133
+ layout.width && Platform.OS !== 'macos'
134
+ ? { left: 0 }
135
+ : { left: `${(100 / routes.length) * navigationState.index}%` },
136
+ { transform },
137
+ width === 'auto' ? { opacity: opacity } : null,
138
+ style,
139
+ ]}
140
+ />
141
+ );
142
+ }
143
+
144
+ const styles = StyleSheet.create({
145
+ indicator: {
146
+ backgroundColor: '#ffeb3b',
147
+ position: 'absolute',
148
+ left: 0,
149
+ bottom: 0,
150
+ right: 0,
151
+ height: 2,
152
+ },
153
+ });
@@ -0,0 +1,319 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ LayoutChangeEvent,
5
+ PressableAndroidRippleConfig,
6
+ StyleProp,
7
+ StyleSheet,
8
+ TextStyle,
9
+ View,
10
+ ViewStyle,
11
+ } from 'react-native';
12
+ import useLatestCallback from 'use-latest-callback';
13
+
14
+ import PlatformPressable from './PlatformPressable';
15
+ import type { NavigationState, Route, Scene } from './types';
16
+
17
+ export type Props<T extends Route> = {
18
+ position: Animated.AnimatedInterpolation<number>;
19
+ route: T;
20
+ navigationState: NavigationState<T>;
21
+ activeColor?: string;
22
+ inactiveColor?: string;
23
+ pressColor?: string;
24
+ pressOpacity?: number;
25
+ getLabelText: (scene: Scene<T>) => string | undefined;
26
+ getAccessible: (scene: Scene<T>) => boolean | undefined;
27
+ getAccessibilityLabel: (scene: Scene<T>) => string | undefined;
28
+ getTestID: (scene: Scene<T>) => string | undefined;
29
+ renderLabel?: (scene: {
30
+ route: T;
31
+ focused: boolean;
32
+ color: string;
33
+ }) => React.ReactNode;
34
+ renderIcon?: (scene: {
35
+ route: T;
36
+ focused: boolean;
37
+ color: string;
38
+ }) => React.ReactNode;
39
+ renderBadge?: (scene: Scene<T>) => React.ReactNode;
40
+ onLayout?: (event: LayoutChangeEvent) => void;
41
+ onPress: () => void;
42
+ onLongPress: () => void;
43
+ defaultTabWidth?: number;
44
+ labelStyle?: StyleProp<TextStyle>;
45
+ style: StyleProp<ViewStyle>;
46
+ android_ripple?: PressableAndroidRippleConfig;
47
+ };
48
+
49
+ const DEFAULT_ACTIVE_COLOR = 'rgba(255, 255, 255, 1)';
50
+ const DEFAULT_INACTIVE_COLOR = 'rgba(255, 255, 255, 0.7)';
51
+
52
+ const getActiveOpacity = (
53
+ position: Animated.AnimatedInterpolation<number>,
54
+ routesLength: number,
55
+ tabIndex: number
56
+ ) => {
57
+ if (routesLength > 1) {
58
+ const inputRange = Array.from({ length: routesLength }, (_, i) => i);
59
+
60
+ return position.interpolate({
61
+ inputRange,
62
+ outputRange: inputRange.map((i) => (i === tabIndex ? 1 : 0)),
63
+ });
64
+ } else {
65
+ return 1;
66
+ }
67
+ };
68
+
69
+ const getInactiveOpacity = (
70
+ position: Animated.AnimatedInterpolation<number>,
71
+ routesLength: number,
72
+ tabIndex: number
73
+ ) => {
74
+ if (routesLength > 1) {
75
+ const inputRange = Array.from({ length: routesLength }, (_, i) => i);
76
+
77
+ return position.interpolate({
78
+ inputRange,
79
+ outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
80
+ });
81
+ } else {
82
+ return 0;
83
+ }
84
+ };
85
+
86
+ type TabBarItemInternalProps<T extends Route> = Omit<
87
+ Props<T>,
88
+ 'navigationState'
89
+ > & {
90
+ isFocused: boolean;
91
+ index: number;
92
+ routesLength: number;
93
+ };
94
+
95
+ const TabBarItemInternal = <T extends Route>({
96
+ getAccessibilityLabel,
97
+ getAccessible,
98
+ getLabelText,
99
+ getTestID,
100
+ onLongPress,
101
+ onPress,
102
+ isFocused,
103
+ position,
104
+ route,
105
+ style,
106
+ inactiveColor: inactiveColorCustom,
107
+ activeColor: activeColorCustom,
108
+ labelStyle,
109
+ onLayout,
110
+ index: tabIndex,
111
+ pressColor,
112
+ pressOpacity,
113
+ renderBadge,
114
+ renderIcon,
115
+ defaultTabWidth,
116
+ routesLength,
117
+ renderLabel: renderLabelCustom,
118
+ android_ripple = { borderless: true },
119
+ }: TabBarItemInternalProps<T>) => {
120
+ const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
121
+
122
+ const activeColor =
123
+ activeColorCustom !== undefined
124
+ ? activeColorCustom
125
+ : typeof labelColorFromStyle === 'string'
126
+ ? labelColorFromStyle
127
+ : DEFAULT_ACTIVE_COLOR;
128
+ const inactiveColor =
129
+ inactiveColorCustom !== undefined
130
+ ? inactiveColorCustom
131
+ : typeof labelColorFromStyle === 'string'
132
+ ? labelColorFromStyle
133
+ : DEFAULT_INACTIVE_COLOR;
134
+
135
+ const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
136
+ const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
137
+
138
+ let icon: React.ReactNode | null = null;
139
+ let label: React.ReactNode | null = null;
140
+
141
+ if (renderIcon) {
142
+ const activeIcon = renderIcon({
143
+ route,
144
+ focused: true,
145
+ color: activeColor,
146
+ });
147
+ const inactiveIcon = renderIcon({
148
+ route,
149
+ focused: false,
150
+ color: inactiveColor,
151
+ });
152
+
153
+ if (inactiveIcon != null && activeIcon != null) {
154
+ icon = (
155
+ <View style={styles.icon}>
156
+ <Animated.View style={{ opacity: inactiveOpacity }}>
157
+ {inactiveIcon}
158
+ </Animated.View>
159
+ <Animated.View
160
+ style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
161
+ >
162
+ {activeIcon}
163
+ </Animated.View>
164
+ </View>
165
+ );
166
+ }
167
+ }
168
+
169
+ const renderLabel =
170
+ renderLabelCustom !== undefined
171
+ ? renderLabelCustom
172
+ : (labelProps: { route: T; color: string }) => {
173
+ const labelText = getLabelText({ route: labelProps.route });
174
+
175
+ if (typeof labelText === 'string') {
176
+ return (
177
+ <Animated.Text
178
+ style={[
179
+ styles.label,
180
+ icon ? { marginTop: 0 } : null,
181
+ labelStyle,
182
+ { color: labelProps.color },
183
+ ]}
184
+ >
185
+ {labelText}
186
+ </Animated.Text>
187
+ );
188
+ }
189
+
190
+ return labelText;
191
+ };
192
+
193
+ if (renderLabel) {
194
+ const activeLabel = renderLabel({
195
+ route,
196
+ focused: true,
197
+ color: activeColor,
198
+ });
199
+ const inactiveLabel = renderLabel({
200
+ route,
201
+ focused: false,
202
+ color: inactiveColor,
203
+ });
204
+
205
+ label = (
206
+ <View>
207
+ <Animated.View style={{ opacity: inactiveOpacity }}>
208
+ {inactiveLabel}
209
+ </Animated.View>
210
+ <Animated.View
211
+ style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
212
+ >
213
+ {activeLabel}
214
+ </Animated.View>
215
+ </View>
216
+ );
217
+ }
218
+
219
+ const tabStyle = StyleSheet.flatten(style);
220
+ const isWidthSet = tabStyle?.width !== undefined;
221
+
222
+ const tabContainerStyle: ViewStyle | null = isWidthSet
223
+ ? null
224
+ : { width: defaultTabWidth };
225
+
226
+ const scene = { route };
227
+
228
+ let accessibilityLabel = getAccessibilityLabel(scene);
229
+
230
+ accessibilityLabel =
231
+ typeof accessibilityLabel !== 'undefined'
232
+ ? accessibilityLabel
233
+ : getLabelText(scene);
234
+
235
+ const badge = renderBadge ? renderBadge(scene) : null;
236
+
237
+ return (
238
+ <PlatformPressable
239
+ android_ripple={android_ripple}
240
+ testID={getTestID(scene)}
241
+ accessible={getAccessible(scene)}
242
+ accessibilityLabel={accessibilityLabel}
243
+ accessibilityRole="tab"
244
+ accessibilityState={{ selected: isFocused }}
245
+ // @ts-ignore: this is to support older React Native versions
246
+ accessibilityStates={isFocused ? ['selected'] : []}
247
+ pressColor={pressColor}
248
+ pressOpacity={pressOpacity}
249
+ delayPressIn={0}
250
+ onLayout={onLayout}
251
+ onPress={onPress}
252
+ onLongPress={onLongPress}
253
+ style={[styles.pressable, tabContainerStyle]}
254
+ >
255
+ <View pointerEvents="none" style={[styles.item, tabStyle]}>
256
+ {icon}
257
+ {label}
258
+ {badge != null ? <View style={styles.badge}>{badge}</View> : null}
259
+ </View>
260
+ </PlatformPressable>
261
+ );
262
+ };
263
+
264
+ const MemoizedTabBarItemInternal = React.memo(
265
+ TabBarItemInternal
266
+ ) as typeof TabBarItemInternal;
267
+
268
+ function TabBarItem<T extends Route>(props: Props<T>) {
269
+ const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
270
+ props;
271
+ const onPressLatest = useLatestCallback(onPress);
272
+ const onLongPressLatest = useLatestCallback(onLongPress);
273
+ const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
274
+
275
+ const tabIndex = navigationState.routes.indexOf(route);
276
+
277
+ return (
278
+ <MemoizedTabBarItemInternal
279
+ {...rest}
280
+ onPress={onPressLatest}
281
+ onLayout={onLayoutLatest}
282
+ onLongPress={onLongPressLatest}
283
+ isFocused={navigationState.index === tabIndex}
284
+ route={route}
285
+ index={tabIndex}
286
+ routesLength={navigationState.routes.length}
287
+ />
288
+ );
289
+ }
290
+
291
+ export default TabBarItem;
292
+
293
+ const styles = StyleSheet.create({
294
+ label: {
295
+ margin: 4,
296
+ backgroundColor: 'transparent',
297
+ textTransform: 'uppercase',
298
+ },
299
+ icon: {
300
+ margin: 2,
301
+ },
302
+ item: {
303
+ flex: 1,
304
+ alignItems: 'center',
305
+ justifyContent: 'center',
306
+ padding: 10,
307
+ minHeight: 48,
308
+ },
309
+ badge: {
310
+ position: 'absolute',
311
+ top: 0,
312
+ right: 0,
313
+ },
314
+ pressable: {
315
+ // The label is not pressable on Windows
316
+ // Adding backgroundColor: 'transparent' seems to fix it
317
+ backgroundColor: 'transparent',
318
+ },
319
+ });
@@ -0,0 +1,155 @@
1
+ import * as React from 'react';
2
+ import {
3
+ LayoutChangeEvent,
4
+ StyleProp,
5
+ StyleSheet,
6
+ View,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+
10
+ import Pager from './Pager';
11
+ import SceneView from './SceneView';
12
+ import TabBar from './TabBar';
13
+ import type {
14
+ Layout,
15
+ NavigationState,
16
+ PagerProps,
17
+ Route,
18
+ SceneRendererProps,
19
+ } from './types';
20
+
21
+ export type Props<T extends Route> = PagerProps & {
22
+ onIndexChange: (index: number) => void;
23
+ navigationState: NavigationState<T>;
24
+ renderScene: (props: SceneRendererProps & { route: T }) => React.ReactNode;
25
+ renderLazyPlaceholder?: (props: { route: T }) => React.ReactNode;
26
+ renderTabBar?: (
27
+ props: SceneRendererProps & { navigationState: NavigationState<T> }
28
+ ) => React.ReactNode;
29
+ tabBarPosition?: 'top' | 'bottom';
30
+ initialLayout?: Partial<Layout>;
31
+ lazy?: ((props: { route: T }) => boolean) | boolean;
32
+ lazyPreloadDistance?: number;
33
+ sceneContainerStyle?: StyleProp<ViewStyle>;
34
+ pagerStyle?: StyleProp<ViewStyle>;
35
+ style?: StyleProp<ViewStyle>;
36
+ };
37
+
38
+ export default function TabView<T extends Route>({
39
+ onIndexChange,
40
+ navigationState,
41
+ renderScene,
42
+ initialLayout,
43
+ keyboardDismissMode = 'auto',
44
+ lazy = false,
45
+ lazyPreloadDistance = 0,
46
+ onSwipeStart,
47
+ onSwipeEnd,
48
+ renderLazyPlaceholder = () => null,
49
+ renderTabBar = (props) => <TabBar {...props} />,
50
+ sceneContainerStyle,
51
+ pagerStyle,
52
+ style,
53
+ swipeEnabled = true,
54
+ tabBarPosition = 'top',
55
+ animationEnabled = true,
56
+ overScrollMode,
57
+ }: Props<T>) {
58
+ const [layout, setLayout] = React.useState({
59
+ width: 0,
60
+ height: 0,
61
+ ...initialLayout,
62
+ });
63
+
64
+ const jumpToIndex = (index: number) => {
65
+ if (index !== navigationState.index) {
66
+ onIndexChange(index);
67
+ }
68
+ };
69
+
70
+ const handleLayout = (e: LayoutChangeEvent) => {
71
+ const { height, width } = e.nativeEvent.layout;
72
+
73
+ setLayout((prevLayout) => {
74
+ if (prevLayout.width === width && prevLayout.height === height) {
75
+ return prevLayout;
76
+ }
77
+
78
+ return { height, width };
79
+ });
80
+ };
81
+
82
+ return (
83
+ <View onLayout={handleLayout} style={[styles.pager, style]}>
84
+ <Pager
85
+ layout={layout}
86
+ navigationState={navigationState}
87
+ keyboardDismissMode={keyboardDismissMode}
88
+ swipeEnabled={swipeEnabled}
89
+ onSwipeStart={onSwipeStart}
90
+ onSwipeEnd={onSwipeEnd}
91
+ onIndexChange={jumpToIndex}
92
+ animationEnabled={animationEnabled}
93
+ overScrollMode={overScrollMode}
94
+ style={pagerStyle}
95
+ >
96
+ {({ position, render, addEnterListener, jumpTo }) => {
97
+ // All of the props here must not change between re-renders
98
+ // This is crucial to optimizing the routes with PureComponent
99
+ const sceneRendererProps = {
100
+ position,
101
+ layout,
102
+ jumpTo,
103
+ };
104
+
105
+ return (
106
+ <React.Fragment>
107
+ {tabBarPosition === 'top' &&
108
+ renderTabBar({
109
+ ...sceneRendererProps,
110
+ navigationState,
111
+ })}
112
+ {render(
113
+ navigationState.routes.map((route, i) => {
114
+ return (
115
+ <SceneView
116
+ {...sceneRendererProps}
117
+ addEnterListener={addEnterListener}
118
+ key={route.key}
119
+ index={i}
120
+ lazy={typeof lazy === 'function' ? lazy({ route }) : lazy}
121
+ lazyPreloadDistance={lazyPreloadDistance}
122
+ navigationState={navigationState}
123
+ style={sceneContainerStyle}
124
+ >
125
+ {({ loading }) =>
126
+ loading
127
+ ? renderLazyPlaceholder({ route })
128
+ : renderScene({
129
+ ...sceneRendererProps,
130
+ route,
131
+ })
132
+ }
133
+ </SceneView>
134
+ );
135
+ })
136
+ )}
137
+ {tabBarPosition === 'bottom' &&
138
+ renderTabBar({
139
+ ...sceneRendererProps,
140
+ navigationState,
141
+ })}
142
+ </React.Fragment>
143
+ );
144
+ }}
145
+ </Pager>
146
+ </View>
147
+ );
148
+ }
149
+
150
+ const styles = StyleSheet.create({
151
+ pager: {
152
+ flex: 1,
153
+ overflow: 'hidden',
154
+ },
155
+ });
package/src/index.tsx ADDED
@@ -0,0 +1,10 @@
1
+ export { default as SceneMap } from './SceneMap';
2
+ export type { Props as TabBarProps } from './TabBar';
3
+ export { default as TabBar } from './TabBar';
4
+ export type { Props as TabBarIndicatorProps } from './TabBarIndicator';
5
+ export { default as TabBarIndicator } from './TabBarIndicator';
6
+ export type { Props as TabBarItemProps } from './TabBarItem';
7
+ export { default as TabBarItem } from './TabBarItem';
8
+ export type { Props as TabViewProps } from './TabView';
9
+ export { default as TabView } from './TabView';
10
+ export type { NavigationState, Route, SceneRendererProps } from './types';
package/src/types.tsx ADDED
@@ -0,0 +1,59 @@
1
+ import type { Animated } from 'react-native';
2
+ import type { PagerViewProps } from 'react-native-pager-view';
3
+
4
+ export type Route = {
5
+ key: string;
6
+ icon?: string;
7
+ title?: string;
8
+ accessible?: boolean;
9
+ accessibilityLabel?: string;
10
+ testID?: string;
11
+ };
12
+
13
+ export type Event = {
14
+ defaultPrevented: boolean;
15
+ preventDefault(): void;
16
+ };
17
+
18
+ export type Scene<T extends Route> = {
19
+ route: T;
20
+ };
21
+
22
+ export type NavigationState<T extends Route> = {
23
+ index: number;
24
+ routes: T[];
25
+ };
26
+
27
+ export type Layout = {
28
+ width: number;
29
+ height: number;
30
+ };
31
+
32
+ export type Listener = (value: number) => void;
33
+
34
+ export type SceneRendererProps = {
35
+ layout: Layout;
36
+ position: Animated.AnimatedInterpolation<number>;
37
+ jumpTo: (key: string) => void;
38
+ };
39
+
40
+ export type EventEmitterProps = {
41
+ addEnterListener: (listener: Listener) => () => void;
42
+ };
43
+
44
+ export type PagerProps = Omit<
45
+ PagerViewProps,
46
+ | 'initialPage'
47
+ | 'scrollEnabled'
48
+ | 'onPageScroll'
49
+ | 'onPageSelected'
50
+ | 'onPageScrollStateChanged'
51
+ | 'keyboardDismissMode'
52
+ | 'children'
53
+ > & {
54
+ keyboardDismissMode?: 'none' | 'on-drag' | 'auto';
55
+ swipeEnabled?: boolean;
56
+ animationEnabled?: boolean;
57
+ onSwipeStart?: () => void;
58
+ onSwipeEnd?: () => void;
59
+ };
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import { Animated } from 'react-native';
3
+
4
+ export default function useAnimatedValue(initialValue: number) {
5
+ const lazyRef = React.useRef<Animated.Value>();
6
+
7
+ if (lazyRef.current === undefined) {
8
+ lazyRef.current = new Animated.Value(initialValue);
9
+ }
10
+
11
+ return lazyRef.current as Animated.Value;
12
+ }