@momo-kits/foundation 0.92.26-beta.23 → 0.92.26-beta.25

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,9 +17,10 @@ const Tab = createBottomTabNavigator();
17
17
  const Stack = createStackNavigator();
18
18
 
19
19
  const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
20
+ const {theme} = useContext(ApplicationContext);
20
21
  let options = {};
21
22
  if (route.params?.options) {
22
- options = getOptions(route.params?.options);
23
+ options = getOptions(route.params?.options, theme);
23
24
  }
24
25
 
25
26
  if (route.params?.nested) {
@@ -1,16 +1,14 @@
1
1
  import React, {
2
- Ref,
2
+ Fragment,
3
3
  useCallback,
4
4
  useContext,
5
5
  useEffect,
6
- useRef,
7
6
  useState,
8
7
  } from 'react';
9
8
  import {
10
9
  Animated,
11
10
  BackHandler,
12
11
  DeviceEventEmitter,
13
- Dimensions,
14
12
  StatusBar,
15
13
  StyleSheet,
16
14
  TouchableOpacity,
@@ -31,17 +29,7 @@ import {scaleSize, Text} from '../Text';
31
29
  import {Icon} from '../Icon';
32
30
  import {PopupNotify} from '../Popup';
33
31
  import {Badge, BadgeDot} from '../Badge';
34
- import {HeaderType} from '../Layout/types';
35
- import Navigation from './Navigation';
36
- import {InputRef, InputSearch, InputSearchProps} from '../Input';
37
32
 
38
- const SCREEN_PADDING = 12;
39
- const BACK_WIDTH = 28;
40
- const {width: screenWidth} = Dimensions.get('window');
41
-
42
- /**
43
- * default navigation button used header nav
44
- */
45
33
  const NavigationButton: React.FC<NavigationButtonProps> = ({
46
34
  icon,
47
35
  tintColor,
@@ -93,18 +81,13 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
93
81
  );
94
82
  };
95
83
 
96
- /**
97
- * default header title used for nav
98
- */
99
84
  const HeaderTitle: React.FC<any> = props => {
100
85
  const {theme} = useContext(ApplicationContext);
101
- const opacity = props.animatedValue?.interpolate(
102
- props.interpolate ?? {
103
- inputRange: [0, 200],
104
- outputRange: [0, 1],
105
- extrapolate: 'clamp',
106
- }
107
- );
86
+ const opacity = props.animatedValue?.interpolate({
87
+ inputRange: [0, 200],
88
+ outputRange: [0, 1],
89
+ extrapolate: 'clamp',
90
+ });
108
91
 
109
92
  return (
110
93
  <Animated.Text
@@ -118,9 +101,6 @@ const HeaderTitle: React.FC<any> = props => {
118
101
  );
119
102
  };
120
103
 
121
- /**
122
- * default header left used for nav
123
- */
124
104
  const HeaderLeft: React.FC<HeaderBackProps> = ({
125
105
  tintColor,
126
106
  preventBack,
@@ -133,7 +113,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
133
113
  useEffect(() => {
134
114
  const backHandler = BackHandler.addEventListener(
135
115
  'hardwareBackPress',
136
- goBackSafe
116
+ goBackSafe,
137
117
  );
138
118
 
139
119
  return () => backHandler.remove();
@@ -188,7 +168,6 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
188
168
  }
189
169
  return true;
190
170
  };
191
-
192
171
  return (
193
172
  <View style={styles.headerLeft}>
194
173
  <NavigationButton
@@ -202,9 +181,6 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
202
181
  );
203
182
  };
204
183
 
205
- /**
206
- * header background for default
207
- */
208
184
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
209
185
  image,
210
186
  animatedValue,
@@ -253,10 +229,6 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
253
229
  );
254
230
  };
255
231
 
256
- /**
257
- * Header custom with image, title, subtitle
258
- * @constructor
259
- */
260
232
  const HeaderCustom: React.FC<TitleCustomProps> = ({
261
233
  title,
262
234
  subTitle,
@@ -291,42 +263,37 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
291
263
  return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
292
264
  };
293
265
 
294
- /**
295
- * main component for header right
296
- */
297
- const HeaderRight: React.FC<any> = ({type, children, onLayout, ...props}) => {
298
- if (type === 'icon') {
299
- let buttons = props?.buttons || [];
300
- if (buttons?.length > 3) {
301
- buttons = buttons.slice(0, 3);
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
+ );
302
273
  }
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
+
303
288
  return (
304
- <View style={styles.headerRightButton} onLayout={onLayout}>
305
- {buttons?.map?.((item: NavigationButtonProps, index: number) => {
306
- return (
307
- <View
308
- key={`HeaderRightAction ${index}`}
309
- style={{
310
- marginLeft: index !== 0 ? Spacing.S : 0,
311
- }}>
312
- <NavigationButton {...item} tintColor={props?.tintColor} />
313
- </View>
314
- );
315
- })}
289
+ <View style={styles.headerButton}>
290
+ {React.cloneElement(validateType(children), {...restProps})}
316
291
  </View>
317
292
  );
318
- }
319
-
320
- return (
321
- <View style={styles.headerRightButton} onLayout={onLayout}>
322
- {children ?? <HeaderToolkitAction {...props} />}
323
- </View>
324
- );
293
+ };
294
+ return <View style={styles.headerRightButton}>{renderAction()}</View>;
325
295
  };
326
296
 
327
- /**
328
- * Header toolkit action
329
- */
330
297
  const HeaderToolkitAction: React.FC<any> = ({
331
298
  tintColor,
332
299
  pinnedTool,
@@ -360,7 +327,7 @@ const HeaderToolkitAction: React.FC<any> = ({
360
327
  (config: any) => {
361
328
  navigator.toolkitConfig = config;
362
329
  setToolConfig(navigator?.toolkitConfig);
363
- }
330
+ },
364
331
  );
365
332
  };
366
333
 
@@ -373,7 +340,7 @@ const HeaderToolkitAction: React.FC<any> = ({
373
340
  const {item} = res;
374
341
  navigator?.toolkitCallback?.(item);
375
342
  getToolkitConfig();
376
- }
343
+ },
377
344
  );
378
345
  };
379
346
 
@@ -390,7 +357,7 @@ const HeaderToolkitAction: React.FC<any> = ({
390
357
  navigator?.maxApi?.dispatchFunction?.(
391
358
  'dismiss',
392
359
  undefined,
393
- undefined
360
+ undefined,
394
361
  );
395
362
  },
396
363
  }}
@@ -421,7 +388,7 @@ const HeaderToolkitAction: React.FC<any> = ({
421
388
  [pinTool?.key],
422
389
  () => {
423
390
  getToolkitConfig();
424
- }
391
+ },
425
392
  );
426
393
  navigator?.toolkitCallback?.(pinTool.key);
427
394
  }}
@@ -430,7 +397,7 @@ const HeaderToolkitAction: React.FC<any> = ({
430
397
  };
431
398
  if (toolConfig) {
432
399
  return (
433
- <View style={Styles.row}>
400
+ <View style={styles.headerRightButton}>
434
401
  {renderPinnedTool()}
435
402
  <View
436
403
  style={[
@@ -438,7 +405,6 @@ const HeaderToolkitAction: React.FC<any> = ({
438
405
  {
439
406
  backgroundColor: backgroundColor ?? '#00000066',
440
407
  borderWidth: backgroundColor ? 0.5 : 0,
441
- marginLeft: renderPinnedTool() ? Spacing.S : 0,
442
408
  },
443
409
  ]}>
444
410
  <TouchableOpacity
@@ -464,9 +430,6 @@ const HeaderToolkitAction: React.FC<any> = ({
464
430
  return <View />;
465
431
  };
466
432
 
467
- /**
468
- * default header banner for header animated
469
- */
470
433
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
471
434
  animatedValue,
472
435
  image,
@@ -507,160 +470,6 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
507
470
  );
508
471
  };
509
472
 
510
- /**
511
- * Header extended with background image
512
- * @constructor
513
- */
514
- const HeaderExtendHeader: React.FC<{
515
- headerType?: HeaderType;
516
- animatedValue: Animated.Value;
517
- heightHeader: number;
518
- inputSearchProps?: InputSearchProps;
519
- inputSearchRef?: Ref<InputRef>;
520
- navigation?: Navigation;
521
- }> = ({
522
- headerType = 'default',
523
- animatedValue,
524
- heightHeader,
525
- inputSearchProps,
526
- inputSearchRef,
527
- navigation,
528
- }) => {
529
- const {theme} = useContext(ApplicationContext);
530
- const [rightSpace, setRightSpace] = useState(0);
531
- const animated = useRef(new Animated.Value(0));
532
-
533
- useEffect(() => {
534
- const listener = animatedValue.addListener(({value}) => {
535
- animated.current.setValue(value);
536
- });
537
- return () => {
538
- animatedValue?.removeListener(listener);
539
- };
540
- }, []);
541
-
542
- const height = animated.current.interpolate({
543
- inputRange: [0, 100],
544
- outputRange: [heightHeader + 52, heightHeader],
545
- extrapolate: 'clamp',
546
- });
547
-
548
- const translateX = animated.current.interpolate({
549
- inputRange: [0, 100],
550
- outputRange: [SCREEN_PADDING, BACK_WIDTH + SCREEN_PADDING * 2],
551
- extrapolate: 'clamp',
552
- });
553
-
554
- if (navigation) {
555
- navigation.onHeaderRightChange = (width: number) => {
556
- setRightSpace(width);
557
- };
558
- }
559
-
560
- const renderInputView = (hasColorBG: boolean = false) => {
561
- return (
562
- <Animated.View
563
- style={{
564
- justifyContent: 'flex-end',
565
- height,
566
- }}>
567
- <Animated.View
568
- style={{
569
- transform: [{translateX}],
570
- marginVertical: Spacing.S,
571
- width: animated.current.interpolate({
572
- inputRange: [0, 100],
573
- outputRange: [
574
- screenWidth - SCREEN_PADDING * 2,
575
- screenWidth - SCREEN_PADDING * 3 - BACK_WIDTH - rightSpace,
576
- ],
577
- extrapolate: 'clamp',
578
- }),
579
- }}>
580
- <InputSearch
581
- {...inputSearchProps}
582
- ref={inputSearchRef}
583
- hasColorBG={hasColorBG}
584
- showButtonText={false}
585
- />
586
- </Animated.View>
587
- </Animated.View>
588
- );
589
- };
590
-
591
- if (inputSearchProps) {
592
- if (headerType === 'surface') {
593
- return (
594
- <>
595
- <Animated.View
596
- style={[
597
- styles.headerBox,
598
- {
599
- backgroundColor: theme.colors.background.surface,
600
- borderBottomWidth: 1,
601
- borderColor: theme.colors.border.default,
602
- height,
603
- },
604
- ]}
605
- />
606
- {renderInputView(true)}
607
- </>
608
- );
609
- }
610
-
611
- if (headerType === 'extended') {
612
- return (
613
- <>
614
- <Animated.View
615
- style={[
616
- styles.headerBox,
617
- {
618
- height,
619
- },
620
- ]}>
621
- <Image
622
- source={{
623
- uri: theme.assets?.headerBackground,
624
- }}
625
- style={styles.headerBackground}
626
- />
627
- </Animated.View>
628
- {renderInputView()}
629
- </>
630
- );
631
- }
632
-
633
- return (
634
- <>
635
- <View style={[styles.headerBox, {height: heightHeader}]}>
636
- <Image
637
- source={{
638
- uri: theme.assets?.headerBackground,
639
- }}
640
- style={styles.headerBackground}
641
- />
642
- </View>
643
- {renderInputView()}
644
- </>
645
- );
646
- }
647
-
648
- if (headerType === 'extended') {
649
- return (
650
- <View style={{minHeight: heightHeader}}>
651
- <Image
652
- source={{
653
- uri: theme.assets?.headerBackground,
654
- }}
655
- style={styles.extendedHeader}
656
- />
657
- </View>
658
- );
659
- }
660
-
661
- return <View />;
662
- };
663
-
664
473
  const styles = StyleSheet.create({
665
474
  navigationButton: {
666
475
  height: 28,
@@ -700,6 +509,7 @@ const styles = StyleSheet.create({
700
509
  paddingRight: Spacing.M,
701
510
  },
702
511
  toolkitContainer: {
512
+ marginLeft: Spacing.S,
703
513
  padding: Spacing.XS,
704
514
  height: 28,
705
515
  borderRadius: 14,
@@ -731,17 +541,6 @@ const styles = StyleSheet.create({
731
541
  top: -Spacing.XS,
732
542
  right: -Spacing.XS,
733
543
  },
734
- extendedHeader: {
735
- aspectRatio: 1.75,
736
- position: 'absolute',
737
- width: '100%',
738
- height: 210,
739
- },
740
- headerBox: {
741
- width: '100%',
742
- position: 'absolute',
743
- overflow: 'hidden',
744
- },
745
544
  });
746
545
 
747
546
  export {
@@ -749,9 +548,8 @@ export {
749
548
  HeaderTitle,
750
549
  HeaderLeft,
751
550
  HeaderBackground,
551
+ HeaderRightAction,
752
552
  HeaderToolkitAction,
753
553
  HeaderCustom,
754
554
  HeaderAnimated,
755
- HeaderRight,
756
- HeaderExtendHeader,
757
555
  };
@@ -21,10 +21,10 @@ const ModalScreen: React.FC<any> = props => {
21
21
  };
22
22
 
23
23
  const Modal: React.FC<ModalParams> = props => {
24
- const {navigator} = useContext(ApplicationContext);
24
+ const {theme, 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);
27
+ const navigation = new Navigation(props.navigation, theme);
28
28
  const params = {
29
29
  ...props.route.params,
30
30
  navigation,
@@ -1,13 +1,17 @@
1
+ import React from 'react';
1
2
  import {NavigationProp} from '@react-navigation/native';
2
- import {NavigationOptions} from './types';
3
+ import {NavigationOptions, Theme} from './types';
4
+ import {HeaderRightAction} from './index';
3
5
  import {getOptions} from './utils';
6
+ import {HeaderToolkitAction} from './Components';
4
7
 
5
8
  class Navigation {
6
9
  instance: NavigationProp<any>;
7
- onHeaderRightChange?: (width: number) => void;
10
+ readonly theme: Theme;
8
11
 
9
- constructor(instance: any) {
12
+ constructor(instance: any, theme: Theme) {
10
13
  this.instance = instance;
14
+ this.theme = theme;
11
15
  }
12
16
 
13
17
  /**
@@ -15,6 +19,16 @@ class Navigation {
15
19
  * @param params
16
20
  */
17
21
  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
+
18
32
  if (params.headerLeft) {
19
33
  console.warn('headerLeft not allow override render by design system!');
20
34
  delete params.headerLeft;
@@ -28,7 +42,7 @@ class Navigation {
28
42
  * @param params
29
43
  */
30
44
  setOptions = (params: NavigationOptions) => {
31
- params = getOptions(this.filterParams(params), this);
45
+ params = getOptions(this.filterParams(params), this.theme);
32
46
  this.instance.setOptions(params);
33
47
  };
34
48
  }
@@ -1,6 +1,6 @@
1
1
  import React, {useContext, useEffect, useLayoutEffect, useRef} from 'react';
2
2
  import {useHeaderHeight} from '@react-navigation/stack';
3
- import {InteractionManager, Linking, View} from 'react-native';
3
+ import {Alert, InteractionManager, Linking, View} from 'react-native';
4
4
  import {ScreenParams} from './types';
5
5
  import Navigation from './Navigation';
6
6
  import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
@@ -88,7 +88,7 @@ const EmptyScreen: React.FC = () => {
88
88
  * @constructor
89
89
  */
90
90
  const StackScreen: React.FC<ScreenParams> = props => {
91
- const {showGrid, navigator} = useContext(ApplicationContext);
91
+ const {theme, 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,7 +104,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
104
104
  const context = useContext<any>(MiniAppContext);
105
105
 
106
106
  const {screen: Component, options, initialParams} = props.route.params;
107
- const navigation = new Navigation(props.navigation);
107
+ const navigation = new Navigation(props.navigation, theme);
108
108
  const heightHeader = useHeaderHeight();
109
109
 
110
110
  const data = {
@@ -122,18 +122,9 @@ const StackScreen: React.FC<ScreenParams> = props => {
122
122
  * set options for screen
123
123
  */
124
124
  useLayoutEffect(() => {
125
- let defaultOptions = {
126
- headerRight: {
127
- type: 'toolkit',
128
- },
129
- };
130
125
  if (options) {
131
- defaultOptions = {
132
- ...defaultOptions,
133
- ...options,
134
- };
126
+ navigation.setOptions(options);
135
127
  }
136
- navigation.setOptions(defaultOptions);
137
128
  }, [options]);
138
129
 
139
130
  /**
@@ -155,6 +146,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
155
146
  }, 5000);
156
147
 
157
148
  return () => {
149
+ clearTimeout(tracked.current.timeoutLoad);
150
+ clearTimeout(tracked.current.timeoutInteraction);
158
151
  onScreenLoad();
159
152
  onScreenInteraction();
160
153
  };
@@ -164,8 +157,10 @@ const StackScreen: React.FC<ScreenParams> = props => {
164
157
  * tracking for screen load
165
158
  */
166
159
  const onScreenLoad = () => {
167
- clearTimeout(tracked.current.timeoutLoad);
168
160
  if (!tracked.current?.releaseLoad) {
161
+ if (timeLoad.current === 0) {
162
+ timeLoad.current = endTime.current - startTime.current;
163
+ }
169
164
  context.autoTracking?.({
170
165
  appId: context.appId,
171
166
  code: context.code,
@@ -179,6 +174,9 @@ const StackScreen: React.FC<ScreenParams> = props => {
179
174
  appId: `auto - ${context.appId}`,
180
175
  message: `${screenName} screen_load_time ${timeLoad.current}`,
181
176
  });
177
+ if (timeLoad.current === 0 && context.enableAutoId) {
178
+ Alert.alert(screenName, "Can't get screen load time");
179
+ }
182
180
  tracked.current.releaseLoad = true;
183
181
  }
184
182
  };
@@ -187,8 +185,10 @@ const StackScreen: React.FC<ScreenParams> = props => {
187
185
  * tracking for screen load
188
186
  */
189
187
  const onScreenInteraction = () => {
190
- clearTimeout(tracked.current.timeoutInteraction);
191
188
  if (!tracked.current?.releaseInteraction) {
189
+ if (timeLoad.current === 0) {
190
+ timeLoad.current = endTime.current - startTime.current;
191
+ }
192
192
  context.autoTracking?.({
193
193
  appId: context.appId,
194
194
  code: context.code,
@@ -203,6 +203,12 @@ const StackScreen: React.FC<ScreenParams> = props => {
203
203
  appId: `auto - ${context.appId}`,
204
204
  message: `${screenName} screen_interaction_time ${timeInteraction.current}`,
205
205
  });
206
+ if (
207
+ timeInteraction.current - timeLoad.current <= 0 &&
208
+ context.enableAutoId
209
+ ) {
210
+ Alert.alert(screenName, "Can't get screen interaction time");
211
+ }
206
212
  tracked.current.releaseInteraction = true;
207
213
  }
208
214
  };
@@ -212,12 +218,12 @@ const StackScreen: React.FC<ScreenParams> = props => {
212
218
  value={{
213
219
  screenName,
214
220
  onElementLoad: () => {
221
+ clearTimeout(timeoutLoad.current);
215
222
  endTime.current = Date.now();
216
223
  interaction.current?.cancel?.();
217
224
  interaction.current = InteractionManager.runAfterInteractions(() => {
218
225
  timeInteraction.current = Date.now() - startTime.current;
219
226
  });
220
- clearTimeout(timeoutLoad.current);
221
227
  timeoutLoad.current = setTimeout(() => {
222
228
  if (timeLoad.current === 0) {
223
229
  timeLoad.current = endTime.current - startTime.current;
@@ -5,6 +5,7 @@ import {
5
5
  HeaderAnimated,
6
6
  HeaderBackground,
7
7
  HeaderCustom,
8
+ HeaderRightAction,
8
9
  HeaderTitle,
9
10
  NavigationButton,
10
11
  } from './Components';
@@ -30,6 +31,7 @@ export {
30
31
  NavigationButton,
31
32
  HeaderTitle,
32
33
  HeaderBackground,
34
+ HeaderRightAction,
33
35
  HeaderCustom,
34
36
  HeaderAnimated,
35
37
  };
@@ -146,29 +146,19 @@ export type RuntimeToolType = {
146
146
  key: string;
147
147
  };
148
148
 
149
- export interface NavigationOptions
150
- extends Omit<StackNavigationOptions, 'headerRight'> {
149
+ export interface NavigationOptions extends StackNavigationOptions {
151
150
  preventBack?: PopupNotifyProps;
152
151
  onPressLeftHeader?: () => void;
153
152
  surface?: boolean;
154
153
  hiddenBack?: boolean;
155
154
  customTitle?: TitleCustomProps;
156
- headerRight?: HeaderRightToolkit | HeaderRightActions | any;
155
+ toolkitParams?: {
156
+ pinnedTool?: PinnedToolType;
157
+ runtimeTools?: RuntimeToolType[];
158
+ preventClose?: PopupNotifyProps;
159
+ };
157
160
  }
158
161
 
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
-
172
162
  export interface HeaderBackProps extends NavigationButtonProps {
173
163
  preventBack?: PopupNotifyProps;
174
164
  onPressLeftHeader?: () => void;