@momo-kits/foundation 0.92.34 → 0.102.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.
@@ -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) {
@@ -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,17 @@ 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';
32
38
 
39
+ const SCREEN_PADDING = 12;
40
+ const BACK_WIDTH = 28;
41
+ const {width: screenWidth} = Dimensions.get('window');
42
+
43
+ /**
44
+ * default navigation button used header nav
45
+ */
33
46
  const NavigationButton: React.FC<NavigationButtonProps> = ({
34
47
  icon,
35
48
  tintColor,
@@ -81,13 +94,18 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
81
94
  );
82
95
  };
83
96
 
97
+ /**
98
+ * default header title used for nav
99
+ */
84
100
  const HeaderTitle: React.FC<any> = props => {
85
101
  const {theme} = useContext(ApplicationContext);
86
- const opacity = props.animatedValue?.interpolate({
87
- inputRange: [0, 200],
88
- outputRange: [0, 1],
89
- extrapolate: 'clamp',
90
- });
102
+ const opacity = props.animatedValue?.interpolate(
103
+ props.interpolate ?? {
104
+ inputRange: [0, 200],
105
+ outputRange: [0, 1],
106
+ extrapolate: 'clamp',
107
+ }
108
+ );
91
109
 
92
110
  return (
93
111
  <Animated.Text
@@ -101,6 +119,9 @@ const HeaderTitle: React.FC<any> = props => {
101
119
  );
102
120
  };
103
121
 
122
+ /**
123
+ * default header left used for nav
124
+ */
104
125
  const HeaderLeft: React.FC<HeaderBackProps> = ({
105
126
  tintColor,
106
127
  preventBack,
@@ -168,6 +189,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
168
189
  }
169
190
  return true;
170
191
  };
192
+
171
193
  return (
172
194
  <View style={styles.headerLeft}>
173
195
  <NavigationButton
@@ -181,6 +203,9 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
181
203
  );
182
204
  };
183
205
 
206
+ /**
207
+ * header background for default
208
+ */
184
209
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
185
210
  image,
186
211
  animatedValue,
@@ -229,6 +254,10 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
229
254
  );
230
255
  };
231
256
 
257
+ /**
258
+ * Header custom with image, title, subtitle
259
+ * @constructor
260
+ */
232
261
  const HeaderCustom: React.FC<TitleCustomProps> = ({
233
262
  title,
234
263
  subTitle,
@@ -263,37 +292,42 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
263
292
  return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
264
293
  };
265
294
 
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
- );
295
+ /**
296
+ * main component for header right
297
+ */
298
+ const HeaderRight: React.FC<any> = ({type, children, onLayout, ...props}) => {
299
+ if (type === 'icon') {
300
+ let buttons = props?.buttons || [];
301
+ if (buttons?.length > 3) {
302
+ buttons = buttons.slice(0, 3);
273
303
  }
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
304
  return (
289
- <View style={styles.headerButton}>
290
- {React.cloneElement(validateType(children), {...restProps})}
305
+ <View style={styles.headerRightButton} onLayout={onLayout}>
306
+ {buttons?.map?.((item: NavigationButtonProps, index: number) => {
307
+ return (
308
+ <View
309
+ key={`HeaderRightAction ${index}`}
310
+ style={{
311
+ marginLeft: index !== 0 ? Spacing.S : 0,
312
+ }}>
313
+ <NavigationButton {...item} tintColor={props?.tintColor} />
314
+ </View>
315
+ );
316
+ })}
291
317
  </View>
292
318
  );
293
- };
294
- return <View style={styles.headerRightButton}>{renderAction()}</View>;
319
+ }
320
+
321
+ return (
322
+ <View style={styles.headerRightButton} onLayout={onLayout}>
323
+ {children ?? <HeaderToolkitAction {...props} />}
324
+ </View>
325
+ );
295
326
  };
296
327
 
328
+ /**
329
+ * Header toolkit action
330
+ */
297
331
  const HeaderToolkitAction: React.FC<any> = ({
298
332
  tintColor,
299
333
  pinnedTool,
@@ -385,7 +419,7 @@ const HeaderToolkitAction: React.FC<any> = ({
385
419
  onPress={() => {
386
420
  navigator?.maxApi?.dispatchFunction?.(
387
421
  'onToolAction',
388
- [pinTool?.key],
422
+ {item: pinTool},
389
423
  () => {
390
424
  getToolkitConfig();
391
425
  }
@@ -397,7 +431,7 @@ const HeaderToolkitAction: React.FC<any> = ({
397
431
  };
398
432
  if (toolConfig) {
399
433
  return (
400
- <View style={styles.headerRightButton}>
434
+ <View style={Styles.row}>
401
435
  {renderPinnedTool()}
402
436
  <View
403
437
  style={[
@@ -405,6 +439,7 @@ const HeaderToolkitAction: React.FC<any> = ({
405
439
  {
406
440
  backgroundColor: backgroundColor ?? '#00000066',
407
441
  borderWidth: backgroundColor ? 0.5 : 0,
442
+ marginLeft: renderPinnedTool() ? Spacing.S : 0,
408
443
  },
409
444
  ]}>
410
445
  <TouchableOpacity
@@ -430,6 +465,9 @@ const HeaderToolkitAction: React.FC<any> = ({
430
465
  return <View />;
431
466
  };
432
467
 
468
+ /**
469
+ * default header banner for header animated
470
+ */
433
471
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
434
472
  animatedValue,
435
473
  image,
@@ -470,6 +508,191 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
470
508
  );
471
509
  };
472
510
 
511
+ /**
512
+ * Header extended with background image
513
+ * @constructor
514
+ */
515
+ const HeaderExtendHeader: React.FC<{
516
+ headerType?: HeaderType;
517
+ animatedValue: Animated.Value;
518
+ heightHeader: number;
519
+ inputSearchProps?: InputSearchProps;
520
+ inputSearchRef?: Ref<InputRef>;
521
+ navigation?: Navigation;
522
+ }> = ({
523
+ headerType = 'default',
524
+ animatedValue,
525
+ heightHeader,
526
+ inputSearchProps,
527
+ inputSearchRef,
528
+ navigation,
529
+ }) => {
530
+ const {theme} = useContext(ApplicationContext);
531
+ const [rightSpace, setRightSpace] = useState(0);
532
+ const animated = useRef(new Animated.Value(0));
533
+
534
+ useEffect(() => {
535
+ const listener = animatedValue.addListener(({value}) => {
536
+ animated.current.setValue(value);
537
+ });
538
+ return () => {
539
+ animatedValue?.removeListener(listener);
540
+ };
541
+ }, []);
542
+
543
+ const height = animated.current.interpolate({
544
+ inputRange: [0, 100],
545
+ outputRange: [heightHeader + 52, heightHeader],
546
+ extrapolate: 'clamp',
547
+ });
548
+
549
+ const translateX = animated.current.interpolate({
550
+ inputRange: [0, 100],
551
+ outputRange: [SCREEN_PADDING, BACK_WIDTH + SCREEN_PADDING * 2],
552
+ extrapolate: 'clamp',
553
+ });
554
+
555
+ if (navigation) {
556
+ navigation.onHeaderRightChange = (width: number) => {
557
+ setRightSpace(width);
558
+ };
559
+ }
560
+
561
+ const renderInputView = (hasColorBG: boolean = false) => {
562
+ return (
563
+ <Animated.View
564
+ style={{
565
+ justifyContent: 'flex-end',
566
+ height,
567
+ }}>
568
+ <Animated.View
569
+ style={{
570
+ transform: [{translateX}],
571
+ marginVertical: Spacing.S,
572
+ width: animated.current.interpolate({
573
+ inputRange: [0, 100],
574
+ outputRange: [
575
+ screenWidth - SCREEN_PADDING * 2,
576
+ screenWidth - SCREEN_PADDING * 3 - BACK_WIDTH - rightSpace,
577
+ ],
578
+ extrapolate: 'clamp',
579
+ }),
580
+ }}>
581
+ <InputSearch
582
+ {...inputSearchProps}
583
+ ref={inputSearchRef}
584
+ hasColorBG={hasColorBG}
585
+ showButtonText={false}
586
+ />
587
+ </Animated.View>
588
+ </Animated.View>
589
+ );
590
+ };
591
+
592
+ if (inputSearchProps) {
593
+ if (headerType === 'surface') {
594
+ return (
595
+ <>
596
+ <Animated.View
597
+ style={[
598
+ styles.headerBox,
599
+ {
600
+ backgroundColor: theme.colors.background.surface,
601
+ borderBottomWidth: 1,
602
+ borderColor: theme.colors.border.default,
603
+ height,
604
+ },
605
+ ]}
606
+ />
607
+ {renderInputView(true)}
608
+ </>
609
+ );
610
+ }
611
+
612
+ if (headerType === 'extended') {
613
+ return (
614
+ <>
615
+ <Animated.View
616
+ style={[
617
+ styles.headerBox,
618
+ {
619
+ height,
620
+ },
621
+ ]}>
622
+ <Image
623
+ source={{
624
+ uri: theme.assets?.headerBackground,
625
+ }}
626
+ style={styles.headerBackground}
627
+ />
628
+ </Animated.View>
629
+ {renderInputView()}
630
+ </>
631
+ );
632
+ }
633
+
634
+ return (
635
+ <>
636
+ <View style={[styles.headerBox, {height: heightHeader}]}>
637
+ <Image
638
+ source={{
639
+ uri: theme.assets?.headerBackground,
640
+ }}
641
+ style={styles.headerBackground}
642
+ />
643
+ </View>
644
+ {renderInputView()}
645
+ </>
646
+ );
647
+ }
648
+
649
+ if (headerType === 'extended') {
650
+ return (
651
+ <View style={{minHeight: heightHeader}}>
652
+ <Image
653
+ source={{
654
+ uri: theme.assets?.headerBackground,
655
+ }}
656
+ style={styles.extendedHeader}
657
+ />
658
+ </View>
659
+ );
660
+ }
661
+
662
+ return <View />;
663
+ };
664
+
665
+ const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
666
+ if (__DEV__) {
667
+ Alert.alert(
668
+ 'HeaderRightAction',
669
+ 'This component is deprecated, please use HeaderRight instead v0.92.34'
670
+ );
671
+ }
672
+ const validateType = (child: React.ReactElement) => {
673
+ return child;
674
+ };
675
+
676
+ const renderAction = () => {
677
+ if (Array.isArray(children)) {
678
+ return children.map((child, index) => {
679
+ return (
680
+ <View key={`HeaderRightAction ${index}`} style={styles.headerButton}>
681
+ {React.cloneElement(validateType(child), {...restProps})}
682
+ </View>
683
+ );
684
+ });
685
+ }
686
+
687
+ return (
688
+ <View style={styles.headerButton}>
689
+ {React.cloneElement(validateType(children), {...restProps})}
690
+ </View>
691
+ );
692
+ };
693
+ return <View style={styles.headerRightButton}>{renderAction()}</View>;
694
+ };
695
+
473
696
  const styles = StyleSheet.create({
474
697
  navigationButton: {
475
698
  height: 28,
@@ -509,7 +732,6 @@ const styles = StyleSheet.create({
509
732
  paddingRight: Spacing.M,
510
733
  },
511
734
  toolkitContainer: {
512
- marginLeft: Spacing.S,
513
735
  padding: Spacing.XS,
514
736
  height: 28,
515
737
  borderRadius: 14,
@@ -541,6 +763,17 @@ const styles = StyleSheet.create({
541
763
  top: -Spacing.XS,
542
764
  right: -Spacing.XS,
543
765
  },
766
+ extendedHeader: {
767
+ aspectRatio: 1.75,
768
+ position: 'absolute',
769
+ width: '100%',
770
+ height: 210,
771
+ },
772
+ headerBox: {
773
+ width: '100%',
774
+ position: 'absolute',
775
+ overflow: 'hidden',
776
+ },
544
777
  });
545
778
 
546
779
  export {
@@ -548,8 +781,10 @@ export {
548
781
  HeaderTitle,
549
782
  HeaderLeft,
550
783
  HeaderBackground,
551
- HeaderRightAction,
552
784
  HeaderToolkitAction,
553
785
  HeaderCustom,
554
786
  HeaderAnimated,
787
+ HeaderRight,
788
+ HeaderExtendHeader,
789
+ HeaderRightAction,
555
790
  };
@@ -21,10 +21,10 @@ const ModalScreen: React.FC<any> = props => {
21
21
  };
22
22
 
23
23
  const Modal: React.FC<ModalParams> = props => {
24
- const {theme, navigator} = useContext(ApplicationContext);
24
+ const {navigator} = useContext(ApplicationContext);
25
25
  const {screen, barrierDismissible} = props.route.params;
26
26
  const Component = useRef(screen).current;
27
- const navigation = new Navigation(props.navigation, theme);
27
+ const navigation = new Navigation(props.navigation);
28
28
  const params = {
29
29
  ...props.route.params,
30
30
  navigation,
@@ -1,17 +1,13 @@
1
- import React from 'react';
2
1
  import {NavigationProp} from '@react-navigation/native';
3
- import {NavigationOptions, Theme} from './types';
4
- import {HeaderRightAction} from './index';
2
+ import {NavigationOptions} from './types';
5
3
  import {getOptions} from './utils';
6
- import {HeaderToolkitAction} from './Components';
7
4
 
8
5
  class Navigation {
9
6
  instance: NavigationProp<any>;
10
- readonly theme: Theme;
7
+ onHeaderRightChange?: (width: number) => void;
11
8
 
12
- constructor(instance: any, theme: Theme) {
9
+ constructor(instance: any) {
13
10
  this.instance = instance;
14
- this.theme = theme;
15
11
  }
16
12
 
17
13
  /**
@@ -19,16 +15,6 @@ class Navigation {
19
15
  * @param params
20
16
  */
21
17
  private filterParams = (params: NavigationOptions) => {
22
- if (params.headerRight) {
23
- const headerRight = params.headerRight?.({}) as React.ReactElement;
24
- const type = headerRight?.type;
25
- if (type !== HeaderRightAction && type !== HeaderToolkitAction) {
26
- console.warn(
27
- 'headerRight contains element type of HeaderRightAction | HeaderToolkitAction, Please check again.',
28
- );
29
- }
30
- }
31
-
32
18
  if (params.headerLeft) {
33
19
  console.warn('headerLeft not allow override render by design system!');
34
20
  delete params.headerLeft;
@@ -42,7 +28,7 @@ class Navigation {
42
28
  * @param params
43
29
  */
44
30
  setOptions = (params: NavigationOptions) => {
45
- params = getOptions(this.filterParams(params), this.theme);
31
+ params = getOptions(this.filterParams(params), this);
46
32
  this.instance.setOptions(params);
47
33
  };
48
34
  }
@@ -88,7 +88,7 @@ const EmptyScreen: React.FC = () => {
88
88
  * @constructor
89
89
  */
90
90
  const StackScreen: React.FC<ScreenParams> = props => {
91
- const {theme, showGrid, navigator} = useContext(ApplicationContext);
91
+ const {showGrid, navigator} = useContext(ApplicationContext);
92
92
  const startTime = useRef(Date.now());
93
93
  const endTime = useRef(Date.now());
94
94
  const timeLoad = useRef(0);
@@ -104,9 +104,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
104
104
  });
105
105
  const interaction = useRef<any>();
106
106
  const context = useContext<any>(MiniAppContext);
107
-
108
107
  const {screen: Component, options, initialParams} = props.route.params;
109
- const navigation = new Navigation(props.navigation, theme);
108
+ const navigation = new Navigation(props.navigation);
110
109
  const heightHeader = useHeaderHeight();
111
110
 
112
111
  const data = {
@@ -124,9 +123,18 @@ const StackScreen: React.FC<ScreenParams> = props => {
124
123
  * set options for screen
125
124
  */
126
125
  useLayoutEffect(() => {
126
+ let defaultOptions = {
127
+ headerRight: {
128
+ type: 'toolkit',
129
+ },
130
+ };
127
131
  if (options) {
128
- navigation.setOptions(options);
132
+ defaultOptions = {
133
+ ...defaultOptions,
134
+ ...options,
135
+ };
129
136
  }
137
+ navigation.setOptions(defaultOptions);
130
138
  navigator?.maxApi?.startTraceScreenLoad?.(screenName, (data: any) => {
131
139
  tracked.current.traceIdLoad = data?.traceId;
132
140
  });
@@ -147,7 +155,6 @@ const StackScreen: React.FC<ScreenParams> = props => {
147
155
  navigator?.showModal({screen: EmptyScreen});
148
156
  }, 300);
149
157
  }
150
-
151
158
  navigator?.maxApi?.of?.({screenName});
152
159
  tracked.current.timeoutLoad = setTimeout(() => {
153
160
  onScreenLoad();
@@ -172,11 +179,13 @@ const StackScreen: React.FC<ScreenParams> = props => {
172
179
  if (timeLoad.current === 0) {
173
180
  timeLoad.current = endTime.current - startTime.current;
174
181
  }
182
+
175
183
  context.autoTracking?.({
176
184
  appId: context.appId,
177
185
  code: context.code,
178
186
  buildNumber: context.buildNumber,
179
187
  screenName,
188
+ action: 'push',
180
189
  componentName: 'Screen',
181
190
  state: 'load',
182
191
  duration: timeLoad.current,
@@ -186,14 +195,18 @@ const StackScreen: React.FC<ScreenParams> = props => {
186
195
  {value: timeLoad.current / 1000},
187
196
  null
188
197
  );
198
+ tracked.current.releaseLoad = true;
199
+
200
+ /**
201
+ * debug
202
+ */
189
203
  navigator?.maxApi?.showToastDebug?.({
190
204
  appId: `auto - ${context.appId}`,
191
205
  message: `${screenName} screen_load_time ${timeLoad.current}`,
192
206
  });
193
- if (timeLoad.current === 0 && context.enableAutoId) {
207
+ if (timeLoad.current <= 0 && context.enableAutoId) {
194
208
  Alert.alert(screenName, "Can't get screen load time");
195
209
  }
196
- tracked.current.releaseLoad = true;
197
210
  }
198
211
  };
199
212
 
@@ -205,10 +218,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
205
218
  if (timeLoad.current === 0) {
206
219
  timeLoad.current = endTime.current - startTime.current;
207
220
  }
208
- if (timeInteraction.current === 0 && context.enableAutoId) {
209
- if (context.enableAutoId) {
210
- Alert.alert(screenName, "Can't get screen interaction time");
211
- }
221
+ if (timeInteraction.current === 0) {
212
222
  timeInteraction.current = timeLoad.current;
213
223
  }
214
224
 
@@ -226,11 +236,18 @@ const StackScreen: React.FC<ScreenParams> = props => {
226
236
  {value: timeInteraction.current / 1000},
227
237
  null
228
238
  );
239
+ tracked.current.releaseInteraction = true;
240
+
241
+ /**
242
+ * debug toast
243
+ */
229
244
  navigator?.maxApi?.showToastDebug?.({
230
245
  appId: `auto - ${context.appId}`,
231
246
  message: `${screenName} screen_interaction_time ${timeInteraction.current}`,
232
247
  });
233
- tracked.current.releaseInteraction = true;
248
+ if (timeInteraction.current <= 0 && context.enableAutoId) {
249
+ Alert.alert(screenName, "Can't get screen interaction time");
250
+ }
234
251
  }
235
252
  };
236
253
 
@@ -5,9 +5,9 @@ import {
5
5
  HeaderAnimated,
6
6
  HeaderBackground,
7
7
  HeaderCustom,
8
- HeaderRightAction,
9
8
  HeaderTitle,
10
9
  NavigationButton,
10
+ HeaderRightAction,
11
11
  } from './Components';
12
12
  import BottomTab from './BottomTab';
13
13
  import {createContext} from 'react';
@@ -29,9 +29,9 @@ export {
29
29
  Screen,
30
30
  BottomTab,
31
31
  NavigationButton,
32
+ HeaderRightAction,
32
33
  HeaderTitle,
33
34
  HeaderBackground,
34
- HeaderRightAction,
35
35
  HeaderCustom,
36
36
  HeaderAnimated,
37
37
  };
@@ -146,19 +146,29 @@ export type RuntimeToolType = {
146
146
  key: string;
147
147
  };
148
148
 
149
- export interface NavigationOptions extends StackNavigationOptions {
149
+ export interface NavigationOptions
150
+ extends Omit<StackNavigationOptions, 'headerRight'> {
150
151
  preventBack?: PopupNotifyProps;
151
152
  onPressLeftHeader?: () => void;
152
153
  surface?: boolean;
153
154
  hiddenBack?: boolean;
154
155
  customTitle?: TitleCustomProps;
155
- toolkitParams?: {
156
- pinnedTool?: PinnedToolType;
157
- runtimeTools?: RuntimeToolType[];
158
- preventClose?: PopupNotifyProps;
159
- };
156
+ headerRight?: HeaderRightToolkit | HeaderRightActions | any;
160
157
  }
161
158
 
159
+ export type HeaderRightToolkit = {
160
+ type?: 'toolkit';
161
+ pinnedTool?: PinnedToolType;
162
+ runtimeTools?: RuntimeToolType[];
163
+ preventClose?: PopupNotifyProps;
164
+ useMore?: boolean;
165
+ };
166
+
167
+ export type HeaderRightActions = {
168
+ type?: 'icon';
169
+ buttons: NavigationButtonProps[];
170
+ };
171
+
162
172
  export interface HeaderBackProps extends NavigationButtonProps {
163
173
  preventBack?: PopupNotifyProps;
164
174
  onPressLeftHeader?: () => void;