@momo-kits/foundation 0.81.6 → 0.81.7-beta.2

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.
@@ -1,6 +1,7 @@
1
1
  import React, {Fragment, useContext, useEffect, useState} from 'react';
2
2
  import {
3
3
  Animated,
4
+ BackHandler,
4
5
  DeviceEventEmitter,
5
6
  StatusBar,
6
7
  StyleSheet,
@@ -149,8 +150,29 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
149
150
  accessibilityLabel = '',
150
151
  }) => {
151
152
  const {navigator} = useContext(ApplicationContext);
153
+
154
+ useEffect(() => {
155
+ const backHandler = BackHandler.addEventListener(
156
+ 'hardwareBackPress',
157
+ goBack,
158
+ );
159
+
160
+ return () => backHandler.remove();
161
+ }, []);
162
+
152
163
  const goBack = () => {
153
164
  const canGoBack = navigator?.ref.current?.canGoBack();
165
+ const currentRoute = navigator?.ref?.current.getCurrentRoute();
166
+
167
+ if (navigator?.trackingInfo) {
168
+ navigator?.maxApi?.trackEvent('service_icon_clicked', {
169
+ ...navigator?.trackingInfo,
170
+ screen_name: currentRoute?.params?.screen?.name ?? currentRoute?.name,
171
+ trigger_id: '111154000000000000',
172
+ icon_name: 'back',
173
+ });
174
+ }
175
+
154
176
  if (canGoBack) {
155
177
  if (preventBack) {
156
178
  navigator?.showModal({
@@ -176,6 +198,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
176
198
  DeviceEventEmitter.emit('dismiss', {requestId});
177
199
  }
178
200
  onPressLeftHeader?.();
201
+ return true;
179
202
  };
180
203
 
181
204
  return (
@@ -308,9 +331,25 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
308
331
  }
309
332
 
310
333
  useEffect(() => {
311
- navigator?.getToolkit().then((toolConfig: any) => {
312
- setToolConfig(toolConfig);
313
- });
334
+ if (navigator?.toolkitConfig) {
335
+ setToolConfig(navigator?.toolkitConfig);
336
+ } else {
337
+ navigator?.requestApi('getToolkit', [undefined], (result: boolean) => {
338
+ if (result) {
339
+ navigator?.requestApi(
340
+ 'getToolkitConfig',
341
+ [undefined],
342
+ (config: any) => {
343
+ navigator.toolkitConfig = {
344
+ ...config?.utilityToolConfig,
345
+ appCode: config?.appCode,
346
+ };
347
+ setToolConfig(navigator?.toolkitConfig);
348
+ },
349
+ );
350
+ }
351
+ });
352
+ }
314
353
  }, []);
315
354
 
316
355
  useEffect(() => {
@@ -26,6 +26,9 @@ import {
26
26
  import {HeaderBackground, HeaderLeft, HeaderTitle} from './Components';
27
27
 
28
28
  const ModalScreen: React.FC<any> = props => {
29
+ /**
30
+ * dismiss callback when modal | bottom sheet dismissed
31
+ */
29
32
  useEffect(() => {
30
33
  return () => {
31
34
  props.route.params?.onDismiss?.();
@@ -40,7 +43,7 @@ const ModalScreen: React.FC<any> = props => {
40
43
 
41
44
  const Modal: React.FC<ModalParams> = props => {
42
45
  const {theme, navigator} = useContext(ApplicationContext);
43
- const {screen, barrierDismissible}: ModalParams = props.route.params;
46
+ const {screen, barrierDismissible, modalName} = props.route.params;
44
47
  const Component = useRef(screen).current;
45
48
  const navigation = new Navigation(props.navigation, theme);
46
49
  const params = {
@@ -50,6 +53,42 @@ const Modal: React.FC<ModalParams> = props => {
50
53
 
51
54
  delete params.screen;
52
55
 
56
+ /**
57
+ * tracking
58
+ */
59
+ useEffect(() => {
60
+ if (navigator?.trackingInfo) {
61
+ const TRIGGER_ID = '111012100000000000';
62
+ const navigationRoutes = props.navigation.getState()?.routes || [];
63
+ const routesLength = navigationRoutes.length;
64
+ let screen_name = navigationRoutes?.[0]?.params?.screen?.name;
65
+ if (routesLength > 1) {
66
+ screen_name = navigationRoutes[routesLength - 2]?.params?.screen?.name;
67
+ }
68
+ const error_code = (screen as Function)?.()?.props?.information;
69
+ navigator?.maxApi?.trackEvent('service_component_displayed', {
70
+ ...navigator?.trackingInfo,
71
+ screen_name,
72
+ trigger_id: '111012100000000000',
73
+ component_type: 'popup',
74
+ component_name: modalName,
75
+ error_code,
76
+ });
77
+
78
+ return () => {
79
+ navigator?.maxApi?.trackEvent('service_icon_clicked', {
80
+ ...navigator?.trackingInfo,
81
+ screen_name,
82
+ trigger_id: '111154100000000001',
83
+ component_type: 'popup',
84
+ component_name: modalName,
85
+ error_code,
86
+ icon_name: 'close',
87
+ });
88
+ };
89
+ }
90
+ }, []);
91
+
53
92
  const onDismiss = () => {
54
93
  if (barrierDismissible) {
55
94
  return;
@@ -181,6 +220,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
181
220
  />
182
221
  </View>
183
222
  <NavigationContainer
223
+ trackingInfo={navigator?.trackingInfo}
224
+ maxApi={navigator?.maxApi}
184
225
  theme={{
185
226
  ...theme,
186
227
  assets: {...theme.assets, headerBackground: undefined},
@@ -223,7 +264,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
223
264
  icon="24_navigation_close"
224
265
  backgroundColor="transparent"
225
266
  useBorder={false}
226
- onPress={bottomSheetRef.current?.dismiss}
267
+ onPress={() => bottomSheetRef.current?.dismiss()}
227
268
  accessibilityLabel={'BottomSheet/Close'}
228
269
  />
229
270
  </HeaderRightAction>
@@ -14,6 +14,10 @@ class Navigation {
14
14
  this.theme = theme;
15
15
  }
16
16
 
17
+ /**
18
+ * navigation options filter by design system rule
19
+ * @param params
20
+ */
17
21
  private filterParams = (params: NavigationOptions) => {
18
22
  if (params.headerRight) {
19
23
  const headerRight = params.headerRight?.({}) as React.ReactElement;
@@ -24,14 +28,21 @@ class Navigation {
24
28
  );
25
29
  }
26
30
  }
31
+
32
+ if (params.headerLeft) {
33
+ console.warn('headerLeft not allow override render by design system!');
34
+ delete params.headerLeft;
35
+ }
36
+
27
37
  return params;
28
38
  };
29
39
 
40
+ /**
41
+ * navigation setOptions support props NavigationOptions base on react-navigation options
42
+ * @param params
43
+ */
30
44
  setOptions = (params: NavigationOptions) => {
31
- if (__DEV__) {
32
- params = this.filterParams(params);
33
- }
34
- params = getOptions(params, this.theme);
45
+ params = getOptions(this.filterParams(params), this.theme);
35
46
  this.instance.setOptions(params);
36
47
  };
37
48
  }
@@ -23,12 +23,16 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
23
23
  options,
24
24
  maxApi,
25
25
  localization,
26
+ trackingInfo,
26
27
  }) => {
27
28
  const navigationRef = React.useRef<NavigationContainerRef>(null);
28
29
  const navigator = useRef(new Navigator(navigationRef));
29
30
  const [showGrid, setShowGrid] = useState(false);
30
31
  const [language, setLanguage] = useState<'vi' | 'en'>('vi');
31
32
 
33
+ /**
34
+ * engine only shake to enable grid view
35
+ */
32
36
  useEffect(() => {
33
37
  const subscription = DeviceEventEmitter.addListener(
34
38
  'onChangeGrid',
@@ -37,10 +41,13 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
37
41
  },
38
42
  );
39
43
  return () => {
40
- subscription.remove();
44
+ subscription?.remove?.();
41
45
  };
42
46
  }, []);
43
47
 
48
+ /**
49
+ * handle mini language & listen change
50
+ */
44
51
  useEffect(() => {
45
52
  maxApi?.getLanguage?.((lang: 'vi' | 'en') => {
46
53
  setLanguage(lang);
@@ -53,8 +60,16 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
53
60
  };
54
61
  }, []);
55
62
 
63
+ /**
64
+ * inject data for navigator
65
+ */
56
66
  navigator.current.maxApi = maxApi;
67
+ navigator.current.trackingInfo = trackingInfo;
57
68
 
69
+ /**
70
+ * translate
71
+ * @param key
72
+ */
58
73
  const translate = (key: string) => {
59
74
  const mergedLocalization: LocalizationObject = {
60
75
  vi: {...localization?.vi, ...kitLocalization.vi},
@@ -4,37 +4,62 @@ import {
4
4
  HeaderToolkitProps,
5
5
  ModalParams,
6
6
  ScreenParams,
7
+ TrackingInfo,
7
8
  } from './types';
8
9
 
9
10
  class Navigator {
10
11
  ref?: any;
11
12
  toolkitConfig?: HeaderToolkitProps;
12
13
  maxApi?: any;
14
+ trackingInfo?: TrackingInfo;
13
15
 
14
16
  constructor(navigation: any) {
15
17
  this.ref = navigation;
16
18
  }
17
19
 
20
+ /**
21
+ * push new stack screen
22
+ * @param params
23
+ */
18
24
  push = (params: ScreenParams) => {
19
25
  this.ref.current.dispatch(StackActions.push('Stack', params));
20
26
  };
21
27
 
28
+ /**
29
+ * replace current screen with new screen
30
+ * @param params
31
+ */
22
32
  replace = (params: ScreenParams) => {
23
33
  this.ref.current.dispatch(StackActions.replace('Stack', params));
24
34
  };
25
-
35
+ /**
36
+ * pop to dismiss a screen
37
+ * @param count
38
+ */
26
39
  pop = (count?: number) => {
27
40
  this.ref.current.dispatch(StackActions.pop(count ?? 1));
28
41
  };
29
42
 
43
+ /**
44
+ * present a new screen, show from bottom iOS
45
+ * @param params
46
+ */
30
47
  present = (params: ScreenParams) => {
31
48
  this.ref.current.dispatch(StackActions.push('Dialog', params));
32
49
  };
33
50
 
51
+ /**
52
+ * show a modal popup
53
+ * @param params
54
+ */
34
55
  showModal = (params: ModalParams) => {
35
56
  this.ref.current.dispatch(StackActions.push('Modal', params));
36
57
  };
37
58
 
59
+ /**
60
+ * show a bottom sheet
61
+ * @param params
62
+ */
38
63
  showBottomSheet = (params: BottomSheetParams) => {
39
64
  this.ref.current.dispatch(
40
65
  StackActions.push('Modal', {
@@ -44,10 +69,17 @@ class Navigator {
44
69
  );
45
70
  };
46
71
 
72
+ /**
73
+ * pop all screen route
74
+ */
47
75
  popToTop = () => {
48
76
  this.ref.current.dispatch(StackActions.popToTop());
49
77
  };
50
78
 
79
+ /**
80
+ * reset a navigation flow with new screen
81
+ * @param params
82
+ */
51
83
  reset = (params: ScreenParams) => {
52
84
  this.ref.current.dispatch(
53
85
  CommonActions.reset({
@@ -63,36 +95,17 @@ class Navigator {
63
95
  );
64
96
  };
65
97
 
98
+ /**
99
+ * request max api for mini app
100
+ * @param func
101
+ * @param args
102
+ * @param callback
103
+ */
66
104
  requestApi = (func: any, args: any = [], callback: any) => {
67
105
  if (this.maxApi) {
68
106
  this.maxApi[func]?.(...args, callback);
69
- } else {
70
- const instance: any = global;
71
- instance?.miniAppApi?.dispatch?.(func, ...args, callback);
72
107
  }
73
108
  };
74
-
75
- getToolkit = async (): Promise<HeaderToolkitProps | undefined> => {
76
- return new Promise(resolve => {
77
- if (this.toolkitConfig) {
78
- resolve(this.toolkitConfig);
79
- return;
80
- }
81
- this.requestApi('getToolkit', [undefined], (result: boolean) => {
82
- if (result) {
83
- this.requestApi('getToolkitConfig', [undefined], (config: any) => {
84
- this.toolkitConfig = {
85
- ...config?.utilityToolConfig,
86
- appCode: config?.appCode,
87
- };
88
- resolve(this.toolkitConfig);
89
- });
90
- } else {
91
- resolve(undefined);
92
- }
93
- });
94
- });
95
- };
96
109
  }
97
110
 
98
111
  export default Navigator;
@@ -1,4 +1,4 @@
1
- import React, {useContext, useLayoutEffect} from 'react';
1
+ import React, {useContext, useEffect, useLayoutEffect} from 'react';
2
2
  import {ScreenParams} from './types';
3
3
  import Navigation from './Navigation';
4
4
  import {ApplicationContext} from './index';
@@ -6,7 +6,7 @@ import {GridSystem} from '../Layout';
6
6
 
7
7
  const StackScreen: React.FC<ScreenParams> = props => {
8
8
  const {screen: Component, options}: ScreenParams = props.route.params;
9
- const {theme, showGrid} = useContext(ApplicationContext);
9
+ const {theme, showGrid, navigator} = useContext(ApplicationContext);
10
10
  const navigation = new Navigation(props.navigation, theme);
11
11
 
12
12
  const params = {
@@ -16,12 +16,36 @@ const StackScreen: React.FC<ScreenParams> = props => {
16
16
 
17
17
  delete params.screen;
18
18
 
19
+ /**
20
+ * set options for screen
21
+ */
19
22
  useLayoutEffect(() => {
20
23
  if (options) {
21
24
  navigation.setOptions(options);
22
25
  }
23
26
  }, [options]);
24
27
 
28
+ /**
29
+ * tracking for screen
30
+ */
31
+ useEffect(() => {
32
+ if (navigator?.trackingInfo) {
33
+ const navigationRoutes = props.navigation.getState()?.routes || [];
34
+ const routesLength = navigationRoutes.length;
35
+ let pre_screen_name = navigationRoutes?.[0]?.params?.screen?.name;
36
+ if (routesLength > 1) {
37
+ pre_screen_name =
38
+ navigationRoutes[routesLength - 2]?.params?.screen?.name;
39
+ }
40
+ navigator?.maxApi?.trackEvent('service_frame_displayed', {
41
+ ...navigator?.trackingInfo,
42
+ screen_name: Component.name,
43
+ pre_screen_name,
44
+ trigger_id: '111378000000000000',
45
+ });
46
+ }
47
+ }, []);
48
+
25
49
  return (
26
50
  <>
27
51
  <Component {...params} />
@@ -79,12 +79,20 @@ export type LocalizationObject = {
79
79
  };
80
80
  };
81
81
 
82
+ export type TrackingInfo = {
83
+ [key: string]: any;
84
+ feature_code: string;
85
+ app_id: string;
86
+ miniapp_version: number;
87
+ };
88
+
82
89
  export type NavigationContainerProps = {
83
90
  screen: Screen;
84
91
  options?: NavigationOptions;
85
92
  theme: Theme;
86
- maxApi?: any;
93
+ maxApi: any;
87
94
  localization?: LocalizationObject;
95
+ trackingInfo?: TrackingInfo;
88
96
  };
89
97
 
90
98
  export type NavigationScreenProps = {
@@ -97,10 +105,12 @@ export type ScreenParams = {
97
105
  [key: string]: any;
98
106
  screen: Screen;
99
107
  options?: NavigationOptions;
108
+ tracking?: any;
100
109
  };
101
110
 
102
111
  export interface ModalParams extends ScreenParams {
103
112
  [key: string]: any;
113
+ modalName?: string;
104
114
  onDismiss?: () => void;
105
115
  barrierDismissible?: boolean;
106
116
  }
@@ -13,6 +13,9 @@ import {
13
13
  import {NavigationOptions, Theme} from './types';
14
14
  import {Colors} from '../Consts';
15
15
 
16
+ /**
17
+ * default options for stack screen
18
+ */
16
19
  const getStackOptions = (): StackNavigationOptions => {
17
20
  return {
18
21
  headerTitleAlign: 'left',
@@ -24,6 +27,9 @@ const getStackOptions = (): StackNavigationOptions => {
24
27
  };
25
28
  };
26
29
 
30
+ /**
31
+ * default options for dialog screen
32
+ */
27
33
  const getDialogOptions = (): StackNavigationOptions => {
28
34
  return {
29
35
  headerTitleAlign: 'left',
@@ -35,6 +41,9 @@ const getDialogOptions = (): StackNavigationOptions => {
35
41
  };
36
42
  };
37
43
 
44
+ /**
45
+ * default options for modal screen
46
+ */
38
47
  const getModalOptions = (): StackNavigationOptions => {
39
48
  return {
40
49
  headerShown: false,
@@ -60,6 +69,9 @@ const getModalOptions = (): StackNavigationOptions => {
60
69
  };
61
70
  };
62
71
 
72
+ /**
73
+ * build react-navigation options
74
+ */
63
75
  const getOptions = (params: NavigationOptions, theme: Theme) => {
64
76
  let options = {};
65
77
 
package/Layout/Card.tsx CHANGED
@@ -145,6 +145,7 @@ const Card: React.FC<CardProps> = ({
145
145
  accessibilityLabel={`Card`}
146
146
  style={[
147
147
  style,
148
+ styles.protectedStyle,
148
149
  styles.card,
149
150
  {
150
151
  width: widthSection,
@@ -0,0 +1,45 @@
1
+ import React, {FC, useContext} from 'react';
2
+ import {SectionList, SectionListProps, View} from 'react-native';
3
+ import Item, {SpanNumber} from './Item';
4
+ import {GridContext} from './index';
5
+ import styles from './styles';
6
+
7
+ export interface ItemSectionListProps extends SectionListProps<any> {
8
+ /**
9
+ * Require key support number columns change grid system
10
+ */
11
+
12
+ key: string;
13
+ /**
14
+ * Represents the width of the Card component in terms of span numbers (1-12).
15
+ */
16
+ widthSpan?: SpanNumber;
17
+ }
18
+
19
+ const ItemSectionList: FC<ItemSectionListProps> = ({
20
+ key,
21
+ renderItem,
22
+ widthSpan,
23
+ style,
24
+ contentContainerStyle,
25
+ ...props
26
+ }) => {
27
+ const {gutterSize, numberOfColumns} = useContext(GridContext);
28
+ const widthItem = (widthSpan ?? numberOfColumns) as SpanNumber;
29
+ const _renderItem = (item: any) => {
30
+ return <Item widthSpan={widthItem}>{renderItem?.(item)}</Item>;
31
+ };
32
+
33
+ return (
34
+ <SectionList
35
+ {...props}
36
+ renderItem={_renderItem}
37
+ horizontal={false}
38
+ ItemSeparatorComponent={() => <View style={{height: gutterSize}} />}
39
+ style={[style, styles.protectedStyle]}
40
+ contentContainerStyle={[contentContainerStyle, styles.protectedStyle]}
41
+ />
42
+ );
43
+ };
44
+
45
+ export default ItemSectionList;
@@ -22,11 +22,6 @@ export interface SectionProps extends ViewProps {
22
22
  */
23
23
  useMargin?: boolean;
24
24
 
25
- /**
26
- * Optional. Background color for the Section component.
27
- */
28
- backgroundColor?: string;
29
-
30
25
  /**
31
26
  * Optional. Background image for the Section component.
32
27
  */
@@ -36,8 +31,8 @@ export interface SectionProps extends ViewProps {
36
31
  const Section: React.FC<SectionProps> = ({
37
32
  children,
38
33
  useMargin = true,
39
- backgroundColor,
40
34
  backgroundImage,
35
+ style,
41
36
  }) => {
42
37
  const {showGrid} = useContext(ApplicationContext);
43
38
  const {numberOfColumns} = useGridSystem();
@@ -156,13 +151,16 @@ const Section: React.FC<SectionProps> = ({
156
151
  }}>
157
152
  <View
158
153
  accessibilityLabel={`Section`}
159
- style={{
160
- width: widthSection,
161
- marginHorizontal: gridContext.margin,
162
- flexDirection: 'row',
163
- flexWrap: 'wrap',
164
- backgroundColor: backgroundColor,
165
- }}>
154
+ style={[
155
+ style,
156
+ styles.protectedStyle,
157
+ {
158
+ width: widthSection,
159
+ marginHorizontal: gridContext.margin,
160
+ flexDirection: 'row',
161
+ flexWrap: 'wrap',
162
+ },
163
+ ]}>
166
164
  {!!backgroundImage && (
167
165
  <Image
168
166
  source={{uri: backgroundImage}}
package/Layout/styles.ts CHANGED
@@ -45,19 +45,7 @@ export default StyleSheet.create({
45
45
  flexDirection: 'row',
46
46
  flexWrap: 'wrap',
47
47
  borderRadius: Radius.M,
48
- paddingTop: undefined,
49
- paddingBottom: undefined,
50
- paddingLeft: undefined,
51
- paddingRight: undefined,
52
- paddingVertical: undefined,
53
- paddingHorizontal: undefined,
54
48
  padding: 12,
55
- margin: undefined,
56
- marginTop: undefined,
57
- marginBottom: undefined,
58
- marginLeft: undefined,
59
- marginRight: undefined,
60
- marginVertical: undefined,
61
49
  },
62
50
  item: {
63
51
  overflow: 'hidden',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.81.6",
3
+ "version": "0.81.7-beta.2",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -38,6 +38,7 @@
38
38
  "metro-react-native-babel-preset": "^0.64.0",
39
39
  "react-test-renderer": "17.0.1",
40
40
  "typescript": "^4.0.3",
41
+ "@momo-platform/versions": "4.1.11",
41
42
  "react-scanner": "^1.1.0"
42
43
  },
43
44
  "author": "@momo-kits/foundation",