@momo-kits/foundation 0.92.6-rc.9 → 0.92.8
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 +225 -0
- package/Application/BottomTab/BottomTabBar.tsx +60 -247
- package/Application/BottomTab/index.tsx +16 -12
- package/Application/Components.tsx +10 -7
- package/Application/ModalScreen.tsx +9 -156
- package/Application/NavigationContainer.tsx +0 -7
- package/Application/StackScreen.tsx +7 -4
- package/Application/index.ts +2 -2
- package/Application/types.ts +19 -20
- package/Application/utils.tsx +2 -24
- package/Button/index.tsx +5 -5
- package/Icon/index.tsx +1 -0
- package/Input/InputMoney.tsx +0 -1
- package/Input/common.tsx +0 -7
- package/Input/index.tsx +7 -0
- package/Layout/FloatingButton.tsx +63 -23
- package/Layout/Screen.tsx +23 -11
- package/Popup/PopupNotify.tsx +14 -10
- package/Title/index.tsx +20 -9
- package/index.ts +8 -2
- package/package.json +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React, {useContext, useEffect, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Dimensions,
|
|
5
|
+
KeyboardAvoidingView,
|
|
6
|
+
Modal,
|
|
7
|
+
PanResponder,
|
|
8
|
+
Platform,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
View,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
import {ApplicationContext} from './index';
|
|
14
|
+
import {BottomSheetParams} from './types';
|
|
15
|
+
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
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
|
+
surface,
|
|
28
|
+
barrierDismissible = false,
|
|
29
|
+
snapPoint = 'content',
|
|
30
|
+
draggable = true,
|
|
31
|
+
dragOnContent = false,
|
|
32
|
+
}: BottomSheetParams = props.route.params;
|
|
33
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
34
|
+
const animatedHeight = useRef(new Animated.Value(0)).current;
|
|
35
|
+
const pan = useRef(new Animated.ValueXY()).current;
|
|
36
|
+
const panResponder = useRef(
|
|
37
|
+
PanResponder.create({
|
|
38
|
+
onStartShouldSetPanResponder: () => draggable,
|
|
39
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
40
|
+
return draggable && dragOnContent && gestureState.dy > 0;
|
|
41
|
+
},
|
|
42
|
+
onPanResponderMove: (e, gestureState) => {
|
|
43
|
+
gestureState.dy > 0 &&
|
|
44
|
+
Animated.event([null, {dy: pan.y}], {useNativeDriver: false})(
|
|
45
|
+
e,
|
|
46
|
+
gestureState,
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
50
|
+
if (gestureState.dy > 100) {
|
|
51
|
+
onDismiss();
|
|
52
|
+
} else {
|
|
53
|
+
Animated.spring(pan, {
|
|
54
|
+
toValue: {x: 0, y: 0},
|
|
55
|
+
useNativeDriver: false,
|
|
56
|
+
}).start();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
).current;
|
|
61
|
+
const estimate = useRef(0.9);
|
|
62
|
+
|
|
63
|
+
let backgroundColor = theme.colors.background.default;
|
|
64
|
+
if (surface) {
|
|
65
|
+
backgroundColor = theme.colors.background.surface;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* handle open & emit dismiss event
|
|
70
|
+
*/
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
const mapSnapPoint: any = {
|
|
74
|
+
'30%': 0.3,
|
|
75
|
+
'50%': 0.5,
|
|
76
|
+
'70%': 0.7,
|
|
77
|
+
'90%': 0.9,
|
|
78
|
+
};
|
|
79
|
+
const point = mapSnapPoint[snapPoint] ?? estimate.current;
|
|
80
|
+
setModalVisible(true);
|
|
81
|
+
Animated.timing(animatedHeight, {
|
|
82
|
+
useNativeDriver: false,
|
|
83
|
+
toValue: heightDevice * point,
|
|
84
|
+
duration: 300,
|
|
85
|
+
}).start();
|
|
86
|
+
}, 100);
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
props.route.params?.onDismiss?.();
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* handler dismiss
|
|
95
|
+
*/
|
|
96
|
+
const onDismiss = (force = false) => {
|
|
97
|
+
if (barrierDismissible && !force) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
Animated.timing(animatedHeight, {
|
|
101
|
+
useNativeDriver: false,
|
|
102
|
+
toValue: 0,
|
|
103
|
+
duration: 200,
|
|
104
|
+
}).start(() => {
|
|
105
|
+
pan.setValue({x: 0, y: 0});
|
|
106
|
+
navigator?.pop();
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Modal
|
|
113
|
+
transparent
|
|
114
|
+
visible={modalVisible}
|
|
115
|
+
onRequestClose={() => onDismiss()}>
|
|
116
|
+
<KeyboardAvoidingView
|
|
117
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
118
|
+
keyboardVerticalOffset={-20}
|
|
119
|
+
style={Styles.flex}>
|
|
120
|
+
<TouchableOpacity
|
|
121
|
+
style={Styles.flex}
|
|
122
|
+
activeOpacity={1}
|
|
123
|
+
onPress={() => onDismiss()}
|
|
124
|
+
/>
|
|
125
|
+
<Animated.View
|
|
126
|
+
{...(dragOnContent && panResponder.panHandlers)}
|
|
127
|
+
style={[
|
|
128
|
+
styles.container,
|
|
129
|
+
{transform: pan.getTranslateTransform()},
|
|
130
|
+
{
|
|
131
|
+
height: animatedHeight,
|
|
132
|
+
backgroundColor,
|
|
133
|
+
},
|
|
134
|
+
]}>
|
|
135
|
+
{draggable && (
|
|
136
|
+
<View
|
|
137
|
+
{...(!dragOnContent && panResponder.panHandlers)}
|
|
138
|
+
style={styles.draggableContainer}>
|
|
139
|
+
<View style={{height: 16}}>
|
|
140
|
+
<View style={styles.indicator} />
|
|
141
|
+
</View>
|
|
142
|
+
<View
|
|
143
|
+
style={[
|
|
144
|
+
styles.header,
|
|
145
|
+
surface && {
|
|
146
|
+
borderBottomColor: theme.colors.border.default,
|
|
147
|
+
borderBottomWidth: 1,
|
|
148
|
+
},
|
|
149
|
+
]}>
|
|
150
|
+
<View style={styles.iconButton} />
|
|
151
|
+
<Text
|
|
152
|
+
style={[Styles.flex, {textAlign: 'center'}]}
|
|
153
|
+
numberOfLines={1}
|
|
154
|
+
typography="header_default_bold">
|
|
155
|
+
{options.title ?? ''}
|
|
156
|
+
</Text>
|
|
157
|
+
<TouchableOpacity
|
|
158
|
+
style={styles.iconButton}
|
|
159
|
+
onPress={() => onDismiss(true)}>
|
|
160
|
+
<Icon source="navigation_close" />
|
|
161
|
+
</TouchableOpacity>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
)}
|
|
165
|
+
<Screen {...props} {...props.route.params} />
|
|
166
|
+
<View style={{height: Math.min(insets.bottom, 21)}} />
|
|
167
|
+
</Animated.View>
|
|
168
|
+
</KeyboardAvoidingView>
|
|
169
|
+
</Modal>
|
|
170
|
+
{snapPoint == 'content' && (
|
|
171
|
+
<View
|
|
172
|
+
style={styles.estimate}
|
|
173
|
+
onLayout={e => {
|
|
174
|
+
estimate.current =
|
|
175
|
+
(e.nativeEvent.layout.height + 68) / heightDevice;
|
|
176
|
+
}}>
|
|
177
|
+
<Screen {...props} {...props.route.params} />
|
|
178
|
+
<View style={{height: Math.min(insets.bottom, 21)}} />
|
|
179
|
+
</View>
|
|
180
|
+
)}
|
|
181
|
+
</>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
export default BottomSheet;
|
|
185
|
+
|
|
186
|
+
const styles = StyleSheet.create({
|
|
187
|
+
container: {
|
|
188
|
+
width: '100%',
|
|
189
|
+
height: 0,
|
|
190
|
+
overflow: 'hidden',
|
|
191
|
+
borderTopLeftRadius: Radius.M,
|
|
192
|
+
borderTopRightRadius: Radius.M,
|
|
193
|
+
},
|
|
194
|
+
draggableContainer: {
|
|
195
|
+
width: '100%',
|
|
196
|
+
alignItems: 'center',
|
|
197
|
+
},
|
|
198
|
+
indicator: {
|
|
199
|
+
width: 40,
|
|
200
|
+
height: 4,
|
|
201
|
+
borderRadius: Radius.S,
|
|
202
|
+
backgroundColor: Colors.black_06,
|
|
203
|
+
position: 'absolute',
|
|
204
|
+
alignSelf: 'center',
|
|
205
|
+
top: Spacing.S,
|
|
206
|
+
},
|
|
207
|
+
header: {
|
|
208
|
+
height: 56,
|
|
209
|
+
flexDirection: 'row',
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
},
|
|
212
|
+
iconButton: {
|
|
213
|
+
width: 24,
|
|
214
|
+
height: 24,
|
|
215
|
+
alignItems: 'center',
|
|
216
|
+
justifyContent: 'center',
|
|
217
|
+
marginHorizontal: Spacing.M,
|
|
218
|
+
},
|
|
219
|
+
estimate: {
|
|
220
|
+
position: 'absolute',
|
|
221
|
+
top: -9999,
|
|
222
|
+
left: -9999,
|
|
223
|
+
opacity: 0,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -1,33 +1,26 @@
|
|
|
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;
|
|
@@ -36,87 +29,8 @@ type Props = BottomTabBarProps & {
|
|
|
36
29
|
accessibilityLabel?: string;
|
|
37
30
|
};
|
|
38
31
|
|
|
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;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
|
49
|
-
const DEFAULT_TABBAR_HEIGHT = 49;
|
|
50
|
-
const COMPACT_TABBAR_HEIGHT = 32;
|
|
51
|
-
|
|
52
|
-
const shouldUseHorizontalLabels = ({
|
|
53
|
-
state,
|
|
54
|
-
layout,
|
|
55
|
-
dimensions,
|
|
56
|
-
adaptive = true,
|
|
57
|
-
labelPosition,
|
|
58
|
-
tabStyle,
|
|
59
|
-
}: Options) => {
|
|
60
|
-
if (labelPosition) {
|
|
61
|
-
return labelPosition === 'beside-icon';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!adaptive) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (layout.width >= 768) {
|
|
69
|
-
// Screen size matches a tablet
|
|
70
|
-
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
|
71
|
-
|
|
72
|
-
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
|
73
|
-
|
|
74
|
-
if (flattenedStyle) {
|
|
75
|
-
if (typeof flattenedStyle.width === 'number') {
|
|
76
|
-
maxTabItemWidth = flattenedStyle.width;
|
|
77
|
-
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
|
78
|
-
maxTabItemWidth = flattenedStyle.maxWidth;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return state.routes.length * maxTabItemWidth <= layout.width;
|
|
83
|
-
} else {
|
|
84
|
-
return dimensions.width > dimensions.height;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
const getPaddingBottom = (insets: EdgeInsets) =>
|
|
88
|
-
Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
|
|
89
|
-
|
|
90
|
-
export const getTabBarHeight = ({
|
|
91
|
-
dimensions,
|
|
92
|
-
insets,
|
|
93
|
-
style,
|
|
94
|
-
...rest
|
|
95
|
-
}: Options & {
|
|
96
|
-
insets: EdgeInsets;
|
|
97
|
-
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
|
98
|
-
}) => {
|
|
99
|
-
// @ts-ignore
|
|
100
|
-
const customHeight = StyleSheet.flatten(style)?.height;
|
|
101
|
-
|
|
102
|
-
if (typeof customHeight === 'number') {
|
|
103
|
-
return customHeight;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const isLandscape = dimensions.width > dimensions.height;
|
|
107
|
-
const horizontalLabels = shouldUseHorizontalLabels({dimensions, ...rest});
|
|
108
|
-
const paddingBottom = getPaddingBottom(insets);
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
Platform.OS === 'ios' &&
|
|
112
|
-
!Platform.isPad &&
|
|
113
|
-
isLandscape &&
|
|
114
|
-
horizontalLabels
|
|
115
|
-
) {
|
|
116
|
-
return COMPACT_TABBAR_HEIGHT + paddingBottom;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return DEFAULT_TABBAR_HEIGHT + paddingBottom;
|
|
32
|
+
const getPaddingBottom = (insets: EdgeInsets) => {
|
|
33
|
+
return Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
|
|
120
34
|
};
|
|
121
35
|
|
|
122
36
|
export default function BottomTabBar({
|
|
@@ -125,129 +39,30 @@ export default function BottomTabBar({
|
|
|
125
39
|
descriptors,
|
|
126
40
|
activeBackgroundColor,
|
|
127
41
|
activeTintColor,
|
|
128
|
-
adaptive,
|
|
129
42
|
allowFontScaling,
|
|
130
43
|
inactiveBackgroundColor,
|
|
131
44
|
inactiveTintColor,
|
|
132
|
-
keyboardHidesTabBar = false,
|
|
133
|
-
labelPosition,
|
|
134
45
|
labelStyle,
|
|
135
46
|
iconStyle,
|
|
136
|
-
safeAreaInsets,
|
|
137
47
|
showLabel,
|
|
138
48
|
style,
|
|
139
49
|
tabStyle,
|
|
140
50
|
floatingButton,
|
|
141
51
|
}: Props) {
|
|
52
|
+
const {theme} = useContext(ApplicationContext);
|
|
142
53
|
const {routes} = state;
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const itemWidth =
|
|
146
|
-
? dimensions.width / 5
|
|
147
|
-
: dimensions.width / state.routes.length;
|
|
148
|
-
|
|
149
|
-
const [layout, setLayout] = React.useState({
|
|
150
|
-
height: 0,
|
|
151
|
-
width: dimensions.width,
|
|
152
|
-
});
|
|
54
|
+
const widthDevice = useWindowDimensions()?.width;
|
|
55
|
+
const useFloating = floatingButton && state.routes.length % 2 === 0;
|
|
56
|
+
const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0));
|
|
153
57
|
const buildLink = useLinkBuilder();
|
|
154
|
-
const {colors} = useTheme();
|
|
155
|
-
|
|
156
|
-
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
|
157
|
-
state,
|
|
158
|
-
dimensions,
|
|
159
|
-
layout,
|
|
160
|
-
adaptive,
|
|
161
|
-
labelPosition,
|
|
162
|
-
tabStyle,
|
|
163
|
-
});
|
|
164
|
-
const focusedRoute = state.routes[state.index] as any;
|
|
165
|
-
const focusedDescriptor = descriptors[focusedRoute?.key] as any;
|
|
166
|
-
const focusedOptions = focusedDescriptor?.options;
|
|
167
|
-
const shouldShowTabBar =
|
|
168
|
-
focusedOptions?.tabBarVisible !== false && !keyboardHidesTabBar;
|
|
169
|
-
const [visible] = React.useState(
|
|
170
|
-
() => new Animated.Value(shouldShowTabBar ? 1 : 0),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
58
|
const indicatorAnimated = React.useRef(new Animated.Value(0)).current;
|
|
174
|
-
|
|
175
|
-
const visibilityAnimationConfigRef = React.useRef(
|
|
176
|
-
focusedOptions?.tabBarVisibilityAnimationConfig,
|
|
177
|
-
);
|
|
178
|
-
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
|
179
|
-
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
|
|
180
|
-
React.useEffect(() => {
|
|
181
|
-
visibilityAnimationConfigRef.current =
|
|
182
|
-
focusedOptions?.tabBarVisibilityAnimationConfig;
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const handleLayout = (e: LayoutChangeEvent) => {
|
|
186
|
-
const {height, width} = e.nativeEvent.layout;
|
|
187
|
-
|
|
188
|
-
onHeightChange?.(height);
|
|
189
|
-
|
|
190
|
-
setLayout(layout => {
|
|
191
|
-
if (height === layout.height && width === layout.width) {
|
|
192
|
-
return layout;
|
|
193
|
-
} else {
|
|
194
|
-
return {
|
|
195
|
-
height,
|
|
196
|
-
width,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
React.useEffect(() => {
|
|
203
|
-
const visibilityAnimationConfig = visibilityAnimationConfigRef.current;
|
|
204
|
-
|
|
205
|
-
if (shouldShowTabBar) {
|
|
206
|
-
const animation =
|
|
207
|
-
visibilityAnimationConfig?.show?.animation === 'spring'
|
|
208
|
-
? Animated.spring
|
|
209
|
-
: Animated.timing;
|
|
210
|
-
|
|
211
|
-
animation(visible, {
|
|
212
|
-
toValue: 1,
|
|
213
|
-
useNativeDriver: true,
|
|
214
|
-
duration: 250,
|
|
215
|
-
...visibilityAnimationConfig?.show?.config,
|
|
216
|
-
}).start(({finished}) => {
|
|
217
|
-
if (finished) {
|
|
218
|
-
setIsTabBarHidden(false);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
} else {
|
|
222
|
-
setIsTabBarHidden(true);
|
|
223
|
-
|
|
224
|
-
const animation =
|
|
225
|
-
visibilityAnimationConfig?.hide?.animation === 'spring'
|
|
226
|
-
? Animated.spring
|
|
227
|
-
: Animated.timing;
|
|
228
|
-
|
|
229
|
-
animation(visible, {
|
|
230
|
-
toValue: 0,
|
|
231
|
-
useNativeDriver: true,
|
|
232
|
-
duration: 200,
|
|
233
|
-
...visibilityAnimationConfig?.hide?.config,
|
|
234
|
-
}).start();
|
|
235
|
-
}
|
|
236
|
-
}, [visible, shouldShowTabBar]);
|
|
237
|
-
const defaultInsets = useSafeAreaInsets();
|
|
238
|
-
|
|
239
|
-
const insets = {
|
|
240
|
-
top: safeAreaInsets?.top ?? defaultInsets.top,
|
|
241
|
-
right: safeAreaInsets?.right ?? defaultInsets.right,
|
|
242
|
-
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
|
243
|
-
left: safeAreaInsets?.left ?? defaultInsets.left,
|
|
244
|
-
};
|
|
59
|
+
const insets = useSafeAreaInsets();
|
|
245
60
|
const paddingBottom = getPaddingBottom(insets);
|
|
246
|
-
|
|
247
|
-
const leftArr = routes.slice(0, 2);
|
|
248
|
-
const rightArr = routes.slice(2, routes.length);
|
|
249
61
|
const focusedTab = state?.routes[state.index]?.name || routes[0]?.params;
|
|
250
62
|
|
|
63
|
+
/**
|
|
64
|
+
* render tab item
|
|
65
|
+
*/
|
|
251
66
|
const renderTabItem = useCallback(
|
|
252
67
|
(data: any[]) => {
|
|
253
68
|
return data?.map((route, index) => {
|
|
@@ -297,7 +112,7 @@ export default function BottomTabBar({
|
|
|
297
112
|
<BottomTabBarItem
|
|
298
113
|
route={route}
|
|
299
114
|
focused={focused}
|
|
300
|
-
horizontal={
|
|
115
|
+
horizontal={false}
|
|
301
116
|
onPress={onPress}
|
|
302
117
|
onLongPress={onLongPress}
|
|
303
118
|
accessibilityLabel={accessibilityLabel}
|
|
@@ -326,22 +141,17 @@ export default function BottomTabBar({
|
|
|
326
141
|
[JSON.stringify(routes), state, descriptors, focusedTab],
|
|
327
142
|
);
|
|
328
143
|
|
|
329
|
-
|
|
144
|
+
/**
|
|
145
|
+
* render floating button
|
|
146
|
+
*/
|
|
147
|
+
const renderFloatingButton = useCallback(() => {
|
|
330
148
|
const renderFloatingButton = () => {
|
|
331
149
|
if (typeof floatingButton?.icon == 'string') {
|
|
332
150
|
return <Icon source={floatingButton?.icon} size={32} color={'white'} />;
|
|
333
|
-
} else if (floatingButton?.icon) {
|
|
334
|
-
return floatingButton?.icon;
|
|
335
|
-
} else {
|
|
336
|
-
return null;
|
|
337
151
|
}
|
|
152
|
+
return floatingButton?.icon;
|
|
338
153
|
};
|
|
339
|
-
|
|
340
|
-
console.warn(
|
|
341
|
-
"Can't render floating button with icon when routes length is not 4",
|
|
342
|
-
);
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
154
|
+
|
|
345
155
|
return (
|
|
346
156
|
<View style={{flex: 1}}>
|
|
347
157
|
<TouchableOpacity
|
|
@@ -350,14 +160,14 @@ export default function BottomTabBar({
|
|
|
350
160
|
<View
|
|
351
161
|
style={{
|
|
352
162
|
...styles.floatingContent,
|
|
353
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
163
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
354
164
|
}}>
|
|
355
165
|
{renderFloatingButton()}
|
|
356
166
|
</View>
|
|
357
167
|
<View
|
|
358
168
|
style={{
|
|
359
169
|
top: -6,
|
|
360
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
170
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
361
171
|
borderRadius: Radius.XL,
|
|
362
172
|
paddingHorizontal: 6,
|
|
363
173
|
}}>
|
|
@@ -371,53 +181,59 @@ export default function BottomTabBar({
|
|
|
371
181
|
</TouchableOpacity>
|
|
372
182
|
</View>
|
|
373
183
|
);
|
|
374
|
-
}, [
|
|
184
|
+
}, [useFloating]);
|
|
375
185
|
|
|
186
|
+
/**
|
|
187
|
+
* animated indicator
|
|
188
|
+
*/
|
|
376
189
|
React.useEffect(() => {
|
|
190
|
+
let index = state.index;
|
|
191
|
+
if (useFloating && index > (state.routes.length - 1) / 2) {
|
|
192
|
+
index += 1;
|
|
193
|
+
}
|
|
377
194
|
Animated.timing(indicatorAnimated, {
|
|
378
|
-
toValue:
|
|
379
|
-
|
|
380
|
-
? itemWidth * (state.index + 1)
|
|
381
|
-
: itemWidth * state.index,
|
|
382
|
-
useNativeDriver: false,
|
|
195
|
+
toValue: itemWidth * index,
|
|
196
|
+
useNativeDriver: true,
|
|
383
197
|
duration: 200,
|
|
384
198
|
}).start();
|
|
385
|
-
}, [state.index, itemWidth, indicatorAnimated,
|
|
199
|
+
}, [state.index, itemWidth, indicatorAnimated, useFloating]);
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* render tab bar
|
|
203
|
+
*/
|
|
204
|
+
const renderTabBar = () => {
|
|
205
|
+
if (useFloating) {
|
|
206
|
+
const middleIndex = routes.length / 2;
|
|
207
|
+
const leftArr = routes.slice(0, middleIndex);
|
|
208
|
+
const rightArr = routes.slice(middleIndex);
|
|
209
|
+
return (
|
|
210
|
+
<>
|
|
211
|
+
{renderTabItem(leftArr)}
|
|
212
|
+
{renderFloatingButton()}
|
|
213
|
+
{renderTabItem(rightArr)}
|
|
214
|
+
</>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return renderTabItem(routes);
|
|
218
|
+
};
|
|
386
219
|
|
|
387
220
|
return (
|
|
388
221
|
<Animated.View
|
|
389
222
|
style={[
|
|
390
223
|
styles.tabBar,
|
|
391
224
|
{
|
|
392
|
-
backgroundColor: colors.
|
|
393
|
-
borderTopColor: colors.border,
|
|
225
|
+
backgroundColor: theme.colors.background.surface,
|
|
226
|
+
borderTopColor: theme.colors.border.default,
|
|
394
227
|
},
|
|
395
228
|
{
|
|
396
|
-
|
|
397
|
-
{
|
|
398
|
-
translateY: visible.interpolate({
|
|
399
|
-
inputRange: [0, 1],
|
|
400
|
-
outputRange: [
|
|
401
|
-
layout.height + paddingBottom + StyleSheet.hairlineWidth,
|
|
402
|
-
0,
|
|
403
|
-
],
|
|
404
|
-
}),
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
// Absolutely position the tab bar so that the content is below it
|
|
408
|
-
// This is needed to avoid gap at bottom when the tab bar is hidden
|
|
409
|
-
position: isTabBarHidden ? 'absolute' : (null as any),
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
paddingBottom: paddingBottom - 0,
|
|
229
|
+
paddingBottom: paddingBottom,
|
|
413
230
|
paddingHorizontal: Math.max(insets.left, insets.right),
|
|
414
231
|
},
|
|
415
232
|
style,
|
|
416
233
|
]}
|
|
417
|
-
pointerEvents={
|
|
418
|
-
onLayout={handleLayout}>
|
|
234
|
+
pointerEvents={'auto'}>
|
|
419
235
|
<View style={styles.content}>
|
|
420
|
-
{
|
|
236
|
+
{useFloating && floatingButton?.container}
|
|
421
237
|
<Animated.View
|
|
422
238
|
style={{
|
|
423
239
|
width: itemWidth,
|
|
@@ -428,13 +244,11 @@ export default function BottomTabBar({
|
|
|
428
244
|
<View
|
|
429
245
|
style={[
|
|
430
246
|
styles.indicator,
|
|
431
|
-
{backgroundColor: activeTintColor ?? colors.primary},
|
|
247
|
+
{backgroundColor: activeTintColor ?? theme.colors.primary},
|
|
432
248
|
]}
|
|
433
249
|
/>
|
|
434
250
|
</Animated.View>
|
|
435
|
-
{
|
|
436
|
-
{isFloating && renderCenterComponent()}
|
|
437
|
-
{isFloating && renderTabItem(rightArr)}
|
|
251
|
+
{renderTabBar()}
|
|
438
252
|
</View>
|
|
439
253
|
</Animated.View>
|
|
440
254
|
);
|
|
@@ -464,9 +278,8 @@ const styles = StyleSheet.create({
|
|
|
464
278
|
justifyContent: 'center',
|
|
465
279
|
height: 48,
|
|
466
280
|
width: 48,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
top: -12,
|
|
281
|
+
borderRadius: 24,
|
|
282
|
+
top: -10,
|
|
470
283
|
},
|
|
471
284
|
indicator: {
|
|
472
285
|
height: 2,
|