@momo-kits/foundation 0.92.7 → 0.92.8-beta.10

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.
@@ -0,0 +1,238 @@
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
+ useBottomInset = true,
33
+ }: BottomSheetParams = props.route.params;
34
+ const [modalVisible, setModalVisible] = useState(false);
35
+ const animatedHeight = useRef(new Animated.Value(0)).current;
36
+ const pan = useRef(new Animated.ValueXY()).current;
37
+ const panResponder = useRef(
38
+ PanResponder.create({
39
+ onStartShouldSetPanResponder: () => draggable,
40
+ onMoveShouldSetPanResponder: (_, gestureState) => {
41
+ return draggable && dragOnContent && gestureState.dy > 0;
42
+ },
43
+ onPanResponderMove: (e, gestureState) => {
44
+ gestureState.dy > 0 &&
45
+ Animated.event([null, {dy: pan.y}], {useNativeDriver: false})(
46
+ e,
47
+ gestureState,
48
+ );
49
+ },
50
+ onPanResponderRelease: (_, gestureState) => {
51
+ if (gestureState.dy > 100) {
52
+ onDismiss();
53
+ } else {
54
+ Animated.spring(pan, {
55
+ toValue: {x: 0, y: 0},
56
+ useNativeDriver: false,
57
+ }).start();
58
+ }
59
+ },
60
+ }),
61
+ ).current;
62
+ const [estimate, setEstimate] = useState(0.9);
63
+ const timeoutMount = useRef<any>();
64
+
65
+ let backgroundColor = theme.colors.background.default;
66
+ if (surface) {
67
+ backgroundColor = theme.colors.background.surface;
68
+ }
69
+
70
+ /**
71
+ * handle open & emit dismiss event
72
+ */
73
+ useEffect(() => {
74
+ clearTimeout(timeoutMount.current);
75
+ timeoutMount.current = setTimeout(() => {
76
+ const mapSnapPoint: any = {
77
+ '30%': 0.3,
78
+ '50%': 0.5,
79
+ '70%': 0.7,
80
+ '90%': 0.9,
81
+ };
82
+ const point = mapSnapPoint[snapPoint] ?? estimate;
83
+ setModalVisible(true);
84
+ Animated.timing(animatedHeight, {
85
+ useNativeDriver: false,
86
+ toValue: heightDevice * point,
87
+ duration: 300,
88
+ }).start();
89
+ }, 100);
90
+ }, [estimate]);
91
+
92
+ /**
93
+ * emit dismiss event
94
+ */
95
+ useEffect(() => {
96
+ return () => {
97
+ props.route.params?.onDismiss?.();
98
+ };
99
+ }, []);
100
+ /**
101
+ * handler dismiss
102
+ */
103
+ const onDismiss = (force = false) => {
104
+ if (barrierDismissible && !force) {
105
+ return;
106
+ }
107
+ Animated.timing(animatedHeight, {
108
+ useNativeDriver: false,
109
+ toValue: 0,
110
+ duration: 200,
111
+ }).start(() => {
112
+ pan.setValue({x: 0, y: 0});
113
+ navigator?.pop();
114
+ });
115
+ };
116
+
117
+ return (
118
+ <>
119
+ <Modal
120
+ transparent
121
+ visible={modalVisible}
122
+ onRequestClose={() => onDismiss()}>
123
+ <KeyboardAvoidingView
124
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
125
+ keyboardVerticalOffset={-20}
126
+ style={Styles.flex}>
127
+ <TouchableOpacity
128
+ style={Styles.flex}
129
+ activeOpacity={1}
130
+ onPress={() => onDismiss()}
131
+ />
132
+ <Animated.View
133
+ {...(dragOnContent && panResponder.panHandlers)}
134
+ style={[
135
+ styles.container,
136
+ {transform: pan.getTranslateTransform()},
137
+ {
138
+ height: animatedHeight,
139
+ backgroundColor,
140
+ },
141
+ ]}>
142
+ {draggable && (
143
+ <View
144
+ {...(!dragOnContent && panResponder.panHandlers)}
145
+ style={styles.draggableContainer}>
146
+ <View style={{height: 16}}>
147
+ <View style={styles.indicator} />
148
+ </View>
149
+ <View
150
+ style={[
151
+ styles.header,
152
+ surface && {
153
+ borderBottomColor: theme.colors.border.default,
154
+ borderBottomWidth: 1,
155
+ },
156
+ ]}>
157
+ <View style={styles.iconButton} />
158
+ <Text
159
+ style={[Styles.flex, {textAlign: 'center'}]}
160
+ numberOfLines={1}
161
+ typography="header_default_bold">
162
+ {options.title ?? ''}
163
+ </Text>
164
+ <TouchableOpacity
165
+ style={styles.iconButton}
166
+ onPress={() => onDismiss(true)}>
167
+ <Icon source="navigation_close" />
168
+ </TouchableOpacity>
169
+ </View>
170
+ </View>
171
+ )}
172
+ <Screen {...props} {...props.route.params} />
173
+ {useBottomInset && (
174
+ <View style={{height: Math.min(insets.bottom, 21)}} />
175
+ )}
176
+ </Animated.View>
177
+ </KeyboardAvoidingView>
178
+ </Modal>
179
+ {snapPoint == 'content' && (
180
+ <View
181
+ style={styles.estimate}
182
+ onLayout={e => {
183
+ const current = (e.nativeEvent.layout.height + 72) / heightDevice;
184
+ if (current != estimate) {
185
+ setEstimate(current);
186
+ }
187
+ }}>
188
+ <Screen {...props} {...props.route.params} />
189
+ {useBottomInset && (
190
+ <View style={{height: Math.min(insets.bottom, 21)}} />
191
+ )}
192
+ </View>
193
+ )}
194
+ </>
195
+ );
196
+ };
197
+ export default BottomSheet;
198
+
199
+ const styles = StyleSheet.create({
200
+ container: {
201
+ width: '100%',
202
+ height: 0,
203
+ overflow: 'hidden',
204
+ borderTopLeftRadius: Radius.M,
205
+ borderTopRightRadius: Radius.M,
206
+ },
207
+ draggableContainer: {
208
+ width: '100%',
209
+ alignItems: 'center',
210
+ },
211
+ indicator: {
212
+ width: 40,
213
+ height: 4,
214
+ borderRadius: Radius.S,
215
+ backgroundColor: Colors.black_06,
216
+ position: 'absolute',
217
+ alignSelf: 'center',
218
+ top: Spacing.S,
219
+ },
220
+ header: {
221
+ height: 56,
222
+ flexDirection: 'row',
223
+ alignItems: 'center',
224
+ },
225
+ iconButton: {
226
+ width: 24,
227
+ height: 24,
228
+ alignItems: 'center',
229
+ justifyContent: 'center',
230
+ marginHorizontal: Spacing.M,
231
+ },
232
+ estimate: {
233
+ position: 'absolute',
234
+ top: -9999,
235
+ left: -9999,
236
+ opacity: 0,
237
+ },
238
+ });
@@ -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
- type Options = {
40
- state: TabNavigationState<ParamListBase>;
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 dimensions = useWindowDimensions();
110
- const isFloating = floatingButton?.icon && state.routes.length == 4;
111
- const itemWidth = isFloating
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={hasHorizontalLabels}
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
- const renderCenterComponent = useCallback(() => {
296
- if (floatingButton?.icon && state.routes.length !== 4) {
297
- console.warn(
298
- "Can't render floating button with icon when routes length is not 4",
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
- }, [floatingButton]);
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
- isFloating && state.index > 1
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, isFloating]);
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.card,
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={isTabBarHidden ? 'none' : 'auto'}
380
- onLayout={handleLayout}>
234
+ pointerEvents={'auto'}>
381
235
  <View style={styles.content}>
382
- {isFloating && floatingButton?.container}
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
- {isFloating ? renderTabItem(leftArr) : renderTabItem(routes)}
398
- {isFloating && renderCenterComponent()}
399
- {isFloating && renderTabItem(rightArr)}
251
+ {renderTabBar()}
400
252
  </View>
401
253
  </Animated.View>
402
254
  );
@@ -22,13 +22,18 @@ const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
22
22
  if (route.params?.options) {
23
23
  options = getOptions(route.params?.options, theme);
24
24
  }
25
+
25
26
  if (route.params?.nested) {
26
27
  return (
27
28
  <Stack.Navigator>
28
29
  <Stack.Screen
29
30
  name="Stack"
30
31
  component={StackScreen}
31
- initialParams={{screen: route.params?.screen, options}}
32
+ initialParams={{
33
+ screen: route.params?.screen,
34
+ options,
35
+ initialParams: route.params?.initialParams,
36
+ }}
32
37
  options={{...getStackOptions()}}
33
38
  />
34
39
  </Stack.Navigator>
@@ -40,7 +45,11 @@ const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
40
45
  <Stack.Screen
41
46
  name="Stack"
42
47
  component={StackScreen}
43
- initialParams={{screen: route.params?.screen, options}}
48
+ initialParams={{
49
+ screen: route.params?.screen,
50
+ options,
51
+ initialParams: route.params?.initialParams,
52
+ }}
44
53
  options={{...getStackOptions(), ...route.params?.options}}
45
54
  />
46
55
  </Stack.Navigator>
@@ -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 {Radius, Spacing, Styles} from '../Consts';
15
- import {Icon} from '../Icon';
16
- import {Text} from '../Text';
11
+ import {Styles} from '../Consts';
17
12
  import Navigation from './Navigation';
18
- import {BottomSheetParams, ModalParams} from './types';
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> = ({
@@ -1,6 +1,6 @@
1
1
  import {StackNavigationOptions} from '@react-navigation/stack';
2
2
  import React from 'react';
3
- import {Animated, ScrollViewProps, ViewProps} from 'react-native';
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,28 @@ export type ScreenParams = {
110
100
  options?: NavigationOptions;
111
101
  };
112
102
 
113
- export interface ModalParams extends ScreenParams {
103
+ export type ModalParams = {
114
104
  [key: string]: any;
105
+ screen: React.ComponentType;
115
106
  onDismiss?: () => void;
116
107
  barrierDismissible?: boolean;
117
- }
108
+ };
118
109
 
119
- export interface BottomSheetParams extends ScreenParams {
110
+ export type BottomSheetParams = {
120
111
  [key: string]: any;
121
- onDismiss?: () => void;
112
+ screen: React.ComponentType;
113
+ options: {
114
+ title: string;
115
+ surface?: boolean;
116
+ };
117
+ snapPoint?: '30%' | '50%' | '70%' | '90%' | 'content';
122
118
  surface?: boolean;
123
- footerComponent?: React.ReactNode;
124
- scrollViewProps?: ScrollViewProps;
125
- }
119
+ onDismiss?: (type?: string) => void;
120
+ barrierDismissible?: boolean;
121
+ draggable?: boolean;
122
+ dragOnContent?: boolean;
123
+ useBottomInset?: boolean;
124
+ };
126
125
 
127
126
  export type NavigationButtonProps = {
128
127
  icon: string;
package/Layout/Card.tsx CHANGED
@@ -144,6 +144,7 @@ const Card: React.FC<CardProps> = ({
144
144
  <GridContext.Provider value={gridContext}>
145
145
  <View
146
146
  {...props}
147
+ onLayout={undefined}
147
148
  style={[
148
149
  {backgroundColor: theme.colors.background.surface},
149
150
  style,
package/Layout/Screen.tsx CHANGED
@@ -296,7 +296,8 @@ const Screen = forwardRef(
296
296
  style={{
297
297
  marginTop: space,
298
298
  flex: item.props?.expanded ? 1 : undefined,
299
- }}>
299
+ }}
300
+ onLayout={item.props?.onLayout}>
300
301
  {item}
301
302
  </View>
302
303
  );
@@ -314,7 +315,8 @@ const Screen = forwardRef(
314
315
  style={{
315
316
  marginTop: space,
316
317
  flex: item?.props?.expanded ? 1 : undefined,
317
- }}>
318
+ }}
319
+ onLayout={item?.props?.onLayout}>
318
320
  {children}
319
321
  </View>
320
322
  );
@@ -151,6 +151,7 @@ const Section: React.FC<SectionProps> = ({
151
151
  }}>
152
152
  <View
153
153
  {...props}
154
+ onLayout={undefined}
154
155
  style={[
155
156
  style,
156
157
  styles.protectedStyle,
package/Loader/index.tsx CHANGED
@@ -1,4 +1,6 @@
1
1
  import Loader from './Loader';
2
2
  import ProgressBar from './ProgressBar';
3
+ import {LoaderProps, ProgressBarProps} from './types';
3
4
 
4
5
  export {Loader, ProgressBar};
6
+ export type {LoaderProps, ProgressBarProps};
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
- setContentWidth(e.nativeEvent.layout.width);
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
- {maxWidth: contentWidth - badgeWidth},
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
- setBadgeWidth(e.nativeEvent.layout.width);
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.7",
3
+ "version": "0.92.8-beta.10",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -18,7 +18,7 @@
18
18
  "@react-navigation/core": "5.16.1",
19
19
  "@react-navigation/native": "5.9.8",
20
20
  "@react-navigation/routers": "5.7.4",
21
- "lottie-react-native": "3.3.2",
21
+ "lottie-react-native": "git+https://gitlab.mservice.com.vn/momo-platform/momo-lottie-react-native.git#test_lottie_ios",
22
22
  "@react-navigation/stack": "https://oauth2:eX-jVhzQdLc343AjD3Sc@gitlab.mservice.com.vn/momo-platform/react-navigation-stack.git"
23
23
  },
24
24
  "peerDependencies": {