@momo-kits/foundation 0.102.3-beta.1 → 0.102.3-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.
@@ -35,6 +35,7 @@ import {Badge, BadgeDot} from '../Badge';
35
35
  import {HeaderType} from '../Layout/types';
36
36
  import Navigation from './Navigation';
37
37
  import {InputRef, InputSearch, InputSearchProps} from '../Input';
38
+ import {BadgeDotAnimation} from '../Badge';
38
39
 
39
40
  const SCREEN_PADDING = 12;
40
41
  const BACK_WIDTH = 28;
@@ -71,6 +72,11 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
71
72
  if (badgeType === 'dot') {
72
73
  return <BadgeDot size="small" style={styles.badgeDot} />;
73
74
  }
75
+ if (badgeType === 'dot-animation') {
76
+ return (
77
+ <BadgeDotAnimation size="small" style={styles.badgeDotAnimation} />
78
+ );
79
+ }
74
80
 
75
81
  if (isNumber(badgeValue)) {
76
82
  return <Badge label={badgeValue} style={styles.badge} />;
@@ -666,7 +672,7 @@ const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
666
672
  if (__DEV__) {
667
673
  Alert.alert(
668
674
  'HeaderRightAction',
669
- 'This component is deprecated, please use HeaderRight instead v0.92.34'
675
+ 'This component is deprecated and will removed 10/2024, please use new type headerRight specs instead v0.92.34'
670
676
  );
671
677
  }
672
678
  const validateType = (child: React.ReactElement) => {
@@ -763,6 +769,11 @@ const styles = StyleSheet.create({
763
769
  top: -Spacing.XS,
764
770
  right: -Spacing.XS,
765
771
  },
772
+ badgeDotAnimation: {
773
+ position: 'absolute',
774
+ top: -Spacing.XS,
775
+ right: -Spacing.XS,
776
+ },
766
777
  extendedHeader: {
767
778
  aspectRatio: 1.75,
768
779
  position: 'absolute',
@@ -0,0 +1,37 @@
1
+ import {NativeModules} from 'react-native';
2
+ import {LocalizationObject} from './types';
3
+ import defaultLanguage from '../Assets/language.json';
4
+
5
+ class Localize {
6
+ private assets: LocalizationObject;
7
+ private currentLanguage: 'en' | 'vi';
8
+
9
+ constructor(assets: LocalizationObject) {
10
+ this.assets = {
11
+ vi: {...assets?.vi, ...defaultLanguage.vi},
12
+ en: {...assets?.en, ...defaultLanguage.en},
13
+ };
14
+ this.currentLanguage = 'vi';
15
+ if (NativeModules?.RNResource?.language === 'en') {
16
+ this.currentLanguage = 'en';
17
+ }
18
+ }
19
+
20
+ translate(key: string) {
21
+ return this.assets[this.currentLanguage]?.[key] || key;
22
+ }
23
+
24
+ translateData(data: {vi: string; en: string}) {
25
+ return data[this.currentLanguage] || data.vi || JSON.stringify(data);
26
+ }
27
+
28
+ addTranslations(translations: LocalizationObject) {
29
+ this.assets = translations;
30
+ }
31
+
32
+ setLanguage(language: 'en' | 'vi') {
33
+ this.currentLanguage = language;
34
+ }
35
+ }
36
+
37
+ export default Localize;
@@ -10,9 +10,9 @@ import StackScreen from './StackScreen';
10
10
  import ModalScreen from './ModalScreen';
11
11
  import Navigator from './Navigator';
12
12
  import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
13
- import {LocalizationObject, NavigationContainerProps} from './types';
13
+ import {NavigationContainerProps} from './types';
14
14
  import {ApplicationContext} from './index';
15
- import kitLocalization from '../Assets/language.json';
15
+ import Localize from './Localize';
16
16
 
17
17
  const Stack = createStackNavigator();
18
18
 
@@ -21,15 +21,18 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
21
21
  theme,
22
22
  options,
23
23
  maxApi,
24
- localization,
25
24
  initialParams,
25
+ localize = new Localize({vi: {}, en: {}}),
26
26
  }) => {
27
27
  const navigationRef = useRef<NavigationContainerRef>(null);
28
28
  const isReady = useRef(false);
29
29
  const navigator = useRef(new Navigator(navigationRef, isReady));
30
30
  const [showGrid, setShowGrid] = useState(false);
31
31
  const config = useRef<any>();
32
-
32
+ /**
33
+ * inject data for navigator
34
+ */
35
+ navigator.current.maxApi = maxApi;
33
36
  /**
34
37
  * handle mini language & listen change
35
38
  * engine only shake to enable grid view
@@ -39,7 +42,7 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
39
42
  'onChangeGrid',
40
43
  enable => {
41
44
  setShowGrid(!!enable);
42
- },
45
+ }
43
46
  );
44
47
 
45
48
  return () => {
@@ -47,22 +50,8 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
47
50
  };
48
51
  }, []);
49
52
 
50
- /**
51
- * inject data for navigator
52
- */
53
- navigator.current.maxApi = maxApi;
54
-
55
- /**
56
- * translate
57
- * @param key
58
- */
59
53
  const translate = (key: string) => {
60
- const language: 'vi' | 'en' = 'vi';
61
- const mergedLocalization: LocalizationObject = {
62
- vi: {...localization?.vi, ...kitLocalization.vi},
63
- en: {...localization?.en, ...kitLocalization.en},
64
- };
65
- return mergedLocalization?.[language]?.[key] || key;
54
+ return localize?.translate(key);
66
55
  };
67
56
 
68
57
  return (
@@ -8,80 +8,6 @@ import {GridSystem} from '../Layout';
8
8
  import {Text} from '../Text';
9
9
  import {Colors, Radius, Spacing} from '../Consts';
10
10
 
11
- /**
12
- * Empty screen for unvalidated screen name
13
- * @constructor
14
- */
15
- const EmptyScreen: React.FC = () => {
16
- return (
17
- <View
18
- style={{
19
- backgroundColor: Colors.black_01,
20
- padding: Spacing.M,
21
- borderRadius: Radius.M,
22
- }}>
23
- <Text typography={'header_m_bold'}>Unvalidated screen name</Text>
24
- <Text
25
- typography={'description_default_regular'}
26
- style={{paddingVertical: Spacing.S}}>
27
- Your screen has not been rendered because Platform has not detected the
28
- screen name. Please migrate to support this feature.
29
- </Text>
30
- <Text typography={'header_default'} style={{width: '100%'}}>
31
- Possible fixes:
32
- </Text>
33
- <View style={{marginLeft: Spacing.S}}>
34
- <Text typography={'body_default_regular'}>1. Off webpack config</Text>
35
- <Text typography={'body_default_regular'}>
36
- 2. Screen name refactoring
37
- </Text>
38
- <View style={{marginLeft: Spacing.S}}>
39
- <Text
40
- typography={'description_default_regular'}
41
- style={{marginBottom: Spacing.S}}>
42
- - Với các screen push/import dạng arrow function ={'>'} đổi thành
43
- named function
44
- </Text>
45
- <Text
46
- typography={'description_default_regular'}
47
- style={{marginBottom: Spacing.S}}>
48
- - Với các screen import dạng High Order Component ={'>'} rename
49
- generic function thành named function
50
- </Text>
51
- <Text
52
- typography={'description_default_regular'}
53
- style={{marginBottom: Spacing.S}}>
54
- - Với Tab Navigator, Platform sẽ lấy Tab Container làm screen name.
55
- Trong trường hợp miniapp muốn track riêng action cho từng tab, tham
56
- khảo theo docs
57
- </Text>
58
- </View>
59
- </View>
60
- <Text typography={'header_s_semibold'}>More information: </Text>
61
- <Text
62
- typography={'body_default_regular'}
63
- color={Colors.blue_01}
64
- onPress={() => {
65
- Linking.openURL(
66
- 'https://gitlab.mservice.com.vn/momo-platform/mini-app/-/tree/vn.momo.uikits/dev/app'
67
- );
68
- }}>
69
- Mini App: vn.momo.uikits/dev
70
- </Text>
71
- <Text
72
- typography={'body_default_regular'}
73
- color={Colors.blue_01}
74
- onPress={() => {
75
- Linking.openURL(
76
- 'https://docs.google.com/presentation/d/1bIFoh8gRXb6hsnGJz4O1Kxg4_oNikB1KuuEt2wULCDM/edit?usp=sharing'
77
- );
78
- }}>
79
- https://docs.google.com/presentation/d/1bIFoh8gRXb6hsnGJz4O1Kxg4_oNikB1KuuEt2wULCDM/edit?usp=sharing
80
- </Text>
81
- </View>
82
- );
83
- };
84
-
85
11
  /**
86
12
  * container for stack screen
87
13
  * @param props
@@ -89,20 +15,20 @@ const EmptyScreen: React.FC = () => {
89
15
  */
90
16
  const StackScreen: React.FC<ScreenParams> = props => {
91
17
  const {showGrid, navigator} = useContext(ApplicationContext);
92
- const startTime = useRef(Date.now());
93
- const endTime = useRef(Date.now());
94
- const timeLoad = useRef(0);
95
- const timeInteraction = useRef(0);
96
- const timeoutLoad = useRef<any>();
97
- const tracked = useRef<any>({
18
+ const tracking = useRef<any>({
19
+ timeoutLoad: undefined,
20
+ timeoutInteraction: undefined,
21
+ timeoutTracking: undefined,
22
+ startTime: Date.now(),
23
+ endTime: Date.now(),
98
24
  traceIdLoad: undefined,
99
25
  traceIdInteraction: undefined,
100
26
  releaseLoad: undefined,
101
27
  releaseInteraction: undefined,
102
- timeoutLoad: undefined,
103
- timeoutInteraction: undefined,
28
+ timeLoad: 0,
29
+ timeInteraction: 0,
104
30
  });
105
- const interaction = useRef<any>();
31
+
106
32
  const context = useContext<any>(MiniAppContext);
107
33
  const {screen: Component, options, initialParams} = props.route.params;
108
34
  const navigation = new Navigation(props.navigation);
@@ -118,6 +44,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
118
44
  delete data.initialParams;
119
45
 
120
46
  const screenName = Component?.name || Component?.type?.name || 'Invalid';
47
+ const routes = props.navigation.getState()?.routes || [];
121
48
 
122
49
  /**
123
50
  * set options for screen
@@ -135,15 +62,6 @@ const StackScreen: React.FC<ScreenParams> = props => {
135
62
  };
136
63
  }
137
64
  navigation.setOptions(defaultOptions);
138
- navigator?.maxApi?.startTraceScreenLoad?.(screenName, (data: any) => {
139
- tracked.current.traceIdLoad = data?.traceId;
140
- });
141
- navigator?.maxApi?.startTraceScreenInteraction?.(
142
- screenName,
143
- (data: any) => {
144
- tracked.current.traceIdInteraction = data?.traceId;
145
- }
146
- );
147
65
  }, [options]);
148
66
 
149
67
  /**
@@ -155,19 +73,39 @@ const StackScreen: React.FC<ScreenParams> = props => {
155
73
  navigator?.showModal({screen: EmptyScreen});
156
74
  }, 300);
157
75
  }
158
- navigator?.maxApi?.of?.({screenName});
159
- tracked.current.timeoutLoad = setTimeout(() => {
76
+
77
+ const subscription = props.navigation?.addListener?.('focus', () => {
78
+ navigator?.maxApi?.of?.({screenName});
79
+ navigator?.maxApi?.getDataObserver('CURRENT_SCREEN', (data: any) => {
80
+ let preScreenName = data?.screenName;
81
+ if (routes?.length > 1) {
82
+ const screen = routes?.[routes?.length - 2]?.params?.screen;
83
+ preScreenName = screen?.name || screen?.type?.name || 'Invalid';
84
+ }
85
+ onScreenNavigated(preScreenName);
86
+ navigator?.maxApi?.setObserver('CURRENT_SCREEN', {screenName});
87
+ });
88
+ });
89
+ navigator?.maxApi?.startTraceScreenLoad?.(screenName, (data: any) => {
90
+ tracking.current.traceIdLoad = data?.traceId;
91
+ });
92
+ navigator?.maxApi?.startTraceScreenInteraction?.(
93
+ screenName,
94
+ (data: any) => {
95
+ tracking.current.traceIdInteraction = data?.traceId;
96
+ }
97
+ );
98
+
99
+ tracking.current.timeoutTracking = setTimeout(() => {
160
100
  onScreenLoad();
161
- }, 5000);
162
- tracked.current.timeoutInteraction = setTimeout(() => {
163
101
  onScreenInteraction();
164
102
  }, 5000);
165
103
 
166
104
  return () => {
167
- clearTimeout(tracked.current.timeoutLoad);
168
- clearTimeout(tracked.current.timeoutInteraction);
169
105
  onScreenLoad();
170
106
  onScreenInteraction();
107
+ clearTimeout(tracking.current.timeoutTracking);
108
+ subscription?.();
171
109
  };
172
110
  }, []);
173
111
 
@@ -175,9 +113,10 @@ const StackScreen: React.FC<ScreenParams> = props => {
175
113
  * tracking for screen load
176
114
  */
177
115
  const onScreenLoad = () => {
178
- if (!tracked.current?.releaseLoad) {
179
- if (timeLoad.current === 0) {
180
- timeLoad.current = endTime.current - startTime.current;
116
+ if (!tracking.current?.releaseLoad) {
117
+ if (tracking.current.timeLoad === 0) {
118
+ tracking.current.timeLoad =
119
+ tracking.current.endTime - tracking.current.startTime;
181
120
  }
182
121
 
183
122
  context.autoTracking?.({
@@ -185,26 +124,26 @@ const StackScreen: React.FC<ScreenParams> = props => {
185
124
  code: context.code,
186
125
  buildNumber: context.buildNumber,
187
126
  screenName,
188
- action: 'push',
189
127
  componentName: 'Screen',
190
128
  state: 'load',
191
- duration: timeLoad.current,
129
+ duration: tracking.current.timeLoad,
192
130
  });
193
131
  navigator?.maxApi?.stopTrace?.(
194
- tracked.current.traceIdLoad,
195
- {value: timeLoad.current / 1000},
132
+ tracking.current.traceIdLoad,
133
+ {value: tracking.current.timeLoad / 1000},
196
134
  null
197
135
  );
198
- tracked.current.releaseLoad = true;
136
+ tracking.current.releaseLoad = true;
199
137
 
200
138
  /**
201
139
  * debug
202
140
  */
203
141
  navigator?.maxApi?.showToastDebug?.({
204
- appId: `auto - ${context.appId}`,
205
- message: `${screenName} screen_load_time ${timeLoad.current}`,
142
+ appId: context.appId,
143
+ message: `${screenName} screen_load_time ${tracking.current.timeLoad}`,
144
+ type: 'ERROR',
206
145
  });
207
- if (timeLoad.current <= 0 && context.enableAutoId) {
146
+ if (tracking.current.timeLoad <= 0 && context.enableAutoId) {
208
147
  Alert.alert(screenName, "Can't get screen load time");
209
148
  }
210
149
  }
@@ -214,12 +153,13 @@ const StackScreen: React.FC<ScreenParams> = props => {
214
153
  * tracking for screen load
215
154
  */
216
155
  const onScreenInteraction = () => {
217
- if (!tracked.current?.releaseInteraction) {
218
- if (timeLoad.current === 0) {
219
- timeLoad.current = endTime.current - startTime.current;
156
+ if (!tracking.current?.releaseInteraction) {
157
+ if (tracking.current.timeLoad === 0) {
158
+ tracking.current.timeLoad =
159
+ tracking.current.endTime - tracking.current.startTime;
220
160
  }
221
- if (timeInteraction.current === 0) {
222
- timeInteraction.current = timeLoad.current;
161
+ if (tracking.current.timeInteraction === 0) {
162
+ tracking.current.timeInteraction = tracking.current.timeLoad;
223
163
  }
224
164
 
225
165
  context.autoTracking?.({
@@ -229,43 +169,74 @@ const StackScreen: React.FC<ScreenParams> = props => {
229
169
  screenName,
230
170
  componentName: 'Screen',
231
171
  state: 'interaction',
232
- duration: timeInteraction.current - timeLoad.current,
172
+ duration: tracking.current.timeInteraction - tracking.current.timeLoad,
173
+ totalDuration: tracking.current.timeInteraction,
233
174
  });
234
175
  navigator?.maxApi?.stopTrace?.(
235
- tracked.current.traceIdInteraction,
236
- {value: timeInteraction.current / 1000},
176
+ tracking.current.traceIdInteraction,
177
+ {value: tracking.current.timeInteraction / 1000},
237
178
  null
238
179
  );
239
- tracked.current.releaseInteraction = true;
180
+ tracking.current.releaseInteraction = true;
240
181
 
241
182
  /**
242
183
  * debug toast
243
184
  */
244
185
  navigator?.maxApi?.showToastDebug?.({
245
- appId: `auto - ${context.appId}`,
246
- message: `${screenName} screen_interaction_time ${timeInteraction.current}`,
186
+ appId: context.appId,
187
+ message: `${screenName} screen_interaction_time ${tracking.current.timeInteraction}`,
188
+ type: 'ERROR',
247
189
  });
248
- if (timeInteraction.current <= 0 && context.enableAutoId) {
190
+ if (tracking.current.timeInteraction <= 0 && context.enableAutoId) {
249
191
  Alert.alert(screenName, "Can't get screen interaction time");
250
192
  }
251
193
  }
252
194
  };
253
195
 
196
+ /**
197
+ * tracking for screen navigated
198
+ */
199
+ const onScreenNavigated = (preScreenName: string) => {
200
+ context.autoTracking?.({
201
+ appId: context.appId,
202
+ code: context.code,
203
+ buildNumber: context.buildNumber,
204
+ preScreenName,
205
+ screenName,
206
+ componentName: 'Screen',
207
+ state: 'navigated',
208
+ });
209
+
210
+ /**
211
+ * debug toast
212
+ */
213
+ navigator?.maxApi?.showToastDebug?.({
214
+ appId: context.appId,
215
+ message: `${screenName} screen_navigated`,
216
+ type: 'ERROR',
217
+ });
218
+ };
219
+
254
220
  return (
255
221
  <ScreenContext.Provider
256
222
  value={{
257
223
  screenName,
258
224
  onElementLoad: () => {
259
- clearTimeout(timeoutLoad.current);
260
- endTime.current = Date.now();
261
- interaction.current?.cancel?.();
262
- interaction.current = InteractionManager.runAfterInteractions(() => {
263
- timeInteraction.current = Date.now() - startTime.current;
264
- });
265
- timeoutLoad.current = setTimeout(() => {
266
- if (timeLoad.current === 0) {
267
- timeLoad.current = endTime.current - startTime.current;
225
+ clearTimeout(tracking.current.timeoutLoad);
226
+ tracking.current.endTime = Date.now();
227
+ tracking.current.timeoutInteraction?.cancel?.();
228
+ tracking.current.timeoutInteraction =
229
+ InteractionManager.runAfterInteractions(() => {
230
+ tracking.current.timeInteraction =
231
+ Date.now() - tracking.current.startTime;
232
+ });
233
+ tracking.current.timeoutLoad = setTimeout(() => {
234
+ if (tracking.current.timeLoad === 0) {
235
+ tracking.current.timeLoad =
236
+ tracking.current.endTime - tracking.current.startTime;
268
237
  }
238
+ onScreenLoad();
239
+ onScreenInteraction();
269
240
  }, 2000);
270
241
  },
271
242
  }}>
@@ -280,4 +251,78 @@ const StackScreen: React.FC<ScreenParams> = props => {
280
251
  );
281
252
  };
282
253
 
254
+ /**
255
+ * Empty screen for unvalidated screen name
256
+ * @constructor
257
+ */
258
+ const EmptyScreen: React.FC = () => {
259
+ return (
260
+ <View
261
+ style={{
262
+ backgroundColor: Colors.black_01,
263
+ padding: Spacing.M,
264
+ borderRadius: Radius.M,
265
+ }}>
266
+ <Text typography={'header_m_bold'}>Unvalidated screen name</Text>
267
+ <Text
268
+ typography={'description_default_regular'}
269
+ style={{paddingVertical: Spacing.S}}>
270
+ Your screen has not been rendered because Platform has not detected the
271
+ screen name. Please migrate to support this feature.
272
+ </Text>
273
+ <Text typography={'header_default'} style={{width: '100%'}}>
274
+ Possible fixes:
275
+ </Text>
276
+ <View style={{marginLeft: Spacing.S}}>
277
+ <Text typography={'body_default_regular'}>1. Off webpack config</Text>
278
+ <Text typography={'body_default_regular'}>
279
+ 2. Screen name refactoring
280
+ </Text>
281
+ <View style={{marginLeft: Spacing.S}}>
282
+ <Text
283
+ typography={'description_default_regular'}
284
+ style={{marginBottom: Spacing.S}}>
285
+ - Với các screen push/import dạng arrow function ={'>'} đổi thành
286
+ named function
287
+ </Text>
288
+ <Text
289
+ typography={'description_default_regular'}
290
+ style={{marginBottom: Spacing.S}}>
291
+ - Với các screen import dạng High Order Component ={'>'} rename
292
+ generic function thành named function
293
+ </Text>
294
+ <Text
295
+ typography={'description_default_regular'}
296
+ style={{marginBottom: Spacing.S}}>
297
+ - Với Tab Navigator, Platform sẽ lấy Tab Container làm screen name.
298
+ Trong trường hợp miniapp muốn track riêng action cho từng tab, tham
299
+ khảo theo docs
300
+ </Text>
301
+ </View>
302
+ </View>
303
+ <Text typography={'header_s_semibold'}>More information: </Text>
304
+ <Text
305
+ typography={'body_default_regular'}
306
+ color={Colors.blue_01}
307
+ onPress={() => {
308
+ Linking.openURL(
309
+ 'https://gitlab.mservice.com.vn/momo-platform/mini-app/-/tree/vn.momo.uikits/dev/app'
310
+ );
311
+ }}>
312
+ Mini App: vn.momo.uikits/dev
313
+ </Text>
314
+ <Text
315
+ typography={'body_default_regular'}
316
+ color={Colors.blue_01}
317
+ onPress={() => {
318
+ Linking.openURL(
319
+ 'https://docs.google.com/presentation/d/1bIFoh8gRXb6hsnGJz4O1Kxg4_oNikB1KuuEt2wULCDM/edit?usp=sharing'
320
+ );
321
+ }}>
322
+ https://docs.google.com/presentation/d/1bIFoh8gRXb6hsnGJz4O1Kxg4_oNikB1KuuEt2wULCDM/edit?usp=sharing
323
+ </Text>
324
+ </View>
325
+ );
326
+ };
327
+
283
328
  export default StackScreen;
@@ -1,5 +1,7 @@
1
1
  import {Platform} from 'react-native';
2
+ import {createContext} from 'react';
2
3
  import {NavigationContainer} from './NavigationContainer';
4
+ import Localize from './Localize';
3
5
  import Screen from '../Layout/Screen';
4
6
  import {
5
7
  HeaderAnimated,
@@ -10,7 +12,6 @@ import {
10
12
  HeaderRightAction,
11
13
  } from './Components';
12
14
  import BottomTab from './BottomTab';
13
- import {createContext} from 'react';
14
15
  import {defaultContext} from '../Consts';
15
16
 
16
17
  const Context = createContext({});
@@ -26,6 +27,7 @@ export {
26
27
  ScreenContext,
27
28
  ComponentContext,
28
29
  NavigationContainer,
30
+ Localize,
29
31
  Screen,
30
32
  BottomTab,
31
33
  NavigationButton,
@@ -1,8 +1,9 @@
1
- import {StackNavigationOptions} from '@react-navigation/stack';
2
1
  import {EventArg} from '@react-navigation/core';
2
+ import {StackNavigationOptions} from '@react-navigation/stack';
3
3
  import React from 'react';
4
4
  import {Animated, ViewProps} from 'react-native';
5
5
  import {PopupNotifyProps} from '../Popup/types';
6
+ import Localize from './Localize';
6
7
  import Navigation from './Navigation';
7
8
  import Navigator from './Navigator';
8
9
 
@@ -85,8 +86,8 @@ export type NavigationContainerProps = {
85
86
  options?: NavigationOptions;
86
87
  theme: Theme;
87
88
  maxApi: any;
88
- localization?: LocalizationObject;
89
89
  initialParams?: any;
90
+ localize: Localize;
90
91
  };
91
92
 
92
93
  export type NavigationScreenProps = {
@@ -129,7 +130,7 @@ export type NavigationButtonProps = {
129
130
  tintColor?: string;
130
131
  useBorder?: boolean;
131
132
  onPress: () => void;
132
- badgeType?: 'dot' | 'number';
133
+ badgeType?: 'dot' | 'number' | 'dot-animation';
133
134
  badgeValue?: number;
134
135
  accessibilityLabel?: string;
135
136
  };
@@ -137,7 +138,7 @@ export type NavigationButtonProps = {
137
138
  export type PinnedToolType = {
138
139
  key: string;
139
140
  badgeValue?: number;
140
- badgeType?: 'number' | 'dot';
141
+ badgeType?: 'number' | 'dot' | 'dot-animation';
141
142
  };
142
143
 
143
144
  export type RuntimeToolType = {
@@ -0,0 +1,95 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {Animated, View} from 'react-native';
3
+ import {BadgeDotProps} from './types';
4
+ import styles from './styles';
5
+
6
+ const DURATION = 500;
7
+
8
+ const BadgeDotAnimation = ({size, style}: BadgeDotProps) => {
9
+ // Refs for animated values
10
+ const scaleAnim = useRef(new Animated.Value(1)).current;
11
+ const waveScaleAnim = useRef(new Animated.Value(1)).current;
12
+ const waveOpacityAnim = useRef(new Animated.Value(0)).current;
13
+
14
+ const dotStyle =
15
+ size === 'small' ? styles.dotAnimationSmall : styles.dotAnimation;
16
+ const waveStyle = size === 'small' ? styles.waveSmall : styles.wave;
17
+
18
+ useEffect(() => {
19
+ // Infinite loop animation for the scale and wave effect
20
+ const animation = Animated.loop(
21
+ Animated.parallel([
22
+ // Dot pulse animation
23
+ Animated.sequence([
24
+ Animated.spring(scaleAnim, {
25
+ toValue: 1, // Scale up slightly
26
+ friction: 5, // Controls the "bounciness" of the spring
27
+ tension: 30, // Controls the "stiffness" of the spring
28
+ useNativeDriver: true,
29
+ }),
30
+ Animated.spring(scaleAnim, {
31
+ toValue: 1.1,
32
+ friction: 5,
33
+ tension: 30,
34
+ useNativeDriver: true,
35
+ }),
36
+ ]), // Wave animation
37
+ Animated.sequence([
38
+ Animated.timing(waveScaleAnim, {
39
+ toValue: 2.5,
40
+ duration: DURATION * 3,
41
+ useNativeDriver: true,
42
+ }),
43
+ Animated.timing(waveScaleAnim, {
44
+ toValue: 1, // Reset wave size
45
+ duration: 0,
46
+ useNativeDriver: true,
47
+ }),
48
+ ]), // Wave opacity animation
49
+ Animated.sequence([
50
+ Animated.timing(waveOpacityAnim, {
51
+ toValue: 0.3, // Wave becomes visible
52
+ duration: DURATION * 2,
53
+ useNativeDriver: true,
54
+ }),
55
+ Animated.timing(waveOpacityAnim, {
56
+ toValue: 0, // Wave fades out
57
+ duration: DURATION,
58
+ useNativeDriver: true,
59
+ }),
60
+ ]),
61
+ ])
62
+ );
63
+ animation.start();
64
+
65
+ return () => {
66
+ animation.stop();
67
+ };
68
+ }, []);
69
+
70
+ return (
71
+ <View style={[styles.dotAnimationContainer, style]}>
72
+ {/* Wave Animation */}
73
+ <Animated.View
74
+ style={[
75
+ waveStyle,
76
+ {
77
+ transform: [{scale: waveScaleAnim}],
78
+ opacity: waveOpacityAnim,
79
+ },
80
+ ]}
81
+ />
82
+ {/* Dot Animation */}
83
+ <Animated.View
84
+ style={[
85
+ dotStyle,
86
+ {
87
+ transform: [{scale: scaleAnim}],
88
+ },
89
+ ]}
90
+ />
91
+ </View>
92
+ );
93
+ };
94
+
95
+ export default BadgeDotAnimation;
package/Badge/index.tsx CHANGED
@@ -1,7 +1,8 @@
1
1
  import Badge from './Badge';
2
2
  import BadgeDot from './BadgeDot';
3
3
  import BadgeRibbon from './BadgeRibbon';
4
+ import BadgeDotAnimation from './BadgeDotAnimation';
4
5
  import {BadgeProps, BadgeDotProps, BadgeRibbonProps} from './types';
5
6
 
6
- export {Badge, BadgeRibbon, BadgeDot};
7
+ export {Badge, BadgeRibbon, BadgeDot, BadgeDotAnimation};
7
8
  export type {BadgeProps, BadgeDotProps, BadgeRibbonProps};
package/Badge/styles.ts CHANGED
@@ -2,6 +2,10 @@ import {StyleSheet} from 'react-native';
2
2
  import {Colors, Radius, Spacing} from '../Consts';
3
3
  import {scaleSize} from '../Text';
4
4
 
5
+ const DOT_SIZE = 16;
6
+
7
+ const DOT_SMALL_SIZE = 10;
8
+
5
9
  export default StyleSheet.create({
6
10
  badge: {
7
11
  paddingHorizontal: Spacing.XS,
@@ -15,21 +19,61 @@ export default StyleSheet.create({
15
19
  borderColor: Colors.black_01,
16
20
  alignSelf: 'baseline',
17
21
  },
22
+ dotAnimationContainer: {
23
+ position: 'relative',
24
+ alignItems: 'center',
25
+ justifyContent: 'center',
26
+ },
18
27
  dot: {
19
- width: 16,
20
- height: 16,
28
+ width: DOT_SIZE,
29
+ height: DOT_SIZE,
21
30
  borderWidth: 2,
22
31
  borderColor: Colors.black_01,
23
32
  backgroundColor: Colors.red_03,
24
- borderRadius: 8,
33
+ borderRadius: DOT_SIZE / 2,
25
34
  },
26
35
  dotSmall: {
27
- width: 10,
28
- height: 10,
36
+ width: DOT_SMALL_SIZE,
37
+ height: DOT_SMALL_SIZE,
29
38
  borderWidth: 1,
30
39
  borderColor: Colors.black_01,
31
40
  backgroundColor: Colors.red_03,
32
- borderRadius: 5,
41
+ borderRadius: DOT_SMALL_SIZE / 2,
42
+ },
43
+ dotAnimation: {
44
+ width: DOT_SIZE,
45
+ height: DOT_SIZE,
46
+ borderColor: Colors.black_01,
47
+ backgroundColor: Colors.red_03,
48
+ borderRadius: DOT_SIZE / 2,
49
+ borderWidth: 0,
50
+ },
51
+ dotAnimationSmall: {
52
+ width: DOT_SMALL_SIZE,
53
+ height: DOT_SMALL_SIZE,
54
+ borderColor: Colors.black_01,
55
+ backgroundColor: Colors.red_03,
56
+ borderRadius: DOT_SMALL_SIZE / 2,
57
+ borderWidth: 0,
58
+ },
59
+ wave: {
60
+ width: DOT_SIZE,
61
+ height: DOT_SIZE,
62
+ borderColor: Colors.black_01,
63
+ backgroundColor: Colors.red_03,
64
+ borderRadius: DOT_SIZE / 2,
65
+ opacity: 0,
66
+ position: 'absolute',
67
+ borderWidth: 0,
68
+ },
69
+ waveSmall: {
70
+ width: DOT_SMALL_SIZE,
71
+ height: DOT_SMALL_SIZE,
72
+ borderColor: Colors.black_01,
73
+ backgroundColor: Colors.red_03,
74
+ borderRadius: DOT_SMALL_SIZE / 2,
75
+ borderWidth: 0,
76
+ position: 'absolute',
33
77
  },
34
78
  ribbon: {
35
79
  alignSelf: 'baseline',
package/Input/styles.ts CHANGED
@@ -32,10 +32,9 @@ export default StyleSheet.create({
32
32
  alignItems: 'center',
33
33
  },
34
34
  floatingIcon: {marginLeft: Spacing.XS},
35
- errorIcon: {marginRight: Spacing.XS},
35
+ errorIcon: {marginRight: Spacing.XS, marginTop: Spacing.XXS},
36
36
  errorView: {
37
37
  flexDirection: 'row',
38
- alignItems: 'center',
39
38
  marginTop: Spacing.XS,
40
39
  },
41
40
  inputView: {
package/Layout/Screen.tsx CHANGED
@@ -132,13 +132,12 @@ const Screen = forwardRef(
132
132
  inputSearchProps,
133
133
  inputSearchRef,
134
134
  }: ScreenProps,
135
- ref
135
+ ref: any
136
136
  ) => {
137
137
  const {theme} = useContext(ApplicationContext);
138
138
  const insets = useSafeAreaInsets();
139
139
  const heightHeader = useHeaderHeight();
140
140
  const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
141
- const scrollViewRef = useRef<any>(ref);
142
141
  const currentTint = useRef(Colors.black_01);
143
142
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
144
143
 
@@ -345,7 +344,7 @@ const Screen = forwardRef(
345
344
  useNativeDriver: true,
346
345
  duration: 300,
347
346
  }).start();
348
- scrollViewRef.current?.scrollTo({y: 0, animated: true});
347
+ ref?.scrollTo({y: 0, animated: true});
349
348
  }
350
349
  scrollViewProps?.onScrollEndDrag?.(e);
351
350
  };
@@ -433,7 +432,7 @@ const Screen = forwardRef(
433
432
 
434
433
  <Component
435
434
  {...scrollViewProps}
436
- ref={scrollViewRef}
435
+ ref={ref}
437
436
  showsVerticalScrollIndicator={false}
438
437
  onScroll={handleScroll}
439
438
  onScrollEndDrag={handleScrollEnd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.102.3-beta.1",
3
+ "version": "0.102.3-optimize.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
package/publish.sh CHANGED
@@ -9,8 +9,8 @@ elif [ "$1" == "latest" ]; then
9
9
  npm version prerelease --preid=rc
10
10
  npm publish --tag latest --access=public
11
11
  else
12
- npm version $(npm view @momo-kits/foundation@beta version)
13
- npm version prerelease --preid=beta
12
+ # npm version $(npm view @momo-kits/foundation@beta version)
13
+ # npm version prerelease --preid=beta
14
14
  npm publish --tag beta --access=public
15
15
  fi
16
16