@momo-kits/foundation 0.92.7 → 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 +55 -203
- package/Application/ModalScreen.tsx +9 -179
- package/Application/NavigationContainer.tsx +0 -7
- package/Application/types.ts +16 -18
- package/Title/index.tsx +20 -9
- package/index.ts +7 -0
- 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,184 +29,40 @@ 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;
|
|
32
|
+
const getPaddingBottom = (insets: EdgeInsets) => {
|
|
33
|
+
return Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
|
|
46
34
|
};
|
|
47
35
|
|
|
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
36
|
export default function BottomTabBar({
|
|
89
37
|
state,
|
|
90
38
|
navigation,
|
|
91
39
|
descriptors,
|
|
92
40
|
activeBackgroundColor,
|
|
93
41
|
activeTintColor,
|
|
94
|
-
adaptive,
|
|
95
42
|
allowFontScaling,
|
|
96
43
|
inactiveBackgroundColor,
|
|
97
44
|
inactiveTintColor,
|
|
98
|
-
keyboardHidesTabBar = false,
|
|
99
|
-
labelPosition,
|
|
100
45
|
labelStyle,
|
|
101
46
|
iconStyle,
|
|
102
|
-
safeAreaInsets,
|
|
103
47
|
showLabel,
|
|
104
48
|
style,
|
|
105
49
|
tabStyle,
|
|
106
50
|
floatingButton,
|
|
107
51
|
}: Props) {
|
|
52
|
+
const {theme} = useContext(ApplicationContext);
|
|
108
53
|
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
|
-
});
|
|
54
|
+
const widthDevice = useWindowDimensions()?.width;
|
|
55
|
+
const useFloating = floatingButton && state.routes.length % 2 === 0;
|
|
56
|
+
const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0));
|
|
119
57
|
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
58
|
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
|
-
};
|
|
59
|
+
const insets = useSafeAreaInsets();
|
|
211
60
|
const paddingBottom = getPaddingBottom(insets);
|
|
212
|
-
|
|
213
|
-
const leftArr = routes.slice(0, 2);
|
|
214
|
-
const rightArr = routes.slice(2, routes.length);
|
|
215
61
|
const focusedTab = state?.routes[state.index]?.name || routes[0]?.params;
|
|
216
62
|
|
|
63
|
+
/**
|
|
64
|
+
* render tab item
|
|
65
|
+
*/
|
|
217
66
|
const renderTabItem = useCallback(
|
|
218
67
|
(data: any[]) => {
|
|
219
68
|
return data?.map((route, index) => {
|
|
@@ -263,7 +112,7 @@ export default function BottomTabBar({
|
|
|
263
112
|
<BottomTabBarItem
|
|
264
113
|
route={route}
|
|
265
114
|
focused={focused}
|
|
266
|
-
horizontal={
|
|
115
|
+
horizontal={false}
|
|
267
116
|
onPress={onPress}
|
|
268
117
|
onLongPress={onLongPress}
|
|
269
118
|
accessibilityLabel={accessibilityLabel}
|
|
@@ -292,13 +141,10 @@ export default function BottomTabBar({
|
|
|
292
141
|
[JSON.stringify(routes), state, descriptors, focusedTab],
|
|
293
142
|
);
|
|
294
143
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
);
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
144
|
+
/**
|
|
145
|
+
* render floating button
|
|
146
|
+
*/
|
|
147
|
+
const renderFloatingButton = useCallback(() => {
|
|
302
148
|
const renderFloatingButton = () => {
|
|
303
149
|
if (typeof floatingButton?.icon == 'string') {
|
|
304
150
|
return <Icon source={floatingButton?.icon} size={32} color={'white'} />;
|
|
@@ -314,14 +160,14 @@ export default function BottomTabBar({
|
|
|
314
160
|
<View
|
|
315
161
|
style={{
|
|
316
162
|
...styles.floatingContent,
|
|
317
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
163
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
318
164
|
}}>
|
|
319
165
|
{renderFloatingButton()}
|
|
320
166
|
</View>
|
|
321
167
|
<View
|
|
322
168
|
style={{
|
|
323
169
|
top: -6,
|
|
324
|
-
backgroundColor: activeTintColor ?? colors.primary,
|
|
170
|
+
backgroundColor: activeTintColor ?? theme.colors.primary,
|
|
325
171
|
borderRadius: Radius.XL,
|
|
326
172
|
paddingHorizontal: 6,
|
|
327
173
|
}}>
|
|
@@ -335,40 +181,49 @@ export default function BottomTabBar({
|
|
|
335
181
|
</TouchableOpacity>
|
|
336
182
|
</View>
|
|
337
183
|
);
|
|
338
|
-
}, [
|
|
184
|
+
}, [useFloating]);
|
|
339
185
|
|
|
186
|
+
/**
|
|
187
|
+
* animated indicator
|
|
188
|
+
*/
|
|
340
189
|
React.useEffect(() => {
|
|
190
|
+
let index = state.index;
|
|
191
|
+
if (useFloating && index > (state.routes.length - 1) / 2) {
|
|
192
|
+
index += 1;
|
|
193
|
+
}
|
|
341
194
|
Animated.timing(indicatorAnimated, {
|
|
342
|
-
toValue:
|
|
343
|
-
|
|
344
|
-
? itemWidth * (state.index + 1)
|
|
345
|
-
: itemWidth * state.index,
|
|
346
|
-
useNativeDriver: false,
|
|
195
|
+
toValue: itemWidth * index,
|
|
196
|
+
useNativeDriver: true,
|
|
347
197
|
duration: 200,
|
|
348
198
|
}).start();
|
|
349
|
-
}, [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
|
+
};
|
|
350
219
|
|
|
351
220
|
return (
|
|
352
221
|
<Animated.View
|
|
353
222
|
style={[
|
|
354
223
|
styles.tabBar,
|
|
355
224
|
{
|
|
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),
|
|
225
|
+
backgroundColor: theme.colors.background.surface,
|
|
226
|
+
borderTopColor: theme.colors.border.default,
|
|
372
227
|
},
|
|
373
228
|
{
|
|
374
229
|
paddingBottom: paddingBottom,
|
|
@@ -376,10 +231,9 @@ export default function BottomTabBar({
|
|
|
376
231
|
},
|
|
377
232
|
style,
|
|
378
233
|
]}
|
|
379
|
-
pointerEvents={
|
|
380
|
-
onLayout={handleLayout}>
|
|
234
|
+
pointerEvents={'auto'}>
|
|
381
235
|
<View style={styles.content}>
|
|
382
|
-
{
|
|
236
|
+
{useFloating && floatingButton?.container}
|
|
383
237
|
<Animated.View
|
|
384
238
|
style={{
|
|
385
239
|
width: itemWidth,
|
|
@@ -390,13 +244,11 @@ export default function BottomTabBar({
|
|
|
390
244
|
<View
|
|
391
245
|
style={[
|
|
392
246
|
styles.indicator,
|
|
393
|
-
{backgroundColor: activeTintColor ?? colors.primary},
|
|
247
|
+
{backgroundColor: activeTintColor ?? theme.colors.primary},
|
|
394
248
|
]}
|
|
395
249
|
/>
|
|
396
250
|
</Animated.View>
|
|
397
|
-
{
|
|
398
|
-
{isFloating && renderCenterComponent()}
|
|
399
|
-
{isFloating && renderTabItem(rightArr)}
|
|
251
|
+
{renderTabBar()}
|
|
400
252
|
</View>
|
|
401
253
|
</Animated.View>
|
|
402
254
|
);
|
|
@@ -4,29 +4,16 @@ import {
|
|
|
4
4
|
Platform,
|
|
5
5
|
Pressable,
|
|
6
6
|
StyleSheet,
|
|
7
|
-
TouchableOpacity,
|
|
8
7
|
View,
|
|
9
8
|
} from 'react-native';
|
|
10
|
-
import {Modalize} from 'react-native-modalize';
|
|
11
|
-
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
12
9
|
|
|
13
10
|
import {ApplicationContext} from './index';
|
|
14
|
-
import {
|
|
15
|
-
import {Icon} from '../Icon';
|
|
16
|
-
import {Text} from '../Text';
|
|
11
|
+
import {Styles} from '../Consts';
|
|
17
12
|
import Navigation from './Navigation';
|
|
18
|
-
import {
|
|
13
|
+
import {ModalParams} from './types';
|
|
14
|
+
import BottomSheet from './BottomSheet';
|
|
19
15
|
|
|
20
16
|
const ModalScreen: React.FC<any> = props => {
|
|
21
|
-
/**
|
|
22
|
-
* dismiss callback when modal | bottom sheet dismissed
|
|
23
|
-
*/
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
return () => {
|
|
26
|
-
props.route.params?.onDismiss?.();
|
|
27
|
-
};
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
17
|
if (props.route?.params?.isBottomSheet) {
|
|
31
18
|
return <BottomSheet {...props} />;
|
|
32
19
|
}
|
|
@@ -42,9 +29,14 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
42
29
|
...props.route.params,
|
|
43
30
|
navigation,
|
|
44
31
|
};
|
|
45
|
-
|
|
46
32
|
delete params.screen;
|
|
47
33
|
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
props.route.params?.onDismiss?.();
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
48
40
|
const onDismiss = () => {
|
|
49
41
|
if (barrierDismissible) {
|
|
50
42
|
return;
|
|
@@ -83,118 +75,6 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
83
75
|
);
|
|
84
76
|
};
|
|
85
77
|
|
|
86
|
-
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
87
|
-
const {theme, navigator} = useContext(ApplicationContext);
|
|
88
|
-
const insets = useSafeAreaInsets();
|
|
89
|
-
|
|
90
|
-
const {
|
|
91
|
-
screen: Screen,
|
|
92
|
-
options,
|
|
93
|
-
footerComponent,
|
|
94
|
-
scrollViewProps,
|
|
95
|
-
surface,
|
|
96
|
-
}: BottomSheetParams = props.route.params;
|
|
97
|
-
|
|
98
|
-
const navigation = new Navigation(props.navigation, theme);
|
|
99
|
-
|
|
100
|
-
const params = {
|
|
101
|
-
...props.route.params,
|
|
102
|
-
navigation,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
delete params.screen;
|
|
106
|
-
const modalRef = useRef<Modalize>(null);
|
|
107
|
-
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
modalRef.current?.open();
|
|
110
|
-
}, []);
|
|
111
|
-
|
|
112
|
-
const handleClose = () => {
|
|
113
|
-
modalRef?.current?.close();
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* build header
|
|
118
|
-
* @param title
|
|
119
|
-
* @param onClose
|
|
120
|
-
*/
|
|
121
|
-
const headerDefault = (title: string, onClose: () => void) => {
|
|
122
|
-
return (
|
|
123
|
-
<View
|
|
124
|
-
style={[
|
|
125
|
-
styles.header,
|
|
126
|
-
surface && {
|
|
127
|
-
borderBottomColor: theme.colors.border.default,
|
|
128
|
-
borderBottomWidth: 1,
|
|
129
|
-
},
|
|
130
|
-
]}>
|
|
131
|
-
<View style={styles.iconButton} />
|
|
132
|
-
<Text
|
|
133
|
-
style={styles.textOption}
|
|
134
|
-
numberOfLines={1}
|
|
135
|
-
typography="header_default_bold">
|
|
136
|
-
{title ?? ''}
|
|
137
|
-
</Text>
|
|
138
|
-
<TouchableOpacity style={styles.iconButton} onPress={onClose}>
|
|
139
|
-
<Icon source="navigation_close" />
|
|
140
|
-
</TouchableOpacity>
|
|
141
|
-
</View>
|
|
142
|
-
);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* build footer
|
|
147
|
-
* @param footerComponent
|
|
148
|
-
*/
|
|
149
|
-
const footerDefault = (footerComponent: React.ReactNode) => {
|
|
150
|
-
if (footerComponent) {
|
|
151
|
-
return (
|
|
152
|
-
<View
|
|
153
|
-
style={{
|
|
154
|
-
marginBottom: Math.min(insets.bottom, 21),
|
|
155
|
-
paddingHorizontal: Spacing.M,
|
|
156
|
-
paddingVertical: Spacing.S,
|
|
157
|
-
}}>
|
|
158
|
-
{footerComponent}
|
|
159
|
-
</View>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<SafeAreaView style={Styles.flex}>
|
|
166
|
-
<Modalize
|
|
167
|
-
keyboardAvoidingOffset={-20}
|
|
168
|
-
adjustToContentHeight
|
|
169
|
-
closeOnOverlayTap
|
|
170
|
-
closeSnapPointStraightEnabled
|
|
171
|
-
onClosed={() => {
|
|
172
|
-
navigator?.pop();
|
|
173
|
-
}}
|
|
174
|
-
handlePosition="inside"
|
|
175
|
-
withHandle={true}
|
|
176
|
-
disableScrollIfPossible={true}
|
|
177
|
-
modalTopOffset={insets.top}
|
|
178
|
-
ref={modalRef}
|
|
179
|
-
scrollViewProps={scrollViewProps}
|
|
180
|
-
HeaderComponent={headerDefault(
|
|
181
|
-
options?.title ?? options?.customTitle?.title ?? '',
|
|
182
|
-
handleClose,
|
|
183
|
-
)}
|
|
184
|
-
modalStyle={{
|
|
185
|
-
backgroundColor: surface
|
|
186
|
-
? theme.colors.background.surface
|
|
187
|
-
: theme.colors.background.default,
|
|
188
|
-
}}
|
|
189
|
-
FooterComponent={footerDefault(footerComponent)}>
|
|
190
|
-
<View style={styles.bottomSheetContainer}>
|
|
191
|
-
<Screen {...props} {...params} />
|
|
192
|
-
</View>
|
|
193
|
-
</Modalize>
|
|
194
|
-
</SafeAreaView>
|
|
195
|
-
);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
78
|
const styles = StyleSheet.create({
|
|
199
79
|
container: {flex: 1, flexDirection: 'row'},
|
|
200
80
|
modalContent: {
|
|
@@ -212,56 +92,6 @@ const styles = StyleSheet.create({
|
|
|
212
92
|
flex: 1,
|
|
213
93
|
minHeight: '10%',
|
|
214
94
|
},
|
|
215
|
-
sheetContainer: {
|
|
216
|
-
height: '100%',
|
|
217
|
-
width: '100%',
|
|
218
|
-
borderTopLeftRadius: Radius.M,
|
|
219
|
-
borderTopRightRadius: Radius.M,
|
|
220
|
-
overflow: 'hidden',
|
|
221
|
-
},
|
|
222
|
-
indicatorContainer: {
|
|
223
|
-
width: '100%',
|
|
224
|
-
paddingTop: Spacing.S,
|
|
225
|
-
paddingBottom: Spacing.XS,
|
|
226
|
-
alignItems: 'center',
|
|
227
|
-
justifyContent: 'center',
|
|
228
|
-
},
|
|
229
|
-
indicator: {
|
|
230
|
-
width: 40,
|
|
231
|
-
height: 4,
|
|
232
|
-
borderRadius: Radius.S,
|
|
233
|
-
},
|
|
234
|
-
bottomSheetContainer: {
|
|
235
|
-
width: '100%',
|
|
236
|
-
},
|
|
237
|
-
header: {
|
|
238
|
-
marginTop: 10,
|
|
239
|
-
height: 56,
|
|
240
|
-
width: '100%',
|
|
241
|
-
alignItems: 'center',
|
|
242
|
-
flexDirection: 'row',
|
|
243
|
-
},
|
|
244
|
-
textOption: {
|
|
245
|
-
fontSize: 16,
|
|
246
|
-
color: '#303233',
|
|
247
|
-
textAlign: 'center',
|
|
248
|
-
fontWeight: 'bold',
|
|
249
|
-
paddingHorizontal: Spacing.L,
|
|
250
|
-
flex: 1,
|
|
251
|
-
},
|
|
252
|
-
iconButton: {
|
|
253
|
-
width: 24,
|
|
254
|
-
height: 24,
|
|
255
|
-
alignItems: 'center',
|
|
256
|
-
justifyContent: 'center',
|
|
257
|
-
marginHorizontal: Spacing.M,
|
|
258
|
-
},
|
|
259
|
-
icon: {
|
|
260
|
-
width: 24,
|
|
261
|
-
height: 24,
|
|
262
|
-
tintColor: '#303233',
|
|
263
|
-
backgroundColor: 'red',
|
|
264
|
-
},
|
|
265
95
|
});
|
|
266
96
|
|
|
267
97
|
export default ModalScreen;
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
NavigationContainer as ReactNavigationContainer,
|
|
5
5
|
NavigationContainerRef,
|
|
6
6
|
} from '@react-navigation/native';
|
|
7
|
-
import {BottomSheetModalProvider} from '@gorhom/bottom-sheet';
|
|
8
7
|
import {createStackNavigator} from '@react-navigation/stack';
|
|
9
8
|
import {DeviceEventEmitter} from 'react-native';
|
|
10
9
|
import StackScreen from './StackScreen';
|
|
@@ -15,12 +14,6 @@ import {LocalizationObject, NavigationContainerProps} from './types';
|
|
|
15
14
|
import {ApplicationContext} from './index';
|
|
16
15
|
import kitLocalization from '../Assets/language.json';
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
* link for deprecated
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
console.log(typeof BottomSheetModalProvider);
|
|
23
|
-
|
|
24
17
|
const Stack = createStackNavigator();
|
|
25
18
|
|
|
26
19
|
const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
package/Application/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {StackNavigationOptions} from '@react-navigation/stack';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {Animated,
|
|
3
|
+
import {Animated, ViewProps} from 'react-native';
|
|
4
4
|
import {PopupNotifyProps} from '../Popup/types';
|
|
5
5
|
import Navigation from './Navigation';
|
|
6
6
|
import Navigator from './Navigator';
|
|
@@ -70,16 +70,6 @@ export type Context = {
|
|
|
70
70
|
translate?: (key: string) => string;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
export type MiniAppInfo = {
|
|
74
|
-
[key: string]: any;
|
|
75
|
-
code: string;
|
|
76
|
-
appId: string;
|
|
77
|
-
buildNumber: number;
|
|
78
|
-
flowPartnerId?: string;
|
|
79
|
-
toolkitConfig?: any;
|
|
80
|
-
fromCode?: string;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
73
|
export type LocalizationObject = {
|
|
84
74
|
vi: {
|
|
85
75
|
[key: string]: string;
|
|
@@ -110,19 +100,27 @@ export type ScreenParams = {
|
|
|
110
100
|
options?: NavigationOptions;
|
|
111
101
|
};
|
|
112
102
|
|
|
113
|
-
export
|
|
103
|
+
export type ModalParams = {
|
|
114
104
|
[key: string]: any;
|
|
105
|
+
screen: Screen;
|
|
115
106
|
onDismiss?: () => void;
|
|
116
107
|
barrierDismissible?: boolean;
|
|
117
|
-
}
|
|
108
|
+
};
|
|
118
109
|
|
|
119
|
-
export
|
|
110
|
+
export type BottomSheetParams = {
|
|
120
111
|
[key: string]: any;
|
|
121
|
-
|
|
112
|
+
screen: Screen;
|
|
113
|
+
options: {
|
|
114
|
+
title: string;
|
|
115
|
+
surface?: boolean;
|
|
116
|
+
};
|
|
117
|
+
snapPoint?: '30%' | '50%' | '70%' | '90%' | 'content';
|
|
122
118
|
surface?: boolean;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
onDismiss?: (type?: string) => void;
|
|
120
|
+
barrierDismissible?: boolean;
|
|
121
|
+
draggable?: boolean;
|
|
122
|
+
dragOnContent?: boolean;
|
|
123
|
+
};
|
|
126
124
|
|
|
127
125
|
export type NavigationButtonProps = {
|
|
128
126
|
icon: string;
|
package/Title/index.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import React, {FC, useContext, useState} from 'react';
|
|
2
2
|
import {Text as RNText, TouchableOpacity, View, ViewStyle} from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
Typography,
|
|
5
|
+
Colors,
|
|
6
|
+
Icon,
|
|
7
|
+
Badge,
|
|
8
|
+
ApplicationContext,
|
|
9
|
+
scaleSize,
|
|
10
|
+
Text,
|
|
11
|
+
} from '@momo-kits/foundation';
|
|
3
12
|
import styles from './styles';
|
|
4
|
-
import {Icon} from '../Icon';
|
|
5
|
-
import {scaleSize, Text} from '../Text';
|
|
6
|
-
import {ApplicationContext} from '../Application';
|
|
7
|
-
import {Badge} from '../Badge';
|
|
8
|
-
import {Typography} from '../Text/types';
|
|
9
13
|
import {TitleProps} from './types';
|
|
10
|
-
import {Colors} from '../Consts';
|
|
11
14
|
|
|
12
15
|
const Title: FC<TitleProps> = ({
|
|
13
16
|
title = 'Title',
|
|
@@ -67,7 +70,9 @@ const Title: FC<TitleProps> = ({
|
|
|
67
70
|
<View style={styles.contentView}>
|
|
68
71
|
<View
|
|
69
72
|
onLayout={e => {
|
|
70
|
-
|
|
73
|
+
if (e.nativeEvent.layout.width !== contentWidth) {
|
|
74
|
+
setContentWidth(e.nativeEvent.layout.width);
|
|
75
|
+
}
|
|
71
76
|
}}
|
|
72
77
|
style={[styles.iconLeftView, flexStyle]}>
|
|
73
78
|
<RNText
|
|
@@ -75,14 +80,19 @@ const Title: FC<TitleProps> = ({
|
|
|
75
80
|
style={[
|
|
76
81
|
styleSheet[typography],
|
|
77
82
|
styles.title,
|
|
78
|
-
{
|
|
83
|
+
{
|
|
84
|
+
maxWidth:
|
|
85
|
+
contentWidth > 0 ? contentWidth - badgeWidth : undefined,
|
|
86
|
+
},
|
|
79
87
|
]}>
|
|
80
88
|
{title}
|
|
81
89
|
</RNText>
|
|
82
90
|
{badgeLabel && (
|
|
83
91
|
<View
|
|
84
92
|
onLayout={e => {
|
|
85
|
-
|
|
93
|
+
if (e.nativeEvent.layout.width !== badgeWidth) {
|
|
94
|
+
setBadgeWidth(e.nativeEvent.layout.width);
|
|
95
|
+
}
|
|
86
96
|
}}
|
|
87
97
|
style={{
|
|
88
98
|
alignItems: 'center',
|
|
@@ -164,6 +174,7 @@ const Title: FC<TitleProps> = ({
|
|
|
164
174
|
if (textOnly) {
|
|
165
175
|
return (
|
|
166
176
|
<View style={isSection && styles.margin}>
|
|
177
|
+
zZ
|
|
167
178
|
<RNText
|
|
168
179
|
numberOfLines={numberOfLines}
|
|
169
180
|
style={[styleSheet[typography], styles.title]}>
|
package/index.ts
CHANGED
|
@@ -49,3 +49,10 @@ export * from './Title';
|
|
|
49
49
|
export * from './Title/types';
|
|
50
50
|
export * from './Badge';
|
|
51
51
|
export * from './Badge/types';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* export trick for keep old mini app working
|
|
55
|
+
* @deprecated
|
|
56
|
+
*/
|
|
57
|
+
export * from 'react-native-modalize';
|
|
58
|
+
export * from '@gorhom/bottom-sheet';
|