@momo-kits/foundation 0.92.7-rc.21 → 0.92.7-rc.23

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 (59) hide show
  1. package/Application/BottomSheet.tsx +216 -0
  2. package/Application/BottomTab/BottomTabBar.tsx +62 -223
  3. package/Application/BottomTab/index.tsx +15 -11
  4. package/Application/Components.tsx +291 -45
  5. package/Application/Localize.ts +44 -0
  6. package/Application/ModalScreen.tsx +11 -181
  7. package/Application/Navigation.ts +4 -18
  8. package/Application/NavigationContainer.tsx +10 -28
  9. package/Application/Navigator.ts +14 -16
  10. package/Application/StackScreen.tsx +222 -42
  11. package/Application/index.ts +5 -3
  12. package/Application/types.ts +47 -29
  13. package/Application/utils.tsx +61 -19
  14. package/Assets/icon_bank.json +1 -1
  15. package/Badge/BadgeDotAnimation.tsx +95 -0
  16. package/Badge/index.tsx +2 -1
  17. package/Badge/styles.ts +50 -6
  18. package/Button/index.tsx +7 -8
  19. package/Icon/index.tsx +1 -1
  20. package/Icon/types.ts +1 -2
  21. package/Image/index.tsx +0 -1
  22. package/Input/Input.tsx +51 -17
  23. package/Input/InputDropDown.tsx +42 -45
  24. package/Input/InputMoney.tsx +65 -6
  25. package/Input/InputOTP.tsx +14 -13
  26. package/Input/InputSearch.tsx +259 -146
  27. package/Input/InputTextArea.tsx +0 -1
  28. package/Input/TextTyping.tsx +113 -0
  29. package/Input/common.tsx +29 -10
  30. package/Input/index.tsx +84 -89
  31. package/Input/styles.ts +44 -7
  32. package/Input/utils.ts +1 -1
  33. package/Layout/Card.tsx +4 -3
  34. package/Layout/FloatingButton.tsx +3 -3
  35. package/Layout/GridSystem.tsx +1 -1
  36. package/Layout/ItemList.tsx +3 -2
  37. package/Layout/Screen.tsx +188 -102
  38. package/Layout/Section.tsx +4 -3
  39. package/Layout/styles.ts +0 -6
  40. package/Layout/types.ts +1 -1
  41. package/Layout/utils.ts +2 -2
  42. package/Loader/index.tsx +2 -0
  43. package/Pagination/PaginationScroll.tsx +2 -2
  44. package/Pagination/styles.ts +1 -1
  45. package/Popup/PopupNotify.tsx +7 -7
  46. package/Popup/PopupPromotion.tsx +1 -1
  47. package/Popup/types.ts +1 -1
  48. package/Tag/index.tsx +9 -2
  49. package/Tag/styles.ts +3 -2
  50. package/Tag/types.ts +12 -0
  51. package/Text/index.tsx +3 -1
  52. package/Text/utils.ts +14 -2
  53. package/Title/index.tsx +20 -7
  54. package/Title/styles.ts +4 -4
  55. package/code-scanner.js +141 -124
  56. package/index.ts +7 -0
  57. package/package.json +5 -4
  58. package/publish.sh +16 -21
  59. package/tsconfig.json +1 -1
@@ -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,
@@ -41,7 +55,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
41
55
  }) => {
42
56
  const {theme} = useContext(ApplicationContext);
43
57
  let buttonStyle: ViewStyle = {backgroundColor: '#00000066'};
44
- if (tintColor != Colors.black_01) {
58
+ if (tintColor !== Colors.black_01) {
45
59
  buttonStyle = {
46
60
  backgroundColor: 'transparent',
47
61
  borderColor: '#00000066',
@@ -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,
@@ -238,7 +273,7 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
238
273
  tintColor,
239
274
  dotColor,
240
275
  }) => {
241
- const avatarStyle = imageShape == 'circle' ? styles.circle : styles.square;
276
+ const avatarStyle = imageShape === 'circle' ? styles.circle : styles.square;
242
277
  const header = (
243
278
  <View style={Styles.row}>
244
279
  <View>
@@ -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,
@@ -306,7 +346,7 @@ const HeaderToolkitAction: React.FC<any> = ({
306
346
  const {onAction} = toolConfig ?? {};
307
347
 
308
348
  let backgroundColor;
309
- if (tintColor != Colors.black_01) {
349
+ if (tintColor !== Colors.black_01) {
310
350
  backgroundColor = 'transparent';
311
351
  }
312
352
 
@@ -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
+ get getAssets() {
25
+ return this.assets;
26
+ }
27
+
28
+ translate(key: string) {
29
+ return this.assets[this.currentLanguage]?.[key] || key;
30
+ }
31
+
32
+ translateData(data: {vi: string; en: string}) {
33
+ return data[this.currentLanguage] || JSON.stringify(data);
34
+ }
35
+
36
+ addTranslations(translations: LocalizationObject) {
37
+ this.assets = {
38
+ vi: {...translations?.vi, ...this.assets.vi},
39
+ en: {...translations?.en, ...this.assets.en},
40
+ };
41
+ }
42
+ }
43
+
44
+ export default Localize;