@momo-kits/foundation 0.112.1-testing.20 → 0.113.0-rc.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.
@@ -109,7 +109,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
109
109
  initialRouteName,
110
110
  floatingButton,
111
111
  }) => {
112
- const {theme} = useContext(ApplicationContext);
112
+ const {theme, navigator} = useContext(ApplicationContext);
113
113
  const insets = useSafeAreaInsets();
114
114
 
115
115
  useEffect(() => {
@@ -127,6 +127,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
127
127
  state?: (e: any) => void;
128
128
  } = {
129
129
  tabPress: e => {
130
+ navigator?.maxApi?.triggerEventVibration?.('light');
130
131
  listeners?.tabPress?.(e);
131
132
  },
132
133
  focus: e => {
@@ -16,7 +16,7 @@ import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
16
16
  import {NavigationContainerProps} from './types';
17
17
  import {ApplicationContext, MiniAppContext} from './index';
18
18
  import Localize from './Localize';
19
- import {defaultTheme, Configs} from '../Consts';
19
+ import {defaultTheme} from '../Consts';
20
20
 
21
21
  const Stack = createStackNavigator();
22
22
 
@@ -36,6 +36,16 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
36
36
  const [showGrid, setShowGrid] = useState(false);
37
37
  const [currentContext, setCurrentContext] = useState({});
38
38
 
39
+ let headerBackground = context?.designConfig?.headerBar;
40
+ let headerGradient = theme.colors?.gradient;
41
+
42
+ if (theme.assets?.headerBackground) {
43
+ headerBackground = theme.assets?.headerBackground;
44
+ }
45
+ if (context?.designConfig?.headerGradient) {
46
+ headerGradient = context?.designConfig?.headerGradient;
47
+ }
48
+
39
49
  /**
40
50
  * inject data for navigator
41
51
  */
@@ -96,9 +106,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
96
106
  });
97
107
  };
98
108
 
99
- const headerBackground = theme.assets?.headerBackground || Configs.headerBar;
100
- const headerGradient = Configs?.headerGradient || theme.colors?.gradient;
101
-
102
109
  navigator.current.setCurrentContext = setCurrentContext;
103
110
 
104
111
  return (
@@ -1,11 +1,11 @@
1
1
  import React, {useContext, useEffect, useLayoutEffect, useRef} from 'react';
2
2
  import {useHeaderHeight} from '@react-navigation/stack';
3
3
  import {Alert, InteractionManager} from 'react-native';
4
- import {ScreenParams} from './types';
4
+ import {ScreenParams, ScreenTrackingParams} from './types';
5
5
  import Navigation from './Navigation';
6
6
  import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
7
7
  import {GridSystem} from '../Layout';
8
-
8
+ import {version} from '../package.json';
9
9
  const runAfterInteractions = InteractionManager.runAfterInteractions;
10
10
 
11
11
  /**
@@ -28,6 +28,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
28
28
  timeLoad: 0,
29
29
  timeInteraction: 0,
30
30
  widgets: [],
31
+ params: undefined,
31
32
  });
32
33
  const widgets = useRef<any>([]);
33
34
  const context = useContext<any>(MiniAppContext);
@@ -117,6 +118,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
117
118
  state: 'load',
118
119
  duration: timeLoad,
119
120
  widgets: tracking.current.widgets,
121
+ params: tracking.current.params,
122
+ version: version,
120
123
  });
121
124
  navigator?.maxApi?.stopTrace?.(
122
125
  tracking.current.traceIdLoad,
@@ -165,6 +168,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
165
168
  state: 'interaction',
166
169
  duration: tracking.current.timeInteraction - timeLoad,
167
170
  totalDuration: tracking.current.timeInteraction,
171
+ params: tracking.current.params,
172
+ version: version,
168
173
  });
169
174
  navigator?.maxApi?.stopTrace?.(
170
175
  tracking.current.traceIdInteraction,
@@ -254,6 +259,9 @@ const StackScreen: React.FC<ScreenParams> = props => {
254
259
  onScreenInteraction();
255
260
  }, 2000);
256
261
  },
262
+ onSetParams: (data: ScreenTrackingParams) => {
263
+ tracking.current.params = data;
264
+ },
257
265
  }}>
258
266
  <Component heightHeader={heightHeader} {...data} />
259
267
  {showGrid && <GridSystem />}
@@ -16,15 +16,17 @@ import {setAutomationID} from './utils';
16
16
  const Context = createContext({});
17
17
  const ApplicationContext = createContext(defaultContext);
18
18
 
19
- const MiniAppContext = (Platform as any).MiniAppContext ?? createContext({});
20
- const ScreenContext = (Platform as any).ScreenContext ?? createContext({});
19
+ const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
20
+ const ScreenContext = (Platform as any).ScreenContext ?? Context;
21
21
  const ComponentContext = (Platform as any).ComponentContext ?? Context;
22
+ const SkeletonContext = createContext({loading: false});
22
23
 
23
24
  export {
24
25
  ApplicationContext,
25
26
  MiniAppContext,
26
27
  ScreenContext,
27
28
  ComponentContext,
29
+ SkeletonContext,
28
30
  NavigationContainer,
29
31
  Localize,
30
32
  HeaderTitle,
@@ -117,6 +117,11 @@ export type ScreenParams = {
117
117
  options?: NavigationOptions;
118
118
  };
119
119
 
120
+ export type ScreenTrackingParams = {
121
+ value1?: any;
122
+ value2?: any;
123
+ };
124
+
120
125
  export type ModalParams = {
121
126
  [key: string]: any;
122
127
  screen: React.ComponentType;
package/Button/index.tsx CHANGED
@@ -7,13 +7,18 @@ import {
7
7
  View,
8
8
  } from 'react-native';
9
9
  import LinearGradient from 'react-native-linear-gradient';
10
- import {ApplicationContext, ComponentContext} from '../Application';
10
+ import {
11
+ ApplicationContext,
12
+ ComponentContext,
13
+ SkeletonContext,
14
+ } from '../Application';
11
15
  import {Text} from '../Text';
12
16
  import {Typography} from '../Text/types';
13
17
  import {Colors, Spacing} from '../Consts';
14
18
  import styles from './styles';
15
19
  import {Icon} from '../Icon';
16
20
  import {Loader} from '../Loader';
21
+ import {Skeleton} from '../Skeleton';
17
22
 
18
23
  const AnimationLinear = Animated.createAnimatedComponent(LinearGradient);
19
24
 
@@ -88,6 +93,7 @@ const Button: FC<ButtonProps> = ({
88
93
  ...rest
89
94
  }) => {
90
95
  const {theme, config} = useContext(ApplicationContext);
96
+ const skeleton = useContext(SkeletonContext);
91
97
  const {gradient, color} = config?.navigationBar?.buttonColors ?? {};
92
98
  let gradientPros;
93
99
  let state = 'enabled';
@@ -289,6 +295,10 @@ const Button: FC<ButtonProps> = ({
289
295
  full && {width: '100%'},
290
296
  ]);
291
297
 
298
+ if (skeleton?.loading) {
299
+ return <Skeleton style={[buttonStyle, {paddingHorizontal: 0}]} />;
300
+ }
301
+
292
302
  return (
293
303
  <ComponentContext.Provider
294
304
  value={{
@@ -229,34 +229,4 @@ const Shadow = {
229
229
  }),
230
230
  };
231
231
 
232
- const Configs = {
233
- headerGradient: undefined,
234
- headerBar: undefined,
235
- trustBanner: {
236
- content: {
237
- vi: 'Bảo mật thông tin & An toàn tài sản của bạn là ưu tiên hàng đầu của MoMo.',
238
- en: "Your data security and money safety are MoMo's top priorities.",
239
- },
240
- subContent: {
241
- vi: 'Tìm hiểu thêm',
242
- en: 'Learn more',
243
- },
244
- pciImage: 'https://static.momocdn.net/app/img/kits/trustBanner/pci_dss.png',
245
- sslImage: 'https://static.momocdn.net/app/img/kits/trustBanner/ssl.png',
246
- momoImage:
247
- 'https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png',
248
- urlConfig: 'login_and_security',
249
- icons: [
250
- 'https://static.momocdn.net/app/img/kits/trustBanner/ic_viettinbank.png',
251
- 'https://static.momocdn.net/app/img/kits/trustBanner/ic_agribank.png',
252
- 'https://static.momocdn.net/app/img/kits/trustBanner/ic_vietcombank.png',
253
- 'https://static.momocdn.net/app/img/kits/trustBanner/ic_bidv.png',
254
- ],
255
- titleWeb: {
256
- vi: 'Thông tin',
257
- en: 'Information',
258
- },
259
- },
260
- };
261
-
262
- export {Colors, Spacing, Radius, Shadow, Configs};
232
+ export {Colors, Spacing, Radius, Shadow};
package/Image/index.tsx CHANGED
@@ -2,7 +2,7 @@ import React, {useContext, useRef, useState} from 'react';
2
2
  import {StyleSheet, View} from 'react-native';
3
3
  import FastImage, {Source} from 'react-native-fast-image';
4
4
  import styles from './styles';
5
- import {ApplicationContext} from '../Application';
5
+ import {ApplicationContext, SkeletonContext} from '../Application';
6
6
  import {Skeleton} from '../Skeleton';
7
7
  import {Icon} from '../Icon';
8
8
  import {Styles} from '../Consts';
@@ -19,6 +19,7 @@ const Image: React.FC<ImageProps> = ({
19
19
  ...rest
20
20
  }) => {
21
21
  const {theme} = useContext(ApplicationContext);
22
+ const skeleton = useContext(SkeletonContext);
22
23
  const error = useRef(false);
23
24
  const [status, setStatus] = useState<Status>('success');
24
25
 
@@ -43,6 +44,15 @@ const Image: React.FC<ImageProps> = ({
43
44
  </View>
44
45
  );
45
46
  }
47
+
48
+ if (skeleton.loading) {
49
+ return (
50
+ <View style={[StyleSheet.absoluteFill, Styles.flexCenter]}>
51
+ <Skeleton />
52
+ </View>
53
+ );
54
+ }
55
+
46
56
  return children;
47
57
  };
48
58
 
@@ -25,8 +25,203 @@ import {
25
25
  import {InputMoneyProps} from './index';
26
26
  import styles from './styles';
27
27
  import {formatMoneyToNumber, formatNumberToMoney} from './utils';
28
- import {Input} from '@momo-kits/foundation';
29
28
 
30
- const InputMoney = Input;
29
+ const InputMoney = forwardRef(
30
+ (
31
+ {
32
+ onChangeText,
33
+ floatingValue,
34
+ floatingIcon,
35
+ size = 'small',
36
+ loading,
37
+ onBlur,
38
+ onFocus,
39
+ errorMessage,
40
+ icon,
41
+ iconColor,
42
+ onPressIcon,
43
+ trailing,
44
+ trailingColor,
45
+ onPressTrailing,
46
+ disabled = false,
47
+ floatingIconColor,
48
+ required = false,
49
+ errorSpacing,
50
+ style,
51
+ params,
52
+ defaultValue = '',
53
+ accessibilityLabel,
54
+ hintText,
55
+ value: _value,
56
+ onPressFloatingIcon,
57
+ placeholder = '0đ',
58
+ currency = 'đ',
59
+ showClearIcon = true,
60
+ ...props
61
+ }: InputMoneyProps,
62
+ ref
63
+ ) => {
64
+ const {theme} = useContext(ApplicationContext);
65
+
66
+ const [focused, setFocused] = useState(false);
67
+ const inputRef = useRef<TextInput>(null);
68
+
69
+ useEffect(() => {
70
+ if (_value) {
71
+ setValue(validateText(_value));
72
+ onChangeText?.(formatMoneyToNumber(_value, currency).toString());
73
+ }
74
+ }, [_value]);
75
+
76
+ const validateText = (text: string) => {
77
+ const valueFormat = formatMoneyToNumber(text, currency);
78
+ return formatNumberToMoney(valueFormat, currency);
79
+ };
80
+
81
+ const [value, setValue] = useState(
82
+ defaultValue ? validateText(defaultValue) : ''
83
+ );
84
+
85
+ const onClearText = () => {
86
+ inputRef?.current?.clear();
87
+ setValue('');
88
+ onChangeText?.('');
89
+ };
90
+
91
+ const _onChangeText = (text: string) => {
92
+ if (text.length < value.length && value.indexOf(text) === 0) {
93
+ text = value.slice(0, -2) + currency;
94
+ }
95
+ setValue(validateText(text));
96
+ onChangeText?.(formatMoneyToNumber(text, currency).toString());
97
+ };
98
+
99
+ useImperativeHandle(ref, () => {
100
+ return {
101
+ clear: onClearText,
102
+ focus: () => inputRef.current?.focus(),
103
+ blur: () => inputRef.current?.blur(),
104
+ setText: _onChangeText,
105
+ };
106
+ });
107
+
108
+ const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
109
+ setFocused(true);
110
+ onFocus?.(e);
111
+ };
112
+
113
+ const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
114
+ setFocused(false);
115
+ onBlur?.(e);
116
+ };
117
+
118
+ const renderInputView = () => {
119
+ const disabledColor = theme.colors.text.disable;
120
+ let textColor = theme.colors.text.default;
121
+ let placeholderColor = theme.colors.text.hint;
122
+ let iconTintColor = trailingColor ?? iconColor;
123
+
124
+ if (disabled) {
125
+ textColor = disabledColor;
126
+ placeholderColor = disabledColor;
127
+ iconTintColor = disabledColor;
128
+ }
129
+
130
+ return (
131
+ <View
132
+ style={[
133
+ styles.inputWrapper,
134
+ {backgroundColor: theme.colors.background.surface},
135
+ getSizeStyle(size),
136
+ getBorderColor(theme, focused, errorMessage, disabled),
137
+ ]}>
138
+ <FloatingView
139
+ floatingValue={floatingValue}
140
+ floatingIconColor={floatingIconColor}
141
+ disabled={disabled}
142
+ required={required}
143
+ floatingIcon={floatingIcon}
144
+ onPress={onPressFloatingIcon}
145
+ />
146
+ <View style={styles.inputView}>
147
+ <TextInput
148
+ {...props}
149
+ accessibilityLabel={accessibilityLabel}
150
+ editable={!disabled}
151
+ ref={inputRef}
152
+ keyboardType={'number-pad'}
153
+ allowFontScaling={false}
154
+ style={[
155
+ styles.moneyInput,
156
+ {
157
+ color: textColor,
158
+ },
159
+ ]}
160
+ value={value}
161
+ onChangeText={_onChangeText}
162
+ onFocus={_onFocus}
163
+ onBlur={_onBlur}
164
+ selectionColor={theme.colors.primary}
165
+ placeholderTextColor={placeholderColor}
166
+ placeholder={placeholder}
167
+ />
168
+ </View>
169
+ <View style={styles.iconView}>
170
+ {showClearIcon && focused && (
171
+ <TouchableOpacity
172
+ style={styles.iconWrapper}
173
+ onPress={onClearText}>
174
+ <Icon
175
+ source="24_navigation_close_circle_full"
176
+ size={16}
177
+ color={theme.colors.text.hint}
178
+ />
179
+ </TouchableOpacity>
180
+ )}
181
+ <RenderTrailing
182
+ color={iconTintColor}
183
+ icon={icon}
184
+ trailing={trailing}
185
+ onPressTrailing={onPressTrailing}
186
+ onPressIcon={onPressIcon}
187
+ loading={loading}
188
+ />
189
+ </View>
190
+ </View>
191
+ );
192
+ };
193
+
194
+ let inputState = 'active';
195
+
196
+ if (value && value?.length > 0) {
197
+ inputState = 'filled';
198
+ }
199
+ if (errorMessage && errorMessage?.length > 0) {
200
+ inputState = 'error';
201
+ }
202
+
203
+ if (disabled) {
204
+ inputState = 'disabled';
205
+ }
206
+
207
+ return (
208
+ <ComponentContext.Provider
209
+ value={{
210
+ componentName: 'InputMoney',
211
+ params,
212
+ state: inputState,
213
+ }}>
214
+ <View style={[style, styles.wrapper]}>
215
+ {renderInputView()}
216
+ <ErrorView
217
+ errorMessage={errorMessage}
218
+ errorSpacing={errorSpacing}
219
+ hintText={hintText}
220
+ />
221
+ </View>
222
+ </ComponentContext.Provider>
223
+ );
224
+ }
225
+ );
31
226
 
32
227
  export default InputMoney;
package/Layout/Screen.tsx CHANGED
@@ -23,11 +23,12 @@ import {
23
23
  ViewProps,
24
24
  } from 'react-native';
25
25
  import {useSafeAreaInsets} from 'react-native-safe-area-context';
26
- import {ApplicationContext} from '../Application';
26
+ import {ApplicationContext, ScreenContext} from '../Application';
27
27
  import Navigation from '../Application/Navigation';
28
28
  import {
29
29
  AnimatedHeader,
30
30
  NavigationOptions,
31
+ ScreenTrackingParams,
31
32
  SearchHeaderProps,
32
33
  } from '../Application/types';
33
34
  import {Colors, Spacing, Styles} from '../Consts';
@@ -148,6 +149,11 @@ export interface ScreenProps extends ViewProps {
148
149
  * Optional. Custom headerBackground Image
149
150
  */
150
151
  headerBackground?: string;
152
+
153
+ /**
154
+ * Optional. Custom tracking params
155
+ */
156
+ trackingParams?: ScreenTrackingParams;
151
157
  }
152
158
 
153
159
  const Screen = forwardRef(
@@ -174,11 +180,13 @@ const Screen = forwardRef(
174
180
  animatedValue: customAnimatedValue,
175
181
  headerBackground,
176
182
  gradientColor,
183
+ trackingParams,
177
184
  }: ScreenProps,
178
185
  ref: any
179
186
  ) => {
180
187
  const screenRef = useRef<View | ScrollView>();
181
188
  const {theme} = useContext(ApplicationContext);
189
+ const screen: any = useContext(ScreenContext);
182
190
  const insets = useSafeAreaInsets();
183
191
  const heightHeader = useHeaderHeight();
184
192
  const animatedValue = useRef<Animated.Value>(
@@ -195,6 +203,11 @@ const Screen = forwardRef(
195
203
  keyboardOffset = -Math.min(insets.bottom, 21);
196
204
  }
197
205
 
206
+ /**
207
+ * inject params for screen tracking
208
+ */
209
+ screen?.onSetParams?.(trackingParams);
210
+
198
211
  /**
199
212
  * export options for screen
200
213
  * @param headerType
@@ -1,9 +1,18 @@
1
1
  import React, {useEffect, useMemo, useRef, useState} from 'react';
2
- import {Animated, Easing, Platform, StyleSheet, View} from 'react-native';
2
+ import {
3
+ Animated,
4
+ Easing,
5
+ LayoutAnimation,
6
+ Platform,
7
+ StyleSheet,
8
+ UIManager,
9
+ View,
10
+ } from 'react-native';
3
11
  import LinearGradient from 'react-native-linear-gradient';
4
12
  import {SkeletonTypes} from './types';
5
13
  import {Colors, Styles} from '../Consts';
6
14
  import styles from './styles';
15
+ import {SkeletonContext} from '../Application';
7
16
 
8
17
  const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
9
18
  const [width, setWidth] = useState(0);
@@ -24,7 +33,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
24
33
  duration: 1000,
25
34
  easing: Easing.linear,
26
35
  useNativeDriver: Platform.OS !== 'web',
27
- }),
36
+ })
28
37
  );
29
38
  }, [beginShimmerPosition]);
30
39
 
@@ -69,4 +78,50 @@ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
69
78
  );
70
79
  };
71
80
 
72
- export {Skeleton};
81
+ /**
82
+ * SkeletonProvider component provides a context for managing the loading state
83
+ * and applies a layout animation when the loading state changes.
84
+ *
85
+ * @param {boolean} loading - Indicates whether the skeleton loading state is active.
86
+ * @param {React.ReactNode} children - The child components to be rendered within the provider.
87
+ *
88
+ * @example
89
+ * <SkeletonProvider loading={true}>
90
+ * <YourComponent />
91
+ * </SkeletonProvider>
92
+ *
93
+ * The layout animation is configured using LayoutAnimation.configureNext, which animates the opacity of the components over 500 milliseconds with an ease-in-ease-out effect.
94
+ */
95
+
96
+ const SkeletonProvider: React.FC<{loading: boolean}> = ({
97
+ loading = true,
98
+ children,
99
+ }) => {
100
+ const [previous, setPrevious] = useState(loading);
101
+
102
+ useEffect(() => {
103
+ if (previous !== loading) {
104
+ if (Platform.OS === 'android') {
105
+ if (UIManager.setLayoutAnimationEnabledExperimental) {
106
+ UIManager.setLayoutAnimationEnabledExperimental(true);
107
+ }
108
+ }
109
+ LayoutAnimation.configureNext({
110
+ duration: 500,
111
+ create: {
112
+ type: LayoutAnimation.Types.easeInEaseOut,
113
+ property: LayoutAnimation.Properties.opacity,
114
+ },
115
+ });
116
+ setPrevious(loading);
117
+ }
118
+ }, [loading]);
119
+
120
+ return (
121
+ <SkeletonContext.Provider value={{loading: previous}}>
122
+ {children}
123
+ </SkeletonContext.Provider>
124
+ );
125
+ };
126
+
127
+ export {Skeleton, SkeletonProvider};
package/Text/index.tsx CHANGED
@@ -1,9 +1,14 @@
1
1
  import React, {useContext} from 'react';
2
- import {Text as RNText, TextProps as RNTextProps} from 'react-native';
2
+ import {Text as RNText, TextProps as RNTextProps, View} from 'react-native';
3
3
  import styles from './styles';
4
4
  import {Typography, TypographyWeight} from './types';
5
- import {ApplicationContext, setAutomationID} from '../Application';
5
+ import {
6
+ ApplicationContext,
7
+ setAutomationID,
8
+ SkeletonContext,
9
+ } from '../Application';
6
10
  import {scaleSize} from './utils';
11
+ import {Skeleton} from '../Skeleton';
7
12
 
8
13
  const SFProText: TypographyWeight = {
9
14
  100: 'Thin',
@@ -125,7 +130,7 @@ const Text: React.FC<TextProps> = ({
125
130
  ...rest
126
131
  }) => {
127
132
  const {theme} = useContext(ApplicationContext);
128
-
133
+ const skeleton = useContext(SkeletonContext);
129
134
  const textStyle = getTypoStyle(typography, fontFamily);
130
135
 
131
136
  if (deprecatedValues.includes(typography)) {
@@ -134,6 +139,25 @@ const Text: React.FC<TextProps> = ({
134
139
  );
135
140
  }
136
141
 
142
+ if (skeleton.loading) {
143
+ return (
144
+ <View style={style}>
145
+ <RNText
146
+ {...rest}
147
+ allowFontScaling={false}
148
+ style={[
149
+ textStyle,
150
+ {
151
+ color: color ?? theme.colors.text.default,
152
+ },
153
+ ]}>
154
+ {children ?? ''}
155
+ </RNText>
156
+ <Skeleton style={{position: 'absolute'}} />
157
+ </View>
158
+ );
159
+ }
160
+
137
161
  return (
138
162
  <RNText
139
163
  {...rest}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.112.1-testing.20",
3
+ "version": "0.113.0-rc.0",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},