@momo-kits/foundation 0.81.1-optimize.21 → 0.81.1-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.
Files changed (45) hide show
  1. package/Application/BottomSheet.tsx +27 -23
  2. package/Application/BottomTab/BottomTabBar.tsx +2 -2
  3. package/Application/BottomTab/index.tsx +2 -3
  4. package/Application/Components.tsx +288 -42
  5. package/Application/Localize.ts +44 -0
  6. package/Application/ModalScreen.tsx +2 -2
  7. package/Application/Navigation.ts +4 -18
  8. package/Application/NavigationContainer.tsx +10 -21
  9. package/Application/Navigator.ts +14 -16
  10. package/Application/StackScreen.tsx +211 -113
  11. package/Application/index.ts +5 -3
  12. package/Application/types.ts +30 -11
  13. package/Application/utils.tsx +60 -18
  14. package/Badge/BadgeDotAnimation.tsx +95 -0
  15. package/Badge/index.tsx +2 -1
  16. package/Badge/styles.ts +50 -6
  17. package/Button/index.tsx +0 -1
  18. package/Icon/types.ts +0 -1
  19. package/Input/Input.tsx +51 -17
  20. package/Input/InputDropDown.tsx +44 -47
  21. package/Input/InputMoney.tsx +56 -6
  22. package/Input/InputOTP.tsx +10 -6
  23. package/Input/InputSearch.tsx +259 -146
  24. package/Input/TextTyping.tsx +113 -0
  25. package/Input/common.tsx +29 -10
  26. package/Input/index.tsx +75 -74
  27. package/Input/styles.ts +43 -7
  28. package/Layout/FloatingButton.tsx +1 -1
  29. package/Layout/GridSystem.tsx +1 -1
  30. package/Layout/ItemList.tsx +3 -2
  31. package/Layout/Screen.tsx +174 -91
  32. package/Layout/styles.ts +0 -6
  33. package/Layout/types.ts +1 -1
  34. package/Pagination/PaginationScroll.tsx +2 -2
  35. package/Pagination/styles.ts +1 -1
  36. package/Popup/PopupNotify.tsx +3 -3
  37. package/Tag/index.tsx +9 -2
  38. package/Tag/styles.ts +3 -2
  39. package/Tag/types.ts +12 -0
  40. package/Title/index.tsx +6 -9
  41. package/Title/styles.ts +4 -4
  42. package/code-scanner.js +4 -4
  43. package/package.json +6 -5
  44. package/publish.sh +12 -22
  45. package/verify.js +36 -42
@@ -1,18 +1,18 @@
1
- import React, {useCallback, useContext, useEffect, useRef} from 'react';
1
+ import React, {useRef, useEffect, useCallback, useContext} from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Dimensions,
5
5
  KeyboardAvoidingView,
6
- Modal,
7
6
  PanResponder,
8
7
  Platform,
9
8
  StyleSheet,
10
9
  TouchableOpacity,
11
10
  View,
11
+ Modal,
12
12
  } from 'react-native';
13
+ import {useSafeAreaInsets} from 'react-native-safe-area-context';
13
14
  import {ApplicationContext} from './index';
14
15
  import {BottomSheetParams} from './types';
15
- import {useSafeAreaInsets} from 'react-native-safe-area-context';
16
16
  import {Colors, Radius, Spacing, Styles} from '../Consts';
17
17
  import {Text} from '../Text';
18
18
  import {Icon} from '../Icon';
@@ -24,6 +24,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
24
24
  const {
25
25
  screen: Screen,
26
26
  options,
27
+ useNativeModal = false,
27
28
  surface,
28
29
  barrierDismissible = false,
29
30
  draggable = true,
@@ -36,6 +37,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
36
37
  x: 0,
37
38
  })
38
39
  ).current;
40
+
39
41
  const panResponder = useRef(
40
42
  PanResponder.create({
41
43
  onStartShouldSetPanResponder: () => draggable,
@@ -61,7 +63,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
61
63
  },
62
64
  })
63
65
  ).current;
64
-
66
+ let Container: any = View;
67
+ if (useNativeModal) {
68
+ Container = Modal;
69
+ }
65
70
  let backgroundColor = theme.colors.background.default;
66
71
  if (surface) {
67
72
  backgroundColor = theme.colors.background.surface;
@@ -88,20 +93,22 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
88
93
  if (barrierDismissible && !force) {
89
94
  return;
90
95
  }
96
+ setTimeout(() => {
97
+ callback?.();
98
+ }, 300);
91
99
  Animated.timing(pan, {
92
100
  toValue: {x: 0, y: heightDevice},
93
101
  useNativeDriver: false,
94
- duration: 100,
102
+ duration: 200,
95
103
  }).start(() => {
96
104
  navigator?.pop();
97
- callback?.();
98
105
  });
99
106
  };
100
107
 
101
108
  /**
102
- * manual close
109
+ * on request close
103
110
  */
104
- const requestClose = useCallback((callback?: () => void) => {
111
+ const onRequestClose = useCallback((callback?: () => void) => {
105
112
  onDismiss(true, callback);
106
113
  }, []);
107
114
 
@@ -146,7 +153,11 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
146
153
  }, []);
147
154
 
148
155
  return (
149
- <Modal transparent visible={true} onRequestClose={() => onDismiss()}>
156
+ <Container
157
+ transparent
158
+ visible={true}
159
+ onRequestClose={() => onDismiss()}
160
+ style={styles.overlay}>
150
161
  <KeyboardAvoidingView
151
162
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
152
163
  keyboardVerticalOffset={keyboardVerticalOffset ?? -20}
@@ -162,7 +173,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
162
173
  <Screen
163
174
  {...props}
164
175
  {...props.route.params}
165
- requestClose={requestClose}
176
+ onRequestClose={onRequestClose}
166
177
  />
167
178
  {useBottomInset && (
168
179
  <View style={{height: Math.min(insets.bottom, 21)}} />
@@ -170,23 +181,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
170
181
  </View>
171
182
  </Animated.View>
172
183
  </KeyboardAvoidingView>
173
- </Modal>
184
+ </Container>
174
185
  );
175
186
  };
176
187
 
177
- export default BottomSheet;
178
-
179
188
  const styles = StyleSheet.create({
180
- container: {
181
- width: '100%',
182
- height: 0,
183
- overflow: 'hidden',
184
- borderTopLeftRadius: Radius.M,
185
- borderTopRightRadius: Radius.M,
186
- },
187
- draggableContainer: {
188
- width: '100%',
189
- alignItems: 'center',
189
+ overlay: {
190
+ ...StyleSheet.absoluteFillObject,
191
+ justifyContent: 'flex-end',
190
192
  },
191
193
  indicator: {
192
194
  width: 40,
@@ -210,3 +212,5 @@ const styles = StyleSheet.create({
210
212
  marginHorizontal: Spacing.M,
211
213
  },
212
214
  });
215
+
216
+ export default BottomSheet;
@@ -125,7 +125,7 @@ export default function BottomTabBar({
125
125
  );
126
126
  });
127
127
  },
128
- [JSON.stringify(routes), state, descriptors, focusedTab],
128
+ [JSON.stringify(routes), state, descriptors, focusedTab]
129
129
  );
130
130
 
131
131
  /**
@@ -153,7 +153,7 @@ export default function BottomTabBar({
153
153
  </View>
154
154
  <View
155
155
  style={{
156
- top: -6,
156
+ top: -4,
157
157
  backgroundColor: activeTintColor ?? theme.colors.primary,
158
158
  borderRadius: Radius.XL,
159
159
  paddingHorizontal: 6,
@@ -17,10 +17,9 @@ const Tab = createBottomTabNavigator();
17
17
  const Stack = createStackNavigator();
18
18
 
19
19
  const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
20
- const {theme} = useContext(ApplicationContext);
21
20
  let options = {};
22
21
  if (route.params?.options) {
23
- options = getOptions(route.params?.options, theme);
22
+ options = getOptions(route.params?.options);
24
23
  }
25
24
 
26
25
  if (route.params?.nested) {
@@ -101,7 +100,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
101
100
  name={item.name}
102
101
  component={TabScreen}
103
102
  initialParams={{...item, nested}}
104
- listeners={listeners}
103
+ listeners={item.listeners ?? listeners}
105
104
  options={{
106
105
  tabBarLabel: ({color, focused}) => {
107
106
  return (
@@ -1,14 +1,17 @@
1
1
  import React, {
2
- Fragment,
2
+ Ref,
3
3
  useCallback,
4
4
  useContext,
5
5
  useEffect,
6
+ useRef,
6
7
  useState,
7
8
  } from 'react';
8
9
  import {
10
+ Alert,
9
11
  Animated,
10
12
  BackHandler,
11
13
  DeviceEventEmitter,
14
+ Dimensions,
12
15
  StatusBar,
13
16
  StyleSheet,
14
17
  TouchableOpacity,
@@ -29,7 +32,18 @@ import {scaleSize, Text} from '../Text';
29
32
  import {Icon} from '../Icon';
30
33
  import {PopupNotify} from '../Popup';
31
34
  import {Badge, BadgeDot} from '../Badge';
35
+ import {HeaderType} from '../Layout/types';
36
+ import Navigation from './Navigation';
37
+ import {InputRef, InputSearch, InputSearchProps} from '../Input';
38
+ import {BadgeDotAnimation} from '../Badge';
32
39
 
40
+ const SCREEN_PADDING = 12;
41
+ const BACK_WIDTH = 28;
42
+ const {width: screenWidth} = Dimensions.get('window');
43
+
44
+ /**
45
+ * default navigation button used header nav
46
+ */
33
47
  const NavigationButton: React.FC<NavigationButtonProps> = ({
34
48
  icon,
35
49
  tintColor,
@@ -58,6 +72,11 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
58
72
  if (badgeType === 'dot') {
59
73
  return <BadgeDot size="small" style={styles.badgeDot} />;
60
74
  }
75
+ if (badgeType === 'dot-animation') {
76
+ return (
77
+ <BadgeDotAnimation size="small" style={styles.badgeDotAnimation} />
78
+ );
79
+ }
61
80
 
62
81
  if (isNumber(badgeValue)) {
63
82
  return <Badge label={badgeValue} style={styles.badge} />;
@@ -65,7 +84,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
65
84
  }, [badgeType, badgeValue]);
66
85
 
67
86
  return (
68
- <View style={{width: scaleSize(28)}}>
87
+ <View style={{width: 28}}>
69
88
  <TouchableOpacity
70
89
  accessibilityLabel={accessibilityLabel}
71
90
  style={[styles.navigationButton, buttonStyle]}
@@ -81,13 +100,18 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
81
100
  );
82
101
  };
83
102
 
103
+ /**
104
+ * default header title used for nav
105
+ */
84
106
  const HeaderTitle: React.FC<any> = props => {
85
107
  const {theme} = useContext(ApplicationContext);
86
- const opacity = props.animatedValue?.interpolate({
87
- inputRange: [0, 200],
88
- outputRange: [0, 1],
89
- extrapolate: 'clamp',
90
- });
108
+ const opacity = props.animatedValue?.interpolate(
109
+ props.interpolate ?? {
110
+ inputRange: [0, 200],
111
+ outputRange: [0, 1],
112
+ extrapolate: 'clamp',
113
+ }
114
+ );
91
115
 
92
116
  return (
93
117
  <Animated.Text
@@ -101,6 +125,9 @@ const HeaderTitle: React.FC<any> = props => {
101
125
  );
102
126
  };
103
127
 
128
+ /**
129
+ * default header left used for nav
130
+ */
104
131
  const HeaderLeft: React.FC<HeaderBackProps> = ({
105
132
  tintColor,
106
133
  preventBack,
@@ -113,7 +140,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
113
140
  useEffect(() => {
114
141
  const backHandler = BackHandler.addEventListener(
115
142
  'hardwareBackPress',
116
- goBackSafe,
143
+ goBackSafe
117
144
  );
118
145
 
119
146
  return () => backHandler.remove();
@@ -168,6 +195,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
168
195
  }
169
196
  return true;
170
197
  };
198
+
171
199
  return (
172
200
  <View style={styles.headerLeft}>
173
201
  <NavigationButton
@@ -181,6 +209,9 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
181
209
  );
182
210
  };
183
211
 
212
+ /**
213
+ * header background for default
214
+ */
184
215
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
185
216
  image,
186
217
  animatedValue,
@@ -229,6 +260,10 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
229
260
  );
230
261
  };
231
262
 
263
+ /**
264
+ * Header custom with image, title, subtitle
265
+ * @constructor
266
+ */
232
267
  const HeaderCustom: React.FC<TitleCustomProps> = ({
233
268
  title,
234
269
  subTitle,
@@ -263,37 +298,42 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
263
298
  return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
264
299
  };
265
300
 
266
- const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
267
- const validateType = (child: React.ReactElement) => {
268
- const valid = child?.type === NavigationButton || child?.type === Fragment;
269
- if (__DEV__ && !valid) {
270
- console.warn(
271
- 'HeaderRightAction contains element type of NavigationButton, Please check again.',
272
- );
301
+ /**
302
+ * main component for header right
303
+ */
304
+ const HeaderRight: React.FC<any> = ({type, children, onLayout, ...props}) => {
305
+ if (type === 'icon') {
306
+ let buttons = props?.buttons || [];
307
+ if (buttons?.length > 3) {
308
+ buttons = buttons.slice(0, 3);
273
309
  }
274
- return child;
275
- };
276
-
277
- const renderAction = () => {
278
- if (Array.isArray(children)) {
279
- return children.map((child, index) => {
280
- return (
281
- <View key={`HeaderRightAction ${index}`} style={styles.headerButton}>
282
- {React.cloneElement(validateType(child), {...restProps})}
283
- </View>
284
- );
285
- });
286
- }
287
-
288
310
  return (
289
- <View style={styles.headerButton}>
290
- {React.cloneElement(validateType(children), {...restProps})}
311
+ <View style={styles.headerRightButton} onLayout={onLayout}>
312
+ {buttons?.map?.((item: NavigationButtonProps, index: number) => {
313
+ return (
314
+ <View
315
+ key={`HeaderRightAction ${index}`}
316
+ style={{
317
+ marginLeft: index !== 0 ? Spacing.S : 0,
318
+ }}>
319
+ <NavigationButton {...item} tintColor={props?.tintColor} />
320
+ </View>
321
+ );
322
+ })}
291
323
  </View>
292
324
  );
293
- };
294
- return <View style={styles.headerRightButton}>{renderAction()}</View>;
325
+ }
326
+
327
+ return (
328
+ <View style={styles.headerRightButton} onLayout={onLayout}>
329
+ {children ?? <HeaderToolkitAction {...props} />}
330
+ </View>
331
+ );
295
332
  };
296
333
 
334
+ /**
335
+ * Header toolkit action
336
+ */
297
337
  const HeaderToolkitAction: React.FC<any> = ({
298
338
  tintColor,
299
339
  pinnedTool,
@@ -327,7 +367,7 @@ const HeaderToolkitAction: React.FC<any> = ({
327
367
  (config: any) => {
328
368
  navigator.toolkitConfig = config;
329
369
  setToolConfig(navigator?.toolkitConfig);
330
- },
370
+ }
331
371
  );
332
372
  };
333
373
 
@@ -338,9 +378,9 @@ const HeaderToolkitAction: React.FC<any> = ({
338
378
  {runtimeTools},
339
379
  (res: {item: {action?: string; key: string}}) => {
340
380
  const {item} = res;
341
- navigator?.toolkitCallback?.(item);
381
+ navigator?.toolkitCallback?.(item.key);
342
382
  getToolkitConfig();
343
- },
383
+ }
344
384
  );
345
385
  };
346
386
 
@@ -357,7 +397,7 @@ const HeaderToolkitAction: React.FC<any> = ({
357
397
  navigator?.maxApi?.dispatchFunction?.(
358
398
  'dismiss',
359
399
  undefined,
360
- undefined,
400
+ undefined
361
401
  );
362
402
  },
363
403
  }}
@@ -385,10 +425,10 @@ const HeaderToolkitAction: React.FC<any> = ({
385
425
  onPress={() => {
386
426
  navigator?.maxApi?.dispatchFunction?.(
387
427
  'onToolAction',
388
- [pinTool?.key],
428
+ {item: pinTool},
389
429
  () => {
390
430
  getToolkitConfig();
391
- },
431
+ }
392
432
  );
393
433
  navigator?.toolkitCallback?.(pinTool.key);
394
434
  }}
@@ -397,7 +437,7 @@ const HeaderToolkitAction: React.FC<any> = ({
397
437
  };
398
438
  if (toolConfig) {
399
439
  return (
400
- <View style={styles.headerRightButton}>
440
+ <View style={Styles.row}>
401
441
  {renderPinnedTool()}
402
442
  <View
403
443
  style={[
@@ -405,6 +445,7 @@ const HeaderToolkitAction: React.FC<any> = ({
405
445
  {
406
446
  backgroundColor: backgroundColor ?? '#00000066',
407
447
  borderWidth: backgroundColor ? 0.5 : 0,
448
+ marginLeft: renderPinnedTool() ? Spacing.S : 0,
408
449
  },
409
450
  ]}>
410
451
  <TouchableOpacity
@@ -430,6 +471,9 @@ const HeaderToolkitAction: React.FC<any> = ({
430
471
  return <View />;
431
472
  };
432
473
 
474
+ /**
475
+ * default header banner for header animated
476
+ */
433
477
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
434
478
  animatedValue,
435
479
  image,
@@ -470,6 +514,191 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
470
514
  );
471
515
  };
472
516
 
517
+ /**
518
+ * Header extended with background image
519
+ * @constructor
520
+ */
521
+ const HeaderExtendHeader: React.FC<{
522
+ headerType?: HeaderType;
523
+ animatedValue: Animated.Value;
524
+ heightHeader: number;
525
+ inputSearchProps?: InputSearchProps;
526
+ inputSearchRef?: Ref<InputRef>;
527
+ navigation?: Navigation;
528
+ }> = ({
529
+ headerType = 'default',
530
+ animatedValue,
531
+ heightHeader,
532
+ inputSearchProps,
533
+ inputSearchRef,
534
+ navigation,
535
+ }) => {
536
+ const {theme} = useContext(ApplicationContext);
537
+ const [rightSpace, setRightSpace] = useState(0);
538
+ const animated = useRef(new Animated.Value(0));
539
+
540
+ useEffect(() => {
541
+ const listener = animatedValue.addListener(({value}) => {
542
+ animated.current.setValue(value);
543
+ });
544
+ return () => {
545
+ animatedValue?.removeListener(listener);
546
+ };
547
+ }, []);
548
+
549
+ const height = animated.current.interpolate({
550
+ inputRange: [0, 100],
551
+ outputRange: [heightHeader + 52, heightHeader],
552
+ extrapolate: 'clamp',
553
+ });
554
+
555
+ const translateX = animated.current.interpolate({
556
+ inputRange: [0, 100],
557
+ outputRange: [SCREEN_PADDING, BACK_WIDTH + SCREEN_PADDING * 2],
558
+ extrapolate: 'clamp',
559
+ });
560
+
561
+ if (navigation) {
562
+ navigation.onHeaderRightChange = (width: number) => {
563
+ setRightSpace(width);
564
+ };
565
+ }
566
+
567
+ const renderInputView = (hasColorBG: boolean = false) => {
568
+ return (
569
+ <Animated.View
570
+ style={{
571
+ justifyContent: 'flex-end',
572
+ height,
573
+ }}>
574
+ <Animated.View
575
+ style={{
576
+ transform: [{translateX}],
577
+ marginVertical: Spacing.S,
578
+ width: animated.current.interpolate({
579
+ inputRange: [0, 100],
580
+ outputRange: [
581
+ screenWidth - SCREEN_PADDING * 2,
582
+ screenWidth - SCREEN_PADDING * 3 - BACK_WIDTH - rightSpace,
583
+ ],
584
+ extrapolate: 'clamp',
585
+ }),
586
+ }}>
587
+ <InputSearch
588
+ {...inputSearchProps}
589
+ ref={inputSearchRef}
590
+ hasColorBG={hasColorBG}
591
+ showButtonText={false}
592
+ />
593
+ </Animated.View>
594
+ </Animated.View>
595
+ );
596
+ };
597
+
598
+ if (inputSearchProps) {
599
+ if (headerType === 'surface') {
600
+ return (
601
+ <>
602
+ <Animated.View
603
+ style={[
604
+ styles.headerBox,
605
+ {
606
+ backgroundColor: theme.colors.background.surface,
607
+ borderBottomWidth: 1,
608
+ borderColor: theme.colors.border.default,
609
+ height,
610
+ },
611
+ ]}
612
+ />
613
+ {renderInputView(true)}
614
+ </>
615
+ );
616
+ }
617
+
618
+ if (headerType === 'extended') {
619
+ return (
620
+ <>
621
+ <Animated.View
622
+ style={[
623
+ styles.headerBox,
624
+ {
625
+ height,
626
+ },
627
+ ]}>
628
+ <Image
629
+ source={{
630
+ uri: theme.assets?.headerBackground,
631
+ }}
632
+ style={styles.headerBackground}
633
+ />
634
+ </Animated.View>
635
+ {renderInputView()}
636
+ </>
637
+ );
638
+ }
639
+
640
+ return (
641
+ <>
642
+ <View style={[styles.headerBox, {height: heightHeader}]}>
643
+ <Image
644
+ source={{
645
+ uri: theme.assets?.headerBackground,
646
+ }}
647
+ style={styles.headerBackground}
648
+ />
649
+ </View>
650
+ {renderInputView()}
651
+ </>
652
+ );
653
+ }
654
+
655
+ if (headerType === 'extended') {
656
+ return (
657
+ <View style={{minHeight: heightHeader}}>
658
+ <Image
659
+ source={{
660
+ uri: theme.assets?.headerBackground,
661
+ }}
662
+ style={styles.extendedHeader}
663
+ />
664
+ </View>
665
+ );
666
+ }
667
+
668
+ return <View />;
669
+ };
670
+
671
+ const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
672
+ if (__DEV__) {
673
+ Alert.alert(
674
+ 'HeaderRightAction',
675
+ 'This component is deprecated and will removed 10/2024, please use new type headerRight specs instead v0.92.34'
676
+ );
677
+ }
678
+ const validateType = (child: React.ReactElement) => {
679
+ return child;
680
+ };
681
+
682
+ const renderAction = () => {
683
+ if (Array.isArray(children)) {
684
+ return children.map((child, index) => {
685
+ return (
686
+ <View key={`HeaderRightAction ${index}`} style={styles.headerButton}>
687
+ {React.cloneElement(validateType(child), {...restProps})}
688
+ </View>
689
+ );
690
+ });
691
+ }
692
+
693
+ return (
694
+ <View style={styles.headerButton}>
695
+ {React.cloneElement(validateType(children), {...restProps})}
696
+ </View>
697
+ );
698
+ };
699
+ return <View style={styles.headerRightButton}>{renderAction()}</View>;
700
+ };
701
+
473
702
  const styles = StyleSheet.create({
474
703
  navigationButton: {
475
704
  height: 28,
@@ -509,7 +738,6 @@ const styles = StyleSheet.create({
509
738
  paddingRight: Spacing.M,
510
739
  },
511
740
  toolkitContainer: {
512
- marginLeft: Spacing.S,
513
741
  padding: Spacing.XS,
514
742
  height: 28,
515
743
  borderRadius: 14,
@@ -541,6 +769,22 @@ const styles = StyleSheet.create({
541
769
  top: -Spacing.XS,
542
770
  right: -Spacing.XS,
543
771
  },
772
+ badgeDotAnimation: {
773
+ position: 'absolute',
774
+ top: -Spacing.XS,
775
+ right: -Spacing.XS,
776
+ },
777
+ extendedHeader: {
778
+ aspectRatio: 1.75,
779
+ position: 'absolute',
780
+ width: '100%',
781
+ height: 210,
782
+ },
783
+ headerBox: {
784
+ width: '100%',
785
+ position: 'absolute',
786
+ overflow: 'hidden',
787
+ },
544
788
  });
545
789
 
546
790
  export {
@@ -548,8 +792,10 @@ export {
548
792
  HeaderTitle,
549
793
  HeaderLeft,
550
794
  HeaderBackground,
551
- HeaderRightAction,
552
795
  HeaderToolkitAction,
553
796
  HeaderCustom,
554
797
  HeaderAnimated,
798
+ HeaderRight,
799
+ HeaderExtendHeader,
800
+ HeaderRightAction,
555
801
  };
@@ -0,0 +1,44 @@
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
+ get getCurrentLanguage() {
21
+ return this.currentLanguage;
22
+ }
23
+
24
+ translate(key: string) {
25
+ return this.assets[this.currentLanguage]?.[key] || key;
26
+ }
27
+
28
+ translateData(data: {vi: string; en: string}) {
29
+ return data[this.currentLanguage] || data.vi || JSON.stringify(data);
30
+ }
31
+
32
+ addTranslations(translations: LocalizationObject) {
33
+ this.assets = {
34
+ vi: {...translations?.vi, ...this.assets.vi},
35
+ en: {...translations?.en, ...this.assets.en},
36
+ };
37
+ }
38
+
39
+ setLanguage(language: 'en' | 'vi') {
40
+ this.currentLanguage = language;
41
+ }
42
+ }
43
+
44
+ export default Localize;