@zoverdoser/react-native-tab-view 1.3.1 → 1.3.3
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/lib/module/PagerViewAdapter.js +29 -12
- package/lib/module/PagerViewAdapter.js.map +1 -1
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/PlatformPressable.js +1 -1
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/TabBar.js +20 -2
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarItem.js +63 -32
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabView.js +2 -0
- package/lib/module/TabView.js.map +1 -1
- package/lib/typescript/src/PagerViewAdapter.d.ts +2 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts +2 -0
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SceneMap.d.ts +1 -1
- package/lib/typescript/src/SceneMap.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +1 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/TabBar.d.ts +1 -1
- package/lib/typescript/src/TabBar.d.ts.map +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItem.d.ts +4 -1
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
- package/lib/typescript/src/TabView.d.ts +1 -1
- package/lib/typescript/src/TabView.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/PagerViewAdapter.tsx +68 -16
- package/src/PanResponderAdapter.tsx +2 -0
- package/src/PlatformPressable.tsx +1 -1
- package/src/TabBar.tsx +39 -2
- package/src/TabBarItem.tsx +111 -47
- package/src/TabView.tsx +2 -1
- package/src/types.tsx +2 -1
package/src/TabBarItem.tsx
CHANGED
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
View,
|
|
10
10
|
type ViewStyle,
|
|
11
11
|
} from 'react-native';
|
|
12
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
12
13
|
import {
|
|
13
14
|
interpolateColor,
|
|
14
15
|
useAnimatedStyle,
|
|
16
|
+
useDerivedValue,
|
|
15
17
|
useSharedValue,
|
|
16
18
|
} from 'react-native-reanimated';
|
|
17
19
|
import useLatestCallback from 'use-latest-callback';
|
|
@@ -27,6 +29,7 @@ import type {
|
|
|
27
29
|
|
|
28
30
|
export type Props<T extends Route> = TabDescriptor<T> & {
|
|
29
31
|
position: Animated.AnimatedInterpolation<number>;
|
|
32
|
+
animatedPosition?: SharedValue<number>;
|
|
30
33
|
route: T;
|
|
31
34
|
navigationState: NavigationState<T>;
|
|
32
35
|
pressColor?: string;
|
|
@@ -55,7 +58,6 @@ type TabBarItemInternalProps<T extends Route> = Omit<
|
|
|
55
58
|
> & {
|
|
56
59
|
isFocused: boolean;
|
|
57
60
|
index: number;
|
|
58
|
-
routesLength: number;
|
|
59
61
|
} & TabDescriptor<T>;
|
|
60
62
|
|
|
61
63
|
const ANDROID_RIPPLE_DEFAULT = { borderless: true };
|
|
@@ -69,6 +71,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
69
71
|
onPress,
|
|
70
72
|
isFocused,
|
|
71
73
|
position,
|
|
74
|
+
animatedPosition,
|
|
72
75
|
style,
|
|
73
76
|
labelStyle,
|
|
74
77
|
onLayout,
|
|
@@ -80,18 +83,14 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
80
83
|
badge: customBadge,
|
|
81
84
|
href,
|
|
82
85
|
labelText,
|
|
83
|
-
routesLength,
|
|
84
86
|
android_ripple = ANDROID_RIPPLE_DEFAULT,
|
|
85
87
|
labelAllowFontScaling,
|
|
86
88
|
route,
|
|
87
89
|
animatedStyles,
|
|
88
90
|
}: TabBarItemInternalProps<T>) => {
|
|
89
91
|
const inputRange = React.useMemo(
|
|
90
|
-
() =>
|
|
91
|
-
|
|
92
|
-
? Array.from({ length: routesLength }, (_, i) => i)
|
|
93
|
-
: [0, 1],
|
|
94
|
-
[routesLength]
|
|
92
|
+
() => [tabIndex - 1, tabIndex, tabIndex + 1],
|
|
93
|
+
[tabIndex]
|
|
95
94
|
);
|
|
96
95
|
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
97
96
|
|
|
@@ -106,70 +105,132 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
106
105
|
? labelColorFromStyle
|
|
107
106
|
: DEFAULT_INACTIVE_COLOR;
|
|
108
107
|
|
|
109
|
-
const
|
|
108
|
+
const fallbackProgress = useSharedValue(isFocused ? 1 : 0);
|
|
110
109
|
|
|
111
110
|
React.useEffect(() => {
|
|
111
|
+
if (animatedPosition != null) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
112
115
|
const listenerId = position.addListener(({ value }) => {
|
|
113
116
|
const progress =
|
|
114
117
|
Math.abs(tabIndex - value) > 1 ? 0 : 1 - Math.abs(tabIndex - value);
|
|
115
|
-
|
|
118
|
+
fallbackProgress.value = progress;
|
|
116
119
|
});
|
|
120
|
+
|
|
117
121
|
return () => {
|
|
118
122
|
position.removeListener(listenerId);
|
|
119
123
|
};
|
|
120
|
-
}, [
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
}, [animatedPosition, fallbackProgress, position, tabIndex]);
|
|
125
|
+
|
|
126
|
+
const reanimatedProgress = useDerivedValue(() => {
|
|
127
|
+
if (animatedPosition != null) {
|
|
128
|
+
return Math.max(0, 1 - Math.abs(tabIndex - animatedPosition.value));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return fallbackProgress.value;
|
|
132
|
+
}, [animatedPosition, fallbackProgress, tabIndex]);
|
|
133
|
+
|
|
134
|
+
const reanimatedStyle = useAnimatedStyle(() => {
|
|
135
|
+
const color = interpolateColor(
|
|
136
|
+
reanimatedProgress.value,
|
|
137
|
+
[0, 1],
|
|
138
|
+
[inactiveColor, activeColor]
|
|
139
|
+
);
|
|
140
|
+
return { color };
|
|
141
|
+
}, [reanimatedProgress, activeColor, inactiveColor]);
|
|
142
|
+
|
|
143
|
+
const inactiveItemOpacity = animatedStyles?.opacity?.[0];
|
|
144
|
+
const activeItemOpacity = animatedStyles?.opacity?.[1];
|
|
145
|
+
const inactiveScale = animatedStyles?.scale?.[0];
|
|
146
|
+
const activeScale = animatedStyles?.scale?.[1];
|
|
147
|
+
|
|
148
|
+
const itemOpacity = React.useMemo(
|
|
149
|
+
() =>
|
|
150
|
+
inactiveItemOpacity !== undefined && activeItemOpacity !== undefined
|
|
151
|
+
? position.interpolate({
|
|
152
|
+
inputRange,
|
|
153
|
+
outputRange: [
|
|
154
|
+
inactiveItemOpacity,
|
|
155
|
+
activeItemOpacity,
|
|
156
|
+
inactiveItemOpacity,
|
|
157
|
+
],
|
|
158
|
+
extrapolate: 'clamp',
|
|
159
|
+
})
|
|
160
|
+
: 1,
|
|
161
|
+
[activeItemOpacity, inactiveItemOpacity, inputRange, position]
|
|
131
162
|
);
|
|
132
163
|
|
|
133
|
-
const
|
|
134
|
-
|
|
164
|
+
const activeOpacity = React.useMemo(
|
|
165
|
+
() =>
|
|
166
|
+
position.interpolate({
|
|
135
167
|
inputRange,
|
|
136
|
-
outputRange:
|
|
137
|
-
i === tabIndex
|
|
138
|
-
? animatedStyles.opacity![1]
|
|
139
|
-
: animatedStyles.opacity![0]
|
|
140
|
-
),
|
|
168
|
+
outputRange: [0, 1, 0],
|
|
141
169
|
extrapolate: 'clamp',
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
170
|
+
}),
|
|
171
|
+
[inputRange, position]
|
|
172
|
+
);
|
|
173
|
+
const inactiveOpacity = React.useMemo(
|
|
174
|
+
() =>
|
|
175
|
+
position.interpolate({
|
|
147
176
|
inputRange,
|
|
148
|
-
outputRange:
|
|
149
|
-
i === tabIndex ? animatedStyles.scale![1] : animatedStyles.scale![0]
|
|
150
|
-
),
|
|
177
|
+
outputRange: [1, 0, 1],
|
|
151
178
|
extrapolate: 'clamp',
|
|
152
|
-
})
|
|
153
|
-
|
|
179
|
+
}),
|
|
180
|
+
[inputRange, position]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const scale = React.useMemo(
|
|
184
|
+
() =>
|
|
185
|
+
inactiveScale !== undefined && activeScale !== undefined
|
|
186
|
+
? position.interpolate({
|
|
187
|
+
inputRange,
|
|
188
|
+
outputRange: [inactiveScale, activeScale, inactiveScale],
|
|
189
|
+
extrapolate: 'clamp',
|
|
190
|
+
})
|
|
191
|
+
: 1,
|
|
192
|
+
[activeScale, inactiveScale, inputRange, position]
|
|
193
|
+
);
|
|
154
194
|
|
|
155
195
|
const icon = React.useMemo(() => {
|
|
156
196
|
if (!customIcon) {
|
|
157
197
|
return null;
|
|
158
198
|
}
|
|
159
199
|
|
|
160
|
-
const
|
|
161
|
-
focused:
|
|
162
|
-
color:
|
|
200
|
+
const inactiveIcon = customIcon({
|
|
201
|
+
focused: false,
|
|
202
|
+
color: inactiveColor,
|
|
203
|
+
size: ICON_SIZE,
|
|
204
|
+
route,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const activeIcon = customIcon({
|
|
208
|
+
focused: true,
|
|
209
|
+
color: activeColor,
|
|
163
210
|
size: ICON_SIZE,
|
|
164
211
|
route,
|
|
165
212
|
});
|
|
166
213
|
|
|
167
214
|
return (
|
|
168
215
|
<View style={styles.icon}>
|
|
169
|
-
<Animated.View style={{ opacity }}>
|
|
216
|
+
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
217
|
+
{inactiveIcon}
|
|
218
|
+
</Animated.View>
|
|
219
|
+
<Animated.View
|
|
220
|
+
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
221
|
+
>
|
|
222
|
+
{activeIcon}
|
|
223
|
+
</Animated.View>
|
|
170
224
|
</View>
|
|
171
225
|
);
|
|
172
|
-
}, [
|
|
226
|
+
}, [
|
|
227
|
+
activeColor,
|
|
228
|
+
activeOpacity,
|
|
229
|
+
customIcon,
|
|
230
|
+
inactiveColor,
|
|
231
|
+
inactiveOpacity,
|
|
232
|
+
route,
|
|
233
|
+
]);
|
|
173
234
|
|
|
174
235
|
const renderLabel = React.useCallback(
|
|
175
236
|
() =>
|
|
@@ -177,7 +238,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
177
238
|
customlabel({
|
|
178
239
|
focused: isFocused,
|
|
179
240
|
style: labelStyle,
|
|
180
|
-
animatedStyles:
|
|
241
|
+
animatedStyles: reanimatedStyle,
|
|
181
242
|
labelText,
|
|
182
243
|
allowFontScaling: labelAllowFontScaling,
|
|
183
244
|
route,
|
|
@@ -187,7 +248,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
187
248
|
icon={icon}
|
|
188
249
|
label={labelText}
|
|
189
250
|
style={labelStyle}
|
|
190
|
-
animatedStyles={
|
|
251
|
+
animatedStyles={reanimatedStyle}
|
|
191
252
|
labelAllowFontScaling={labelAllowFontScaling}
|
|
192
253
|
/>
|
|
193
254
|
),
|
|
@@ -199,7 +260,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
199
260
|
route,
|
|
200
261
|
icon,
|
|
201
262
|
isFocused,
|
|
202
|
-
|
|
263
|
+
reanimatedStyle,
|
|
203
264
|
]
|
|
204
265
|
);
|
|
205
266
|
|
|
@@ -232,7 +293,11 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
232
293
|
>
|
|
233
294
|
<Animated.View
|
|
234
295
|
pointerEvents="none"
|
|
235
|
-
style={[
|
|
296
|
+
style={[
|
|
297
|
+
styles.item,
|
|
298
|
+
tabStyle,
|
|
299
|
+
{ opacity: itemOpacity, transform: [{ scale }] },
|
|
300
|
+
]}
|
|
236
301
|
>
|
|
237
302
|
{icon}
|
|
238
303
|
<View>{renderLabel()}</View>
|
|
@@ -267,7 +332,6 @@ export function TabBarItem<T extends Route>(props: Props<T>) {
|
|
|
267
332
|
isFocused={navigationState.index === tabIndex}
|
|
268
333
|
route={route}
|
|
269
334
|
index={tabIndex}
|
|
270
|
-
routesLength={navigationState.routes.length}
|
|
271
335
|
/>
|
|
272
336
|
);
|
|
273
337
|
}
|
package/src/TabView.tsx
CHANGED
|
@@ -132,11 +132,12 @@ export function TabView<T extends Route>({
|
|
|
132
132
|
style={pagerStyle}
|
|
133
133
|
layoutDirection={direction}
|
|
134
134
|
>
|
|
135
|
-
{({ position, render, addEnterListener, jumpTo }) => {
|
|
135
|
+
{({ position, animatedPosition, render, addEnterListener, jumpTo }) => {
|
|
136
136
|
// All the props here must not change between re-renders
|
|
137
137
|
// This is crucial to optimizing the routes with PureComponent
|
|
138
138
|
const sceneRendererProps = {
|
|
139
139
|
position,
|
|
140
|
+
animatedPosition,
|
|
140
141
|
layout,
|
|
141
142
|
jumpTo,
|
|
142
143
|
};
|
package/src/types.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native';
|
|
2
2
|
import type { PagerViewProps } from 'react-native-pager-view';
|
|
3
|
-
import type { AnimatedStyle } from 'react-native-reanimated';
|
|
3
|
+
import type { AnimatedStyle, SharedValue } from 'react-native-reanimated';
|
|
4
4
|
|
|
5
5
|
export type TabDescriptor<T extends Route> = {
|
|
6
6
|
accessibilityLabel?: string;
|
|
@@ -63,6 +63,7 @@ export type Listener = (value: number) => void;
|
|
|
63
63
|
export type SceneRendererProps = {
|
|
64
64
|
layout: Layout;
|
|
65
65
|
position: Animated.AnimatedInterpolation<number>;
|
|
66
|
+
animatedPosition?: SharedValue<number>;
|
|
66
67
|
jumpTo: (key: string) => void;
|
|
67
68
|
};
|
|
68
69
|
|