@momo-kits/foundation 0.110.1-beta.7 → 0.110.1-optimize.1

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.
@@ -20,6 +20,7 @@ import {Icon} from '../Icon';
20
20
  const BottomSheet: React.FC<BottomSheetParams> = props => {
21
21
  const {theme, navigator} = useContext(ApplicationContext);
22
22
  const heightDevice = Dimensions.get('screen').height;
23
+ const action = useRef<undefined | string>();
23
24
  const insets = useSafeAreaInsets();
24
25
  const {
25
26
  screen: Screen,
@@ -53,6 +54,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
53
54
  },
54
55
  onPanResponderRelease: (_, gestureState) => {
55
56
  if (gestureState.dy > 100) {
57
+ action.current = 'gesture';
56
58
  onDismiss();
57
59
  } else {
58
60
  Animated.spring(pan, {
@@ -82,7 +84,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
82
84
  duration: 150,
83
85
  }).start();
84
86
  return () => {
85
- props.route.params?.onDismiss?.();
87
+ props.route.params?.onDismiss?.(action.current);
86
88
  };
87
89
  }, []);
88
90
 
@@ -150,7 +152,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
150
152
  )}
151
153
  <TouchableOpacity
152
154
  style={styles.iconButton}
153
- onPress={() => onDismiss(true)}>
155
+ onPress={() => {
156
+ action.current = 'icon_close';
157
+ onDismiss(true);
158
+ }}>
154
159
  <Icon source="navigation_close" />
155
160
  </TouchableOpacity>
156
161
  </View>
@@ -162,7 +167,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
162
167
  <Container
163
168
  transparent
164
169
  visible={true}
165
- onRequestClose={() => onDismiss()}
170
+ onRequestClose={() => {
171
+ onDismiss();
172
+ }}
166
173
  style={styles.overlay}>
167
174
  <KeyboardAvoidingView
168
175
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
@@ -171,7 +178,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
171
178
  <TouchableOpacity
172
179
  style={Styles.flex}
173
180
  activeOpacity={1}
174
- onPress={() => onDismiss()}
181
+ onPress={() => {
182
+ action.current = 'touch';
183
+ onDismiss();
184
+ }}
175
185
  />
176
186
  <Animated.View style={{transform: pan.getTranslateTransform()}}>
177
187
  {renderHeader()}
@@ -10,6 +10,7 @@ const ServiceItem: React.FC<any> = ({service}) => {
10
10
  const {title, description, icon, onPress} = service;
11
11
  const serviceTitle = translate?.(title);
12
12
  const serviceDescription = translate?.(description);
13
+
13
14
  return (
14
15
  <TouchableOpacity onPress={onPress} style={Styles.row}>
15
16
  <View style={styles.iconWrapper}>
@@ -35,6 +36,20 @@ const BottomSheetHelpCenter: React.FC<any> = ({onRequestClose}) => {
35
36
  const context = useContext<any>(MiniAppContext);
36
37
 
37
38
  const onPressFaq = () => {
39
+ const routes = navigator?.ref.current?.getRootState()?.routes || [];
40
+ const routesLength = routes.length;
41
+ let screenName = routes?.[0]?.params?.screen?.name;
42
+ if (routesLength > 1) {
43
+ screenName = routes[routesLength - 2]?.params?.screen?.name;
44
+ }
45
+
46
+ context?.autoTracking?.({
47
+ ...context,
48
+ componentName: 'TouchableOpacity',
49
+ componentId: 'bottomsheet_faq_item',
50
+ screenName: screenName,
51
+ });
52
+
38
53
  onRequestClose?.(() => {
39
54
  navigator?.maxApi?.startFeatureCode?.(
40
55
  'helpcenter_problemlevel1',
@@ -44,6 +59,20 @@ const BottomSheetHelpCenter: React.FC<any> = ({onRequestClose}) => {
44
59
  };
45
60
 
46
61
  const onPressChatbot = () => {
62
+ const routes = navigator?.ref.current?.getRootState()?.routes || [];
63
+ const routesLength = routes.length;
64
+ let screenName = routes?.[0]?.params?.screen?.name;
65
+ if (routesLength > 1) {
66
+ screenName = routes[routesLength - 2]?.params?.screen?.name;
67
+ }
68
+
69
+ context?.autoTracking?.({
70
+ ...context,
71
+ componentName: 'TouchableOpacity',
72
+ componentId: 'bottomsheet_chatbot_item',
73
+ screenName: screenName,
74
+ });
75
+
47
76
  onRequestClose?.(() => {
48
77
  navigator?.maxApi?.getDataObserver('CURRENT_SCREEN', (data: any) => {
49
78
  let screenName = data?.screenName;
@@ -61,6 +90,20 @@ const BottomSheetHelpCenter: React.FC<any> = ({onRequestClose}) => {
61
90
  };
62
91
 
63
92
  const onPressFeedback = () => {
93
+ const routes = navigator?.ref.current?.getRootState()?.routes || [];
94
+ const routesLength = routes.length;
95
+ let screenName = routes?.[0]?.params?.screen?.name;
96
+ if (routesLength > 1) {
97
+ screenName = routes[routesLength - 2]?.params?.screen?.name;
98
+ }
99
+
100
+ context?.autoTracking?.({
101
+ ...context,
102
+ componentName: 'TouchableOpacity',
103
+ componentId: 'bottomsheet_feedback_item',
104
+ screenName: screenName,
105
+ });
106
+
64
107
  onRequestClose?.(() => {
65
108
  navigator?.maxApi?.startFeatureCode?.('feedback', {
66
109
  forService: 'navigation',
@@ -48,9 +48,17 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
48
48
  {
49
49
  backgroundColor: theme.colors.background.surface,
50
50
  opacity: opacityBackground,
51
+ overflow: 'hidden',
51
52
  },
52
- ]}
53
- />
53
+ ]}>
54
+ {theme?.assets?.headerBackground && (
55
+ <Image
56
+ style={styles.headerBackground}
57
+ source={{uri: theme?.assets?.headerBackground}}
58
+ loading={false}
59
+ />
60
+ )}
61
+ </Animated.View>
54
62
  <View style={styles.gradientContainer}>
55
63
  {useGradient && (
56
64
  <LinearGradientAnimated
@@ -107,9 +115,8 @@ const styles = StyleSheet.create({
107
115
  },
108
116
  headerBackground: {
109
117
  width: '100%',
110
- height: undefined,
111
118
  position: 'absolute',
112
- aspectRatio: 375 / 154,
119
+ aspectRatio: 375 / 152,
113
120
  },
114
121
  dividerHeader: {
115
122
  borderBottomWidth: 1,
@@ -105,9 +105,17 @@ const HeaderExtendHeader: React.FC<{
105
105
  width: '100%',
106
106
  height: heightHeader,
107
107
  zIndex: 1,
108
+ overflow: 'hidden',
108
109
  },
109
- ]}
110
- />
110
+ ]}>
111
+ {headerBackground && (
112
+ <Image
113
+ style={styles.headerBackground}
114
+ source={{uri: headerBackground}}
115
+ loading={false}
116
+ />
117
+ )}
118
+ </Animated.View>
111
119
 
112
120
  <Animated.View
113
121
  style={[
@@ -175,9 +183,17 @@ const HeaderExtendHeader: React.FC<{
175
183
  height: heightHeader,
176
184
  backgroundColor: theme.colors.background.surface,
177
185
  opacity: opacityBackground,
186
+ overflow: 'hidden',
178
187
  },
179
- ]}
180
- />
188
+ ]}>
189
+ {headerBackground && (
190
+ <Image
191
+ style={styles.headerBackground}
192
+ source={{uri: headerBackground}}
193
+ loading={false}
194
+ />
195
+ )}
196
+ </Animated.View>
181
197
  <LinearGradientAnimated
182
198
  colors={[gradientColor, gradientColor + '00']}
183
199
  style={[styles.extendedHeader, {opacity: opacityGradient}]}>
@@ -12,6 +12,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
12
12
  tintColor,
13
13
  preventBack,
14
14
  onPressLeftHeader,
15
+ onBackHandler,
15
16
  }) => {
16
17
  const context = useContext<any>(MiniAppContext);
17
18
  const {navigator} = useContext(ApplicationContext);
@@ -29,18 +30,12 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
29
30
  const goBack = () => {
30
31
  const canGoBack = navigator?.ref?.current?.canGoBack?.();
31
32
  const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
32
- const params = {
33
+ context?.autoTracking?.({
33
34
  ...context,
34
- screen_name: currentRoute?.params?.screen?.name ?? currentRoute?.name,
35
- trigger_id: '111154000000000000',
36
- icon_name: 'back',
37
- };
38
- navigator?.maxApi?.showToastDebug?.({
39
- appId: `auto - ${context.appId}`,
40
- message: 'service_icon_clicked',
41
- params,
35
+ componentName: 'IconButton',
36
+ componentId: 'navigation_back',
37
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
42
38
  });
43
- navigator?.maxApi?.trackEvent?.('service_icon_clicked', params);
44
39
 
45
40
  if (canGoBack) {
46
41
  navigator?.ref?.current?.goBack?.();
@@ -49,7 +44,6 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
49
44
  } else {
50
45
  (global as any)?.miniAppApi?.dispatch?.('dismiss', context);
51
46
  }
52
- onPressLeftHeader?.();
53
47
  };
54
48
 
55
49
  if (preventBack) {
@@ -68,7 +62,12 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
68
62
  ),
69
63
  });
70
64
  } else {
71
- goBack();
65
+ onPressLeftHeader?.();
66
+ if (typeof onBackHandler === 'function') {
67
+ onBackHandler(goBack);
68
+ } else {
69
+ goBack();
70
+ }
72
71
  }
73
72
  return true;
74
73
  };
@@ -15,6 +15,7 @@ import {scaleSize, Text} from '../../Text';
15
15
  import {BottomSheetHelpCenter} from './BottomSheetHelpCenter';
16
16
  import {Image} from '../../Image';
17
17
 
18
+ const DID_SYNC_NEW_HOME = 'did_sync_new_home';
18
19
  /**
19
20
  * main component for header right
20
21
  */
@@ -64,11 +65,22 @@ const HeaderToolkitAction: React.FC<any> = ({
64
65
  useEffect(() => {
65
66
  if (useShortcut) {
66
67
  checkAppIsFavorite();
68
+
69
+ const favoriteObserver = navigator?.maxApi?.observer?.(
70
+ DID_SYNC_NEW_HOME,
71
+ (updatedData: any) => {
72
+ const status = updatedData?.items?.includes(context?.code);
73
+ setIsFavorite(status);
74
+ }
75
+ );
76
+ return () => {
77
+ favoriteObserver?.remove?.();
78
+ };
67
79
  }
68
- }, [useShortcut, useMore, tools]);
80
+ }, [useShortcut, context]);
69
81
 
70
82
  let buttonStyle: ViewStyle = {};
71
- if (tintColor === Colors.black_01) {
83
+ if (tintColor === Colors.black_01 || tintColor === 'white') {
72
84
  buttonStyle = {
73
85
  backgroundColor: '#00000099',
74
86
  borderColor: '#FFFFFF33',
@@ -78,22 +90,27 @@ const HeaderToolkitAction: React.FC<any> = ({
78
90
  /**
79
91
  * check app is favorite
80
92
  */
81
- const checkAppIsFavorite = () => {
93
+ const checkAppIsFavorite = () =>
82
94
  navigator?.maxApi?.dispatchFunction?.(
83
95
  'isFavoriteApp',
84
- context?.app_code,
96
+ context?.code,
85
97
  (result: boolean) => {
86
- if (result !== isFavorite) {
87
- setIsFavorite(result);
88
- }
98
+ setIsFavorite(result);
89
99
  }
90
100
  );
91
- };
92
101
 
93
102
  /**
94
103
  * close navigation container
95
104
  */
96
105
  const onClose = () => {
106
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
107
+ context?.autoTracking?.({
108
+ ...context,
109
+ componentName: 'IconButton',
110
+ componentId: 'navigation_close',
111
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
112
+ });
113
+
97
114
  if (preventClose) {
98
115
  navigator?.showModal({
99
116
  screen: () => (
@@ -122,6 +139,13 @@ const HeaderToolkitAction: React.FC<any> = ({
122
139
  * on press shortcut
123
140
  */
124
141
  const onPressShortcut = () => {
142
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
143
+ context?.autoTracking?.({
144
+ ...context,
145
+ componentName: 'IconButton',
146
+ componentId: 'navigation_shortcut',
147
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
148
+ });
125
149
  setIsLoading(true);
126
150
  navigator?.maxApi?.dispatchFunction?.(
127
151
  'onToolAction',
@@ -147,8 +171,13 @@ const HeaderToolkitAction: React.FC<any> = ({
147
171
  const onPressHelpCenter = () => {
148
172
  const appName = translate?.(context?.name);
149
173
  const appDescription = translate?.(context?.description);
150
- const isShowDescription =
151
- context?.description && Object.keys(context?.description).length > 0;
174
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
175
+ context?.autoTracking?.({
176
+ ...context,
177
+ componentName: 'IconButton',
178
+ componentId: 'navigation_helpcenter',
179
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
180
+ });
152
181
 
153
182
  navigator?.showBottomSheet({
154
183
  options: {
@@ -160,7 +189,7 @@ const HeaderToolkitAction: React.FC<any> = ({
160
189
  />
161
190
  <View>
162
191
  <Text typography={'label_default_medium'}>{appName}</Text>
163
- {isShowDescription && (
192
+ {appDescription !== '{}' && (
164
193
  <Text
165
194
  typography={'description_default_regular'}
166
195
  color={Colors.black_12}>
@@ -180,6 +209,14 @@ const HeaderToolkitAction: React.FC<any> = ({
180
209
  * on press icon more
181
210
  */
182
211
  const onPressIconMore = () => {
212
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
213
+ context?.autoTracking?.({
214
+ ...context,
215
+ componentName: 'IconButton',
216
+ componentId: 'navigation_more',
217
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
218
+ });
219
+
183
220
  navigator?.maxApi?.dispatchFunction?.('showTools', tools, (key: string) => {
184
221
  for (const group of tools) {
185
222
  const pressedTool = group?.items?.find?.(
@@ -210,7 +247,16 @@ const HeaderToolkitAction: React.FC<any> = ({
210
247
  (group: ToolGroup) => group?.items?.length === 1
211
248
  )?.items[0];
212
249
  iconShortcut = singleTool?.icon;
213
- shortcutOnPress = singleTool?.onPress;
250
+ shortcutOnPress = () => {
251
+ singleTool?.onPress?.();
252
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
253
+ context?.autoTracking?.({
254
+ ...context,
255
+ componentName: 'IconButton',
256
+ componentId: 'navigation_shortcut',
257
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
258
+ });
259
+ };
214
260
  }
215
261
 
216
262
  const showBadge = tools.some((group: ToolGroup) =>
@@ -8,10 +8,11 @@ import {
8
8
  } from 'react-native';
9
9
  import {ApplicationContext, MiniAppContext} from '../index';
10
10
  import {exportFontFamily, scaleSize, Text} from '../../Text';
11
- import {Colors, Spacing, Styles} from '../../Consts';
11
+ import {Colors, Spacing, Radius, Styles} from '../../Consts';
12
12
  import {TitleJourneyProps, TitleLocationProps, TitleUserProps} from '../types';
13
13
  import {Image} from '../../Image';
14
14
  import {Icon} from '../../Icon';
15
+ import {Skeleton} from '../../Skeleton';
15
16
 
16
17
  /**
17
18
  * default header title used for nav
@@ -69,13 +70,16 @@ const TitleCustom: React.FC<any> = ({type, data, ...props}) => {
69
70
  const TitleUser: React.FC<TitleUserProps> = ({
70
71
  title,
71
72
  subTitle,
72
- image,
73
+ image: image,
73
74
  tintColor,
74
75
  dotColor,
75
- verify,
76
76
  onPress,
77
+ verify,
78
+ icons,
79
+ isLoading,
77
80
  }) => {
78
81
  const {theme} = useContext(ApplicationContext);
82
+ const maxWidth = icons?.length === 2 ? '73%' : '100%';
79
83
  const getShortName = (name: string) => {
80
84
  const words = name.split(' ');
81
85
  const lastTwoWords = words.slice(-2);
@@ -83,17 +87,214 @@ const TitleUser: React.FC<TitleUserProps> = ({
83
87
  };
84
88
 
85
89
  const renderImage = () => {
86
- if (!!image) {
90
+ const avatarWidth = Spacing.XXL;
91
+ if (Array.isArray(image) && image.length > 0) {
92
+ switch (image.length) {
93
+ case 1: {
94
+ return image.map((i: string) => (
95
+ <Image source={{uri: i}} style={styles.circle} />
96
+ ));
97
+ }
98
+ case 2: {
99
+ return (
100
+ <View style={[Styles.flex, styles.groupAvtContainer]}>
101
+ <Image
102
+ source={{uri: image[0]}}
103
+ style={[
104
+ styles.circleMedium,
105
+ {position: 'absolute', top: 0, right: 0, zIndex: 0},
106
+ ]}
107
+ />
108
+ <Image
109
+ source={{uri: image[1]}}
110
+ style={[
111
+ styles.circleMedium,
112
+ {position: 'absolute', left: 0, bottom: 0, zIndex: 1},
113
+ ]}
114
+ />
115
+ </View>
116
+ );
117
+ }
118
+ case 3: {
119
+ return (
120
+ <View style={[Styles.flex, styles.groupAvtContainer]}>
121
+ <Image
122
+ source={{uri: image[0]}}
123
+ style={[
124
+ styles.circleSmall,
125
+ {position: 'absolute', bottom: 2, right: 2, zIndex: 0},
126
+ ]}
127
+ />
128
+ <Image
129
+ source={{uri: image[1]}}
130
+ style={[
131
+ styles.circleSmall,
132
+ {
133
+ position: 'absolute',
134
+ top: 2,
135
+ left: avatarWidth / 2 - 16 / 2,
136
+ zIndex: 1,
137
+ },
138
+ ]}
139
+ />
140
+ <Image
141
+ source={{uri: image[2]}}
142
+ style={[
143
+ styles.circleSmall,
144
+ {position: 'absolute', left: 2, bottom: 2, zIndex: 2},
145
+ ]}
146
+ />
147
+ </View>
148
+ );
149
+ }
150
+ case 4: {
151
+ return (
152
+ <View style={[Styles.flex, styles.groupAvtContainer]}>
153
+ <Image
154
+ source={{uri: image[0]}}
155
+ style={[
156
+ styles.circleSmall,
157
+ {position: 'absolute', bottom: 2, left: 2, zIndex: 0},
158
+ ]}
159
+ />
160
+ <Image
161
+ source={{uri: image[1]}}
162
+ style={[
163
+ styles.circleSmall,
164
+ {position: 'absolute', bottom: 2, right: 2, zIndex: 1},
165
+ ]}
166
+ />
167
+ <Image
168
+ source={{uri: image[2]}}
169
+ style={[
170
+ styles.circleSmall,
171
+ {position: 'absolute', top: 2, right: 2, zIndex: 2},
172
+ ]}
173
+ />
174
+ <Image
175
+ source={{uri: image[2]}}
176
+ style={[
177
+ styles.circleSmall,
178
+ {position: 'absolute', top: 2, left: 2, zIndex: 3},
179
+ ]}
180
+ />
181
+ </View>
182
+ );
183
+ }
184
+ default: {
185
+ return (
186
+ <View style={[Styles.flex, styles.groupAvtContainer]}>
187
+ <Image
188
+ source={{uri: image[0]}}
189
+ style={[
190
+ styles.circleSmall,
191
+ {position: 'absolute', left: 2, bottom: 2, zIndex: 0},
192
+ ]}
193
+ />
194
+ <Image
195
+ source={{uri: image[1]}}
196
+ style={[
197
+ styles.circleSmall,
198
+ {position: 'absolute', top: 2, right: 2, zIndex: 2},
199
+ ]}
200
+ />
201
+ <Image
202
+ source={{uri: image[2]}}
203
+ style={[
204
+ styles.circleSmall,
205
+ {position: 'absolute', left: 2, top: 2, zIndex: 3},
206
+ ]}
207
+ />
208
+ <View
209
+ style={[
210
+ styles.circleSmall,
211
+ {
212
+ position: 'absolute',
213
+ bottom: 2,
214
+ right: 2,
215
+ zIndex: 1,
216
+ backgroundColor: Colors.pink_09,
217
+ },
218
+ ]}>
219
+ <Text
220
+ color={Colors.pink_03}
221
+ typography={'description_xs_regular'}>
222
+ {`+${image.length - 3}`}
223
+ </Text>
224
+ </View>
225
+ </View>
226
+ );
227
+ }
228
+ }
229
+ }
230
+
231
+ if (typeof image === 'string') {
87
232
  return <Image source={{uri: image}} style={styles.circle} />;
88
- } else {
89
- return (
233
+ }
234
+
235
+ return (
236
+ <View
237
+ style={[
238
+ styles.imageContainer,
239
+ {borderColor: theme.colors.border.default},
240
+ ]}>
90
241
  <Text color={Colors.pink_03} typography={'description_xs_regular'}>
91
242
  {getShortName(title)}
92
243
  </Text>
93
- );
94
- }
244
+ </View>
245
+ );
246
+ };
247
+
248
+ const renderVerifyIcon = () => {
249
+ const renderIcons = () => {
250
+ if (icons && icons.length > 0) {
251
+ return icons.map(icon => (
252
+ <Image
253
+ source={{
254
+ uri: icon,
255
+ }}
256
+ style={styles.verifiedIcon}
257
+ />
258
+ ));
259
+ }
260
+ };
261
+
262
+ return <View style={Styles.row}>{renderIcons()}</View>;
263
+ };
264
+
265
+ const renderShimmer = () => {
266
+ return (
267
+ <View style={Styles.row}>
268
+ <View
269
+ style={[
270
+ styles.imageContainer,
271
+ {
272
+ borderColor: theme.colors.border.default,
273
+ marginRight: Spacing.M / 2,
274
+ },
275
+ ]}>
276
+ <Skeleton />
277
+ </View>
278
+ <View style={Styles.flex}>
279
+ <View style={styles.shimmerTitle}>
280
+ <Skeleton />
281
+ </View>
282
+ <View style={{height: Spacing.XXS}} />
283
+ <View style={styles.shimmerDescription}>
284
+ <Skeleton />
285
+ </View>
286
+ </View>
287
+ </View>
288
+ );
95
289
  };
96
290
 
291
+ if (verify) {
292
+ console.warn(`Warning: The verify value "${verify}" is deprecated.`);
293
+ }
294
+
295
+ if (isLoading) {
296
+ return renderShimmer();
297
+ }
97
298
  return (
98
299
  <TouchableOpacity
99
300
  style={styles.headerTitleContainer}
@@ -101,18 +302,12 @@ const TitleUser: React.FC<TitleUserProps> = ({
101
302
  disabled={onPress === undefined}>
102
303
  <View style={[Styles.row, Styles.flex]}>
103
304
  <View>
104
- <View
105
- style={[
106
- styles.imageContainer,
107
- {borderColor: theme.colors.border.default},
108
- ]}>
109
- {renderImage()}
110
- </View>
305
+ {renderImage()}
111
306
  {!!dotColor && (
112
307
  <View style={[styles.dotAvatar, {backgroundColor: dotColor}]} />
113
308
  )}
114
309
  </View>
115
- <View style={[Styles.flex, {marginLeft: 6}]}>
310
+ <View style={[Styles.flex, {marginLeft: 6, maxWidth: maxWidth}]}>
116
311
  <View style={Styles.row}>
117
312
  <Text
118
313
  typography="action_xs_bold"
@@ -120,14 +315,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
120
315
  numberOfLines={1}>
121
316
  {title}
122
317
  </Text>
123
- {verify && (
124
- <Image
125
- source={{
126
- uri: 'https://static.momocdn.net/app/img/kits/verified.png',
127
- }}
128
- style={styles.verifiedIcon}
129
- />
130
- )}
318
+ {renderVerifyIcon()}
131
319
  </View>
132
320
  {!!subTitle && (
133
321
  <Text
@@ -153,7 +341,24 @@ const TitleLocation: React.FC<TitleLocationProps> = ({
153
341
  location,
154
342
  description,
155
343
  onPress,
344
+ isLoading,
156
345
  }) => {
346
+ const renderShimmer = () => {
347
+ return (
348
+ <View style={Styles.flex}>
349
+ <View style={styles.shimmerDescription}>
350
+ <Skeleton />
351
+ </View>
352
+ <View style={{height: Spacing.XXS}} />
353
+ <View style={styles.shimmerTitle}>
354
+ <Skeleton />
355
+ </View>
356
+ </View>
357
+ );
358
+ };
359
+ if (isLoading) {
360
+ return renderShimmer();
361
+ }
157
362
  return (
158
363
  <TouchableOpacity
159
364
  style={[styles.headerTitleContainer, {alignItems: 'flex-start'}]}
@@ -193,14 +398,36 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
193
398
  iconColor,
194
399
  tintColor,
195
400
  onPress,
401
+ isLoading,
196
402
  }) => {
403
+ const {width} = Dimensions.get('screen');
404
+ const startWidth = width * 0.25;
405
+ const endWidth = width * 0.75 - 212;
406
+ const renderShimmer = () => {
407
+ return (
408
+ <View style={Styles.flex}>
409
+ <View style={styles.shimmerTitle}>
410
+ <Skeleton />
411
+ </View>
412
+ <View style={{height: Spacing.XXS}} />
413
+ <View style={styles.shimmerDescription}>
414
+ <Skeleton />
415
+ </View>
416
+ </View>
417
+ );
418
+ };
419
+ if (isLoading) {
420
+ return renderShimmer();
421
+ }
197
422
  return (
198
- <TouchableOpacity
199
- style={[styles.headerTitleContainer, {overflow: 'hidden'}]}
200
- onPress={onPress}>
423
+ <TouchableOpacity style={styles.headerTitleContainer} onPress={onPress}>
201
424
  <View style={styles.rowJourney}>
202
425
  <View style={Styles.row}>
203
- <Text typography="action_xs_bold" color={tintColor} numberOfLines={1}>
426
+ <Text
427
+ typography="action_xs_bold"
428
+ style={{maxWidth: startWidth}}
429
+ color={tintColor}
430
+ numberOfLines={1}>
204
431
  {start}
205
432
  </Text>
206
433
  <Icon
@@ -211,6 +438,7 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
211
438
  />
212
439
  {!!end && (
213
440
  <Text
441
+ style={{maxWidth: endWidth}}
214
442
  typography="action_xs_bold"
215
443
  color={tintColor}
216
444
  numberOfLines={1}>
@@ -270,6 +498,28 @@ const styles = StyleSheet.create({
270
498
  circle: {
271
499
  width: 32,
272
500
  height: 32,
501
+ borderRadius: 16,
502
+ borderWidth: 0.5,
503
+ borderColor: Colors.black_04,
504
+ },
505
+ circleMedium: {
506
+ width: 24,
507
+ height: 24,
508
+ borderRadius: 16,
509
+ borderWidth: 0.5,
510
+ borderColor: Colors.black_04,
511
+ },
512
+ circleSmall: {
513
+ width: 16,
514
+ height: 16,
515
+ borderRadius: 16,
516
+ borderWidth: 0.5,
517
+ borderColor: Colors.black_04,
518
+ },
519
+ groupAvtContainer: {
520
+ width: Spacing.XXL,
521
+ height: Spacing.XXL,
522
+ position: 'relative',
273
523
  },
274
524
  imageContainer: {
275
525
  width: 32,
@@ -291,7 +541,19 @@ const styles = StyleSheet.create({
291
541
  borderWidth: 2,
292
542
  borderColor: Colors.black_01,
293
543
  },
294
- verifiedIcon: {width: 12, height: 12, marginLeft: Spacing.XS},
544
+ verifiedIcon: {width: 16, height: 16, marginLeft: Spacing.XS},
545
+ shimmerTitle: {
546
+ height: 18,
547
+ width: 120,
548
+ borderRadius: Radius.XS,
549
+ overflow: 'hidden',
550
+ },
551
+ shimmerDescription: {
552
+ height: 12,
553
+ width: 120,
554
+ borderRadius: Radius.XS,
555
+ overflow: 'hidden',
556
+ },
295
557
  });
296
558
 
297
559
  export {HeaderTitle, TitleCustom};
@@ -45,18 +45,13 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
45
45
  const goBack = () => {
46
46
  const canGoBack = navigator?.ref?.current?.canGoBack?.();
47
47
  const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
48
- const params = {
48
+ context?.autoTracking?.({
49
49
  ...context,
50
- screen_name: currentRoute?.params?.screen?.name ?? currentRoute?.name,
51
- trigger_id: '111154000000000000',
52
- icon_name: 'back',
53
- };
54
- navigator?.maxApi?.showToastDebug?.({
55
- appId: `auto - ${context.appId}`,
56
- message: 'service_icon_clicked',
57
- params,
50
+ componentName: 'SearchHeader',
51
+ componentId: 'search_back',
52
+ screenName: currentRoute?.params?.screen?.name ?? currentRoute?.name,
58
53
  });
59
- navigator?.maxApi?.trackEvent?.('service_icon_clicked', params);
54
+
60
55
  if (canGoBack) {
61
56
  navigator?.ref?.current?.goBack?.();
62
57
  } else if (navigator?.maxApi) {
@@ -1,4 +1,4 @@
1
- import React, {useEffect, useRef, useState} from 'react';
1
+ import React, {useContext, useEffect, useRef, useState} from 'react';
2
2
  import {SafeAreaProvider} from 'react-native-safe-area-context';
3
3
  import {
4
4
  NavigationContainer as ReactNavigationContainer,
@@ -8,13 +8,13 @@ import {
8
8
  createStackNavigator,
9
9
  StackNavigationOptions,
10
10
  } from '@react-navigation/stack';
11
- import {DeviceEventEmitter} from 'react-native';
11
+ import {DeviceEventEmitter, NativeModules} from 'react-native';
12
12
  import StackScreen from './StackScreen';
13
13
  import ModalScreen from './ModalScreen';
14
14
  import Navigator from './Navigator';
15
15
  import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
16
16
  import {NavigationContainerProps} from './types';
17
- import {ApplicationContext} from './index';
17
+ import {ApplicationContext, MiniAppContext} from './index';
18
18
  import Localize from './Localize';
19
19
  import {defaultTheme} from '../Consts';
20
20
 
@@ -28,11 +28,20 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
28
28
  initialParams,
29
29
  localize = new Localize({vi: {}, en: {}}),
30
30
  }) => {
31
+ const context = useContext<any>(MiniAppContext);
31
32
  const navigationRef = useRef<NavigationContainerRef>(null);
33
+ const routes = useRef<any>();
32
34
  const isReady = useRef(false);
33
35
  const navigator = useRef(new Navigator(navigationRef, isReady));
34
36
  const [showGrid, setShowGrid] = useState(false);
35
- const [configHeader, setConfigHeader] = useState(undefined);
37
+ let config = null;
38
+ try {
39
+ config = JSON.parse(
40
+ NativeModules?.ConfigsModule?.getConfigSync?.('DESIGN_SYSTEM') || ''
41
+ );
42
+ } catch (e) {
43
+ console.log(e);
44
+ }
36
45
 
37
46
  /**
38
47
  * inject data for navigator
@@ -63,15 +72,29 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
63
72
  return localize?.translate(data as string);
64
73
  };
65
74
 
66
- useEffect(() => {
67
- maxApi?.getFeatureById('platform_config', (data: any) => {
68
- const parsedConfig = JSON.parse(data?.param);
69
- const header = parsedConfig?.navigationBar?.headerBar;
70
- if (header) {
71
- setConfigHeader(header);
72
- }
75
+ const onScreenNavigated = (
76
+ preScreenName: string,
77
+ screenName: string,
78
+ action: string
79
+ ) => {
80
+ context?.autoTracking?.({
81
+ ...context,
82
+ preScreenName,
83
+ screenName,
84
+ componentName: 'Screen',
85
+ state: 'navigated',
86
+ action,
73
87
  });
74
- }, []);
88
+
89
+ /**
90
+ * debug toast
91
+ */
92
+ maxApi?.showToastDebug?.({
93
+ appId: context.appId,
94
+ message: `${screenName} screen_navigated`,
95
+ type: 'ERROR',
96
+ });
97
+ };
75
98
 
76
99
  return (
77
100
  <SafeAreaProvider>
@@ -81,7 +104,8 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
81
104
  theme: {
82
105
  ...theme,
83
106
  assets: {
84
- headerBackground: theme.assets?.headerBackground || configHeader,
107
+ headerBackground:
108
+ theme.assets?.headerBackground || config?.headerBar,
85
109
  },
86
110
  },
87
111
  showGrid,
@@ -102,6 +126,27 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
102
126
  ref={navigationRef}
103
127
  onReady={() => {
104
128
  isReady.current = true;
129
+ routes.current = navigationRef.current?.getRootState?.()?.routes;
130
+ }}
131
+ onStateChange={state => {
132
+ const lastedRoute: any = state?.routes?.[state?.routes?.length - 1];
133
+ const oldRoute: any = routes.current?.[routes.current?.length - 1];
134
+ const lasted = lastedRoute?.params?.screen;
135
+ const previous = oldRoute?.params?.screen;
136
+ if (routes.current?.length < (state?.routes?.length ?? 0)) {
137
+ onScreenNavigated(
138
+ previous?.name ?? previous?.type?.name,
139
+ lasted?.name ?? lasted?.type?.name,
140
+ 'push'
141
+ );
142
+ } else if (routes.current?.length > (state?.routes?.length ?? 0)) {
143
+ onScreenNavigated(
144
+ previous?.name ?? previous?.type?.name,
145
+ lasted?.name ?? lasted?.type?.name,
146
+ 'back'
147
+ );
148
+ }
149
+ routes.current = state?.routes;
105
150
  }}
106
151
  independent={true}>
107
152
  <Stack.Navigator initialRouteName="Stack" headerMode="screen">
@@ -6,6 +6,8 @@ import Navigation from './Navigation';
6
6
  import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
7
7
  import {GridSystem} from '../Layout';
8
8
 
9
+ const runAfterInteractions = InteractionManager.runAfterInteractions;
10
+
9
11
  /**
10
12
  * container for stack screen
11
13
  * @param props
@@ -42,7 +44,6 @@ const StackScreen: React.FC<ScreenParams> = props => {
42
44
  delete data.initialParams;
43
45
 
44
46
  const screenName = Component?.name || Component?.type?.name || 'Invalid';
45
- const routes = props.navigation.getState()?.routes || [];
46
47
 
47
48
  /**
48
49
  * set options for screen
@@ -67,15 +68,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
67
68
 
68
69
  const subscription = props.navigation?.addListener?.('focus', () => {
69
70
  navigator?.maxApi?.of?.({screenName});
70
- navigator?.maxApi?.getDataObserver('CURRENT_SCREEN', (data: any) => {
71
- let preScreenName = data?.screenName;
72
- if (routes?.length > 1) {
73
- const screen = routes?.[routes?.length - 2]?.params?.screen;
74
- preScreenName = screen?.name || screen?.type?.name || 'Invalid';
75
- }
76
- onScreenNavigated(preScreenName);
77
- navigator?.maxApi?.setObserver('CURRENT_SCREEN', {screenName});
78
- });
71
+ navigator?.maxApi?.setObserver('CURRENT_SCREEN', {screenName});
79
72
  });
80
73
  navigator?.maxApi?.startTraceScreenLoad?.(
81
74
  screenName,
@@ -115,7 +108,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
115
108
  timeLoad = tracking.current.endTime - tracking.current.startTime;
116
109
  }
117
110
 
118
- context.autoTracking?.({
111
+ context?.autoTracking?.({
119
112
  ...context,
120
113
  screenName,
121
114
  componentName: 'Screen',
@@ -162,7 +155,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
162
155
  tracking.current.timeInteraction = timeLoad;
163
156
  }
164
157
 
165
- context.autoTracking?.({
158
+ context?.autoTracking?.({
166
159
  ...context,
167
160
  screenName,
168
161
  componentName: 'Screen',
@@ -188,46 +181,39 @@ const StackScreen: React.FC<ScreenParams> = props => {
188
181
  }
189
182
  };
190
183
 
191
- /**
192
- * tracking for screen navigated
193
- */
194
- const onScreenNavigated = (preScreenName: string) => {
195
- context.autoTracking?.({
196
- ...context,
197
- preScreenName,
198
- screenName,
199
- componentName: 'Screen',
200
- state: 'navigated',
201
- });
202
-
203
- /**
204
- * debug toast
205
- */
206
- navigator?.maxApi?.showToastDebug?.({
207
- appId: context.appId,
208
- message: `${screenName} screen_navigated`,
209
- type: 'ERROR',
210
- });
211
- };
212
-
213
184
  return (
214
185
  <ScreenContext.Provider
215
186
  value={{
216
187
  screenName,
217
188
  onElementLoad: (data: any) => {
189
+ /**
190
+ * tracking for element screen load
191
+ */
218
192
  clearTimeout(tracking.current.timeoutLoad);
219
193
  tracking.current.endTime = Date.now();
220
194
  tracking.current.timeoutInteraction?.cancel?.();
221
- tracking.current.timeoutInteraction =
222
- InteractionManager.runAfterInteractions(() => {
223
- tracking.current.timeInteraction =
224
- Date.now() - tracking.current.startTime;
225
- });
226
-
195
+ tracking.current.timeoutInteraction = runAfterInteractions(() => {
196
+ tracking.current.timeInteraction =
197
+ Date.now() - tracking.current.startTime;
198
+ });
199
+
200
+ /**
201
+ * support for debug last element
202
+ */
227
203
  if (data?.componentName) {
228
204
  lastElement.current = data;
229
205
  }
230
206
 
207
+ /**
208
+ * for stop tracking when user interaction
209
+ */
210
+ if (data.interaction) {
211
+ onScreenLoad();
212
+ onScreenInteraction();
213
+ }
214
+ /**
215
+ * timeout for handle tracking screen
216
+ */
231
217
  tracking.current.timeoutLoad = setTimeout(() => {
232
218
  const time = tracking.current.endTime - tracking.current.startTime;
233
219
  if (tracking.current.timeLoad === 0) {
@@ -158,6 +158,7 @@ export interface NavigationOptions
158
158
  extends Omit<StackNavigationOptions, 'headerRight' | 'headerTitle'> {
159
159
  preventBack?: PopupNotifyProps;
160
160
  onPressLeftHeader?: () => void;
161
+ onBackHandler?: (goBack: () => void) => void;
161
162
  hiddenBack?: boolean;
162
163
  headerTitle?: HeaderTitleProps | string;
163
164
  headerRight?: OnBoarding | HeaderRightToolkit | any;
@@ -178,6 +179,7 @@ export type HeaderRightToolkit = {
178
179
  export interface HeaderBackProps extends NavigationButtonProps {
179
180
  preventBack?: PopupNotifyProps;
180
181
  onPressLeftHeader?: () => void;
182
+ onBackHandler?: (goBack: () => void) => void;
181
183
  }
182
184
 
183
185
  export type HeaderBackgroundProps = {
@@ -193,11 +195,13 @@ export type HeaderBackgroundProps = {
193
195
  export type TitleUserProps = {
194
196
  title: string;
195
197
  subTitle?: string;
196
- image?: string;
198
+ image?: string[] | string;
197
199
  dotColor?: string;
198
200
  verify?: boolean;
199
201
  tintColor?: string;
200
202
  onPress?: () => void;
203
+ icons?: string[];
204
+ isLoading?: boolean;
201
205
  };
202
206
 
203
207
  export type TitleLocationProps = {
@@ -205,6 +209,7 @@ export type TitleLocationProps = {
205
209
  location: string;
206
210
  tintColor?: string;
207
211
  onPress?: () => void;
212
+ isLoading?: boolean;
208
213
  };
209
214
 
210
215
  export type TitleJourneyProps = {
@@ -215,6 +220,7 @@ export type TitleJourneyProps = {
215
220
  iconColor?: string;
216
221
  tintColor?: string;
217
222
  onPress?: () => void;
223
+ isLoading?: boolean;
218
224
  };
219
225
 
220
226
  export type HeaderToolkitProps = {
@@ -87,6 +87,7 @@ const getOptions = (
87
87
  */
88
88
  if (
89
89
  typeof params.onPressLeftHeader === 'function' ||
90
+ typeof params.onBackHandler === 'function' ||
90
91
  params.preventBack !== undefined ||
91
92
  typeof params.hiddenBack === 'boolean'
92
93
  ) {
@@ -95,6 +96,7 @@ const getOptions = (
95
96
  {...props}
96
97
  preventBack={params.preventBack}
97
98
  onPressLeftHeader={params.onPressLeftHeader}
99
+ onBackHandler={params.onBackHandler}
98
100
  />
99
101
  );
100
102
  if (params.hiddenBack) {
package/Button/index.tsx CHANGED
@@ -88,7 +88,6 @@ const Button: FC<ButtonProps> = ({
88
88
  ...rest
89
89
  }) => {
90
90
  const {theme, config} = useContext(ApplicationContext);
91
- const component = useContext<any>(ComponentContext);
92
91
  const {gradient, color} = config?.navigationBar?.buttonColors ?? {};
93
92
  let gradientPros;
94
93
  let state = 'enabled';
@@ -293,7 +292,6 @@ const Button: FC<ButtonProps> = ({
293
292
  return (
294
293
  <ComponentContext.Provider
295
294
  value={{
296
- ...component,
297
295
  componentName: 'Button',
298
296
  params,
299
297
  state: state,
package/Input/Input.tsx CHANGED
@@ -67,7 +67,6 @@ const Input = forwardRef(
67
67
  ref
68
68
  ) => {
69
69
  const {theme} = useContext(ApplicationContext);
70
- const component = useContext<any>(ComponentContext);
71
70
  const [focused, setFocused] = useState(false);
72
71
  const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
73
72
  const [secureTextInput, setSecureTextInput] = useState(secureTextEntry);
@@ -270,7 +269,6 @@ const Input = forwardRef(
270
269
  return (
271
270
  <ComponentContext.Provider
272
271
  value={{
273
- ...component,
274
272
  componentName: 'Input',
275
273
  params,
276
274
  state: inputState,
@@ -30,7 +30,6 @@ const InputDropDown = ({
30
30
  multiline,
31
31
  }: InputDropDownProps) => {
32
32
  const {theme} = useContext(ApplicationContext);
33
- const component = useContext<any>(ComponentContext);
34
33
 
35
34
  /**
36
35
  * Render the input view
@@ -106,7 +105,6 @@ const InputDropDown = ({
106
105
  return (
107
106
  <ComponentContext.Provider
108
107
  value={{
109
- ...component,
110
108
  componentName: 'InputDropDown',
111
109
  params,
112
110
  state: 'enabled',
@@ -62,7 +62,6 @@ const InputMoney = forwardRef(
62
62
  ref
63
63
  ) => {
64
64
  const {theme} = useContext(ApplicationContext);
65
- const component = useContext<any>(ComponentContext);
66
65
 
67
66
  const [focused, setFocused] = useState(false);
68
67
  const inputRef = useRef<TextInput>(null);
@@ -207,7 +206,6 @@ const InputMoney = forwardRef(
207
206
  return (
208
207
  <ComponentContext.Provider
209
208
  value={{
210
- ...component,
211
209
  componentName: 'InputMoney',
212
210
  params,
213
211
  state: inputState,
@@ -88,7 +88,6 @@ 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);
92
91
 
93
92
  useImperativeHandle(ref, () => ({
94
93
  onChangeText: (text: string) => {
@@ -230,7 +229,6 @@ const InputOTP = forwardRef(
230
229
  return (
231
230
  <ComponentContext.Provider
232
231
  value={{
233
- ...component,
234
232
  componentName: 'InputOTP',
235
233
  params,
236
234
  state: 'enabled',
@@ -139,7 +139,6 @@ 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
143
  const [focused, setFocused] = useState(false);
145
144
  const [haveValue, setHaveValue] = useState(!!value || !!defaultValue);
@@ -272,7 +271,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
272
271
  return (
273
272
  <ComponentContext.Provider
274
273
  value={{
275
- ...component,
276
274
  componentName: 'InputSearch',
277
275
  params,
278
276
  state: inputState,
package/Title/index.tsx CHANGED
@@ -10,6 +10,12 @@ import {TitleProps} from './types';
10
10
  import {Typography} from '../Text/types';
11
11
 
12
12
  const Title: FC<TitleProps> = ({
13
+ accessibilityLabelIcon,
14
+ accessibilityLabelTitle,
15
+ accessibilityLabelRightAction,
16
+ accessibilityLabelDescription,
17
+ accessibilityLabelBadge,
18
+ accessibilityLabelTrailingAction,
13
19
  title = 'Title',
14
20
  type = 'section',
15
21
  size = 'medium',
@@ -56,7 +62,7 @@ const Title: FC<TitleProps> = ({
56
62
  }
57
63
 
58
64
  return (
59
- <View style={[styles.iconView, iconStyle]}>
65
+ <View accessibilityLabel={accessibilityLabelIcon} style={[styles.iconView, iconStyle]}>
60
66
  <Icon color={iconColor} source={icon} />
61
67
  </View>
62
68
  );
@@ -73,6 +79,7 @@ const Title: FC<TitleProps> = ({
73
79
  }}
74
80
  style={[styles.iconLeftView, flexStyle]}>
75
81
  <RNText
82
+ accessibilityLabel={accessibilityLabelTitle}
76
83
  numberOfLines={numberOfLines}
77
84
  style={[
78
85
  styleSheet[typography],
@@ -93,7 +100,9 @@ const Title: FC<TitleProps> = ({
93
100
  }}
94
101
  style={{
95
102
  alignItems: 'center',
96
- }}>
103
+ }}
104
+ accessibilityLabel={accessibilityLabelBadge}
105
+ >
97
106
  <Badge style={styles.badge} label={badgeLabel} />
98
107
  </View>
99
108
  )}
@@ -101,6 +110,7 @@ const Title: FC<TitleProps> = ({
101
110
  </View>
102
111
  {description && (
103
112
  <Text
113
+ accessibilityLabel={accessibilityLabelDescription}
104
114
  style={styles.description}
105
115
  color={theme.colors.text.secondary}
106
116
  typography={'description_default_regular'}>
@@ -114,6 +124,7 @@ const Title: FC<TitleProps> = ({
114
124
  const renderActionLeft = () => {
115
125
  return (
116
126
  <TouchableOpacity
127
+ accessibilityLabel={accessibilityLabelTrailingAction}
117
128
  onPress={onPressTrailingAction}
118
129
  style={styles.iconLeftView}
119
130
  hitSlop={{top: 10, bottom: 10, left: 50, right: 10}}>
@@ -157,7 +168,9 @@ const Title: FC<TitleProps> = ({
157
168
  {
158
169
  backgroundColor: theme.colors.primary + '0F',
159
170
  },
160
- ]}>
171
+ ]}
172
+ accessibilityLabel={accessibilityLabelRightAction}
173
+ >
161
174
  <Icon
162
175
  source={'arrow_chevron_right_small'}
163
176
  size={scaleSize(22)}
@@ -165,7 +178,7 @@ const Title: FC<TitleProps> = ({
165
178
  />
166
179
  </TouchableOpacity>
167
180
  ) : (
168
- <TouchableOpacity onPress={onPressRightAction}>
181
+ <TouchableOpacity onPress={onPressRightAction} accessibilityLabel={accessibilityLabelRightAction}>
169
182
  <Text color={theme.colors.primary} typography={buttonTypo}>
170
183
  {buttonTitle}
171
184
  </Text>
@@ -179,6 +192,7 @@ const Title: FC<TitleProps> = ({
179
192
  return (
180
193
  <View style={isSection && styles.margin}>
181
194
  <RNText
195
+ accessibilityLabel={accessibilityLabelTitle}
182
196
  numberOfLines={numberOfLines}
183
197
  style={[styleSheet[typography], styles.title]}>
184
198
  {title}
package/Title/types.ts CHANGED
@@ -17,4 +17,10 @@ export type TitleProps = {
17
17
  onPressTrailingAction?: () => void;
18
18
  textOnly?: boolean;
19
19
  style?: ViewStyle;
20
+ accessibilityLabelTitle?: string;
21
+ accessibilityLabelRightAction?: string;
22
+ accessibilityLabelDescription?: string;
23
+ accessibilityLabelIcon?: string;
24
+ accessibilityLabelBadge?: string;
25
+ accessibilityLabelTrailingAction?: string;
20
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.110.1-beta.7",
3
+ "version": "0.110.1-optimize.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},