react-native-tab-view 3.2.0 → 3.3.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 +4 -0
- package/lib/commonjs/PagerViewAdapter.js +25 -8
- package/lib/commonjs/PagerViewAdapter.js.map +1 -1
- package/lib/commonjs/PanResponderAdapter.js +29 -20
- package/lib/commonjs/PanResponderAdapter.js.map +1 -1
- package/lib/commonjs/SceneMap.js +9 -12
- package/lib/commonjs/SceneMap.js.map +1 -1
- package/lib/commonjs/SceneView.js +54 -101
- package/lib/commonjs/SceneView.js.map +1 -1
- package/lib/commonjs/TabBar.js +327 -327
- package/lib/commonjs/TabBar.js.map +1 -1
- package/lib/commonjs/TabBarIndicator.js +81 -99
- package/lib/commonjs/TabBarIndicator.js.map +1 -1
- package/lib/commonjs/TabBarItem.js +184 -161
- package/lib/commonjs/TabBarItem.js.map +1 -1
- package/lib/commonjs/TabView.js +3 -1
- package/lib/commonjs/TabView.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/PagerViewAdapter.js +25 -8
- package/lib/module/PagerViewAdapter.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +29 -19
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/SceneMap.js +9 -14
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +53 -98
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/TabBar.js +323 -323
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +74 -92
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +178 -154
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabView.js +3 -1
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/PagerViewAdapter.d.ts +1 -1
- package/lib/typescript/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/SceneMap.d.ts +5 -3
- package/lib/typescript/SceneView.d.ts +1 -18
- package/lib/typescript/TabBar.d.ts +7 -38
- package/lib/typescript/TabBarIndicator.d.ts +2 -10
- package/lib/typescript/TabBarItem.d.ts +3 -5
- package/lib/typescript/TabView.d.ts +1 -1
- package/lib/typescript/types.d.ts +1 -0
- package/package.json +4 -1
- package/src/PagerViewAdapter.tsx +29 -10
- package/src/PanResponderAdapter.tsx +29 -20
- package/src/SceneMap.tsx +11 -7
- package/src/SceneView.tsx +70 -106
- package/src/TabBar.tsx +451 -391
- package/src/TabBarIndicator.tsx +108 -114
- package/src/TabBarItem.tsx +214 -185
- package/src/TabView.tsx +2 -0
- package/src/types.tsx +1 -0
package/src/TabBarIndicator.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'react-native';
|
|
11
11
|
|
|
12
12
|
import type { Route, SceneRendererProps, NavigationState } from './types';
|
|
13
|
+
import useAnimatedValue from './useAnimatedValue';
|
|
13
14
|
|
|
14
15
|
export type GetTabWidth = (index: number) => number;
|
|
15
16
|
|
|
@@ -21,126 +22,119 @@ export type Props<T extends Route> = SceneRendererProps & {
|
|
|
21
22
|
gap?: number;
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const getTranslateX = (
|
|
26
|
+
position: Animated.AnimatedInterpolation,
|
|
27
|
+
routes: Route[],
|
|
28
|
+
getTabWidth: GetTabWidth,
|
|
29
|
+
gap?: number
|
|
30
|
+
) => {
|
|
31
|
+
const inputRange = routes.map((_, i) => i);
|
|
32
|
+
|
|
33
|
+
// every index contains widths at all previous indices
|
|
34
|
+
const outputRange = routes.reduce<number[]>((acc, _, i) => {
|
|
35
|
+
if (i === 0) return [0];
|
|
36
|
+
return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const translateX = position.interpolate({
|
|
40
|
+
inputRange,
|
|
41
|
+
outputRange,
|
|
42
|
+
extrapolate: 'clamp',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
|
|
46
|
+
};
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
export default function TabBarIndicator<T extends Route>({
|
|
49
|
+
getTabWidth,
|
|
50
|
+
layout,
|
|
51
|
+
navigationState,
|
|
52
|
+
position,
|
|
53
|
+
width,
|
|
54
|
+
gap,
|
|
55
|
+
style,
|
|
56
|
+
}: Props<T>) {
|
|
57
|
+
const isIndicatorShown = React.useRef(false);
|
|
58
|
+
const isWidthDynamic = width === 'auto';
|
|
59
|
+
|
|
60
|
+
const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
|
|
61
|
+
|
|
62
|
+
const hasMeasuredTabWidths =
|
|
63
|
+
Boolean(layout.width) &&
|
|
64
|
+
navigationState.routes.every((_, i) => getTabWidth(i));
|
|
65
|
+
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const fadeInIndicator = () => {
|
|
68
|
+
if (
|
|
69
|
+
!isIndicatorShown.current &&
|
|
70
|
+
isWidthDynamic &&
|
|
71
|
+
// We should fade-in the indicator when we have widths for all the tab items
|
|
72
|
+
hasMeasuredTabWidths
|
|
73
|
+
) {
|
|
74
|
+
isIndicatorShown.current = true;
|
|
75
|
+
|
|
76
|
+
Animated.timing(opacity, {
|
|
77
|
+
toValue: 1,
|
|
78
|
+
duration: 150,
|
|
79
|
+
easing: Easing.in(Easing.linear),
|
|
80
|
+
useNativeDriver: true,
|
|
81
|
+
}).start();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
fadeInIndicator();
|
|
86
|
+
|
|
87
|
+
return () => opacity.stopAnimation();
|
|
88
|
+
}, [hasMeasuredTabWidths, isWidthDynamic, opacity]);
|
|
89
|
+
|
|
90
|
+
const { routes } = navigationState;
|
|
91
|
+
|
|
92
|
+
const transform = [];
|
|
93
|
+
|
|
94
|
+
if (layout.width) {
|
|
95
|
+
const translateX =
|
|
96
|
+
routes.length > 1 ? getTranslateX(position, routes, getTabWidth, gap) : 0;
|
|
97
|
+
|
|
98
|
+
transform.push({ translateX });
|
|
33
99
|
}
|
|
34
100
|
|
|
35
|
-
|
|
36
|
-
const { navigationState, layout, width, getTabWidth } = this.props;
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
!this.isIndicatorShown &&
|
|
40
|
-
width === 'auto' &&
|
|
41
|
-
layout.width &&
|
|
42
|
-
// We should fade-in the indicator when we have widths for all the tab items
|
|
43
|
-
navigationState.routes.every((_, i) => getTabWidth(i))
|
|
44
|
-
) {
|
|
45
|
-
this.isIndicatorShown = true;
|
|
46
|
-
|
|
47
|
-
Animated.timing(this.opacity, {
|
|
48
|
-
toValue: 1,
|
|
49
|
-
duration: 150,
|
|
50
|
-
easing: Easing.in(Easing.linear),
|
|
51
|
-
useNativeDriver: true,
|
|
52
|
-
}).start();
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
private isIndicatorShown = false;
|
|
57
|
-
|
|
58
|
-
private opacity = new Animated.Value(this.props.width === 'auto' ? 0 : 1);
|
|
59
|
-
|
|
60
|
-
private getTranslateX = (
|
|
61
|
-
position: Animated.AnimatedInterpolation,
|
|
62
|
-
routes: Route[],
|
|
63
|
-
getTabWidth: GetTabWidth,
|
|
64
|
-
gap?: number
|
|
65
|
-
) => {
|
|
101
|
+
if (width === 'auto') {
|
|
66
102
|
const inputRange = routes.map((_, i) => i);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
render() {
|
|
84
|
-
const {
|
|
85
|
-
position,
|
|
86
|
-
navigationState,
|
|
87
|
-
getTabWidth,
|
|
88
|
-
width,
|
|
89
|
-
style,
|
|
90
|
-
layout,
|
|
91
|
-
gap,
|
|
92
|
-
} = this.props;
|
|
93
|
-
const { routes } = navigationState;
|
|
94
|
-
|
|
95
|
-
const transform = [];
|
|
96
|
-
|
|
97
|
-
if (layout.width) {
|
|
98
|
-
const translateX =
|
|
99
|
-
routes.length > 1
|
|
100
|
-
? this.getTranslateX(position, routes, getTabWidth, gap)
|
|
101
|
-
: 0;
|
|
102
|
-
|
|
103
|
-
transform.push({ translateX });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (width === 'auto') {
|
|
107
|
-
const inputRange = routes.map((_, i) => i);
|
|
108
|
-
const outputRange = inputRange.map(getTabWidth);
|
|
109
|
-
|
|
110
|
-
transform.push(
|
|
111
|
-
{
|
|
112
|
-
scaleX:
|
|
113
|
-
routes.length > 1
|
|
114
|
-
? position.interpolate({
|
|
115
|
-
inputRange,
|
|
116
|
-
outputRange,
|
|
117
|
-
extrapolate: 'clamp',
|
|
118
|
-
})
|
|
119
|
-
: outputRange[0],
|
|
120
|
-
},
|
|
121
|
-
{ translateX: 0.5 }
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<Animated.View
|
|
127
|
-
style={[
|
|
128
|
-
styles.indicator,
|
|
129
|
-
{ width: width === 'auto' ? 1 : width },
|
|
130
|
-
// If layout is not available, use `left` property for positioning the indicator
|
|
131
|
-
// This avoids rendering delay until we are able to calculate translateX
|
|
132
|
-
// If platform is macos use `left` property as `transform` is broken at the moment.
|
|
133
|
-
// See: https://github.com/microsoft/react-native-macos/issues/280
|
|
134
|
-
layout.width && Platform.OS !== 'macos'
|
|
135
|
-
? { left: 0 }
|
|
136
|
-
: { left: `${(100 / routes.length) * navigationState.index}%` },
|
|
137
|
-
{ transform },
|
|
138
|
-
width === 'auto' ? { opacity: this.opacity } : null,
|
|
139
|
-
style,
|
|
140
|
-
]}
|
|
141
|
-
/>
|
|
103
|
+
const outputRange = inputRange.map(getTabWidth);
|
|
104
|
+
|
|
105
|
+
transform.push(
|
|
106
|
+
{
|
|
107
|
+
scaleX:
|
|
108
|
+
routes.length > 1
|
|
109
|
+
? position.interpolate({
|
|
110
|
+
inputRange,
|
|
111
|
+
outputRange,
|
|
112
|
+
extrapolate: 'clamp',
|
|
113
|
+
})
|
|
114
|
+
: outputRange[0],
|
|
115
|
+
},
|
|
116
|
+
{ translateX: 0.5 }
|
|
142
117
|
);
|
|
143
118
|
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Animated.View
|
|
122
|
+
style={[
|
|
123
|
+
styles.indicator,
|
|
124
|
+
{ width: width === 'auto' ? 1 : width },
|
|
125
|
+
// If layout is not available, use `left` property for positioning the indicator
|
|
126
|
+
// This avoids rendering delay until we are able to calculate translateX
|
|
127
|
+
// If platform is macos use `left` property as `transform` is broken at the moment.
|
|
128
|
+
// See: https://github.com/microsoft/react-native-macos/issues/280
|
|
129
|
+
layout.width && Platform.OS !== 'macos'
|
|
130
|
+
? { left: 0 }
|
|
131
|
+
: { left: `${(100 / routes.length) * navigationState.index}%` },
|
|
132
|
+
{ transform },
|
|
133
|
+
width === 'auto' ? { opacity: opacity } : null,
|
|
134
|
+
style,
|
|
135
|
+
]}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
const styles = StyleSheet.create({
|
package/src/TabBarItem.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
TextStyle,
|
|
9
9
|
ViewStyle,
|
|
10
10
|
} from 'react-native';
|
|
11
|
+
import useLatestCallback from 'use-latest-callback';
|
|
11
12
|
import PlatformPressable from './PlatformPressable';
|
|
12
13
|
import type { Scene, Route, NavigationState } from './types';
|
|
13
14
|
|
|
@@ -37,6 +38,7 @@ export type Props<T extends Route> = {
|
|
|
37
38
|
onLayout?: (event: LayoutChangeEvent) => void;
|
|
38
39
|
onPress: () => void;
|
|
39
40
|
onLongPress: () => void;
|
|
41
|
+
defaultTabWidth?: number;
|
|
40
42
|
labelStyle?: StyleProp<TextStyle>;
|
|
41
43
|
style: StyleProp<ViewStyle>;
|
|
42
44
|
};
|
|
@@ -44,219 +46,246 @@ export type Props<T extends Route> = {
|
|
|
44
46
|
const DEFAULT_ACTIVE_COLOR = 'rgba(255, 255, 255, 1)';
|
|
45
47
|
const DEFAULT_INACTIVE_COLOR = 'rgba(255, 255, 255, 0.7)';
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
) => {
|
|
55
|
-
if (routes.length > 1) {
|
|
56
|
-
const inputRange = routes.map((_, i) => i);
|
|
49
|
+
const getActiveOpacity = (
|
|
50
|
+
position: Animated.AnimatedInterpolation,
|
|
51
|
+
routesLength: number,
|
|
52
|
+
tabIndex: number
|
|
53
|
+
) => {
|
|
54
|
+
if (routesLength > 1) {
|
|
55
|
+
const inputRange = Array.from({ length: routesLength }, (_, i) => i);
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
private getInactiveOpacity = (
|
|
68
|
-
position: Animated.AnimatedInterpolation,
|
|
69
|
-
routes: Route[],
|
|
70
|
-
tabIndex: number
|
|
71
|
-
) => {
|
|
72
|
-
if (routes.length > 1) {
|
|
73
|
-
const inputRange = routes.map((_: Route, i: number) => i);
|
|
74
|
-
|
|
75
|
-
return position.interpolate({
|
|
76
|
-
inputRange,
|
|
77
|
-
outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
|
|
78
|
-
});
|
|
79
|
-
} else {
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
render() {
|
|
85
|
-
const {
|
|
86
|
-
route,
|
|
87
|
-
position,
|
|
88
|
-
navigationState,
|
|
89
|
-
renderLabel: renderLabelCustom,
|
|
90
|
-
renderIcon,
|
|
91
|
-
renderBadge,
|
|
92
|
-
getLabelText,
|
|
93
|
-
getTestID,
|
|
94
|
-
getAccessibilityLabel,
|
|
95
|
-
getAccessible,
|
|
96
|
-
activeColor: activeColorCustom,
|
|
97
|
-
inactiveColor: inactiveColorCustom,
|
|
98
|
-
pressColor,
|
|
99
|
-
pressOpacity,
|
|
100
|
-
labelStyle,
|
|
101
|
-
style,
|
|
102
|
-
onLayout,
|
|
103
|
-
onPress,
|
|
104
|
-
onLongPress,
|
|
105
|
-
} = this.props;
|
|
106
|
-
|
|
107
|
-
const tabIndex = navigationState.routes.indexOf(route);
|
|
108
|
-
const isFocused = navigationState.index === tabIndex;
|
|
109
|
-
|
|
110
|
-
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
111
|
-
|
|
112
|
-
const activeColor =
|
|
113
|
-
activeColorCustom !== undefined
|
|
114
|
-
? activeColorCustom
|
|
115
|
-
: typeof labelColorFromStyle === 'string'
|
|
116
|
-
? labelColorFromStyle
|
|
117
|
-
: DEFAULT_ACTIVE_COLOR;
|
|
118
|
-
const inactiveColor =
|
|
119
|
-
inactiveColorCustom !== undefined
|
|
120
|
-
? inactiveColorCustom
|
|
121
|
-
: typeof labelColorFromStyle === 'string'
|
|
122
|
-
? labelColorFromStyle
|
|
123
|
-
: DEFAULT_INACTIVE_COLOR;
|
|
57
|
+
return position.interpolate({
|
|
58
|
+
inputRange,
|
|
59
|
+
outputRange: inputRange.map((i) => (i === tabIndex ? 1 : 0)),
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
124
65
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
navigationState.routes,
|
|
133
|
-
tabIndex
|
|
134
|
-
);
|
|
66
|
+
const getInactiveOpacity = (
|
|
67
|
+
position: Animated.AnimatedInterpolation,
|
|
68
|
+
routesLength: number,
|
|
69
|
+
tabIndex: number
|
|
70
|
+
) => {
|
|
71
|
+
if (routesLength > 1) {
|
|
72
|
+
const inputRange = Array.from({ length: routesLength }, (_, i) => i);
|
|
135
73
|
|
|
136
|
-
|
|
137
|
-
|
|
74
|
+
return position.interpolate({
|
|
75
|
+
inputRange,
|
|
76
|
+
outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
138
82
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
focused: false,
|
|
148
|
-
color: inactiveColor,
|
|
149
|
-
});
|
|
83
|
+
type TabBarItemInternalProps<T extends Route> = Omit<
|
|
84
|
+
Props<T>,
|
|
85
|
+
'navigationState'
|
|
86
|
+
> & {
|
|
87
|
+
isFocused: boolean;
|
|
88
|
+
index: number;
|
|
89
|
+
routesLength: number;
|
|
90
|
+
};
|
|
150
91
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
92
|
+
const TabBarItemInternal = <T extends Route>({
|
|
93
|
+
getAccessibilityLabel,
|
|
94
|
+
getAccessible,
|
|
95
|
+
getLabelText,
|
|
96
|
+
getTestID,
|
|
97
|
+
onLongPress,
|
|
98
|
+
onPress,
|
|
99
|
+
isFocused,
|
|
100
|
+
position,
|
|
101
|
+
route,
|
|
102
|
+
style,
|
|
103
|
+
inactiveColor: inactiveColorCustom,
|
|
104
|
+
activeColor: activeColorCustom,
|
|
105
|
+
labelStyle,
|
|
106
|
+
onLayout,
|
|
107
|
+
index: tabIndex,
|
|
108
|
+
pressColor,
|
|
109
|
+
pressOpacity,
|
|
110
|
+
renderBadge,
|
|
111
|
+
renderIcon,
|
|
112
|
+
defaultTabWidth,
|
|
113
|
+
routesLength,
|
|
114
|
+
renderLabel: renderLabelCustom,
|
|
115
|
+
}: TabBarItemInternalProps<T>) => {
|
|
116
|
+
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
166
117
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
118
|
+
const activeColor =
|
|
119
|
+
activeColorCustom !== undefined
|
|
120
|
+
? activeColorCustom
|
|
121
|
+
: typeof labelColorFromStyle === 'string'
|
|
122
|
+
? labelColorFromStyle
|
|
123
|
+
: DEFAULT_ACTIVE_COLOR;
|
|
124
|
+
const inactiveColor =
|
|
125
|
+
inactiveColorCustom !== undefined
|
|
126
|
+
? inactiveColorCustom
|
|
127
|
+
: typeof labelColorFromStyle === 'string'
|
|
128
|
+
? labelColorFromStyle
|
|
129
|
+
: DEFAULT_INACTIVE_COLOR;
|
|
172
130
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<Animated.Text
|
|
176
|
-
style={[
|
|
177
|
-
styles.label,
|
|
178
|
-
icon ? { marginTop: 0 } : null,
|
|
179
|
-
labelStyle,
|
|
180
|
-
{ color },
|
|
181
|
-
]}
|
|
182
|
-
>
|
|
183
|
-
{labelText}
|
|
184
|
-
</Animated.Text>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
131
|
+
const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
|
|
132
|
+
const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
|
|
187
133
|
|
|
188
|
-
|
|
189
|
-
|
|
134
|
+
let icon: React.ReactNode | null = null;
|
|
135
|
+
let label: React.ReactNode | null = null;
|
|
190
136
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
137
|
+
if (renderIcon) {
|
|
138
|
+
const activeIcon = renderIcon({
|
|
139
|
+
route,
|
|
140
|
+
focused: true,
|
|
141
|
+
color: activeColor,
|
|
142
|
+
});
|
|
143
|
+
const inactiveIcon = renderIcon({
|
|
144
|
+
route,
|
|
145
|
+
focused: false,
|
|
146
|
+
color: inactiveColor,
|
|
147
|
+
});
|
|
202
148
|
|
|
203
|
-
|
|
204
|
-
|
|
149
|
+
if (inactiveIcon != null && activeIcon != null) {
|
|
150
|
+
icon = (
|
|
151
|
+
<View style={styles.icon}>
|
|
205
152
|
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
206
|
-
{
|
|
153
|
+
{inactiveIcon}
|
|
207
154
|
</Animated.View>
|
|
208
155
|
<Animated.View
|
|
209
156
|
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
210
157
|
>
|
|
211
|
-
{
|
|
158
|
+
{activeIcon}
|
|
212
159
|
</Animated.View>
|
|
213
160
|
</View>
|
|
214
161
|
);
|
|
215
162
|
}
|
|
163
|
+
}
|
|
216
164
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
165
|
+
const renderLabel =
|
|
166
|
+
renderLabelCustom !== undefined
|
|
167
|
+
? renderLabelCustom
|
|
168
|
+
: (labelProps: { route: T; color: string }) => {
|
|
169
|
+
const labelText = getLabelText({ route: labelProps.route });
|
|
222
170
|
|
|
223
|
-
|
|
171
|
+
if (typeof labelText === 'string') {
|
|
172
|
+
return (
|
|
173
|
+
<Animated.Text
|
|
174
|
+
style={[
|
|
175
|
+
styles.label,
|
|
176
|
+
icon ? { marginTop: 0 } : null,
|
|
177
|
+
labelStyle,
|
|
178
|
+
{ color: labelProps.color },
|
|
179
|
+
]}
|
|
180
|
+
>
|
|
181
|
+
{labelText}
|
|
182
|
+
</Animated.Text>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
224
185
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
? accessibilityLabel
|
|
228
|
-
: getLabelText(scene);
|
|
186
|
+
return labelText;
|
|
187
|
+
};
|
|
229
188
|
|
|
230
|
-
|
|
189
|
+
if (renderLabel) {
|
|
190
|
+
const activeLabel = renderLabel({
|
|
191
|
+
route,
|
|
192
|
+
focused: true,
|
|
193
|
+
color: activeColor,
|
|
194
|
+
});
|
|
195
|
+
const inactiveLabel = renderLabel({
|
|
196
|
+
route,
|
|
197
|
+
focused: false,
|
|
198
|
+
color: inactiveColor,
|
|
199
|
+
});
|
|
231
200
|
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
pressOpacity={pressOpacity}
|
|
244
|
-
delayPressIn={0}
|
|
245
|
-
onLayout={onLayout}
|
|
246
|
-
onPress={onPress}
|
|
247
|
-
onLongPress={onLongPress}
|
|
248
|
-
style={[styles.pressable, tabContainerStyle]}
|
|
249
|
-
>
|
|
250
|
-
<View pointerEvents="none" style={[styles.item, tabStyle]}>
|
|
251
|
-
{icon}
|
|
252
|
-
{label}
|
|
253
|
-
{badge != null ? <View style={styles.badge}>{badge}</View> : null}
|
|
254
|
-
</View>
|
|
255
|
-
</PlatformPressable>
|
|
201
|
+
label = (
|
|
202
|
+
<View>
|
|
203
|
+
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
204
|
+
{inactiveLabel}
|
|
205
|
+
</Animated.View>
|
|
206
|
+
<Animated.View
|
|
207
|
+
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
208
|
+
>
|
|
209
|
+
{activeLabel}
|
|
210
|
+
</Animated.View>
|
|
211
|
+
</View>
|
|
256
212
|
);
|
|
257
213
|
}
|
|
214
|
+
|
|
215
|
+
const tabStyle = StyleSheet.flatten(style);
|
|
216
|
+
const isWidthSet = tabStyle?.width !== undefined;
|
|
217
|
+
|
|
218
|
+
const tabContainerStyle: ViewStyle | null = isWidthSet
|
|
219
|
+
? null
|
|
220
|
+
: { width: defaultTabWidth };
|
|
221
|
+
|
|
222
|
+
const scene = { route };
|
|
223
|
+
|
|
224
|
+
let accessibilityLabel = getAccessibilityLabel(scene);
|
|
225
|
+
|
|
226
|
+
accessibilityLabel =
|
|
227
|
+
typeof accessibilityLabel !== 'undefined'
|
|
228
|
+
? accessibilityLabel
|
|
229
|
+
: getLabelText(scene);
|
|
230
|
+
|
|
231
|
+
const badge = renderBadge ? renderBadge(scene) : null;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<PlatformPressable
|
|
235
|
+
android_ripple={{ borderless: true }}
|
|
236
|
+
testID={getTestID(scene)}
|
|
237
|
+
accessible={getAccessible(scene)}
|
|
238
|
+
accessibilityLabel={accessibilityLabel}
|
|
239
|
+
accessibilityRole="tab"
|
|
240
|
+
accessibilityState={{ selected: isFocused }}
|
|
241
|
+
// @ts-ignore: this is to support older React Native versions
|
|
242
|
+
accessibilityStates={isFocused ? ['selected'] : []}
|
|
243
|
+
pressColor={pressColor}
|
|
244
|
+
pressOpacity={pressOpacity}
|
|
245
|
+
delayPressIn={0}
|
|
246
|
+
onLayout={onLayout}
|
|
247
|
+
onPress={onPress}
|
|
248
|
+
onLongPress={onLongPress}
|
|
249
|
+
style={[styles.pressable, tabContainerStyle]}
|
|
250
|
+
>
|
|
251
|
+
<View pointerEvents="none" style={[styles.item, tabStyle]}>
|
|
252
|
+
{icon}
|
|
253
|
+
{label}
|
|
254
|
+
{badge != null ? <View style={styles.badge}>{badge}</View> : null}
|
|
255
|
+
</View>
|
|
256
|
+
</PlatformPressable>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const MemoizedTabBarItemInternal = React.memo(
|
|
261
|
+
TabBarItemInternal
|
|
262
|
+
) as typeof TabBarItemInternal;
|
|
263
|
+
|
|
264
|
+
function TabBarItem<T extends Route>(props: Props<T>) {
|
|
265
|
+
const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
|
|
266
|
+
props;
|
|
267
|
+
const onPressLatest = useLatestCallback(onPress);
|
|
268
|
+
const onLongPressLatest = useLatestCallback(onLongPress);
|
|
269
|
+
const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
|
|
270
|
+
|
|
271
|
+
const tabIndex = navigationState.routes.indexOf(route);
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<MemoizedTabBarItemInternal
|
|
275
|
+
{...rest}
|
|
276
|
+
onPress={onPressLatest}
|
|
277
|
+
onLayout={onLayoutLatest}
|
|
278
|
+
onLongPress={onLongPressLatest}
|
|
279
|
+
isFocused={navigationState.index === tabIndex}
|
|
280
|
+
route={route}
|
|
281
|
+
index={tabIndex}
|
|
282
|
+
routesLength={navigationState.routes.length}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
258
285
|
}
|
|
259
286
|
|
|
287
|
+
export default TabBarItem;
|
|
288
|
+
|
|
260
289
|
const styles = StyleSheet.create({
|
|
261
290
|
label: {
|
|
262
291
|
margin: 4,
|