@momo-kits/foundation 0.92.26-beta.22 → 0.92.26-beta.24

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