@react-native-ohos/react-native-tab-view 4.0.11-rc.1
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/LICENSE +21 -0
- package/README.OpenSource +11 -0
- package/README.md +9 -0
- package/lib/module/Pager.android.js +4 -0
- package/lib/module/Pager.android.js.map +1 -0
- package/lib/module/Pager.ios.js +4 -0
- package/lib/module/Pager.ios.js.map +1 -0
- package/lib/module/Pager.js +4 -0
- package/lib/module/Pager.js.map +1 -0
- package/lib/module/PagerViewAdapter.js +126 -0
- package/lib/module/PagerViewAdapter.js.map +1 -0
- package/lib/module/PanResponderAdapter.js +200 -0
- package/lib/module/PanResponderAdapter.js.map +1 -0
- package/lib/module/PlatformPressable.js +59 -0
- package/lib/module/PlatformPressable.js.map +1 -0
- package/lib/module/SceneMap.js +24 -0
- package/lib/module/SceneMap.js.map +1 -0
- package/lib/module/SceneView.js +73 -0
- package/lib/module/SceneView.js.map +1 -0
- package/lib/module/TabBar.js +472 -0
- package/lib/module/TabBar.js.map +1 -0
- package/lib/module/TabBarIndicator.js +122 -0
- package/lib/module/TabBarIndicator.js.map +1 -0
- package/lib/module/TabBarItem.js +218 -0
- package/lib/module/TabBarItem.js.map +1 -0
- package/lib/module/TabBarItemLabel.js +33 -0
- package/lib/module/TabBarItemLabel.js.map +1 -0
- package/lib/module/TabView.js +140 -0
- package/lib/module/TabView.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useAnimatedValue.js +12 -0
- package/lib/module/useAnimatedValue.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Pager.android.d.ts +2 -0
- package/lib/typescript/src/Pager.android.d.ts.map +1 -0
- package/lib/typescript/src/Pager.d.ts +2 -0
- package/lib/typescript/src/Pager.d.ts.map +1 -0
- package/lib/typescript/src/Pager.ios.d.ts +2 -0
- package/lib/typescript/src/Pager.ios.d.ts.map +1 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts +15 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -0
- package/lib/typescript/src/PanResponderAdapter.d.ts +16 -0
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -0
- package/lib/typescript/src/PlatformPressable.d.ts +13 -0
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -0
- package/lib/typescript/src/SceneMap.d.ts +10 -0
- package/lib/typescript/src/SceneMap.d.ts.map +1 -0
- package/lib/typescript/src/SceneView.d.ts +16 -0
- package/lib/typescript/src/SceneView.d.ts.map +1 -0
- package/lib/typescript/src/TabBar.d.ts +32 -0
- package/lib/typescript/src/TabBar.d.ts.map +1 -0
- package/lib/typescript/src/TabBarIndicator.d.ts +15 -0
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -0
- package/lib/typescript/src/TabBarItem.d.ts +19 -0
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -0
- 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 +30 -0
- package/lib/typescript/src/TabView.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +11 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +70 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useAnimatedValue.d.ts +3 -0
- package/lib/typescript/src/useAnimatedValue.d.ts.map +1 -0
- package/package.json +79 -0
- package/src/Pager.android.tsx +1 -0
- package/src/Pager.ios.tsx +1 -0
- package/src/Pager.tsx +1 -0
- package/src/PagerViewAdapter.tsx +182 -0
- package/src/PanResponderAdapter.tsx +339 -0
- package/src/PlatformPressable.tsx +75 -0
- package/src/SceneMap.tsx +30 -0
- package/src/SceneView.tsx +107 -0
- package/src/TabBar.tsx +729 -0
- package/src/TabBarIndicator.tsx +190 -0
- package/src/TabBarItem.tsx +305 -0
- package/src/TabBarItemLabel.tsx +42 -0
- package/src/TabView.tsx +195 -0
- package/src/index.tsx +15 -0
- package/src/types.tsx +87 -0
- package/src/useAnimatedValue.tsx +12 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
Platform,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
LocaleDirection,
|
|
13
|
+
NavigationState,
|
|
14
|
+
Route,
|
|
15
|
+
SceneRendererProps,
|
|
16
|
+
} from './types';
|
|
17
|
+
import { useAnimatedValue } from './useAnimatedValue';
|
|
18
|
+
|
|
19
|
+
export type GetTabWidth = (index: number) => number;
|
|
20
|
+
|
|
21
|
+
export type Props<T extends Route> = SceneRendererProps & {
|
|
22
|
+
navigationState: NavigationState<T>;
|
|
23
|
+
width: 'auto' | `${number}%` | number;
|
|
24
|
+
getTabWidth: GetTabWidth;
|
|
25
|
+
direction: LocaleDirection;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
gap?: number;
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const useNativeDriver = Platform.OS !== 'web';
|
|
32
|
+
|
|
33
|
+
const getTranslateX = (
|
|
34
|
+
position: Animated.AnimatedInterpolation<number>,
|
|
35
|
+
routes: Route[],
|
|
36
|
+
getTabWidth: GetTabWidth,
|
|
37
|
+
direction: LocaleDirection,
|
|
38
|
+
gap?: number,
|
|
39
|
+
width?: number | string
|
|
40
|
+
) => {
|
|
41
|
+
const inputRange = routes.map((_, i) => i);
|
|
42
|
+
|
|
43
|
+
// every index contains widths at all previous indices
|
|
44
|
+
const outputRange = routes.reduce<number[]>((acc, _, i) => {
|
|
45
|
+
if (typeof width === 'number') {
|
|
46
|
+
if (i === 0) return [getTabWidth(i) / 2 - width / 2];
|
|
47
|
+
|
|
48
|
+
let sumTabWidth = 0;
|
|
49
|
+
for (let j = 0; j < i; j++) {
|
|
50
|
+
sumTabWidth += getTabWidth(j);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
...acc,
|
|
55
|
+
sumTabWidth + getTabWidth(i) / 2 + (gap ? gap * i : 0) - width / 2,
|
|
56
|
+
];
|
|
57
|
+
} else {
|
|
58
|
+
if (i === 0) return [0];
|
|
59
|
+
return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const translateX = position.interpolate({
|
|
64
|
+
inputRange,
|
|
65
|
+
outputRange,
|
|
66
|
+
extrapolate: 'clamp',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return Animated.multiply(translateX, direction === 'rtl' ? -1 : 1);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function TabBarIndicator<T extends Route>({
|
|
73
|
+
getTabWidth,
|
|
74
|
+
layout,
|
|
75
|
+
navigationState,
|
|
76
|
+
position,
|
|
77
|
+
width,
|
|
78
|
+
direction,
|
|
79
|
+
gap,
|
|
80
|
+
style,
|
|
81
|
+
children,
|
|
82
|
+
}: Props<T>) {
|
|
83
|
+
const isIndicatorShown = React.useRef(false);
|
|
84
|
+
const isWidthDynamic = width === 'auto';
|
|
85
|
+
|
|
86
|
+
const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
|
|
87
|
+
|
|
88
|
+
const indicatorVisible = isWidthDynamic
|
|
89
|
+
? layout.width &&
|
|
90
|
+
navigationState.routes
|
|
91
|
+
.slice(0, navigationState.index)
|
|
92
|
+
.every((_, r) => getTabWidth(r))
|
|
93
|
+
: true;
|
|
94
|
+
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
const fadeInIndicator = () => {
|
|
97
|
+
if (
|
|
98
|
+
!isIndicatorShown.current &&
|
|
99
|
+
isWidthDynamic &&
|
|
100
|
+
// We should fade-in the indicator when we have widths for all the tab items
|
|
101
|
+
indicatorVisible
|
|
102
|
+
) {
|
|
103
|
+
isIndicatorShown.current = true;
|
|
104
|
+
|
|
105
|
+
Animated.timing(opacity, {
|
|
106
|
+
toValue: 1,
|
|
107
|
+
duration: 150,
|
|
108
|
+
easing: Easing.in(Easing.linear),
|
|
109
|
+
useNativeDriver,
|
|
110
|
+
}).start();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
fadeInIndicator();
|
|
115
|
+
|
|
116
|
+
return () => opacity.stopAnimation();
|
|
117
|
+
}, [indicatorVisible, isWidthDynamic, opacity]);
|
|
118
|
+
|
|
119
|
+
const { routes } = navigationState;
|
|
120
|
+
|
|
121
|
+
const transform = [];
|
|
122
|
+
|
|
123
|
+
if (layout.width) {
|
|
124
|
+
const translateX =
|
|
125
|
+
routes.length > 1
|
|
126
|
+
? getTranslateX(position, routes, getTabWidth, direction, gap, width)
|
|
127
|
+
: 0;
|
|
128
|
+
|
|
129
|
+
transform.push({ translateX });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (width === 'auto') {
|
|
133
|
+
const inputRange = routes.map((_, i) => i);
|
|
134
|
+
const outputRange = inputRange.map(getTabWidth);
|
|
135
|
+
|
|
136
|
+
transform.push(
|
|
137
|
+
{
|
|
138
|
+
scaleX:
|
|
139
|
+
routes.length > 1
|
|
140
|
+
? position.interpolate({
|
|
141
|
+
inputRange,
|
|
142
|
+
outputRange,
|
|
143
|
+
extrapolate: 'clamp',
|
|
144
|
+
})
|
|
145
|
+
: outputRange[0],
|
|
146
|
+
},
|
|
147
|
+
{ translateX: direction === 'rtl' ? -0.5 : 0.5 }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const styleList: StyleProp<ViewStyle> = [];
|
|
152
|
+
|
|
153
|
+
// scaleX doesn't work properly on chrome and opera for linux and android
|
|
154
|
+
if (Platform.OS === 'web' && width === 'auto') {
|
|
155
|
+
styleList.push(
|
|
156
|
+
{ width: transform[1].scaleX },
|
|
157
|
+
{ left: transform[0].translateX }
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
styleList.push(
|
|
161
|
+
{ width: width === 'auto' ? 1 : width },
|
|
162
|
+
{ start: `${(100 / routes.length) * navigationState.index}%` },
|
|
163
|
+
{ transform }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Animated.View
|
|
169
|
+
style={[
|
|
170
|
+
styles.indicator,
|
|
171
|
+
styleList,
|
|
172
|
+
width === 'auto' ? { opacity: opacity } : null,
|
|
173
|
+
style,
|
|
174
|
+
]}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</Animated.View>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const styles = StyleSheet.create({
|
|
182
|
+
indicator: {
|
|
183
|
+
backgroundColor: '#ffeb3b',
|
|
184
|
+
position: 'absolute',
|
|
185
|
+
start: 0,
|
|
186
|
+
bottom: 0,
|
|
187
|
+
end: 0,
|
|
188
|
+
height: 2,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
type LayoutChangeEvent,
|
|
5
|
+
Platform,
|
|
6
|
+
type PressableAndroidRippleConfig,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
View,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import useLatestCallback from 'use-latest-callback';
|
|
13
|
+
|
|
14
|
+
import { PlatformPressable } from './PlatformPressable';
|
|
15
|
+
import { TabBarItemLabel } from './TabBarItemLabel';
|
|
16
|
+
import type { NavigationState, Route, TabDescriptor } from './types';
|
|
17
|
+
|
|
18
|
+
export type Props<T extends Route> = TabDescriptor<T> & {
|
|
19
|
+
position: Animated.AnimatedInterpolation<number>;
|
|
20
|
+
route: T;
|
|
21
|
+
navigationState: NavigationState<T>;
|
|
22
|
+
activeColor?: string;
|
|
23
|
+
inactiveColor?: string;
|
|
24
|
+
pressColor?: string;
|
|
25
|
+
pressOpacity?: number;
|
|
26
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
27
|
+
onPress: () => void;
|
|
28
|
+
onLongPress: () => void;
|
|
29
|
+
defaultTabWidth?: number;
|
|
30
|
+
style: StyleProp<ViewStyle>;
|
|
31
|
+
android_ripple?: PressableAndroidRippleConfig;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DEFAULT_ACTIVE_COLOR = 'rgba(255, 255, 255, 1)';
|
|
35
|
+
const DEFAULT_INACTIVE_COLOR = 'rgba(255, 255, 255, 0.7)';
|
|
36
|
+
const ICON_SIZE = 24;
|
|
37
|
+
|
|
38
|
+
const getActiveOpacity = (
|
|
39
|
+
position: Animated.AnimatedInterpolation<number>,
|
|
40
|
+
routesLength: number,
|
|
41
|
+
tabIndex: number
|
|
42
|
+
) => {
|
|
43
|
+
if (routesLength > 1) {
|
|
44
|
+
const inputRange = Array.from({ length: routesLength }, (_, i) => i);
|
|
45
|
+
|
|
46
|
+
return position.interpolate({
|
|
47
|
+
inputRange,
|
|
48
|
+
outputRange: inputRange.map((i) => (i === tabIndex ? 1 : 0)),
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getInactiveOpacity = (
|
|
56
|
+
position: Animated.AnimatedInterpolation<number>,
|
|
57
|
+
routesLength: number,
|
|
58
|
+
tabIndex: number
|
|
59
|
+
) => {
|
|
60
|
+
if (routesLength > 1) {
|
|
61
|
+
const inputRange = Array.from({ length: routesLength }, (_, i) => i);
|
|
62
|
+
|
|
63
|
+
return position.interpolate({
|
|
64
|
+
inputRange,
|
|
65
|
+
outputRange: inputRange.map((i: number) => (i === tabIndex ? 0 : 1)),
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type TabBarItemInternalProps<T extends Route> = Omit<
|
|
73
|
+
Props<T>,
|
|
74
|
+
| 'navigationState'
|
|
75
|
+
| 'getAccessibilityLabel'
|
|
76
|
+
| 'getLabelText'
|
|
77
|
+
| 'getTestID'
|
|
78
|
+
| 'getAccessible'
|
|
79
|
+
| 'options'
|
|
80
|
+
> & {
|
|
81
|
+
isFocused: boolean;
|
|
82
|
+
index: number;
|
|
83
|
+
routesLength: number;
|
|
84
|
+
} & TabDescriptor<T>;
|
|
85
|
+
|
|
86
|
+
const ANDROID_RIPPLE_DEFAULT = { borderless: true };
|
|
87
|
+
|
|
88
|
+
const TabBarItemInternal = <T extends Route>({
|
|
89
|
+
accessibilityLabel,
|
|
90
|
+
accessible,
|
|
91
|
+
label: customlabel,
|
|
92
|
+
testID,
|
|
93
|
+
onLongPress,
|
|
94
|
+
onPress,
|
|
95
|
+
isFocused,
|
|
96
|
+
position,
|
|
97
|
+
style,
|
|
98
|
+
inactiveColor: inactiveColorCustom,
|
|
99
|
+
activeColor: activeColorCustom,
|
|
100
|
+
labelStyle,
|
|
101
|
+
onLayout,
|
|
102
|
+
index: tabIndex,
|
|
103
|
+
pressColor,
|
|
104
|
+
pressOpacity,
|
|
105
|
+
defaultTabWidth,
|
|
106
|
+
icon: customIcon,
|
|
107
|
+
badge: customBadge,
|
|
108
|
+
href,
|
|
109
|
+
labelText,
|
|
110
|
+
routesLength,
|
|
111
|
+
android_ripple = ANDROID_RIPPLE_DEFAULT,
|
|
112
|
+
labelAllowFontScaling,
|
|
113
|
+
route,
|
|
114
|
+
}: TabBarItemInternalProps<T>) => {
|
|
115
|
+
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
116
|
+
|
|
117
|
+
const activeColor =
|
|
118
|
+
activeColorCustom !== undefined
|
|
119
|
+
? activeColorCustom
|
|
120
|
+
: typeof labelColorFromStyle === 'string'
|
|
121
|
+
? labelColorFromStyle
|
|
122
|
+
: DEFAULT_ACTIVE_COLOR;
|
|
123
|
+
const inactiveColor =
|
|
124
|
+
inactiveColorCustom !== undefined
|
|
125
|
+
? inactiveColorCustom
|
|
126
|
+
: typeof labelColorFromStyle === 'string'
|
|
127
|
+
? labelColorFromStyle
|
|
128
|
+
: DEFAULT_INACTIVE_COLOR;
|
|
129
|
+
|
|
130
|
+
const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
|
|
131
|
+
const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
|
|
132
|
+
|
|
133
|
+
const icon = React.useMemo(() => {
|
|
134
|
+
if (!customIcon) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const inactiveIcon = customIcon({
|
|
139
|
+
focused: false,
|
|
140
|
+
color: inactiveColor,
|
|
141
|
+
size: ICON_SIZE,
|
|
142
|
+
route,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const activeIcon = customIcon({
|
|
146
|
+
focused: true,
|
|
147
|
+
color: activeColor,
|
|
148
|
+
size: ICON_SIZE,
|
|
149
|
+
route,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<View style={styles.icon}>
|
|
154
|
+
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
155
|
+
{inactiveIcon}
|
|
156
|
+
</Animated.View>
|
|
157
|
+
<Animated.View
|
|
158
|
+
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
159
|
+
>
|
|
160
|
+
{activeIcon}
|
|
161
|
+
</Animated.View>
|
|
162
|
+
</View>
|
|
163
|
+
);
|
|
164
|
+
}, [
|
|
165
|
+
activeColor,
|
|
166
|
+
activeOpacity,
|
|
167
|
+
customIcon,
|
|
168
|
+
inactiveColor,
|
|
169
|
+
inactiveOpacity,
|
|
170
|
+
route,
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const renderLabel = React.useCallback(
|
|
174
|
+
(focused: boolean) =>
|
|
175
|
+
customlabel ? (
|
|
176
|
+
customlabel({
|
|
177
|
+
focused,
|
|
178
|
+
color: focused ? activeColor : inactiveColor,
|
|
179
|
+
style: labelStyle,
|
|
180
|
+
labelText,
|
|
181
|
+
allowFontScaling: labelAllowFontScaling,
|
|
182
|
+
route,
|
|
183
|
+
})
|
|
184
|
+
) : (
|
|
185
|
+
<TabBarItemLabel
|
|
186
|
+
color={focused ? activeColor : inactiveColor}
|
|
187
|
+
icon={icon}
|
|
188
|
+
label={labelText}
|
|
189
|
+
style={labelStyle}
|
|
190
|
+
/>
|
|
191
|
+
),
|
|
192
|
+
[
|
|
193
|
+
customlabel,
|
|
194
|
+
activeColor,
|
|
195
|
+
labelStyle,
|
|
196
|
+
labelText,
|
|
197
|
+
labelAllowFontScaling,
|
|
198
|
+
route,
|
|
199
|
+
inactiveColor,
|
|
200
|
+
icon,
|
|
201
|
+
]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const tabStyle = StyleSheet.flatten(style);
|
|
205
|
+
const isWidthSet = tabStyle?.width !== undefined;
|
|
206
|
+
|
|
207
|
+
const tabContainerStyle: ViewStyle | null = isWidthSet
|
|
208
|
+
? null
|
|
209
|
+
: { width: defaultTabWidth };
|
|
210
|
+
|
|
211
|
+
accessibilityLabel =
|
|
212
|
+
typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText;
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<PlatformPressable
|
|
216
|
+
android_ripple={android_ripple}
|
|
217
|
+
testID={testID}
|
|
218
|
+
accessible={accessible}
|
|
219
|
+
accessibilityLabel={accessibilityLabel}
|
|
220
|
+
accessibilityRole="tab"
|
|
221
|
+
accessibilityState={{ selected: isFocused }}
|
|
222
|
+
pressColor={pressColor}
|
|
223
|
+
pressOpacity={pressOpacity}
|
|
224
|
+
unstable_pressDelay={0}
|
|
225
|
+
onLayout={onLayout}
|
|
226
|
+
onPress={onPress}
|
|
227
|
+
onLongPress={onLongPress}
|
|
228
|
+
href={href}
|
|
229
|
+
style={[styles.pressable, tabContainerStyle]}
|
|
230
|
+
>
|
|
231
|
+
<View pointerEvents="none" style={[styles.item, tabStyle]}>
|
|
232
|
+
{icon}
|
|
233
|
+
<View>
|
|
234
|
+
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
235
|
+
{renderLabel(false)}
|
|
236
|
+
</Animated.View>
|
|
237
|
+
<Animated.View
|
|
238
|
+
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
239
|
+
>
|
|
240
|
+
{renderLabel(true)}
|
|
241
|
+
</Animated.View>
|
|
242
|
+
</View>
|
|
243
|
+
{customBadge != null ? (
|
|
244
|
+
<View style={styles.badge}>{customBadge({ route })}</View>
|
|
245
|
+
) : null}
|
|
246
|
+
</View>
|
|
247
|
+
</PlatformPressable>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const MemoizedTabBarItemInternal = React.memo(
|
|
252
|
+
TabBarItemInternal
|
|
253
|
+
) as typeof TabBarItemInternal;
|
|
254
|
+
|
|
255
|
+
export function TabBarItem<T extends Route>(props: Props<T>) {
|
|
256
|
+
const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
|
|
257
|
+
props;
|
|
258
|
+
|
|
259
|
+
const onPressLatest = useLatestCallback(onPress);
|
|
260
|
+
const onLongPressLatest = useLatestCallback(onLongPress);
|
|
261
|
+
const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
|
|
262
|
+
|
|
263
|
+
const tabIndex = navigationState.routes.indexOf(route);
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<MemoizedTabBarItemInternal
|
|
267
|
+
{...rest}
|
|
268
|
+
onPress={onPressLatest}
|
|
269
|
+
onLayout={onLayoutLatest}
|
|
270
|
+
onLongPress={onLongPressLatest}
|
|
271
|
+
isFocused={navigationState.index === tabIndex}
|
|
272
|
+
route={route}
|
|
273
|
+
index={tabIndex}
|
|
274
|
+
routesLength={navigationState.routes.length}
|
|
275
|
+
/>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const styles = StyleSheet.create({
|
|
280
|
+
icon: {
|
|
281
|
+
margin: 2,
|
|
282
|
+
},
|
|
283
|
+
item: {
|
|
284
|
+
flex: 1,
|
|
285
|
+
alignItems: 'center',
|
|
286
|
+
justifyContent: 'center',
|
|
287
|
+
padding: 10,
|
|
288
|
+
minHeight: 48,
|
|
289
|
+
},
|
|
290
|
+
badge: {
|
|
291
|
+
position: 'absolute',
|
|
292
|
+
top: 0,
|
|
293
|
+
end: 0,
|
|
294
|
+
},
|
|
295
|
+
pressable: {
|
|
296
|
+
// The label is not pressable on Windows
|
|
297
|
+
// Adding backgroundColor: 'transparent' seems to fix it
|
|
298
|
+
backgroundColor: 'transparent',
|
|
299
|
+
...Platform.select({
|
|
300
|
+
// Roundness for iPad hover effect
|
|
301
|
+
ios: { borderRadius: 10 },
|
|
302
|
+
default: null,
|
|
303
|
+
}),
|
|
304
|
+
},
|
|
305
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
style: StyleProp<ViewStyle>;
|
|
9
|
+
icon: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TabBarItemLabel = React.memo(
|
|
13
|
+
({ color, label, style, 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
|
+
style,
|
|
24
|
+
{ color: color },
|
|
25
|
+
]}
|
|
26
|
+
>
|
|
27
|
+
{label}
|
|
28
|
+
</Animated.Text>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
TabBarItemLabel.displayName = 'TabBarItemLabel';
|
|
34
|
+
|
|
35
|
+
const styles = StyleSheet.create({
|
|
36
|
+
label: {
|
|
37
|
+
margin: 4,
|
|
38
|
+
fontSize: 14,
|
|
39
|
+
fontWeight: '500',
|
|
40
|
+
backgroundColor: 'transparent',
|
|
41
|
+
},
|
|
42
|
+
});
|