@momo-kits/foundation 0.80.8 → 0.81.1-beta.0

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.
Files changed (87) hide show
  1. package/Application/BottomSheet.tsx +212 -0
  2. package/Application/BottomTab/BottomTabBar.tsx +278 -0
  3. package/Application/BottomTab/index.tsx +143 -0
  4. package/Application/Components.tsx +384 -213
  5. package/Application/ModalScreen.tsx +14 -178
  6. package/Application/Navigation.ts +15 -19
  7. package/Application/NavigationContainer.tsx +66 -63
  8. package/Application/Navigator.ts +106 -53
  9. package/Application/StackScreen.tsx +123 -9
  10. package/Application/index.ts +19 -6
  11. package/Application/types.ts +83 -26
  12. package/Application/utils.tsx +62 -74
  13. package/Assets/icon.json +83 -5
  14. package/Assets/icon_bank.json +506 -0
  15. package/Assets/language.json +3 -1
  16. package/Badge/Badge.tsx +56 -0
  17. package/Badge/BadgeDot.tsx +11 -0
  18. package/Badge/BadgeRibbon.tsx +125 -0
  19. package/Badge/Shape.tsx +29 -0
  20. package/Badge/index.tsx +7 -0
  21. package/Badge/styles.ts +58 -0
  22. package/Badge/types.ts +63 -0
  23. package/Button/index.tsx +64 -28
  24. package/Button/styles.ts +19 -0
  25. package/CheckBox/index.tsx +37 -27
  26. package/CheckBox/types.ts +5 -3
  27. package/Consts/colors+spacing+radius.ts +18 -0
  28. package/Icon/index.tsx +14 -9
  29. package/Icon/types.ts +4 -3
  30. package/IconButton/index.tsx +24 -15
  31. package/Image/index.tsx +62 -47
  32. package/Image/styles.ts +1 -9
  33. package/Image/types.ts +5 -1
  34. package/Input/Input.tsx +104 -25
  35. package/Input/InputDropDown.tsx +115 -110
  36. package/Input/InputMoney.tsx +79 -18
  37. package/Input/InputOTP.tsx +190 -149
  38. package/Input/InputSearch.tsx +96 -44
  39. package/Input/InputTextArea.tsx +33 -17
  40. package/Input/common.tsx +26 -17
  41. package/Input/index.tsx +108 -41
  42. package/Input/styles.ts +5 -7
  43. package/Input/utils.ts +1 -1
  44. package/Layout/AnimatedInputSearch.tsx +44 -0
  45. package/Layout/Card.tsx +37 -13
  46. package/Layout/FloatingButton.tsx +168 -0
  47. package/Layout/GridSystem.tsx +2 -3
  48. package/Layout/Item.tsx +1 -2
  49. package/Layout/ItemList.tsx +55 -0
  50. package/Layout/ItemSectionList.tsx +39 -0
  51. package/Layout/Screen.tsx +404 -181
  52. package/Layout/Section.tsx +22 -22
  53. package/Layout/TextTyping.tsx +114 -0
  54. package/Layout/index.ts +12 -2
  55. package/Layout/styles.ts +46 -13
  56. package/Layout/types.ts +13 -5
  57. package/Layout/utils.ts +7 -12
  58. package/Loader/index.tsx +2 -0
  59. package/Pagination/PaginationDot.tsx +6 -1
  60. package/Pagination/PaginationNumber.tsx +7 -4
  61. package/Pagination/PaginationScroll.tsx +0 -1
  62. package/Pagination/PaginationWhiteDot.tsx +6 -1
  63. package/Pagination/styles.ts +3 -0
  64. package/Pagination/types.ts +2 -0
  65. package/Popup/PopupNotify.tsx +108 -40
  66. package/Popup/PopupPromotion.tsx +45 -10
  67. package/Popup/types.ts +13 -10
  68. package/Radio/index.tsx +35 -21
  69. package/Radio/types.ts +7 -5
  70. package/Skeleton/index.tsx +1 -1
  71. package/Switch/index.tsx +32 -19
  72. package/Switch/types.ts +5 -3
  73. package/Tag/index.tsx +2 -4
  74. package/Text/index.tsx +26 -6
  75. package/Text/styles.ts +38 -0
  76. package/Text/types.ts +25 -13
  77. package/Title/index.tsx +177 -64
  78. package/Title/styles.ts +48 -15
  79. package/Title/types.ts +16 -7
  80. package/code-scanner.js +125 -16
  81. package/index.ts +9 -0
  82. package/migrate_typo.js +56 -0
  83. package/package.json +8 -7
  84. package/publish.sh +21 -16
  85. package/tsconfig.json +1 -2
  86. package/verify.js +24 -6
  87. package/Application/BottomTab.tsx +0 -65
@@ -1,32 +1,146 @@
1
- import React, {useContext, 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} from './index';
4
+ import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
5
5
  import {GridSystem} from '../Layout';
6
+ import {useHeaderHeight} from '@react-navigation/stack';
7
+ import {Alert, InteractionManager} from 'react-native';
6
8
 
7
9
  const StackScreen: React.FC<ScreenParams> = props => {
8
- const {screen: Component, options}: ScreenParams = props.route.params;
9
- const {theme, showGrid} = useContext(ApplicationContext);
10
+ const {theme, showGrid, navigator} = useContext(ApplicationContext);
11
+ const startTime = useRef(Date.now());
12
+ const endTime = useRef(Date.now());
13
+ const timeLoad = useRef(0);
14
+ const timeInteraction = useRef(0);
15
+ const timeoutLoad = useRef<any>();
16
+ const tracked = useRef<any>({
17
+ releaseLoad: undefined,
18
+ releaseInteraction: undefined,
19
+ timeoutLoad: undefined,
20
+ timeoutInteraction: undefined,
21
+ });
22
+ const interaction = useRef<any>();
23
+ const context = useContext<any>(MiniAppContext);
24
+
25
+ const {screen: Component, options, initialParams} = props.route.params;
10
26
  const navigation = new Navigation(props.navigation, theme);
27
+ const heightHeader = useHeaderHeight();
11
28
 
12
- const params = {
29
+ const data = {
30
+ ...initialParams,
13
31
  ...props.route.params,
14
32
  navigation,
15
33
  };
16
34
 
17
- delete params.screen;
35
+ delete data.screen;
36
+ delete data.initialParams;
37
+
38
+ const screenName = Component?.name || Component?.type?.name || 'Invalid';
18
39
 
40
+ /**
41
+ * set options for screen
42
+ */
19
43
  useLayoutEffect(() => {
20
44
  if (options) {
21
45
  navigation.setOptions(options);
22
46
  }
23
47
  }, [options]);
24
48
 
49
+ /**
50
+ * tracking for screen
51
+ */
52
+ useEffect(() => {
53
+ if (__DEV__ && (!screenName || screenName == 'Invalid')) {
54
+ Alert.alert('Design system', `Unvalidated screen name "${screenName}" !`);
55
+ }
56
+
57
+ navigator?.maxApi?.of?.({screenName});
58
+ tracked.current.timeoutLoad = setTimeout(() => {
59
+ onScreenLoad();
60
+ }, 5000);
61
+ tracked.current.timeoutInteraction = setTimeout(() => {
62
+ onScreenInteraction();
63
+ }, 5000);
64
+
65
+ return () => {
66
+ onScreenLoad();
67
+ onScreenInteraction();
68
+ };
69
+ }, []);
70
+
71
+ /**
72
+ * tracking for screen load
73
+ */
74
+ const onScreenLoad = () => {
75
+ clearTimeout(tracked.current.timeoutLoad);
76
+ if (!tracked.current?.releaseLoad) {
77
+ context.autoTracking?.({
78
+ appId: context.appId,
79
+ code: context.code,
80
+ buildNumber: context.buildNumber,
81
+ screenName,
82
+ componentName: 'Screen',
83
+ state: 'load',
84
+ duration: timeLoad.current,
85
+ });
86
+ navigator?.maxApi?.showToastDebug?.({
87
+ appId: `auto - ${context.appId}`,
88
+ message: `${screenName} screen_load_time ${timeLoad.current}`,
89
+ });
90
+ tracked.current.releaseLoad = true;
91
+ }
92
+ };
93
+
94
+ /**
95
+ * tracking for screen load
96
+ */
97
+ const onScreenInteraction = () => {
98
+ clearTimeout(tracked.current.timeoutInteraction);
99
+ if (!tracked.current?.releaseInteraction) {
100
+ context.autoTracking?.({
101
+ appId: context.appId,
102
+ code: context.code,
103
+ buildNumber: context.buildNumber,
104
+ screenName,
105
+ componentName: 'Screen',
106
+ state: 'interaction',
107
+ duration: timeInteraction.current - timeLoad.current,
108
+ });
109
+
110
+ navigator?.maxApi?.showToastDebug?.({
111
+ appId: `auto - ${context.appId}`,
112
+ message: `${screenName} screen_interaction_time ${timeInteraction.current}`,
113
+ });
114
+ tracked.current.releaseInteraction = true;
115
+ }
116
+ };
117
+
25
118
  return (
26
- <>
27
- <Component {...params} />
119
+ <ScreenContext.Provider
120
+ value={{
121
+ screenName,
122
+ onElementLoad: () => {
123
+ endTime.current = Date.now();
124
+ interaction.current?.cancel?.();
125
+ interaction.current = InteractionManager.runAfterInteractions(() => {
126
+ timeInteraction.current = Date.now() - startTime.current;
127
+ });
128
+ clearTimeout(timeoutLoad.current);
129
+ timeoutLoad.current = setTimeout(() => {
130
+ if (timeLoad.current === 0) {
131
+ timeLoad.current = endTime.current - startTime.current;
132
+ }
133
+ }, 2000);
134
+ },
135
+ }}>
136
+ <Component
137
+ heightHeader={heightHeader}
138
+ {...data}
139
+ onScreenLoad={onScreenLoad}
140
+ onScreenInteraction={onScreenInteraction}
141
+ />
28
142
  {showGrid && <GridSystem />}
29
- </>
143
+ </ScreenContext.Provider>
30
144
  );
31
145
  };
32
146
 
@@ -1,24 +1,37 @@
1
+ import {Platform} from 'react-native';
1
2
  import {NavigationContainer} from './NavigationContainer';
2
3
  import Screen from '../Layout/Screen';
3
4
  import {
5
+ HeaderAnimated,
6
+ HeaderBackground,
7
+ HeaderCustom,
4
8
  HeaderRightAction,
5
- NavigationButton,
6
- HeaderToolkitAction,
7
9
  HeaderTitle,
10
+ NavigationButton,
8
11
  } from './Components';
9
12
  import BottomTab from './BottomTab';
10
13
  import {createContext} from 'react';
11
14
  import {defaultContext} from '../Consts';
12
15
 
16
+ const Context = createContext({});
13
17
  const ApplicationContext = createContext(defaultContext);
14
18
 
19
+ const MiniAppContext = (Platform as any).MiniAppContext ?? createContext({});
20
+ const ScreenContext = (Platform as any).ScreenContext ?? createContext({});
21
+ const ComponentContext = (Platform as any).ComponentContext ?? Context;
22
+
15
23
  export {
16
- NavigationContainer,
17
24
  ApplicationContext,
25
+ MiniAppContext,
26
+ ScreenContext,
27
+ ComponentContext,
28
+ NavigationContainer,
18
29
  Screen,
30
+ BottomTab,
19
31
  NavigationButton,
20
- HeaderRightAction,
21
- HeaderToolkitAction,
22
32
  HeaderTitle,
23
- BottomTab,
33
+ HeaderBackground,
34
+ HeaderRightAction,
35
+ HeaderCustom,
36
+ HeaderAnimated,
24
37
  };
@@ -1,8 +1,9 @@
1
- import {Animated} from 'react-native';
2
- import React from 'react';
3
- import Navigator from './Navigator';
1
+ import {StackNavigationOptions} from '@react-navigation/stack';
2
+ import React, {ReactElement} from 'react';
3
+ import {Animated, ViewProps} from 'react-native';
4
4
  import {PopupNotifyProps} from '../Popup/types';
5
5
  import Navigation from './Navigation';
6
+ import Navigator from './Navigator';
6
7
 
7
8
  type Screen = React.ComponentType<NavigationScreenProps>;
8
9
 
@@ -62,10 +63,10 @@ export type Theme = {
62
63
  };
63
64
 
64
65
  export type Context = {
66
+ [key: string]: any;
65
67
  theme: Theme;
66
68
  navigator?: Navigator;
67
69
  showGrid?: boolean;
68
- language?: 'vi' | 'en';
69
70
  translate?: (key: string) => string;
70
71
  };
71
72
 
@@ -82,8 +83,9 @@ export type NavigationContainerProps = {
82
83
  screen: Screen;
83
84
  options?: NavigationOptions;
84
85
  theme: Theme;
85
- maxApi?: any;
86
+ maxApi: any;
86
87
  localization?: LocalizationObject;
88
+ initialParams?: any;
87
89
  };
88
90
 
89
91
  export type NavigationScreenProps = {
@@ -98,52 +100,87 @@ export type ScreenParams = {
98
100
  options?: NavigationOptions;
99
101
  };
100
102
 
101
- export interface ModalParams extends ScreenParams {
103
+ export type ModalParams = {
102
104
  [key: string]: any;
105
+ screen: React.ComponentType;
103
106
  onDismiss?: () => void;
104
107
  barrierDismissible?: boolean;
105
- }
108
+ };
106
109
 
107
- export interface BottomSheetParams extends ScreenParams {
110
+ export type BottomSheetParams = {
108
111
  [key: string]: any;
109
- onDismiss?: () => void;
110
- snapPoint?: '30%' | '50%' | '70%' | '90%';
111
- }
112
+ screen: React.ComponentType;
113
+ options: {
114
+ title: string;
115
+ surface?: boolean;
116
+ };
117
+ surface?: boolean;
118
+ onDismiss?: (type?: string) => void;
119
+ barrierDismissible?: boolean;
120
+ draggable?: boolean;
121
+ useBottomInset?: boolean;
122
+ keyboardVerticalOffset?: number;
123
+ };
112
124
 
113
125
  export type NavigationButtonProps = {
114
126
  icon: string;
115
127
  tintColor?: string;
116
- backgroundColor?: string;
117
128
  useBorder?: boolean;
118
129
  onPress: () => void;
130
+ badgeType?: 'dot' | 'number';
131
+ badgeValue?: number;
119
132
  accessibilityLabel?: string;
120
133
  };
121
134
 
122
- export type NavigationOptions = {
135
+ export type PinnedToolType = {
136
+ key: string;
137
+ badgeValue?: number;
138
+ badgeType?: 'number' | 'dot';
139
+ };
140
+
141
+ export type RuntimeToolType = {
142
+ title: {vi: string; en: string};
143
+ icon: string;
144
+ key: string;
145
+ };
146
+
147
+ export interface NavigationOptions
148
+ extends Omit<StackNavigationOptions, 'headerRight'> {
123
149
  preventBack?: PopupNotifyProps;
124
150
  onPressLeftHeader?: () => void;
125
151
  surface?: boolean;
126
- headerTransparent?: boolean;
127
152
  hiddenBack?: boolean;
128
- title?: string;
129
- headerTitle?: (props?: any) => React.ReactElement;
130
- headerTitleAlign?: 'left' | 'center';
131
153
  customTitle?: TitleCustomProps;
132
- headerRight?: (props?: any) => React.ReactElement;
133
- animatedValue?: Animated.Value;
154
+ headerRight?: HeaderRightToolkit | HeaderRightActions | Element;
155
+ }
156
+
157
+ export type HeaderRightToolkit = {
158
+ type?: 'toolkit';
159
+ pinnedTool?: PinnedToolType;
160
+ runtimeTools?: RuntimeToolType[];
161
+ preventClose?: PopupNotifyProps;
162
+ useMore?: boolean;
163
+ };
164
+
165
+ export type HeaderRightActions = {
166
+ type?: 'iconButton';
167
+ buttons: IconButton[];
168
+ };
169
+
170
+ export type IconButton = {
171
+ icon: string;
172
+ onPress: () => void;
134
173
  };
135
174
 
136
175
  export interface HeaderBackProps extends NavigationButtonProps {
137
176
  preventBack?: PopupNotifyProps;
138
177
  onPressLeftHeader?: () => void;
139
- accessibilityLabel?: string;
140
178
  }
141
179
 
142
180
  export type HeaderBackgroundProps = {
143
181
  image?: string | null;
144
- backgroundColor?: string | null;
145
182
  animatedValue?: Animated.Value;
146
- useBorder?: boolean;
183
+ useSurface?: boolean;
147
184
  };
148
185
 
149
186
  export type TitleCustomProps = {
@@ -159,26 +196,46 @@ export type TitleCustomProps = {
159
196
  export type HeaderToolkitProps = {
160
197
  [key: string]: any;
161
198
  appCode: string;
162
- hidden: boolean;
163
- type: string;
164
- onAction?: (action: string) => void;
199
+ configs: any[];
165
200
  };
166
201
 
202
+ export interface HeaderAnimatedProps extends ViewProps {
203
+ animatedValue: Animated.Value;
204
+ image: string;
205
+ }
206
+
167
207
  export type BottomTabItemProps = {
168
208
  name: string;
169
209
  label: string;
170
210
  icon: string;
171
211
  showDot?: boolean;
172
- badgeLabel?: string;
212
+ badgeLabel?: string | number;
173
213
  screen: Screen;
174
214
  options: NavigationOptions;
215
+ initialParams?: any;
175
216
  };
176
217
 
177
218
  export type BottomTabProps = {
219
+ nested?: boolean;
178
220
  tabs: BottomTabItemProps[];
221
+ initialRouteName?: string;
179
222
  navigation: Navigation;
180
223
  listeners?: {
181
224
  tabPress?: any;
182
225
  focus?: any;
183
226
  };
227
+ floatingButton?: FloatingButtonProps;
228
+ };
229
+
230
+ export type FloatingButtonProps = {
231
+ container?: React.ReactNode;
232
+ icon: string | React.ReactNode;
233
+ label: string;
234
+ onPress: () => void;
235
+ };
236
+
237
+ export type AnimatedHeader = {
238
+ type: 'default' | 'surface';
239
+ component?: (props?: any) => React.ReactElement;
240
+ headerTitle?: (props?: any) => React.ReactElement;
184
241
  };
@@ -13,39 +13,37 @@ import {
13
13
  import {NavigationOptions, Theme} from './types';
14
14
  import {Colors} from '../Consts';
15
15
 
16
- const getTintColor = (theme: Theme): any => {
17
- if (theme.assets?.headerBackground) {
18
- return {
19
- headerTintColor: Colors.black_01,
20
- };
21
- }
22
- return {
23
- headerTintColor: theme.colors.text.default,
24
- };
25
- };
26
-
27
- const getStackOptions = (theme: Theme): StackNavigationOptions => {
16
+ /**
17
+ * default options for stack screen
18
+ */
19
+ const getStackOptions = (): StackNavigationOptions => {
28
20
  return {
29
21
  headerTitleAlign: 'left',
30
22
  headerTitle: HeaderTitle,
31
- headerBackground: HeaderBackground,
23
+ headerBackground: (props: any) => <HeaderBackground {...props} />,
32
24
  headerLeft: (props: any) => <HeaderLeft {...props} />,
33
25
  headerRight: (props: any) => <HeaderToolkitAction {...props} />,
34
- ...getTintColor(theme),
26
+ headerTintColor: Colors.black_01,
35
27
  };
36
28
  };
37
29
 
38
- const getDialogOptions = (theme: Theme): StackNavigationOptions => {
30
+ /**
31
+ * default options for dialog screen
32
+ */
33
+ const getDialogOptions = (): StackNavigationOptions => {
39
34
  return {
40
35
  headerTitleAlign: 'left',
41
36
  headerTitle: HeaderTitle,
42
37
  headerLeft: (props: any) => <HeaderLeft {...props} />,
43
- headerBackground: HeaderBackground,
44
- ...getTintColor(theme),
38
+ headerBackground: (props: any) => <HeaderBackground {...props} />,
39
+ headerTintColor: Colors.black_01,
45
40
  ...TransitionPresets.ModalTransition,
46
41
  };
47
42
  };
48
43
 
44
+ /**
45
+ * default options for modal screen
46
+ */
49
47
  const getModalOptions = (): StackNavigationOptions => {
50
48
  return {
51
49
  headerShown: false,
@@ -71,55 +69,47 @@ const getModalOptions = (): StackNavigationOptions => {
71
69
  };
72
70
  };
73
71
 
72
+ /**
73
+ * build react-navigation options
74
+ */
74
75
  const getOptions = (params: NavigationOptions, theme: Theme) => {
75
- let backgroundProps = {};
76
- let titleProps = {};
77
76
  let options = {};
78
77
 
79
- if (typeof params.onPressLeftHeader === 'function') {
80
- options = {
81
- ...options,
82
- headerLeft: (props: any) => (
83
- <HeaderLeft
84
- {...props}
85
- preventBack={params.preventBack}
86
- onPressLeftHeader={params.onPressLeftHeader}
87
- />
88
- ),
89
- };
90
- }
91
-
92
- if (params.preventBack) {
93
- options = {
94
- ...options,
95
- headerLeft: (props: any) => (
96
- <HeaderLeft {...props} preventBack={params.preventBack} />
97
- ),
98
- };
99
- }
100
-
101
- if (params.surface == true) {
78
+ if (
79
+ typeof params.onPressLeftHeader === 'function' ||
80
+ params.preventBack !== undefined ||
81
+ typeof params.hiddenBack === 'boolean'
82
+ ) {
102
83
  options = {
103
84
  ...options,
104
- ...getTintColor({
105
- ...theme,
106
- assets: {...theme.assets, headerBackground: undefined},
107
- }),
108
- };
109
- backgroundProps = {
110
- ...backgroundProps,
111
- image: null,
112
- backgroundColor: theme.colors.background.surface,
113
- useBorder: true,
85
+ headerLeft: params.hiddenBack
86
+ ? null
87
+ : (props: any) => (
88
+ <HeaderLeft
89
+ {...props}
90
+ preventBack={params.preventBack}
91
+ onPressLeftHeader={params.onPressLeftHeader}
92
+ />
93
+ ),
114
94
  };
115
- } else {
116
- backgroundProps = {};
117
- options = {...options, ...getTintColor(theme)};
118
95
  }
119
96
 
120
- if (params.animatedValue) {
121
- backgroundProps = {...backgroundProps, animatedValue: params.animatedValue};
122
- titleProps = {...titleProps, animatedValue: params.animatedValue};
97
+ if (typeof params.surface === 'boolean') {
98
+ if (params.surface) {
99
+ options = {
100
+ ...options,
101
+ headerTintColor: theme.colors.text.default,
102
+ headerBackground: () => (
103
+ <HeaderBackground image={null} useSurface={true} />
104
+ ),
105
+ };
106
+ } else {
107
+ options = {
108
+ ...options,
109
+ headerTintColor: Colors.black_01,
110
+ headerBackground: HeaderBackground,
111
+ };
112
+ }
123
113
  }
124
114
 
125
115
  if (params.customTitle) {
@@ -130,27 +120,25 @@ const getOptions = (params: NavigationOptions, theme: Theme) => {
130
120
  return <HeaderCustom {...params.customTitle} {...props} />;
131
121
  },
132
122
  };
133
- } else if (!params.headerTitle) {
134
- options = {
135
- ...options,
136
- headerTitle: (props: any) => {
137
- return <HeaderTitle {...titleProps} {...props} />;
138
- },
139
- };
140
- }
141
-
142
- if (params.hiddenBack) {
143
- options = {
144
- ...options,
145
- headerLeft: null,
146
- };
147
123
  }
148
124
 
149
125
  return {
150
126
  ...params,
151
127
  ...options,
152
- headerBackground: () => <HeaderBackground {...backgroundProps} />,
153
128
  };
154
129
  };
155
130
 
156
- export {getStackOptions, getDialogOptions, getModalOptions, getOptions};
131
+ const formatNameLength = (name = '') => {
132
+ let formattedName = name;
133
+ if (formattedName.length > 100) {
134
+ formattedName = formattedName.substring(0, 100);
135
+ }
136
+ return formattedName;
137
+ };
138
+ export {
139
+ getStackOptions,
140
+ getDialogOptions,
141
+ getModalOptions,
142
+ getOptions,
143
+ formatNameLength,
144
+ };