@momo-kits/foundation 0.92.7-rc.21 → 0.92.7-rc.22
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/Application/BottomSheet.tsx +216 -0
- package/Application/BottomTab/BottomTabBar.tsx +62 -223
- package/Application/BottomTab/index.tsx +15 -11
- package/Application/Components.tsx +291 -45
- package/Application/Localize.ts +44 -0
- package/Application/ModalScreen.tsx +11 -181
- package/Application/Navigation.ts +4 -18
- package/Application/NavigationContainer.tsx +10 -28
- package/Application/Navigator.ts +14 -16
- package/Application/StackScreen.tsx +222 -42
- package/Application/index.ts +5 -3
- package/Application/types.ts +47 -29
- package/Application/utils.tsx +61 -19
- package/Assets/icon_bank.json +1 -1
- package/Badge/BadgeDotAnimation.tsx +95 -0
- package/Badge/index.tsx +2 -1
- package/Badge/styles.ts +50 -6
- package/Button/index.tsx +7 -8
- package/Icon/index.tsx +1 -1
- package/Icon/types.ts +1 -2
- package/Image/index.tsx +0 -1
- package/Input/Input.tsx +51 -17
- package/Input/InputDropDown.tsx +42 -45
- package/Input/InputMoney.tsx +65 -6
- package/Input/InputOTP.tsx +14 -13
- package/Input/InputSearch.tsx +259 -146
- package/Input/InputTextArea.tsx +0 -1
- package/Input/TextTyping.tsx +113 -0
- package/Input/common.tsx +29 -10
- package/Input/index.tsx +84 -89
- package/Input/styles.ts +44 -7
- package/Input/utils.ts +1 -1
- package/Layout/Card.tsx +4 -3
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/GridSystem.tsx +1 -1
- package/Layout/ItemList.tsx +3 -2
- package/Layout/Screen.tsx +188 -102
- package/Layout/Section.tsx +4 -3
- package/Layout/styles.ts +0 -6
- package/Layout/types.ts +1 -1
- package/Layout/utils.ts +2 -2
- package/Loader/index.tsx +2 -0
- package/Pagination/PaginationScroll.tsx +2 -2
- package/Pagination/styles.ts +1 -1
- package/Popup/PopupNotify.tsx +7 -7
- package/Popup/PopupPromotion.tsx +1 -1
- package/Popup/types.ts +1 -1
- package/Tag/index.tsx +9 -2
- package/Tag/styles.ts +3 -2
- package/Tag/types.ts +12 -0
- package/Title/index.tsx +20 -7
- package/Title/styles.ts +4 -4
- package/code-scanner.js +141 -124
- package/index.ts +7 -0
- package/package.json +5 -4
- package/publish.sh +16 -21
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import React, {useRef, useEffect, useCallback, useContext} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Dimensions,
|
|
5
|
+
KeyboardAvoidingView,
|
|
6
|
+
PanResponder,
|
|
7
|
+
Platform,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
View,
|
|
11
|
+
Modal,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
14
|
+
import {ApplicationContext} from './index';
|
|
15
|
+
import {BottomSheetParams} from './types';
|
|
16
|
+
import {Colors, Radius, Spacing, Styles} from '../Consts';
|
|
17
|
+
import {Text} from '../Text';
|
|
18
|
+
import {Icon} from '../Icon';
|
|
19
|
+
|
|
20
|
+
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
21
|
+
const {theme, navigator} = useContext(ApplicationContext);
|
|
22
|
+
const heightDevice = Dimensions.get('screen').height;
|
|
23
|
+
const insets = useSafeAreaInsets();
|
|
24
|
+
const {
|
|
25
|
+
screen: Screen,
|
|
26
|
+
options,
|
|
27
|
+
useNativeModal = false,
|
|
28
|
+
surface,
|
|
29
|
+
barrierDismissible = false,
|
|
30
|
+
draggable = true,
|
|
31
|
+
useBottomInset = true,
|
|
32
|
+
keyboardVerticalOffset,
|
|
33
|
+
}: BottomSheetParams = props.route.params;
|
|
34
|
+
const pan = useRef(
|
|
35
|
+
new Animated.ValueXY({
|
|
36
|
+
y: heightDevice,
|
|
37
|
+
x: 0,
|
|
38
|
+
})
|
|
39
|
+
).current;
|
|
40
|
+
|
|
41
|
+
const panResponder = useRef(
|
|
42
|
+
PanResponder.create({
|
|
43
|
+
onStartShouldSetPanResponder: () => draggable,
|
|
44
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
45
|
+
return draggable && gestureState.dy > 0;
|
|
46
|
+
},
|
|
47
|
+
onPanResponderMove: (e, gestureState) => {
|
|
48
|
+
gestureState.dy > 0 &&
|
|
49
|
+
Animated.event([null, {dy: pan.y}], {useNativeDriver: false})(
|
|
50
|
+
e,
|
|
51
|
+
gestureState
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
55
|
+
if (gestureState.dy > 100) {
|
|
56
|
+
onDismiss();
|
|
57
|
+
} else {
|
|
58
|
+
Animated.spring(pan, {
|
|
59
|
+
toValue: {x: 0, y: 0},
|
|
60
|
+
useNativeDriver: false,
|
|
61
|
+
}).start();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
).current;
|
|
66
|
+
let Container: any = View;
|
|
67
|
+
if (useNativeModal) {
|
|
68
|
+
Container = Modal;
|
|
69
|
+
}
|
|
70
|
+
let backgroundColor = theme.colors.background.default;
|
|
71
|
+
if (surface) {
|
|
72
|
+
backgroundColor = theme.colors.background.surface;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* emit dismiss event
|
|
77
|
+
*/
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
Animated.timing(pan, {
|
|
80
|
+
toValue: {x: 0, y: 0},
|
|
81
|
+
useNativeDriver: false,
|
|
82
|
+
duration: 150,
|
|
83
|
+
}).start();
|
|
84
|
+
return () => {
|
|
85
|
+
props.route.params?.onDismiss?.();
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* handler dismiss
|
|
91
|
+
*/
|
|
92
|
+
const onDismiss = (force = false, callback?: () => void) => {
|
|
93
|
+
if (barrierDismissible && !force) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
callback?.();
|
|
98
|
+
}, 300);
|
|
99
|
+
Animated.timing(pan, {
|
|
100
|
+
toValue: {x: 0, y: heightDevice},
|
|
101
|
+
useNativeDriver: false,
|
|
102
|
+
duration: 200,
|
|
103
|
+
}).start(() => {
|
|
104
|
+
navigator?.pop();
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* on request close
|
|
110
|
+
*/
|
|
111
|
+
const onRequestClose = useCallback((callback?: () => void) => {
|
|
112
|
+
onDismiss(true, callback);
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* render header
|
|
117
|
+
*/
|
|
118
|
+
const renderHeader = useCallback(() => {
|
|
119
|
+
return (
|
|
120
|
+
<View
|
|
121
|
+
style={{
|
|
122
|
+
backgroundColor,
|
|
123
|
+
borderTopLeftRadius: Radius.M,
|
|
124
|
+
borderTopRightRadius: Radius.M,
|
|
125
|
+
}}
|
|
126
|
+
{...panResponder.panHandlers}>
|
|
127
|
+
<View style={{height: 16}}>
|
|
128
|
+
<View style={styles.indicator} />
|
|
129
|
+
</View>
|
|
130
|
+
<View
|
|
131
|
+
style={[
|
|
132
|
+
styles.header,
|
|
133
|
+
surface && {
|
|
134
|
+
borderBottomColor: theme.colors.border.default,
|
|
135
|
+
borderBottomWidth: 1,
|
|
136
|
+
},
|
|
137
|
+
]}>
|
|
138
|
+
<View style={styles.iconButton} />
|
|
139
|
+
<Text
|
|
140
|
+
style={[Styles.flex, {textAlign: 'center'}]}
|
|
141
|
+
numberOfLines={1}
|
|
142
|
+
typography="header_default_bold">
|
|
143
|
+
{options.title ?? ''}
|
|
144
|
+
</Text>
|
|
145
|
+
<TouchableOpacity
|
|
146
|
+
style={styles.iconButton}
|
|
147
|
+
onPress={() => onDismiss(true)}>
|
|
148
|
+
<Icon source="navigation_close" />
|
|
149
|
+
</TouchableOpacity>
|
|
150
|
+
</View>
|
|
151
|
+
</View>
|
|
152
|
+
);
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<Container
|
|
157
|
+
transparent
|
|
158
|
+
visible={true}
|
|
159
|
+
onRequestClose={() => onDismiss()}
|
|
160
|
+
style={styles.overlay}>
|
|
161
|
+
<KeyboardAvoidingView
|
|
162
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
163
|
+
keyboardVerticalOffset={keyboardVerticalOffset ?? -20}
|
|
164
|
+
style={[Styles.flex, {justifyContent: 'flex-end'}]}>
|
|
165
|
+
<TouchableOpacity
|
|
166
|
+
style={Styles.flex}
|
|
167
|
+
activeOpacity={1}
|
|
168
|
+
onPress={() => onDismiss()}
|
|
169
|
+
/>
|
|
170
|
+
<Animated.View style={{transform: pan.getTranslateTransform()}}>
|
|
171
|
+
{renderHeader()}
|
|
172
|
+
<View style={{backgroundColor: backgroundColor}}>
|
|
173
|
+
<Screen
|
|
174
|
+
{...props}
|
|
175
|
+
{...props.route.params}
|
|
176
|
+
onRequestClose={onRequestClose}
|
|
177
|
+
/>
|
|
178
|
+
{useBottomInset && (
|
|
179
|
+
<View style={{height: Math.min(insets.bottom, 21)}} />
|
|
180
|
+
)}
|
|
181
|
+
</View>
|
|
182
|
+
</Animated.View>
|
|
183
|
+
</KeyboardAvoidingView>
|
|
184
|
+
</Container>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const styles = StyleSheet.create({
|
|
189
|
+
overlay: {
|
|
190
|
+
...StyleSheet.absoluteFillObject,
|
|
191
|
+
justifyContent: 'flex-end',
|
|
192
|
+
},
|
|
193
|
+
indicator: {
|
|
194
|
+
width: 40,
|
|
195
|
+
height: 4,
|
|
196
|
+
borderRadius: Radius.S,
|
|
197
|
+
backgroundColor: Colors.black_06,
|
|
198
|
+
position: 'absolute',
|
|
199
|
+
alignSelf: 'center',
|
|
200
|
+
top: Spacing.S,
|
|
201
|
+
},
|
|
202
|
+
header: {
|
|
203
|
+
height: 56,
|
|
204
|
+
flexDirection: 'row',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
},
|
|
207
|
+
iconButton: {
|
|
208
|
+
width: 24,
|
|
209
|
+
height: 24,
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
justifyContent: 'center',
|
|
212
|
+
marginHorizontal: Spacing.M,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export default BottomSheet;
|
|
@@ -1,222 +1,70 @@
|
|
|
1
1
|
import {BottomTabBarProps} from '@react-navigation/bottom-tabs';
|
|
2
|
-
import {LabelPosition} from '@react-navigation/bottom-tabs/src/types';
|
|
3
|
-
import BottomTabBarHeightCallbackContext from '@react-navigation/bottom-tabs/src/utils/BottomTabBarHeightCallbackContext';
|
|
4
2
|
import BottomTabBarItem from '@react-navigation/bottom-tabs/src/views/BottomTabItem';
|
|
5
3
|
import {
|
|
6
4
|
CommonActions,
|
|
7
5
|
NavigationContext,
|
|
8
6
|
NavigationRouteContext,
|
|
9
|
-
ParamListBase,
|
|
10
|
-
TabNavigationState,
|
|
11
7
|
useLinkBuilder,
|
|
12
|
-
useTheme,
|
|
13
8
|
} from '@react-navigation/native';
|
|
14
|
-
import React, {useCallback} from 'react';
|
|
9
|
+
import React, {useCallback, useContext} from 'react';
|
|
15
10
|
import {
|
|
16
11
|
Animated,
|
|
17
|
-
LayoutChangeEvent,
|
|
18
12
|
Platform,
|
|
19
|
-
StyleProp,
|
|
20
13
|
StyleSheet,
|
|
21
14
|
TouchableOpacity,
|
|
22
15
|
useWindowDimensions,
|
|
23
16
|
View,
|
|
24
|
-
ViewStyle,
|
|
25
17
|
} from 'react-native';
|
|
26
18
|
import {EdgeInsets, useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
27
19
|
import {Radius} from '../../Consts';
|
|
28
20
|
import {Icon} from '../../Icon';
|
|
29
21
|
import {Text} from '../../Text';
|
|
30
22
|
import {FloatingButtonProps} from '../types';
|
|
23
|
+
import {ApplicationContext} from '../index';
|
|
31
24
|
|
|
32
25
|
type Props = BottomTabBarProps & {
|
|
33
26
|
activeTintColor?: string;
|
|
34
27
|
inactiveTintColor?: string;
|
|
35
28
|
floatingButton?: FloatingButtonProps;
|
|
36
|
-
accessibilityLabel?: string;
|
|
37
29
|
};
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
layout: {height: number; width: number};
|
|
42
|
-
dimensions: {height: number; width: number};
|
|
43
|
-
tabStyle: StyleProp<ViewStyle>;
|
|
44
|
-
labelPosition: LabelPosition | undefined;
|
|
45
|
-
adaptive: boolean | undefined;
|
|
31
|
+
const getPaddingBottom = (insets: EdgeInsets) => {
|
|
32
|
+
return Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
|
|
46
33
|
};
|
|
47
34
|
|
|
48
|
-
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
|
49
|
-
|
|
50
|
-
const shouldUseHorizontalLabels = ({
|
|
51
|
-
state,
|
|
52
|
-
layout,
|
|
53
|
-
dimensions,
|
|
54
|
-
adaptive = true,
|
|
55
|
-
labelPosition,
|
|
56
|
-
tabStyle,
|
|
57
|
-
}: Options) => {
|
|
58
|
-
if (labelPosition) {
|
|
59
|
-
return labelPosition === 'beside-icon';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (!adaptive) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (layout.width >= 768) {
|
|
67
|
-
// Screen size matches a tablet
|
|
68
|
-
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
|
69
|
-
|
|
70
|
-
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
|
71
|
-
|
|
72
|
-
if (flattenedStyle) {
|
|
73
|
-
if (typeof flattenedStyle.width === 'number') {
|
|
74
|
-
maxTabItemWidth = flattenedStyle.width;
|
|
75
|
-
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
|
76
|
-
maxTabItemWidth = flattenedStyle.maxWidth;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return state.routes.length * maxTabItemWidth <= layout.width;
|
|
81
|
-
} else {
|
|
82
|
-
return dimensions.width > dimensions.height;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const getPaddingBottom = (insets: EdgeInsets) =>
|
|
86
|
-
Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
|
|
87
|
-
|
|
88
35
|
export default function BottomTabBar({
|
|
89
36
|
state,
|
|
90
37
|
navigation,
|
|
91
38
|
descriptors,
|
|
92
39
|
activeBackgroundColor,
|
|
93
40
|
activeTintColor,
|
|
94
|
-
adaptive,
|
|
95
41
|
allowFontScaling,
|
|
96
42
|
inactiveBackgroundColor,
|
|
97
43
|
inactiveTintColor,
|
|
98
|
-
keyboardHidesTabBar = false,
|
|
99
|
-
labelPosition,
|
|
100
44
|
labelStyle,
|
|
101
45
|
iconStyle,
|
|
102
|
-
safeAreaInsets,
|
|
103
46
|
showLabel,
|
|
104
47
|
style,
|
|
105
48
|
tabStyle,
|
|
106
49
|
floatingButton,
|
|
107
50
|
}: Props) {
|
|
51
|
+
const {theme} = useContext(ApplicationContext);
|
|
108
52
|
const {routes} = state;
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const itemWidth =
|
|
112
|
-
? dimensions.width / 5
|
|
113
|
-
: dimensions.width / state.routes.length;
|
|
114
|
-
|
|
115
|
-
const [layout, setLayout] = React.useState({
|
|
116
|
-
height: 0,
|
|
117
|
-
width: dimensions.width,
|
|
118
|
-
});
|
|
53
|
+
const widthDevice = useWindowDimensions()?.width;
|
|
54
|
+
const useFloating = floatingButton && state.routes.length % 2 === 0;
|
|
55
|
+
const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0));
|
|
119
56
|
const buildLink = useLinkBuilder();
|
|
120
|
-
const {colors} = useTheme();
|
|
121
|
-
|
|
122
|
-
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
|
123
|
-
state,
|
|
124
|
-
dimensions,
|
|
125
|
-
layout,
|
|
126
|
-
adaptive,
|
|
127
|
-
labelPosition,
|
|
128
|
-
tabStyle,
|
|
129
|
-
});
|
|
130
|
-
const focusedRoute = state.routes[state.index] as any;
|
|
131
|
-
const focusedDescriptor = descriptors[focusedRoute?.key] as any;
|
|
132
|
-
const focusedOptions = focusedDescriptor?.options;
|
|
133
|
-
const shouldShowTabBar =
|
|
134
|
-
focusedOptions?.tabBarVisible !== false && !keyboardHidesTabBar;
|
|
135
|
-
const [visible] = React.useState(
|
|
136
|
-
() => new Animated.Value(shouldShowTabBar ? 1 : 0),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
57
|
const indicatorAnimated = React.useRef(new Animated.Value(0)).current;
|
|
140
|
-
|
|
141
|
-
const visibilityAnimationConfigRef = React.useRef(
|
|
142
|
-
focusedOptions?.tabBarVisibilityAnimationConfig,
|
|
143
|
-
);
|
|
144
|
-
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
|
145
|
-
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
|
|
146
|
-
React.useEffect(() => {
|
|
147
|
-
visibilityAnimationConfigRef.current =
|
|
148
|
-
focusedOptions?.tabBarVisibilityAnimationConfig;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const handleLayout = (e: LayoutChangeEvent) => {
|
|
152
|
-
const {height, width} = e.nativeEvent.layout;
|
|
153
|
-
|
|
154
|
-
onHeightChange?.(height);
|
|
155
|
-
|
|
156
|
-
setLayout(layout => {
|
|
157
|
-
if (height === layout.height && width === layout.width) {
|
|
158
|
-
return layout;
|
|
159
|
-
} else {
|
|
160
|
-
return {
|
|
161
|
-
height,
|
|
162
|
-
width,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
React.useEffect(() => {
|
|
169
|
-
const visibilityAnimationConfig = visibilityAnimationConfigRef.current;
|
|
170
|
-
|
|
171
|
-
if (shouldShowTabBar) {
|
|
172
|
-
const animation =
|
|
173
|
-
visibilityAnimationConfig?.show?.animation === 'spring'
|
|
174
|
-
? Animated.spring
|
|
175
|
-
: Animated.timing;
|
|
176
|
-
|
|
177
|
-
animation(visible, {
|
|
178
|
-
toValue: 1,
|
|
179
|
-
useNativeDriver: true,
|
|
180
|
-
duration: 250,
|
|
181
|
-
...visibilityAnimationConfig?.show?.config,
|
|
182
|
-
}).start(({finished}) => {
|
|
183
|
-
if (finished) {
|
|
184
|
-
setIsTabBarHidden(false);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
} else {
|
|
188
|
-
setIsTabBarHidden(true);
|
|
189
|
-
|
|
190
|
-
const animation =
|
|
191
|
-
visibilityAnimationConfig?.hide?.animation === 'spring'
|
|
192
|
-
? Animated.spring
|
|
193
|
-
: Animated.timing;
|
|
194
|
-
|
|
195
|
-
animation(visible, {
|
|
196
|
-
toValue: 0,
|
|
197
|
-
useNativeDriver: true,
|
|
198
|
-
duration: 200,
|
|
199
|
-
...visibilityAnimationConfig?.hide?.config,
|
|
200
|
-
}).start();
|
|
201
|
-
}
|
|
202
|
-
}, [visible, shouldShowTabBar]);
|
|
203
|
-
const defaultInsets = useSafeAreaInsets();
|
|
204
|
-
|
|
205
|
-
const insets = {
|
|
206
|
-
top: safeAreaInsets?.top ?? defaultInsets.top,
|
|
207
|
-
right: safeAreaInsets?.right ?? defaultInsets.right,
|
|
208
|
-
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
|
209
|
-
left: safeAreaInsets?.left ?? defaultInsets.left,
|
|
210
|
-
};
|
|
58
|
+
const insets = useSafeAreaInsets();
|
|
211
59
|
const paddingBottom = getPaddingBottom(insets);
|
|
212
|
-
|
|
213
|
-
const leftArr = routes.slice(0, 2);
|
|
214
|
-
const rightArr = routes.slice(2, routes.length);
|
|
215
60
|
const focusedTab = state?.routes[state.index]?.name || routes[0]?.params;
|
|
216
61
|
|
|
62
|
+
/**
|
|
63
|
+
* render tab item
|
|
64
|
+
*/
|
|
217
65
|
const renderTabItem = useCallback(
|
|
218
66
|
(data: any[]) => {
|
|
219
|
-
return data?.map((route,
|
|
67
|
+
return data?.map((route, _) => {
|
|
220
68
|
const focused = route.params.name === focusedTab;
|
|
221
69
|
const {options} = descriptors[route?.key] as any;
|
|
222
70
|
const onPress = () => {
|
|
@@ -241,19 +89,7 @@ export default function BottomTabBar({
|
|
|
241
89
|
});
|
|
242
90
|
};
|
|
243
91
|
|
|
244
|
-
const label =
|
|
245
|
-
options.tabBarLabel !== undefined
|
|
246
|
-
? options.tabBarLabel
|
|
247
|
-
: options.title !== undefined
|
|
248
|
-
? options.title
|
|
249
|
-
: route.name;
|
|
250
|
-
|
|
251
|
-
const accessibilityLabel =
|
|
252
|
-
options.tabBarAccessibilityLabel !== undefined
|
|
253
|
-
? options.tabBarAccessibilityLabel
|
|
254
|
-
: typeof label === 'string'
|
|
255
|
-
? `${label}, tab, ${index + 1} of ${routes.length}`
|
|
256
|
-
: undefined;
|
|
92
|
+
const label = options.tabBarLabel || options.title || route.name;
|
|
257
93
|
|
|
258
94
|
return (
|
|
259
95
|
<NavigationContext.Provider
|
|
@@ -263,10 +99,10 @@ export default function BottomTabBar({
|
|
|
263
99
|
<BottomTabBarItem
|
|
264
100
|
route={route}
|
|
265
101
|
focused={focused}
|
|
266
|
-
horizontal={
|
|
102
|
+
horizontal={false}
|
|
267
103
|
onPress={onPress}
|
|
268
104
|
onLongPress={onLongPress}
|
|
269
|
-
accessibilityLabel={
|
|
105
|
+
accessibilityLabel={`tab_item_${route.name}`}
|
|
270
106
|
to={buildLink(route.name, route.params)}
|
|
271
107
|
testID={options.tabBarTestID}
|
|
272
108
|
allowFontScaling={allowFontScaling}
|
|
@@ -289,18 +125,15 @@ export default function BottomTabBar({
|
|
|
289
125
|
);
|
|
290
126
|
});
|
|
291
127
|
},
|
|
292
|
-
[JSON.stringify(routes), state, descriptors, focusedTab]
|
|
128
|
+
[JSON.stringify(routes), state, descriptors, focusedTab]
|
|
293
129
|
);
|
|
294
130
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
);
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
131
|
+
/**
|
|
132
|
+
* render floating button
|
|
133
|
+
*/
|
|
134
|
+
const renderFloatingButton = useCallback(() => {
|
|
302
135
|
const renderFloatingButton = () => {
|
|
303
|
-
if (typeof floatingButton?.icon
|
|
136
|
+
if (typeof floatingButton?.icon === 'string') {
|
|
304
137
|
return <Icon source={floatingButton?.icon} size={32} color={'white'} />;
|
|
305
138
|
}
|
|
306
139
|
return floatingButton?.icon;
|
|
@@ -314,14 +147,14 @@ export default function BottomTabBar({
|
|
|
314
147
|
<View
|
|
315
148
|
style={{
|
|
316
149
|
...styles.floatingContent,
|
|
317
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
150
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
318
151
|
}}>
|
|
319
152
|
{renderFloatingButton()}
|
|
320
153
|
</View>
|
|
321
154
|
<View
|
|
322
155
|
style={{
|
|
323
|
-
top: -
|
|
324
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
156
|
+
top: -4,
|
|
157
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
325
158
|
borderRadius: Radius.XL,
|
|
326
159
|
paddingHorizontal: 6,
|
|
327
160
|
}}>
|
|
@@ -335,40 +168,49 @@ export default function BottomTabBar({
|
|
|
335
168
|
</TouchableOpacity>
|
|
336
169
|
</View>
|
|
337
170
|
);
|
|
338
|
-
}, [
|
|
171
|
+
}, [useFloating]);
|
|
339
172
|
|
|
173
|
+
/**
|
|
174
|
+
* animated indicator
|
|
175
|
+
*/
|
|
340
176
|
React.useEffect(() => {
|
|
177
|
+
let index = state.index;
|
|
178
|
+
if (useFloating && index > (state.routes.length - 1) / 2) {
|
|
179
|
+
index += 1;
|
|
180
|
+
}
|
|
341
181
|
Animated.timing(indicatorAnimated, {
|
|
342
|
-
toValue:
|
|
343
|
-
|
|
344
|
-
? itemWidth * (state.index + 1)
|
|
345
|
-
: itemWidth * state.index,
|
|
346
|
-
useNativeDriver: false,
|
|
182
|
+
toValue: itemWidth * index,
|
|
183
|
+
useNativeDriver: true,
|
|
347
184
|
duration: 200,
|
|
348
185
|
}).start();
|
|
349
|
-
}, [state.index, itemWidth, indicatorAnimated,
|
|
186
|
+
}, [state.index, itemWidth, indicatorAnimated, useFloating]);
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* render tab bar
|
|
190
|
+
*/
|
|
191
|
+
const renderTabBar = () => {
|
|
192
|
+
if (useFloating) {
|
|
193
|
+
const middleIndex = routes.length / 2;
|
|
194
|
+
const leftArr = routes.slice(0, middleIndex);
|
|
195
|
+
const rightArr = routes.slice(middleIndex);
|
|
196
|
+
return (
|
|
197
|
+
<>
|
|
198
|
+
{renderTabItem(leftArr)}
|
|
199
|
+
{renderFloatingButton()}
|
|
200
|
+
{renderTabItem(rightArr)}
|
|
201
|
+
</>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return renderTabItem(routes);
|
|
205
|
+
};
|
|
350
206
|
|
|
351
207
|
return (
|
|
352
208
|
<Animated.View
|
|
353
209
|
style={[
|
|
354
210
|
styles.tabBar,
|
|
355
211
|
{
|
|
356
|
-
backgroundColor: colors.
|
|
357
|
-
borderTopColor: colors.border,
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
transform: [
|
|
361
|
-
{
|
|
362
|
-
translateY: visible.interpolate({
|
|
363
|
-
inputRange: [0, 1],
|
|
364
|
-
outputRange: [
|
|
365
|
-
layout.height + paddingBottom + StyleSheet.hairlineWidth,
|
|
366
|
-
0,
|
|
367
|
-
],
|
|
368
|
-
}),
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
position: isTabBarHidden ? 'absolute' : (null as any),
|
|
212
|
+
backgroundColor: theme.colors.background.surface,
|
|
213
|
+
borderTopColor: theme.colors.border.default,
|
|
372
214
|
},
|
|
373
215
|
{
|
|
374
216
|
paddingBottom: paddingBottom,
|
|
@@ -376,10 +218,9 @@ export default function BottomTabBar({
|
|
|
376
218
|
},
|
|
377
219
|
style,
|
|
378
220
|
]}
|
|
379
|
-
pointerEvents={
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
{isFloating && floatingButton?.container}
|
|
221
|
+
pointerEvents={'auto'}>
|
|
222
|
+
<View style={styles.content} accessibilityLabel={'bottom_tab_bar'}>
|
|
223
|
+
{useFloating && floatingButton?.container}
|
|
383
224
|
<Animated.View
|
|
384
225
|
style={{
|
|
385
226
|
width: itemWidth,
|
|
@@ -390,13 +231,11 @@ export default function BottomTabBar({
|
|
|
390
231
|
<View
|
|
391
232
|
style={[
|
|
392
233
|
styles.indicator,
|
|
393
|
-
{backgroundColor: activeTintColor ?? colors.primary},
|
|
234
|
+
{backgroundColor: activeTintColor ?? theme.colors.primary},
|
|
394
235
|
]}
|
|
395
236
|
/>
|
|
396
237
|
</Animated.View>
|
|
397
|
-
{
|
|
398
|
-
{isFloating && renderCenterComponent()}
|
|
399
|
-
{isFloating && renderTabItem(rightArr)}
|
|
238
|
+
{renderTabBar()}
|
|
400
239
|
</View>
|
|
401
240
|
</Animated.View>
|
|
402
241
|
);
|
|
@@ -17,18 +17,22 @@ const Tab = createBottomTabNavigator();
|
|
|
17
17
|
const Stack = createStackNavigator();
|
|
18
18
|
|
|
19
19
|
const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
|
|
20
|
-
const {theme} = useContext(ApplicationContext);
|
|
21
20
|
let options = {};
|
|
22
21
|
if (route.params?.options) {
|
|
23
|
-
options = getOptions(route.params?.options
|
|
22
|
+
options = getOptions(route.params?.options);
|
|
24
23
|
}
|
|
24
|
+
|
|
25
25
|
if (route.params?.nested) {
|
|
26
26
|
return (
|
|
27
27
|
<Stack.Navigator>
|
|
28
28
|
<Stack.Screen
|
|
29
29
|
name="Stack"
|
|
30
30
|
component={StackScreen}
|
|
31
|
-
initialParams={{
|
|
31
|
+
initialParams={{
|
|
32
|
+
screen: route.params?.screen,
|
|
33
|
+
options,
|
|
34
|
+
initialParams: route.params?.initialParams,
|
|
35
|
+
}}
|
|
32
36
|
options={{...getStackOptions()}}
|
|
33
37
|
/>
|
|
34
38
|
</Stack.Navigator>
|
|
@@ -40,7 +44,11 @@ const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
|
|
|
40
44
|
<Stack.Screen
|
|
41
45
|
name="Stack"
|
|
42
46
|
component={StackScreen}
|
|
43
|
-
initialParams={{
|
|
47
|
+
initialParams={{
|
|
48
|
+
screen: route.params?.screen,
|
|
49
|
+
options,
|
|
50
|
+
initialParams: route.params?.initialParams,
|
|
51
|
+
}}
|
|
44
52
|
options={{...getStackOptions(), ...route.params?.options}}
|
|
45
53
|
/>
|
|
46
54
|
</Stack.Navigator>
|
|
@@ -72,11 +80,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
72
80
|
initialRouteName={initialRouteName}
|
|
73
81
|
backBehavior={'firstRoute'}
|
|
74
82
|
tabBar={props => (
|
|
75
|
-
<BottomTabBar
|
|
76
|
-
{...props}
|
|
77
|
-
accessibilityLabel={'BottomTabBar'}
|
|
78
|
-
floatingButton={floatingButton}
|
|
79
|
-
/>
|
|
83
|
+
<BottomTabBar {...props} floatingButton={floatingButton} />
|
|
80
84
|
)}
|
|
81
85
|
tabBarOptions={{
|
|
82
86
|
style: {
|
|
@@ -89,14 +93,14 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
89
93
|
}}>
|
|
90
94
|
{tabs.map(item => {
|
|
91
95
|
const isNum = !isNaN(parseInt(`${item?.badgeLabel}`));
|
|
92
|
-
const isDot = typeof item?.badgeLabel
|
|
96
|
+
const isDot = typeof item?.badgeLabel === 'string' && !item?.badgeLabel;
|
|
93
97
|
return (
|
|
94
98
|
<Tab.Screen
|
|
95
99
|
key={item.name}
|
|
96
100
|
name={item.name}
|
|
97
101
|
component={TabScreen}
|
|
98
102
|
initialParams={{...item, nested}}
|
|
99
|
-
listeners={listeners}
|
|
103
|
+
listeners={item.listeners ?? listeners}
|
|
100
104
|
options={{
|
|
101
105
|
tabBarLabel: ({color, focused}) => {
|
|
102
106
|
return (
|