@momo-kits/foundation 0.109.1-optimize.11 → 0.109.1-optimize.14

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.
@@ -135,13 +135,19 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
135
135
  borderBottomWidth: 1,
136
136
  },
137
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>
138
+ {options.header ? (
139
+ <View style={Styles.flex}>{options.header}</View>
140
+ ) : (
141
+ <>
142
+ <View style={styles.iconButton} />
143
+ <Text
144
+ style={[Styles.flex, {textAlign: 'center'}]}
145
+ numberOfLines={1}
146
+ typography="header_default_bold">
147
+ {options.title ?? ''}
148
+ </Text>
149
+ </>
150
+ )}
145
151
  <TouchableOpacity
146
152
  style={styles.iconButton}
147
153
  onPress={() => onDismiss(true)}>
@@ -101,7 +101,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
101
101
  <BottomTabBar
102
102
  {...props}
103
103
  floatingButton={floatingButton}
104
- inactiveTintColor={theme.colors.text.secondary}
104
+ inactiveTintColor={theme.colors.text.hint}
105
105
  />
106
106
  )}
107
107
  tabBarOptions={{
@@ -0,0 +1,147 @@
1
+ import React, {useContext} from 'react';
2
+ import {StyleSheet, TouchableOpacity, View} from 'react-native';
3
+ import {ApplicationContext, MiniAppContext} from '../index';
4
+ import {Colors, Radius, Spacing, Styles} from '../../Consts';
5
+ import {Text} from '../../Text';
6
+ import {Icon} from '../../Icon';
7
+
8
+ const ServiceItem: React.FC<any> = ({service}) => {
9
+ const {theme, translate} = useContext(ApplicationContext);
10
+ const {title, description, icon, onPress} = service;
11
+ const serviceTitle = translate?.(title);
12
+ const serviceDescription = translate?.(description);
13
+ return (
14
+ <TouchableOpacity onPress={onPress} style={Styles.row}>
15
+ <View style={styles.iconWrapper}>
16
+ <Icon color={theme.colors.text.hint} source={icon} size={28} />
17
+ </View>
18
+ <View>
19
+ <View style={Styles.row}>
20
+ <Text typography={'action_xs_bold'}>{serviceTitle}</Text>
21
+ <Icon source={'arrow_chevron_right_small'} size={16} />
22
+ </View>
23
+ <Text
24
+ typography={'description_xs_regular'}
25
+ color={theme.colors.text.hint}>
26
+ {serviceDescription}
27
+ </Text>
28
+ </View>
29
+ </TouchableOpacity>
30
+ );
31
+ };
32
+
33
+ const BottomSheetHelpCenter: React.FC<any> = ({onRequestClose}) => {
34
+ const {theme, navigator} = useContext(ApplicationContext);
35
+ const context = useContext<any>(MiniAppContext);
36
+
37
+ const onPressFaq = () => {
38
+ onRequestClose?.(() => {
39
+ navigator?.maxApi?.startFeatureCode?.(
40
+ 'helpcenter_problemlevel1',
41
+ context?.toolkitConfig?.faq
42
+ );
43
+ });
44
+ };
45
+
46
+ const onPressChatbot = () => {
47
+ onRequestClose?.(() => {
48
+ navigator?.maxApi?.getDataObserver('CURRENT_SCREEN', (data: any) => {
49
+ let screenName = data?.screenName;
50
+ navigator?.maxApi?.startFeatureCode?.('chatbot', {
51
+ botId: 'botGptCs',
52
+ forwardParams: {
53
+ forService: 'navigation',
54
+ mini_app_id: context?.appId,
55
+ feature_code: context?.code,
56
+ screen_name: screenName,
57
+ },
58
+ });
59
+ });
60
+ });
61
+ };
62
+
63
+ const onPressFeedback = () => {
64
+ onRequestClose?.(() => {
65
+ navigator?.maxApi?.startFeatureCode?.('feedback', {
66
+ forService: 'navigation',
67
+ loggedStatus: true,
68
+ application: {
69
+ appId: context?.appId,
70
+ appCode: context?.code,
71
+ appName: context?.name?.['en'],
72
+ buildNumber: context?.buildNumber,
73
+ },
74
+ });
75
+ });
76
+ };
77
+
78
+ const services = [
79
+ {
80
+ title: {vi: 'Câu hỏi thường gặp', en: 'Câu hỏi thường gặp'},
81
+ description: {
82
+ vi: 'Giải đáp các thắc mắc mọi người thường gặp',
83
+ en: 'Giải đáp các thắc mắc mọi người thường gặp',
84
+ },
85
+ icon: 'notifications_circle_question',
86
+ onPress: onPressFaq,
87
+ },
88
+ {
89
+ title: {vi: 'Hỗ trợ trực tuyến', en: 'Hỗ trợ trực tuyến'},
90
+ description: {
91
+ vi: 'Trả lời mọi câu hỏi của bạn 24/7',
92
+ en: 'Trả lời mọi câu hỏi của bạn 24/7',
93
+ },
94
+ icon: 'ic_support',
95
+ onPress: onPressChatbot,
96
+ },
97
+ {
98
+ title: {vi: 'Chia sẻ góp ý', en: 'Chia sẻ góp ý'},
99
+ description: {
100
+ vi: 'Đề xuất cải thiện hoặc báo lỗi sản phẩm/dịch vụ',
101
+ en: 'Đề xuất cải thiện hoặc báo lỗi sản phẩm/dịch vụ',
102
+ },
103
+ icon: 'file_mail',
104
+ onPress: onPressFeedback,
105
+ },
106
+ ];
107
+
108
+ return (
109
+ <View
110
+ style={[
111
+ styles.container,
112
+ {
113
+ backgroundColor: theme.colors.background.surface,
114
+ },
115
+ ]}>
116
+ {services.map((item, index) => {
117
+ return (
118
+ <>
119
+ <ServiceItem service={item} />
120
+ {index !== services.length - 1 && <View style={styles.divider} />}
121
+ </>
122
+ );
123
+ })}
124
+ </View>
125
+ );
126
+ };
127
+
128
+ const styles = StyleSheet.create({
129
+ container: {height: 300, width: '100%', padding: Spacing.M},
130
+ divider: {
131
+ marginVertical: Spacing.M,
132
+ backgroundColor: Colors.black_02,
133
+ height: 1,
134
+ width: '100%',
135
+ },
136
+ iconWrapper: {
137
+ width: 36,
138
+ height: 36,
139
+ backgroundColor: Colors.black_02,
140
+ borderRadius: Radius.M,
141
+ marginRight: Spacing.S,
142
+ alignItems: 'center',
143
+ justifyContent: 'center',
144
+ },
145
+ });
146
+
147
+ export {BottomSheetHelpCenter};
@@ -3,10 +3,11 @@ import LinearGradient from 'react-native-linear-gradient';
3
3
  import {ApplicationContext, MiniAppContext} from '../index';
4
4
  import {Animated, Dimensions, Platform, StyleSheet, View} from 'react-native';
5
5
  import {HeaderType} from '../../Layout/types';
6
- import {InputRef, InputSearch, InputSearchProps} from '../../Input';
6
+ import {InputRef, InputSearch} from '../../Input';
7
7
  import Navigation from '../Navigation';
8
8
  import {Colors, Radius, Spacing} from '../../Consts';
9
9
  import {Image} from '../../Image';
10
+ import {SearchHeaderProps} from '../types';
10
11
 
11
12
  const SCREEN_PADDING = 12;
12
13
  const BACK_WIDTH = 28;
@@ -23,7 +24,7 @@ const HeaderExtendHeader: React.FC<{
23
24
  animatedValue: Animated.Value;
24
25
  heightHeader: number;
25
26
  headerRightWidth: number;
26
- inputSearchProps?: InputSearchProps;
27
+ inputSearchProps?: SearchHeaderProps;
27
28
  inputSearchRef?: Ref<InputRef>;
28
29
  navigation?: Navigation;
29
30
  useShadowHeader?: boolean;
@@ -44,6 +45,7 @@ const HeaderExtendHeader: React.FC<{
44
45
  const animated = useRef(new Animated.Value(0));
45
46
  const gradientColor = customGradientColor ?? theme.colors.gradient;
46
47
  const headerBackground = customBackground ?? theme.assets?.headerBackground;
48
+ const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
47
49
 
48
50
  const opacityBackground = animatedValue?.interpolate({
49
51
  inputRange: [0, 52],
@@ -73,7 +75,7 @@ const HeaderExtendHeader: React.FC<{
73
75
 
74
76
  const translateX = animated.current.interpolate({
75
77
  inputRange: [0, 100],
76
- outputRange: [SCREEN_PADDING, BACK_WIDTH + 20],
78
+ outputRange: [SCREEN_PADDING, leftPosition],
77
79
  extrapolate: 'clamp',
78
80
  });
79
81
 
@@ -137,7 +139,7 @@ const HeaderExtendHeader: React.FC<{
137
139
  inputRange: [0, 100],
138
140
  outputRange: [
139
141
  screenWidth - SCREEN_PADDING * 2,
140
- screenWidth - (BACK_WIDTH + 32) - headerRightWidth,
142
+ screenWidth - leftPosition - 12 - headerRightWidth,
141
143
  ],
142
144
  extrapolate: 'clamp',
143
145
  }),
@@ -242,14 +244,8 @@ const styles = StyleSheet.create({
242
244
  shadowRadius: 10,
243
245
  },
244
246
  android: {
245
- shadowColor: Colors.black_17,
246
- shadowOffset: {
247
- width: 3,
248
- height: 3,
249
- },
250
- shadowOpacity: 0.12,
251
- shadowRadius: 10,
252
- elevation: 10,
247
+ borderBottomWidth: 1,
248
+ borderColor: Colors.black_04,
253
249
  },
254
250
  }),
255
251
  },
@@ -41,6 +41,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
41
41
  params,
42
42
  });
43
43
  navigator?.maxApi?.trackEvent?.('service_icon_clicked', params);
44
+
44
45
  if (canGoBack) {
45
46
  navigator?.ref?.current?.goBack?.();
46
47
  } else if (navigator?.maxApi) {
@@ -9,17 +9,17 @@ import {
9
9
  import {ApplicationContext, MiniAppContext, NavigationButton} from '../index';
10
10
  import {Colors, Spacing, Styles} from '../../Consts';
11
11
  import {PopupNotify} from '../../Popup';
12
- import {Tool} from '../types';
12
+ import {Tool, ToolGroup} from '../types';
13
13
  import {Icon} from '../../Icon';
14
14
  import {scaleSize, Text} from '../../Text';
15
+ import {BottomSheetHelpCenter} from './BottomSheetHelpCenter';
16
+ import {Image} from '../../Image';
15
17
 
16
18
  const WHITE_LIST = [
17
19
  'vn.momo.appx',
18
20
  'vn.momo.transactionhistory',
19
21
  'vn.momo.promotionhub',
20
22
  ];
21
- const HELP_CENTER_CODE = 'helpcenter';
22
- const HELP_CENTER_ICON = 'help_center';
23
23
 
24
24
  /**
25
25
  * main component for header right
@@ -29,6 +29,7 @@ const HeaderRight: React.FC<any> = props => {
29
29
  const {useOnBoarding = false, buttonOnBoarding, onPress} = headerRight;
30
30
  const context = useContext<any>(MiniAppContext);
31
31
  const {translate} = useContext(ApplicationContext);
32
+
32
33
  if (
33
34
  WHITE_LIST.includes(context?.appId) &&
34
35
  typeof props.headerRight === 'function'
@@ -36,10 +37,6 @@ const HeaderRight: React.FC<any> = props => {
36
37
  return props.headerRight(props);
37
38
  }
38
39
 
39
- if (context?.toolkitConfig?.hidden === true) {
40
- return <View />;
41
- }
42
-
43
40
  if (useOnBoarding && onPress) {
44
41
  return (
45
42
  <TouchableOpacity onPress={onPress} style={styles.onBoarding}>
@@ -68,9 +65,8 @@ const HeaderToolkitAction: React.FC<any> = ({
68
65
  useMore = false,
69
66
  tools = [],
70
67
  }) => {
71
- const {navigator} = useContext(ApplicationContext);
68
+ const {navigator, translate} = useContext(ApplicationContext);
72
69
  const context = useContext<any>(MiniAppContext);
73
- const showIconMore = tools && tools.length > 1;
74
70
 
75
71
  const [isFavorite, setIsFavorite] = useState<boolean>(false);
76
72
  const [isLoading, setIsLoading] = useState(false);
@@ -89,6 +85,9 @@ const HeaderToolkitAction: React.FC<any> = ({
89
85
  };
90
86
  }
91
87
 
88
+ /**
89
+ * check app is favorite
90
+ */
92
91
  const checkAppIsFavorite = () => {
93
92
  navigator?.maxApi?.dispatchFunction?.(
94
93
  'isFavoriteApp',
@@ -101,6 +100,9 @@ const HeaderToolkitAction: React.FC<any> = ({
101
100
  );
102
101
  };
103
102
 
103
+ /**
104
+ * close navigation container
105
+ */
104
106
  const onClose = () => {
105
107
  if (preventClose) {
106
108
  navigator?.showModal({
@@ -126,6 +128,9 @@ const HeaderToolkitAction: React.FC<any> = ({
126
128
  }
127
129
  };
128
130
 
131
+ /**
132
+ * on press shortcut
133
+ */
129
134
  const onPressShortcut = () => {
130
135
  setIsLoading(true);
131
136
  navigator?.maxApi?.dispatchFunction?.(
@@ -139,41 +144,83 @@ const HeaderToolkitAction: React.FC<any> = ({
139
144
  if (success) {
140
145
  setIsFavorite(!isFavorite);
141
146
  }
142
- setIsLoading(false);
143
147
  }
144
148
  );
149
+ setTimeout(() => {
150
+ setIsLoading(false);
151
+ }, 1000);
145
152
  };
146
153
 
154
+ /**
155
+ * on press help center
156
+ */
147
157
  const onPressHelpCenter = () => {
148
- navigator?.maxApi?.dispatchFunction?.(
149
- 'startFeatureCode',
150
- HELP_CENTER_CODE,
151
- () => {}
152
- );
158
+ const appName = translate?.(context?.name);
159
+ const appDescription = translate?.(context?.description);
160
+ navigator?.showBottomSheet({
161
+ options: {
162
+ header: (
163
+ <View style={[Styles.row, {paddingLeft: Spacing.M}]}>
164
+ <Image
165
+ source={{uri: context?.icon}}
166
+ style={{width: 40, height: 40, marginRight: Spacing.S}}
167
+ />
168
+ <View>
169
+ <Text typography={'label_default_medium'}>{appName}</Text>
170
+ <Text
171
+ typography={'description_default_regular'}
172
+ color={Colors.black_12}>
173
+ {appDescription}
174
+ </Text>
175
+ </View>
176
+ </View>
177
+ ),
178
+ },
179
+ surface: true,
180
+ screen: BottomSheetHelpCenter,
181
+ });
153
182
  };
154
183
 
184
+ /**
185
+ * on press icon more
186
+ */
155
187
  const onPressIconMore = () => {
156
188
  navigator?.maxApi?.dispatchFunction?.('showTools', tools, (key: string) => {
157
- const pressedTools = tools.find((tool: Tool) => tool.key === key);
158
- if (pressedTools) {
159
- pressedTools?.onPress();
189
+ for (const group of tools) {
190
+ const pressedTool = group?.items?.find?.(
191
+ (tool: Tool) => tool?.key === key
192
+ );
193
+ if (pressedTool) {
194
+ pressedTool?.onPress();
195
+ break;
196
+ }
160
197
  }
161
198
  });
162
199
  };
163
200
 
164
201
  let iconShortcut = isFavorite ? 'pin_star_checked' : 'pin_star';
165
202
  let shortcutOnPress = onPressShortcut;
166
- let isHaveOneTool = tools && tools.length === 1;
203
+ const totalTools = tools.reduce(
204
+ (count: number, group: ToolGroup) => count + group?.items?.length,
205
+ 0
206
+ );
207
+ const showIconMore = tools && totalTools > 1;
208
+ const isHaveOneTool = tools && totalTools === 1;
167
209
 
168
210
  if (showIconMore || (useMore && isHaveOneTool)) {
169
211
  iconShortcut = 'navigation_more_icon';
170
212
  shortcutOnPress = onPressIconMore;
171
213
  } else if (isHaveOneTool) {
172
- iconShortcut = tools[0]?.icon;
173
- shortcutOnPress = tools[0]?.onPress;
214
+ const singleTool = tools.find(
215
+ (group: ToolGroup) => group?.items?.length === 1
216
+ )?.items[0];
217
+ iconShortcut = singleTool?.icon;
218
+ shortcutOnPress = singleTool?.onPress;
174
219
  }
175
220
 
176
- const showBadge = tools.some((tool: Tool) => tool.showBadge);
221
+ const showBadge = tools.some((group: ToolGroup) =>
222
+ group?.items?.some?.(tool => tool?.showBadge)
223
+ );
177
224
 
178
225
  return (
179
226
  <View style={Styles.row}>
@@ -187,14 +234,18 @@ const HeaderToolkitAction: React.FC<any> = ({
187
234
  />
188
235
  )}
189
236
  <View style={[styles.toolkitContainer, buttonStyle]}>
190
- <TouchableOpacity
191
- accessibilityLabel={'btn_navigation_help_center'}
192
- style={styles.toolkitButton}
193
- onPress={onPressHelpCenter}
194
- hitSlop={{top: 7, bottom: 7, left: 7, right: 0}}>
195
- <Icon color={tintColor} source={HELP_CENTER_ICON} size={20} />
196
- </TouchableOpacity>
197
- <View style={[styles.divider, {backgroundColor: tintColor}]} />
237
+ {context.appId !== 'vn.momo.helpcenter' && (
238
+ <>
239
+ <TouchableOpacity
240
+ accessibilityLabel={'btn_navigation_help_center'}
241
+ style={styles.toolkitButton}
242
+ onPress={onPressHelpCenter}
243
+ hitSlop={{top: 7, bottom: 7, left: 7, right: 0}}>
244
+ <Icon color={tintColor} source={'help_center'} size={20} />
245
+ </TouchableOpacity>
246
+ <View style={[styles.divider, {backgroundColor: tintColor}]} />
247
+ </>
248
+ )}
198
249
  <TouchableOpacity
199
250
  accessibilityLabel={'btn_navigation_close'}
200
251
  onPress={onClose}
@@ -27,7 +27,7 @@ const HeaderTitle: React.FC<any> = props => {
27
27
  );
28
28
 
29
29
  return (
30
- <View pointerEvents={'none'}>
30
+ <View pointerEvents={'none'} style={styles.headerTitleContainer}>
31
31
  <Animated.Text
32
32
  {...props}
33
33
  accessibilityLabel={'title_navigation_header'}
@@ -35,6 +35,7 @@ const HeaderTitle: React.FC<any> = props => {
35
35
  styles.title,
36
36
  {fontFamily: `${theme.font}-Bold`, opacity, color: props.tintColor},
37
37
  ]}
38
+ numberOfLines={1}
38
39
  />
39
40
  <Verified />
40
41
  </View>
@@ -95,7 +96,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
95
96
  style={styles.headerTitleContainer}
96
97
  onPress={onPress}
97
98
  disabled={onPress === undefined}>
98
- <View style={Styles.row}>
99
+ <View style={[Styles.row, Styles.flex]}>
99
100
  <View>
100
101
  <View
101
102
  style={[
@@ -108,7 +109,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
108
109
  <View style={[styles.dotAvatar, {backgroundColor: dotColor}]} />
109
110
  )}
110
111
  </View>
111
- <View style={[Styles.flex, {paddingHorizontal: 6}]}>
112
+ <View style={[Styles.flex, {marginLeft: 6}]}>
112
113
  <View style={Styles.row}>
113
114
  <Text
114
115
  typography="action_xs_bold"
@@ -127,10 +128,9 @@ const TitleUser: React.FC<TitleUserProps> = ({
127
128
  </View>
128
129
  {!!subTitle && (
129
130
  <Text
130
- typography="description_default_regular"
131
+ typography="description_xs_regular"
131
132
  color={tintColor}
132
- numberOfLines={1}
133
- style={{marginTop: -2}}>
133
+ numberOfLines={1}>
134
134
  {subTitle}
135
135
  </Text>
136
136
  )}
@@ -157,7 +157,7 @@ const TitleLocation: React.FC<TitleLocationProps> = ({
157
157
  onPress={onPress}>
158
158
  {!!description && (
159
159
  <Text
160
- typography="description_default_regular"
160
+ typography="description_xs_regular"
161
161
  color={tintColor}
162
162
  numberOfLines={1}>
163
163
  {description}
@@ -192,7 +192,9 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
192
192
  onPress,
193
193
  }) => {
194
194
  return (
195
- <TouchableOpacity style={styles.headerTitleContainer} onPress={onPress}>
195
+ <TouchableOpacity
196
+ style={[styles.headerTitleContainer, {overflow: 'hidden'}]}
197
+ onPress={onPress}>
196
198
  <View style={styles.rowJourney}>
197
199
  <View style={Styles.row}>
198
200
  <Text typography="action_xs_bold" color={tintColor} numberOfLines={1}>
@@ -223,7 +225,7 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
223
225
  {!!description && (
224
226
  <View style={styles.rowJourney}>
225
227
  <Text
226
- typography="description_default_regular"
228
+ typography="description_xs_regular"
227
229
  color={tintColor}
228
230
  numberOfLines={1}>
229
231
  {description}
@@ -259,9 +261,8 @@ const styles = StyleSheet.create({
259
261
  alignSelf: 'center',
260
262
  },
261
263
  headerTitleContainer: {
262
- alignItems: 'center',
263
264
  justifyContent: 'center',
264
- width: Dimensions.get('window').width - 158,
265
+ maxWidth: Dimensions.get('window').width - 190,
265
266
  },
266
267
  circle: {
267
268
  width: 32,
@@ -287,7 +288,7 @@ const styles = StyleSheet.create({
287
288
  borderWidth: 2,
288
289
  borderColor: Colors.black_01,
289
290
  },
290
- verifiedIcon: {marginHorizontal: Spacing.XS, width: 12, height: 12},
291
+ verifiedIcon: {width: 12, height: 12, marginLeft: Spacing.XS},
291
292
  });
292
293
 
293
294
  export {HeaderTitle, TitleCustom};
@@ -32,12 +32,12 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
32
32
  const isReady = useRef(false);
33
33
  const navigator = useRef(new Navigator(navigationRef, isReady));
34
34
  const [showGrid, setShowGrid] = useState(false);
35
- const config = useRef<any>();
36
35
 
37
36
  /**
38
37
  * inject data for navigator
39
38
  */
40
39
  navigator.current.maxApi = maxApi;
40
+
41
41
  /**
42
42
  * handle mini language & listen change
43
43
  * engine only shake to enable grid view
@@ -55,8 +55,11 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
55
55
  };
56
56
  }, []);
57
57
 
58
- const translate = (key: string) => {
59
- return localize?.translate(key);
58
+ const translate = (data?: string | {vi: string; en: string}) => {
59
+ if (data && typeof data !== 'string') {
60
+ return localize?.translateData(data);
61
+ }
62
+ return localize?.translate(data as string);
60
63
  };
61
64
 
62
65
  return (
@@ -64,7 +67,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
64
67
  <ApplicationContext.Provider
65
68
  value={{
66
69
  navigator: navigator.current,
67
- config: config.current,
68
70
  theme,
69
71
  showGrid,
70
72
  translate,
@@ -1,15 +1,9 @@
1
1
  import {CommonActions, StackActions} from '@react-navigation/native';
2
- import {
3
- BottomSheetParams,
4
- HeaderToolkitProps,
5
- ModalParams,
6
- ScreenParams,
7
- } from './types';
2
+ import {BottomSheetParams, ModalParams, ScreenParams} from './types';
8
3
 
9
4
  class Navigator {
10
5
  ref?: any;
11
6
  isReady?: any;
12
- toolkitConfig?: HeaderToolkitProps;
13
7
  maxApi?: any;
14
8
  dismissData?: any;
15
9
  toolkitCallback?: (key: string) => void;
@@ -111,16 +105,14 @@ class Navigator {
111
105
  };
112
106
 
113
107
  /**
114
- * navigate
108
+ * navigate route name bottom tab only
115
109
  * @param name
116
- * @param params
117
110
  */
118
- navigate = (name: string, params: any) => {
111
+ navigate = (name: string) => {
119
112
  if (this.isReady.current) {
120
113
  this.ref.current?.dispatch?.(
121
114
  CommonActions.navigate({
122
115
  name,
123
- params,
124
116
  })
125
117
  );
126
118
  }
@@ -1,6 +1,6 @@
1
1
  import React, {useContext, useEffect, useLayoutEffect, useRef} from 'react';
2
2
  import {useHeaderHeight} from '@react-navigation/stack';
3
- import {InteractionManager} from 'react-native';
3
+ import {Alert, InteractionManager} from 'react-native';
4
4
  import {ScreenParams} from './types';
5
5
  import Navigation from './Navigation';
6
6
  import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
@@ -27,6 +27,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
27
27
  timeInteraction: 0,
28
28
  });
29
29
  const context = useContext<any>(MiniAppContext);
30
+ const lastElement = useRef<any>(null);
30
31
  const {screen: Component, options, initialParams} = props.route.params;
31
32
  const navigation = new Navigation(props.navigation, context);
32
33
  const heightHeader = useHeaderHeight();
@@ -76,11 +77,16 @@ const StackScreen: React.FC<ScreenParams> = props => {
76
77
  navigator?.maxApi?.setObserver('CURRENT_SCREEN', {screenName});
77
78
  });
78
79
  });
79
- navigator?.maxApi?.startTraceScreenLoad?.(screenName, (data: any) => {
80
- tracking.current.traceIdLoad = data?.traceId;
81
- });
80
+ navigator?.maxApi?.startTraceScreenLoad?.(
81
+ screenName,
82
+ context,
83
+ (data: any) => {
84
+ tracking.current.traceIdLoad = data?.traceId;
85
+ }
86
+ );
82
87
  navigator?.maxApi?.startTraceScreenInteraction?.(
83
88
  screenName,
89
+ context,
84
90
  (data: any) => {
85
91
  tracking.current.traceIdInteraction = data?.traceId;
86
92
  }
@@ -131,6 +137,15 @@ const StackScreen: React.FC<ScreenParams> = props => {
131
137
  message: `${screenName} screen_load_time ${timeLoad}`,
132
138
  type: 'ERROR',
133
139
  });
140
+ if (
141
+ context.debugLastElement &&
142
+ lastElement.current?.children?.current?.length > 0
143
+ ) {
144
+ Alert.alert(
145
+ `${screenName}- load ${timeLoad}ms`,
146
+ JSON.stringify(lastElement.current?.children?.current)
147
+ );
148
+ }
134
149
  }
135
150
  };
136
151
 
@@ -199,7 +214,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
199
214
  <ScreenContext.Provider
200
215
  value={{
201
216
  screenName,
202
- onElementLoad: () => {
217
+ onElementLoad: (data: any) => {
203
218
  clearTimeout(tracking.current.timeoutLoad);
204
219
  tracking.current.endTime = Date.now();
205
220
  tracking.current.timeoutInteraction?.cancel?.();
@@ -208,11 +223,19 @@ const StackScreen: React.FC<ScreenParams> = props => {
208
223
  tracking.current.timeInteraction =
209
224
  Date.now() - tracking.current.startTime;
210
225
  });
226
+
227
+ if (data?.componentName) {
228
+ lastElement.current = data;
229
+ }
230
+
211
231
  tracking.current.timeoutLoad = setTimeout(() => {
212
232
  const time = tracking.current.endTime - tracking.current.startTime;
213
233
  if (tracking.current.timeLoad === 0) {
214
234
  tracking.current.timeLoad = time;
215
235
  }
236
+
237
+ onScreenLoad();
238
+ onScreenInteraction();
216
239
  }, 2000);
217
240
  },
218
241
  }}>
@@ -71,7 +71,7 @@ export type Context = {
71
71
  theme: Theme;
72
72
  navigator?: Navigator;
73
73
  showGrid?: boolean;
74
- translate?: (key: string) => string;
74
+ translate?: (data: string | {vi: string; en: string}) => string;
75
75
  };
76
76
 
77
77
  export type LocalizationObject = {
@@ -83,9 +83,14 @@ export type LocalizationObject = {
83
83
  };
84
84
  };
85
85
 
86
+ export type ToolGroup = {
87
+ title: {vi: string; en: string};
88
+ items: Tool[];
89
+ };
90
+
86
91
  export type Tool = {
87
92
  icon: string;
88
- name: string | {vi: string; en: string};
93
+ name: {vi: string; en: string};
89
94
  key: string;
90
95
  showBadge?: boolean;
91
96
  onPress: () => void;
@@ -123,7 +128,8 @@ export type BottomSheetParams = {
123
128
  [key: string]: any;
124
129
  screen: React.ComponentType;
125
130
  options: {
126
- title: string;
131
+ header?: React.ReactNode;
132
+ title?: string;
127
133
  };
128
134
  useNativeModal?: boolean;
129
135
  surface?: boolean;
@@ -164,7 +170,7 @@ export type OnBoarding = {
164
170
 
165
171
  export type HeaderRightToolkit = {
166
172
  useShortcut?: boolean;
167
- tools?: Tool[];
173
+ tools?: ToolGroup[];
168
174
  preventClose?: PopupNotifyProps;
169
175
  useMore?: boolean;
170
176
  };
@@ -264,4 +270,5 @@ export interface SearchHeaderProps extends InputSearchProps {
264
270
  ref?: React.RefObject<InputRef>;
265
271
  animatedValue?: Animated.Value;
266
272
  headerRightWidth?: 0 | 74 | 110 | number;
273
+ leftPosition?: 12 | 48 | number;
267
274
  }
@@ -122,7 +122,11 @@ const getOptions = (
122
122
  if (params.headerRight) {
123
123
  options = {
124
124
  ...options,
125
- ...exportHeaderRight(params),
125
+ ...{
126
+ headerRight: (props: any) => {
127
+ return <HeaderRight {...props} headerRight={params.headerRight} />;
128
+ },
129
+ },
126
130
  };
127
131
  }
128
132
 
@@ -132,14 +136,6 @@ const getOptions = (
132
136
  };
133
137
  };
134
138
 
135
- const exportHeaderRight = (params: NavigationOptions) => {
136
- return {
137
- headerRight: (props: any) => {
138
- return <HeaderRight {...props} headerRight={params.headerRight} />;
139
- },
140
- };
141
- };
142
-
143
139
  const exportHeaderTitle = (
144
140
  params: NavigationOptions,
145
141
  animatedValue?: Animated.Value
@@ -158,7 +154,15 @@ const exportHeaderTitle = (
158
154
  },
159
155
  };
160
156
  } else if (typeof params.headerTitle === 'string') {
161
- delete params.headerTitle;
157
+ return {
158
+ headerTitle: (props: any) => (
159
+ <HeaderTitle
160
+ {...props}
161
+ children={params.headerTitle}
162
+ animatedValue={animatedValue}
163
+ />
164
+ ),
165
+ };
162
166
  }
163
167
  return {};
164
168
  };
@@ -177,6 +181,5 @@ export {
177
181
  getModalOptions,
178
182
  getOptions,
179
183
  exportHeaderTitle,
180
- exportHeaderRight,
181
184
  formatNameLength,
182
185
  };
package/Assets/icon.json CHANGED
@@ -4045,5 +4045,8 @@
4045
4045
  },
4046
4046
  "navigation_more_icon": {
4047
4047
  "uri": "data:image/png;base64,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"
4048
+ },
4049
+ "ic_support": {
4050
+ "uri": "https://static.momocdn.net/app/img/kits/ic_support.png"
4048
4051
  }
4049
4052
  }
package/Button/index.tsx CHANGED
@@ -88,6 +88,7 @@ const Button: FC<ButtonProps> = ({
88
88
  ...rest
89
89
  }) => {
90
90
  const {theme, config} = useContext(ApplicationContext);
91
+ const component = useContext<any>(ComponentContext);
91
92
  const {gradient, color} = config?.navigationBar?.buttonColors ?? {};
92
93
  let gradientPros;
93
94
  let state = 'enabled';
@@ -292,10 +293,10 @@ const Button: FC<ButtonProps> = ({
292
293
  return (
293
294
  <ComponentContext.Provider
294
295
  value={{
296
+ ...component,
295
297
  componentName: 'Button',
296
298
  params,
297
299
  state: state,
298
- componentId: accessibilityLabel,
299
300
  componentValue: title,
300
301
  action: 'click',
301
302
  }}>
@@ -5,7 +5,6 @@ import styles from './styles';
5
5
  import {ApplicationContext, ComponentContext} from '../Application';
6
6
  import {Text} from '../Text';
7
7
  import {Icon} from '../Icon';
8
- import {formatNameLength} from '../Application/utils';
9
8
 
10
9
  const CheckBox: FC<CheckBoxProps> = ({
11
10
  value,
@@ -43,9 +42,7 @@ const CheckBox: FC<CheckBoxProps> = ({
43
42
  componentName: 'CheckBox',
44
43
  params,
45
44
  state: disabled ? 'disabled' : 'filled',
46
- componentId: accessibilityLabel,
47
45
  action: 'click',
48
- componentLabel: formatNameLength(label || ''),
49
46
  componentValue: indeterminate ? 'undetermined' : value,
50
47
  }}>
51
48
  <TouchableOpacity
package/Consts/theme.ts CHANGED
@@ -113,8 +113,8 @@ const defaultContext: Context = {
113
113
  theme: defaultTheme,
114
114
  navigator: undefined,
115
115
  showGrid: false,
116
- translate: (key: string) => {
117
- return key;
116
+ translate: () => {
117
+ return '';
118
118
  },
119
119
  };
120
120
 
@@ -118,7 +118,6 @@ const IconButton: React.FC<IconButtonProps> = ({
118
118
  componentName: 'IconButton',
119
119
  params,
120
120
  state: type === 'disabled' ? 'disabled' : 'enabled',
121
- componentId: accessibilityLabel,
122
121
  componentValue: icon,
123
122
  action: 'click',
124
123
  }}>
package/Input/Input.tsx CHANGED
@@ -65,6 +65,7 @@ const Input = forwardRef(
65
65
  ref
66
66
  ) => {
67
67
  const {theme} = useContext(ApplicationContext);
68
+ const component = useContext<any>(ComponentContext);
68
69
  const [focused, setFocused] = useState(false);
69
70
  const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
70
71
  const [secureTextInput, setSecureTextInput] = useState(secureTextEntry);
@@ -260,11 +261,10 @@ const Input = forwardRef(
260
261
  return (
261
262
  <ComponentContext.Provider
262
263
  value={{
264
+ ...component,
263
265
  componentName: 'Input',
264
266
  params,
265
- componentLabel: floatingValue,
266
267
  state: inputState,
267
- componentId: accessibilityLabel,
268
268
  }}>
269
269
  <View style={[style, styles.wrapper]}>
270
270
  {renderInputView()}
@@ -30,6 +30,7 @@ const InputDropDown = ({
30
30
  multiline,
31
31
  }: InputDropDownProps) => {
32
32
  const {theme} = useContext(ApplicationContext);
33
+ const component = useContext<any>(ComponentContext);
33
34
 
34
35
  /**
35
36
  * Render the input view
@@ -105,11 +106,10 @@ const InputDropDown = ({
105
106
  return (
106
107
  <ComponentContext.Provider
107
108
  value={{
109
+ ...component,
108
110
  componentName: 'InputDropDown',
109
111
  params,
110
112
  state: 'enabled',
111
- componentLabel: floatingValue,
112
- componentId: accessibilityLabel,
113
113
  }}>
114
114
  <TouchableOpacity
115
115
  activeOpacity={0.6}
@@ -59,6 +59,8 @@ const InputMoney = forwardRef(
59
59
  ref
60
60
  ) => {
61
61
  const {theme} = useContext(ApplicationContext);
62
+ const component = useContext<any>(ComponentContext);
63
+
62
64
  const [focused, setFocused] = useState(false);
63
65
  const inputRef = useRef<TextInput>(null);
64
66
 
@@ -202,11 +204,10 @@ const InputMoney = forwardRef(
202
204
  return (
203
205
  <ComponentContext.Provider
204
206
  value={{
207
+ ...component,
205
208
  componentName: 'InputMoney',
206
209
  params,
207
- componentLabel: floatingValue,
208
210
  state: inputState,
209
- componentId: accessibilityLabel,
210
211
  }}>
211
212
  <View style={[style, styles.wrapper]}>
212
213
  {renderInputView()}
@@ -88,6 +88,7 @@ const InputOTP = forwardRef(
88
88
  const [focused, setFocused] = useState(false);
89
89
  const inputRef = useRef<TextInput | null>(null);
90
90
  const {theme} = useContext(ApplicationContext);
91
+ const component = useContext<any>(ComponentContext);
91
92
 
92
93
  useImperativeHandle(ref, () => ({
93
94
  onChangeText: (text: string) => {
@@ -229,11 +230,10 @@ const InputOTP = forwardRef(
229
230
  return (
230
231
  <ComponentContext.Provider
231
232
  value={{
233
+ ...component,
232
234
  componentName: 'InputOTP',
233
235
  params,
234
- componentLabel: floatingValue,
235
236
  state: 'enabled',
236
- componentId: accessibilityLabel,
237
237
  }}>
238
238
  <View style={[style, styles.otpWrapper]}>
239
239
  {renderInputView()}
@@ -139,6 +139,8 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
139
139
  ref
140
140
  ) => {
141
141
  const {theme} = useContext(ApplicationContext);
142
+ const component = useContext<any>(ComponentContext);
143
+
142
144
  const [focused, setFocused] = useState(false);
143
145
  const [haveValue, setHaveValue] = useState(!!value || !!defaultValue);
144
146
  const inputRef = useRef<TextInput | null>(null);
@@ -264,10 +266,10 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
264
266
  return (
265
267
  <ComponentContext.Provider
266
268
  value={{
269
+ ...component,
267
270
  componentName: 'InputSearch',
268
271
  params,
269
272
  state: inputState,
270
- componentId: accessibilityLabel,
271
273
  }}>
272
274
  <View style={[style, styles.searchInputContainer]}>
273
275
  <View
@@ -47,7 +47,7 @@ const InputTextArea = forwardRef(
47
47
  hintText,
48
48
  ...props
49
49
  }: InputTextAreaProps,
50
- ref,
50
+ ref
51
51
  ) => {
52
52
  const {theme} = useContext(ApplicationContext);
53
53
  const [focused, setFocused] = useState(false);
@@ -173,7 +173,7 @@ const InputTextArea = forwardRef(
173
173
  />
174
174
  </View>
175
175
  );
176
- },
176
+ }
177
177
  );
178
178
 
179
179
  export default InputTextArea;
package/Layout/Screen.tsx CHANGED
@@ -35,7 +35,7 @@ import {FloatingButton, FloatingButtonProps} from './FloatingButton';
35
35
  import {Card, Section, validateChildren} from './index';
36
36
  import styles from './styles';
37
37
  import {HeaderType} from './types';
38
- import {InputRef, InputSearchProps} from '../Input';
38
+ import {InputRef} from '../Input';
39
39
  import {exportHeaderTitle, getOptions} from '../Application/utils';
40
40
  import {
41
41
  HeaderBackground,
@@ -122,7 +122,7 @@ export interface ScreenProps extends ViewProps {
122
122
  /**
123
123
  * Optional. specs for input search.
124
124
  */
125
- inputSearchProps?: InputSearchProps;
125
+ inputSearchProps?: SearchHeaderProps;
126
126
 
127
127
  /**
128
128
  * Optional. Ref for input search.
@@ -385,10 +385,7 @@ const Screen = forwardRef(
385
385
  useImperativeHandle(ref, () => ({
386
386
  ...screenRef.current,
387
387
  setOptions: (params: NavigationOptions) => {
388
- const options = getOptions(
389
- params,
390
- animatedHeader ? animatedValue.current : undefined
391
- );
388
+ const options = getOptions(params, undefined);
392
389
  navigation?.instance?.setOptions(options);
393
390
  },
394
391
  setSearchHeader: setSearchHeader,
package/Radio/index.tsx CHANGED
@@ -6,7 +6,6 @@ import {Text} from '../Text';
6
6
  import styles from './styles';
7
7
  import {ApplicationContext, ComponentContext} from '../Application';
8
8
  import {Spacing} from '../Consts';
9
- import {formatNameLength} from '../Application/utils';
10
9
 
11
10
  const Radio: FC<RadioProps> = ({
12
11
  value,
@@ -46,8 +45,6 @@ const Radio: FC<RadioProps> = ({
46
45
  componentName: 'Radio',
47
46
  params,
48
47
  state: disabled ? 'disabled' : 'enabled',
49
- componentId: accessibilityLabel,
50
- componentLabel: formatNameLength(label || ''),
51
48
  componentValue: selected,
52
49
  action: 'click',
53
50
  }}>
package/Switch/index.tsx CHANGED
@@ -27,7 +27,6 @@ const Switch: FC<SwitchProps> = ({
27
27
  componentName: 'Switch',
28
28
  params,
29
29
  state: disabled ? 'disabled' : 'enabled',
30
- componentId: accessibilityLabel,
31
30
  componentValue: value,
32
31
  action: 'click',
33
32
  }}>
package/Text/index.tsx CHANGED
@@ -109,19 +109,18 @@ const Text: React.FC<TextProps> = ({
109
109
  };
110
110
  };
111
111
 
112
-
113
112
  const textStyle = getTypoStyle(typography);
114
113
 
115
114
  if (deprecatedValues.includes(typography)) {
116
115
  console.warn(
117
- `Warning: The typography value "${typography}" is deprecated.`,
116
+ `Warning: The typography value "${typography}" is deprecated.`
118
117
  );
119
118
  }
120
119
 
121
120
  return (
122
121
  <RNText
123
122
  {...rest}
124
- {...getAccessibilityID((rest.accessibilityLabel))}
123
+ {...getAccessibilityID(rest.accessibilityLabel)}
125
124
  style={[style, textStyle, {color: color ?? theme.colors.text.default}]}>
126
125
  {children ?? ''}
127
126
  </RNText>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.109.1-optimize.11",
3
+ "version": "0.109.1-optimize.14",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},