@momo-kits/foundation 0.92.26-beta.2 → 0.92.26-beta.21

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.
@@ -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;
@@ -74,7 +79,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
74
79
  Animated.timing(pan, {
75
80
  toValue: {x: 0, y: 0},
76
81
  useNativeDriver: false,
77
- duration: 200,
82
+ duration: 150,
78
83
  }).start();
79
84
  return () => {
80
85
  props.route.params?.onDismiss?.();
@@ -101,9 +106,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
101
106
  };
102
107
 
103
108
  /**
104
- * manual close
109
+ * on request close
105
110
  */
106
- const requestClose = useCallback((callback?: () => void) => {
111
+ const onRequestClose = useCallback((callback?: () => void) => {
107
112
  onDismiss(true, callback);
108
113
  }, []);
109
114
 
@@ -148,7 +153,11 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
148
153
  }, []);
149
154
 
150
155
  return (
151
- <Modal transparent visible={true} onRequestClose={() => onDismiss()}>
156
+ <Container
157
+ transparent
158
+ visible={true}
159
+ onRequestClose={() => onDismiss()}
160
+ style={styles.overlay}>
152
161
  <KeyboardAvoidingView
153
162
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
154
163
  keyboardVerticalOffset={keyboardVerticalOffset ?? -20}
@@ -164,11 +173,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
164
173
  <Screen
165
174
  {...props}
166
175
  {...props.route.params}
167
- // requestClose={
168
- // props.requestClose ??
169
- // props.route.params?.requestClose ??
170
- // requestClose
171
- // }
176
+ onRequestClose={onRequestClose}
172
177
  />
173
178
  {useBottomInset && (
174
179
  <View style={{height: Math.min(insets.bottom, 21)}} />
@@ -176,23 +181,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
176
181
  </View>
177
182
  </Animated.View>
178
183
  </KeyboardAvoidingView>
179
- </Modal>
184
+ </Container>
180
185
  );
181
186
  };
182
187
 
183
- export default BottomSheet;
184
-
185
188
  const styles = StyleSheet.create({
186
- container: {
187
- width: '100%',
188
- height: 0,
189
- overflow: 'hidden',
190
- borderTopLeftRadius: Radius.M,
191
- borderTopRightRadius: Radius.M,
192
- },
193
- draggableContainer: {
194
- width: '100%',
195
- alignItems: 'center',
189
+ overlay: {
190
+ ...StyleSheet.absoluteFillObject,
191
+ justifyContent: 'flex-end',
196
192
  },
197
193
  indicator: {
198
194
  width: 40,
@@ -216,3 +212,5 @@ const styles = StyleSheet.create({
216
212
  marginHorizontal: Spacing.M,
217
213
  },
218
214
  });
215
+
216
+ export default BottomSheet;
@@ -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,15 @@
1
1
  import React, {
2
- Fragment,
3
2
  useCallback,
4
3
  useContext,
5
4
  useEffect,
5
+ useRef,
6
6
  useState,
7
7
  } from 'react';
8
8
  import {
9
9
  Animated,
10
10
  BackHandler,
11
11
  DeviceEventEmitter,
12
+ Dimensions,
12
13
  StatusBar,
13
14
  StyleSheet,
14
15
  TouchableOpacity,
@@ -29,7 +30,17 @@ import {scaleSize, Text} from '../Text';
29
30
  import {Icon} from '../Icon';
30
31
  import {PopupNotify} from '../Popup';
31
32
  import {Badge, BadgeDot} from '../Badge';
33
+ import {HeaderType} from '../Layout/types';
34
+ import Navigation from './Navigation';
35
+ import {InputSearch, InputSearchProps} from '../Input';
32
36
 
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
+ */
33
44
  const NavigationButton: React.FC<NavigationButtonProps> = ({
34
45
  icon,
35
46
  tintColor,
@@ -81,13 +92,18 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
81
92
  );
82
93
  };
83
94
 
95
+ /**
96
+ * default header title used for nav
97
+ */
84
98
  const HeaderTitle: React.FC<any> = props => {
85
99
  const {theme} = useContext(ApplicationContext);
86
- const opacity = props.animatedValue?.interpolate({
87
- inputRange: [0, 200],
88
- outputRange: [0, 1],
89
- extrapolate: 'clamp',
90
- });
100
+ const opacity = props.animatedValue?.interpolate(
101
+ props.interpolate ?? {
102
+ inputRange: [0, 200],
103
+ outputRange: [0, 1],
104
+ extrapolate: 'clamp',
105
+ }
106
+ );
91
107
 
92
108
  return (
93
109
  <Animated.Text
@@ -101,6 +117,9 @@ const HeaderTitle: React.FC<any> = props => {
101
117
  );
102
118
  };
103
119
 
120
+ /**
121
+ * default header left used for nav
122
+ */
104
123
  const HeaderLeft: React.FC<HeaderBackProps> = ({
105
124
  tintColor,
106
125
  preventBack,
@@ -113,7 +132,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
113
132
  useEffect(() => {
114
133
  const backHandler = BackHandler.addEventListener(
115
134
  'hardwareBackPress',
116
- goBackSafe,
135
+ goBackSafe
117
136
  );
118
137
 
119
138
  return () => backHandler.remove();
@@ -168,6 +187,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
168
187
  }
169
188
  return true;
170
189
  };
190
+
171
191
  return (
172
192
  <View style={styles.headerLeft}>
173
193
  <NavigationButton
@@ -181,6 +201,9 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
181
201
  );
182
202
  };
183
203
 
204
+ /**
205
+ * header background for default
206
+ */
184
207
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
185
208
  image,
186
209
  animatedValue,
@@ -229,6 +252,10 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
229
252
  );
230
253
  };
231
254
 
255
+ /**
256
+ * Header custom with image, title, subtitle
257
+ * @constructor
258
+ */
232
259
  const HeaderCustom: React.FC<TitleCustomProps> = ({
233
260
  title,
234
261
  subTitle,
@@ -263,37 +290,42 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
263
290
  return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
264
291
  };
265
292
 
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
- );
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);
273
301
  }
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
302
  return (
289
- <View style={styles.headerButton}>
290
- {React.cloneElement(validateType(children), {...restProps})}
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
+ })}
291
315
  </View>
292
316
  );
293
- };
294
- return <View style={styles.headerRightButton}>{renderAction()}</View>;
317
+ }
318
+
319
+ return (
320
+ <View style={styles.headerRightButton} onLayout={onLayout}>
321
+ {children ?? <HeaderToolkitAction {...props} />}
322
+ </View>
323
+ );
295
324
  };
296
325
 
326
+ /**
327
+ * Header toolkit action
328
+ */
297
329
  const HeaderToolkitAction: React.FC<any> = ({
298
330
  tintColor,
299
331
  pinnedTool,
@@ -327,7 +359,7 @@ const HeaderToolkitAction: React.FC<any> = ({
327
359
  (config: any) => {
328
360
  navigator.toolkitConfig = config;
329
361
  setToolConfig(navigator?.toolkitConfig);
330
- },
362
+ }
331
363
  );
332
364
  };
333
365
 
@@ -340,7 +372,7 @@ const HeaderToolkitAction: React.FC<any> = ({
340
372
  const {item} = res;
341
373
  navigator?.toolkitCallback?.(item);
342
374
  getToolkitConfig();
343
- },
375
+ }
344
376
  );
345
377
  };
346
378
 
@@ -357,7 +389,7 @@ const HeaderToolkitAction: React.FC<any> = ({
357
389
  navigator?.maxApi?.dispatchFunction?.(
358
390
  'dismiss',
359
391
  undefined,
360
- undefined,
392
+ undefined
361
393
  );
362
394
  },
363
395
  }}
@@ -388,7 +420,7 @@ const HeaderToolkitAction: React.FC<any> = ({
388
420
  [pinTool?.key],
389
421
  () => {
390
422
  getToolkitConfig();
391
- },
423
+ }
392
424
  );
393
425
  navigator?.toolkitCallback?.(pinTool.key);
394
426
  }}
@@ -397,7 +429,7 @@ const HeaderToolkitAction: React.FC<any> = ({
397
429
  };
398
430
  if (toolConfig) {
399
431
  return (
400
- <View style={styles.headerRightButton}>
432
+ <View style={Styles.row}>
401
433
  {renderPinnedTool()}
402
434
  <View
403
435
  style={[
@@ -405,6 +437,7 @@ const HeaderToolkitAction: React.FC<any> = ({
405
437
  {
406
438
  backgroundColor: backgroundColor ?? '#00000066',
407
439
  borderWidth: backgroundColor ? 0.5 : 0,
440
+ marginLeft: renderPinnedTool() ? Spacing.S : 0,
408
441
  },
409
442
  ]}>
410
443
  <TouchableOpacity
@@ -430,6 +463,9 @@ const HeaderToolkitAction: React.FC<any> = ({
430
463
  return <View />;
431
464
  };
432
465
 
466
+ /**
467
+ * default header banner for header animated
468
+ */
433
469
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
434
470
  animatedValue,
435
471
  image,
@@ -470,6 +506,155 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
470
506
  );
471
507
  };
472
508
 
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
+
473
658
  const styles = StyleSheet.create({
474
659
  navigationButton: {
475
660
  height: 28,
@@ -509,7 +694,6 @@ const styles = StyleSheet.create({
509
694
  paddingRight: Spacing.M,
510
695
  },
511
696
  toolkitContainer: {
512
- marginLeft: Spacing.S,
513
697
  padding: Spacing.XS,
514
698
  height: 28,
515
699
  borderRadius: 14,
@@ -541,6 +725,17 @@ const styles = StyleSheet.create({
541
725
  top: -Spacing.XS,
542
726
  right: -Spacing.XS,
543
727
  },
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
+ },
544
739
  });
545
740
 
546
741
  export {
@@ -548,8 +743,9 @@ export {
548
743
  HeaderTitle,
549
744
  HeaderLeft,
550
745
  HeaderBackground,
551
- HeaderRightAction,
552
746
  HeaderToolkitAction,
553
747
  HeaderCustom,
554
748
  HeaderAnimated,
749
+ HeaderRight,
750
+ HeaderExtendHeader,
555
751
  };
@@ -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,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, theme);
107
+ const navigation = new Navigation(props.navigation);
108
108
  const heightHeader = useHeaderHeight();
109
109
 
110
110
  const data = {
@@ -122,9 +122,18 @@ 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
+ };
125
130
  if (options) {
126
- navigation.setOptions(options);
131
+ defaultOptions = {
132
+ ...defaultOptions,
133
+ ...options,
134
+ };
127
135
  }
136
+ navigation.setOptions(defaultOptions);
128
137
  }, [options]);
129
138
 
130
139
  /**
@@ -5,7 +5,6 @@ import {
5
5
  HeaderAnimated,
6
6
  HeaderBackground,
7
7
  HeaderCustom,
8
- HeaderRightAction,
9
8
  HeaderTitle,
10
9
  NavigationButton,
11
10
  } from './Components';
@@ -31,7 +30,6 @@ export {
31
30
  NavigationButton,
32
31
  HeaderTitle,
33
32
  HeaderBackground,
34
- HeaderRightAction,
35
33
  HeaderCustom,
36
34
  HeaderAnimated,
37
35
  };