@momo-kits/foundation 0.89.5 → 0.92.1-beta.16

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.
@@ -8,13 +8,15 @@ import {
8
8
 
9
9
  class Navigator {
10
10
  ref?: any;
11
+ isReady?: any;
11
12
  toolkitConfig?: HeaderToolkitProps;
12
13
  maxApi?: any;
13
14
  dismissData?: any;
14
15
  toolkitCallback?: (item: any) => void;
15
16
 
16
- constructor(navigation: any) {
17
+ constructor(navigation: any, isReady: any) {
17
18
  this.ref = navigation;
19
+ this.isReady = isReady;
18
20
  }
19
21
 
20
22
  /**
@@ -22,7 +24,9 @@ class Navigator {
22
24
  * @param params
23
25
  */
24
26
  push = (params: ScreenParams) => {
25
- this.ref.current.dispatch(StackActions.push('Stack', params));
27
+ if (this.isReady.current) {
28
+ this.ref.current.dispatch(StackActions.push('Stack', params));
29
+ }
26
30
  };
27
31
 
28
32
  /**
@@ -30,14 +34,18 @@ class Navigator {
30
34
  * @param params
31
35
  */
32
36
  replace = (params: ScreenParams) => {
33
- this.ref.current.dispatch(StackActions.replace('Stack', params));
37
+ if (this.isReady.current) {
38
+ this.ref.current.dispatch(StackActions.replace('Stack', params));
39
+ }
34
40
  };
35
41
  /**
36
42
  * pop to dismiss a screen
37
43
  * @param count
38
44
  */
39
45
  pop = (count?: number) => {
40
- this.ref.current.dispatch(StackActions.pop(count ?? 1));
46
+ if (this.isReady.current) {
47
+ this.ref.current.dispatch(StackActions.pop(count ?? 1));
48
+ }
41
49
  };
42
50
 
43
51
  /**
@@ -45,7 +53,9 @@ class Navigator {
45
53
  * @param params
46
54
  */
47
55
  present = (params: ScreenParams) => {
48
- this.ref.current.dispatch(StackActions.push('Dialog', params));
56
+ if (this.isReady.current) {
57
+ this.ref.current.dispatch(StackActions.push('Dialog', params));
58
+ }
49
59
  };
50
60
 
51
61
  /**
@@ -53,7 +63,9 @@ class Navigator {
53
63
  * @param params
54
64
  */
55
65
  showModal = (params: ModalParams) => {
56
- this.ref.current.dispatch(StackActions.push('Modal', params));
66
+ if (this.isReady.current) {
67
+ this.ref.current.dispatch(StackActions.push('Modal', params));
68
+ }
57
69
  };
58
70
 
59
71
  /**
@@ -61,19 +73,23 @@ class Navigator {
61
73
  * @param params
62
74
  */
63
75
  showBottomSheet = (params: BottomSheetParams) => {
64
- this.ref.current.dispatch(
65
- StackActions.push('Modal', {
66
- ...params,
67
- isBottomSheet: true,
68
- }),
69
- );
76
+ if (this.isReady.current) {
77
+ this.ref.current.dispatch(
78
+ StackActions.push('Modal', {
79
+ ...params,
80
+ isBottomSheet: true,
81
+ }),
82
+ );
83
+ }
70
84
  };
71
85
 
72
86
  /**
73
87
  * pop all screen route
74
88
  */
75
89
  popToTop = () => {
76
- this.ref.current.dispatch(StackActions.popToTop());
90
+ if (this.isReady.current) {
91
+ this.ref.current.dispatch(StackActions.popToTop());
92
+ }
77
93
  };
78
94
 
79
95
  /**
@@ -82,12 +98,14 @@ class Navigator {
82
98
  * @param params
83
99
  */
84
100
  navigate = (name: string, params: any) => {
85
- this.ref.current.dispatch(
86
- CommonActions.navigate({
87
- name,
88
- params,
89
- }),
90
- );
101
+ if (this.isReady.current) {
102
+ this.ref.current.dispatch(
103
+ CommonActions.navigate({
104
+ name,
105
+ params,
106
+ }),
107
+ );
108
+ }
91
109
  };
92
110
 
93
111
  /**
@@ -95,18 +113,20 @@ class Navigator {
95
113
  * @param params
96
114
  */
97
115
  reset = (params: ScreenParams) => {
98
- this.ref.current.dispatch(
99
- CommonActions.reset({
100
- index: 0,
101
- routes: [
102
- {
103
- name: 'Stack',
104
- key: `Stack_${new Date().getTime()}`,
105
- params,
106
- },
107
- ],
108
- }),
109
- );
116
+ if (this.isReady.current) {
117
+ this.ref.current.dispatch(
118
+ CommonActions.reset({
119
+ index: 0,
120
+ routes: [
121
+ {
122
+ name: 'Stack',
123
+ key: `Stack_${new Date().getTime()}`,
124
+ params,
125
+ },
126
+ ],
127
+ }),
128
+ );
129
+ }
110
130
  };
111
131
 
112
132
  /**
@@ -1,29 +1,54 @@
1
- import React, {useContext, useEffect, useLayoutEffect} from 'react';
1
+ import React, {useContext, useEffect, useLayoutEffect, useRef} from 'react';
2
2
  import {ScreenParams} from './types';
3
3
  import Navigation from './Navigation';
4
- import {ApplicationContext, MiniAppContext} from './index';
4
+ import {ApplicationContext} from './index';
5
5
  import {GridSystem} from '../Layout';
6
6
  import {useHeaderHeight} from '@react-navigation/stack';
7
+ import {Alert, InteractionManager} from 'react-native';
8
+
9
+ const {
10
+ ScreenContext,
11
+ MiniAppContext,
12
+ } = require('react-native/Libraries/Components/ScreenContext');
7
13
 
8
14
  const StackScreen: React.FC<ScreenParams> = props => {
9
15
  const {theme, showGrid, navigator} = useContext(ApplicationContext);
10
- const context = useContext(MiniAppContext);
16
+ const startTime = useRef(Date.now());
17
+ const endTime = useRef(Date.now());
18
+ const timeLoad = useRef(0);
19
+ const timeout = useRef<any>();
20
+
21
+ const context = useContext<any>(MiniAppContext);
11
22
 
12
- const {screen: Component, options}: ScreenParams = props.route.params;
23
+ const {screen: Component, options, initialParams} = props.route.params;
13
24
  const navigation = new Navigation(props.navigation, theme);
14
25
  const heightHeader = useHeaderHeight();
15
26
 
16
- const params = {
27
+ const data = {
17
28
  ...props.route.params,
29
+ ...initialParams,
18
30
  navigation,
19
31
  };
20
32
 
21
- delete params.screen;
33
+ delete data.screen;
34
+ delete data.initialParams;
35
+
36
+ const screenName = Component?.name || Component?.type?.name || 'Invalid';
22
37
 
23
38
  /**
24
39
  * set options for screen
25
40
  */
26
41
  useLayoutEffect(() => {
42
+ const host = (global as any).miniAppApi?.currentHost || {};
43
+ host.screenName = screenName;
44
+ InteractionManager.runAfterInteractions(() => {
45
+ navigator?.maxApi?.showToastDebug?.({
46
+ appId: `auto - ${context.appId}`,
47
+ message: `${screenName} screen_interaction_time ${
48
+ Date.now() - startTime.current
49
+ }`,
50
+ });
51
+ });
27
52
  if (options) {
28
53
  navigation.setOptions(options);
29
54
  }
@@ -33,9 +58,18 @@ const StackScreen: React.FC<ScreenParams> = props => {
33
58
  * tracking for screen
34
59
  */
35
60
  useEffect(() => {
61
+ if (__DEV__) {
62
+ if (!screenName || screenName === 'screen') {
63
+ Alert.alert(
64
+ 'Design system',
65
+ `Unvalidated screen name "${screenName}" !`,
66
+ );
67
+ }
68
+ }
69
+
36
70
  const tracking = {
37
71
  ...context,
38
- screen_name: Component.name,
72
+ screen_name: screenName,
39
73
  };
40
74
 
41
75
  const routes = props.navigation.getState()?.routes || [];
@@ -45,26 +79,63 @@ const StackScreen: React.FC<ScreenParams> = props => {
45
79
  pre_screen_name = routes[routesLength - 2]?.params?.screen?.name;
46
80
  }
47
81
 
48
- navigator?.maxApi?.trackEvent?.('service_frame_displayed', {
82
+ const params = {
49
83
  ...tracking,
50
84
  pre_screen_name,
51
85
  trigger_id: '111378000000000000',
86
+ };
87
+ navigator?.maxApi?.trackEvent?.('service_frame_displayed', params);
88
+ navigator?.maxApi?.showToastDebug?.({
89
+ appId: `auto - ${context.appId}`,
90
+ message: `${screenName} service_frame_displayed`,
91
+ params,
52
92
  });
53
93
 
54
94
  return props.navigation.addListener('beforeRemove', () => {
55
- navigator?.maxApi?.trackEvent?.('service_icon_clicked', {
95
+ const params = {
56
96
  ...tracking,
57
97
  trigger_id: '111154000000000000',
58
98
  icon_name: 'back',
99
+ };
100
+ navigator?.maxApi?.trackEvent?.('service_icon_clicked', params);
101
+ navigator?.maxApi?.showToastDebug?.({
102
+ appId: `auto - ${context.appId}`,
103
+ message: `${screenName} service_icon_clicked`,
104
+ params,
59
105
  });
60
106
  });
61
107
  }, []);
62
108
 
63
109
  return (
64
- <>
65
- <Component heightHeader={heightHeader} {...params} />
110
+ <ScreenContext.Provider
111
+ value={{
112
+ appId: context.appId,
113
+ code: context.code,
114
+ screenName,
115
+ autoTracking: (data: any) => {
116
+ navigator?.maxApi?.dispatchFunction?.('autoTracking', data);
117
+ },
118
+ onElementLoad: () => {
119
+ endTime.current = Date.now();
120
+ clearTimeout(timeout.current);
121
+ timeout.current = setTimeout(() => {
122
+ if (timeLoad.current === 0) {
123
+ timeLoad.current = endTime.current - startTime.current;
124
+ navigator?.maxApi?.showToastDebug?.({
125
+ appId: `auto - ${context.appId}`,
126
+ message: `${screenName} screen_load_time ${timeLoad.current}`,
127
+ });
128
+ }
129
+ }, 2000);
130
+ },
131
+ }}>
132
+ <Component
133
+ heightHeader={heightHeader}
134
+ {...data}
135
+ onScreenLoad={() => {}}
136
+ />
66
137
  {showGrid && <GridSystem />}
67
- </>
138
+ </ScreenContext.Provider>
68
139
  );
69
140
  };
70
141
 
@@ -4,10 +4,8 @@ import {
4
4
  HeaderAnimated,
5
5
  HeaderBackground,
6
6
  HeaderCustom,
7
- HeaderLeft,
8
7
  HeaderRightAction,
9
8
  HeaderTitle,
10
- HeaderToolkitAction,
11
9
  NavigationButton,
12
10
  } from './Components';
13
11
  import BottomTab from './BottomTab';
@@ -32,10 +30,8 @@ export {
32
30
  BottomTab,
33
31
  NavigationButton,
34
32
  HeaderTitle,
35
- HeaderLeft,
36
33
  HeaderBackground,
37
34
  HeaderRightAction,
38
- HeaderToolkitAction,
39
35
  HeaderCustom,
40
36
  HeaderAnimated,
41
37
  };
@@ -1,9 +1,9 @@
1
- import {Animated} from 'react-native';
2
1
  import {StackNavigationOptions} from '@react-navigation/stack';
3
2
  import React from 'react';
4
- import Navigator from './Navigator';
3
+ import {Animated, FlatListProps, ViewProps} from 'react-native';
5
4
  import {PopupNotifyProps} from '../Popup/types';
6
5
  import Navigation from './Navigation';
6
+ import Navigator from './Navigator';
7
7
 
8
8
  type Screen = React.ComponentType<NavigationScreenProps>;
9
9
 
@@ -95,6 +95,7 @@ export type NavigationContainerProps = {
95
95
  theme: Theme;
96
96
  maxApi: any;
97
97
  localization?: LocalizationObject;
98
+ initialParams?: any;
98
99
  };
99
100
 
100
101
  export type NavigationScreenProps = {
@@ -118,8 +119,8 @@ export interface ModalParams extends ScreenParams {
118
119
  export interface BottomSheetParams extends ScreenParams {
119
120
  [key: string]: any;
120
121
  onDismiss?: () => void;
121
- snapPoint?: '30%' | '50%' | '70%' | '90%' | 'content';
122
- indicatorColor?: string;
122
+ footerComponent?: React.ReactNode;
123
+ flatListProps: FlatListProps<any>;
123
124
  }
124
125
 
125
126
  export type NavigationButtonProps = {
@@ -129,6 +130,7 @@ export type NavigationButtonProps = {
129
130
  onPress: () => void;
130
131
  badgeType?: 'dot' | 'number';
131
132
  badgeValue?: number;
133
+ accessibilityID?: string;
132
134
  };
133
135
 
134
136
  export type PinnedToolType = {
@@ -152,6 +154,7 @@ export interface NavigationOptions extends StackNavigationOptions {
152
154
  toolkitParams?: {
153
155
  pinnedTool?: PinnedToolType;
154
156
  runtimeTools?: RuntimeToolType[];
157
+ preventClose?: PopupNotifyProps;
155
158
  };
156
159
  }
157
160
 
@@ -183,11 +186,10 @@ export type HeaderToolkitProps = {
183
186
  configs: any[];
184
187
  };
185
188
 
186
- export type HeaderAnimatedProps = {
189
+ export interface HeaderAnimatedProps extends ViewProps {
187
190
  animatedValue: Animated.Value;
188
191
  image: string;
189
- onPress?: () => void;
190
- };
192
+ }
191
193
 
192
194
  export type BottomTabItemProps = {
193
195
  name: string;
@@ -208,6 +210,14 @@ export type BottomTabProps = {
208
210
  tabPress?: any;
209
211
  focus?: any;
210
212
  };
213
+ floatingButton?: FloatingButtonProps;
214
+ };
215
+
216
+ export type FloatingButtonProps = {
217
+ container?: React.ReactNode;
218
+ icon: string | React.ReactNode;
219
+ label: string;
220
+ onPress: () => void;
211
221
  };
212
222
 
213
223
  export type AnimatedHeader = {
@@ -12,7 +12,8 @@ import {
12
12
  } from './Components';
13
13
  import {NavigationOptions, Theme} from './types';
14
14
  import {Colors} from '../Consts';
15
-
15
+ import {Platform} from 'react-native';
16
+ const isIOS = Platform.OS === 'ios';
16
17
  /**
17
18
  * default options for stack screen
18
19
  */
@@ -126,19 +127,14 @@ const getOptions = (params: NavigationOptions, theme: Theme) => {
126
127
  };
127
128
  }
128
129
 
130
+ if (params.toolkitParams) {
129
131
  options = {
130
132
  ...options,
131
133
  headerRight: (props: any) => {
132
- return (
133
- <HeaderToolkitAction
134
- preventBack={params.preventBack}
135
- pinnedTool={params.toolkitParams?.pinnedTool}
136
- runtimeTools={params.toolkitParams?.runtimeTools}
137
- {...props}
138
- />
139
- );
134
+ return <HeaderToolkitAction {...params.toolkitParams} {...props} />;
140
135
  },
141
136
  };
137
+ }
142
138
 
143
139
  return {
144
140
  ...params,
@@ -146,4 +142,71 @@ const getOptions = (params: NavigationOptions, theme: Theme) => {
146
142
  };
147
143
  };
148
144
 
149
- export {getStackOptions, getDialogOptions, getModalOptions, getOptions};
145
+ const getAccessibilityID = (
146
+ key = '',
147
+ override: string | undefined = undefined,
148
+ ) => {
149
+ if (isIOS) {
150
+ return {
151
+ accessible: true,
152
+ testID: override || key,
153
+ };
154
+ } else {
155
+ return {
156
+ accessibilityLabel: override || key,
157
+ };
158
+ }
159
+ };
160
+
161
+ const cleanNameForTracking = (word = ''): string => {
162
+ return word
163
+ .normalize('NFD')
164
+ .replace(/[\u0300-\u036f |\s]/g, '')
165
+ .toLowerCase();
166
+ };
167
+
168
+ const formatComponentName = (word = '') => {
169
+ const accentsMap = {
170
+ a: 'á|à|ả|ã|ạ|â|ấ|ầ|ẩ|ẫ|ậ|ă|ắ|ằ|ẳ|ẵ|ặ',
171
+ A: 'Á|À|Ả|Ã|Ạ|Â|Ấ|Ầ|Ẩ|Ẫ|Ậ|Ă|Ắ|Ằ|Ẳ|Ẵ|Ặ',
172
+ e: 'é|è|ẻ|ẽ|ẹ|ê|ế|ề|ể|ễ|ệ',
173
+ E: 'É|È|Ẻ|Ẽ|Ẹ|Ê|Ế|Ề|Ể|Ễ|Ệ',
174
+ i: 'í|ì|ỉ|ĩ|ị',
175
+ I: 'Í|Ì|Ỉ|Ĩ|Ị',
176
+ o: 'ó|ò|ỏ|õ|ọ|ô|ố|ồ|ổ|ỗ|ộ|ơ|ớ|ờ|ở|ỡ|ợ',
177
+ O: 'Ó|Ò|Ỏ|Õ|Ọ|Ô|Ố|Ồ|Ổ|Ỗ|Ộ|Ơ|Ớ|Ờ|Ở|Ỡ|Ợ',
178
+ u: 'ú|ù|ủ|ũ|ụ|ư|ứ|ừ|ử|ữ|ự',
179
+ U: 'Ú|Ù|Ủ|Ũ|Ụ|Ư|Ứ|Ừ|Ử|Ữ|Ự',
180
+ y: 'ý|ỳ|ỷ|ỹ|ỵ',
181
+ Y: 'Ý|Ỳ|Ỷ|Ỹ|Ỵ',
182
+ d: 'đ',
183
+ D: 'Đ',
184
+ };
185
+
186
+ for (const [nonAccent, accentGroup] of Object.entries(accentsMap)) {
187
+ const accents = new RegExp(accentGroup, 'g');
188
+ word = word.replace(accents, nonAccent);
189
+ }
190
+
191
+ word = word.replace(/\s+/g, '_').toLowerCase();
192
+
193
+ return word;
194
+ };
195
+
196
+ const formatNameLength = (name = '') => {
197
+ let formattedName = name;
198
+ if (formattedName.length > 100) {
199
+ formattedName = formattedName.substring(0, 100);
200
+ }
201
+ return formattedName;
202
+ };
203
+ export {
204
+ getStackOptions,
205
+ getDialogOptions,
206
+ getModalOptions,
207
+ getOptions,
208
+ formatComponentName,
209
+ formatNameLength,
210
+ getAccessibilityID,
211
+ cleanNameForTracking,
212
+ };
package/Badge/Badge.tsx CHANGED
@@ -46,7 +46,7 @@ const Badge: FC<BadgeProps> = ({label = 'Label', style, backgroundColor}) => {
46
46
 
47
47
  return (
48
48
  <View style={[style, styles.badge, {backgroundColor: badgeColor}]}>
49
- <Text color={Colors.black_01} typography={'action_xs_bold'}>
49
+ <Text color={Colors.black_01} typography={'action_xxs_bold'}>
50
50
  {formatTitle()}
51
51
  </Text>
52
52
  </View>
package/Button/index.tsx CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  TouchableOpacityProps,
7
7
  View,
8
8
  } from 'react-native';
9
-
10
9
  import LinearGradient from 'react-native-linear-gradient';
11
10
  import {ApplicationContext} from '../Application';
12
11
  import {Text} from '../Text';
@@ -15,6 +14,16 @@ import {Colors, Spacing} from '../Consts';
15
14
  import styles from './styles';
16
15
  import {Icon} from '../Icon';
17
16
  import {Loader} from '../Loader';
17
+ import {
18
+ cleanNameForTracking,
19
+ formatComponentName,
20
+ getAccessibilityID,
21
+ } from '../Application/utils';
22
+
23
+ const {
24
+ ScreenContext,
25
+ MiniAppContext,
26
+ } = require('react-native/Libraries/Components/ScreenContext');
18
27
 
19
28
  const AnimationLinear = Animated.createAnimatedComponent(LinearGradient);
20
29
 
@@ -70,6 +79,10 @@ export interface ButtonProps extends TouchableOpacityProps {
70
79
  * A callback function that is called when the button is pressed.
71
80
  */
72
81
  onPress: () => void;
82
+
83
+ params?: any;
84
+
85
+ componentId?: string;
73
86
  }
74
87
 
75
88
  const Button: FC<ButtonProps> = ({
@@ -82,9 +95,13 @@ const Button: FC<ButtonProps> = ({
82
95
  loading = false,
83
96
  title = 'Button',
84
97
  onPress,
98
+ params,
99
+ componentId,
100
+ accessibilityLabel,
85
101
  ...rest
86
102
  }) => {
87
103
  const {theme, config} = useContext(ApplicationContext);
104
+ const {appId, code} = useContext<any>(MiniAppContext);
88
105
  const {gradient, color} = config?.navigationBar?.buttonColors ?? {};
89
106
  let gradientPros;
90
107
  if (gradient?.length > 0 && type == 'primary') {
@@ -94,6 +111,8 @@ const Button: FC<ButtonProps> = ({
94
111
  end: {x: 0, y: 0.5},
95
112
  };
96
113
  }
114
+
115
+ const screenInfo = useContext<any>(ScreenContext);
97
116
  const getSizeStyle = () => {
98
117
  const styleSheet: {[key: string]: any} = styles;
99
118
  return styleSheet[size ?? 'small'];
@@ -291,19 +310,40 @@ const Button: FC<ButtonProps> = ({
291
310
  const activeOpacity = type === 'disabled' ? 0.75 : 0.5;
292
311
 
293
312
  return (
294
- <TouchableOpacity
295
- {...rest}
296
- activeOpacity={activeOpacity}
297
- onPress={onPressButton}
298
- style={buttonStyle}>
299
- {renderLeading()}
300
- {renderTitle()}
301
- {renderTrailing()}
302
- {gradientPros && (
303
- <AnimationLinear {...gradientPros} style={styles.gradientView} />
304
- )}
305
- {gradientPros && <View style={styles.strokeView} />}
306
- </TouchableOpacity>
313
+ <ScreenContext.Provider
314
+ value={{
315
+ ...screenInfo,
316
+ componentName: 'Button',
317
+ params,
318
+ state: loading
319
+ ? 'loading'
320
+ : type === 'disabled'
321
+ ? 'disabled'
322
+ : 'enabled',
323
+ componentId,
324
+ componentValue: formatComponentName(title),
325
+ action: 'click',
326
+ }}>
327
+ <TouchableOpacity
328
+ {...rest}
329
+ {...getAccessibilityID(
330
+ `${appId}/${code}/${screenInfo.screenName}/btn_${cleanNameForTracking(
331
+ title,
332
+ )}`,
333
+ accessibilityLabel,
334
+ )}
335
+ activeOpacity={activeOpacity}
336
+ onPress={onPressButton}
337
+ style={buttonStyle}>
338
+ {renderLeading()}
339
+ {renderTitle()}
340
+ {renderTrailing()}
341
+ {gradientPros && (
342
+ <AnimationLinear {...gradientPros} style={styles.gradientView} />
343
+ )}
344
+ {gradientPros && <View style={styles.strokeView} />}
345
+ </TouchableOpacity>
346
+ </ScreenContext.Provider>
307
347
  );
308
348
  };
309
349