@momo-kits/foundation 0.92.6-rc.3 → 0.92.6-rc.4

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,5 +1,6 @@
1
1
  import React, {useContext, useEffect, useRef} from 'react';
2
2
  import {
3
+ Dimensions,
3
4
  KeyboardAvoidingView,
4
5
  Platform,
5
6
  Pressable,
@@ -10,7 +11,7 @@ import {
10
11
  import {Modalize} from 'react-native-modalize';
11
12
  import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
12
13
 
13
- import {ApplicationContext} from '.';
14
+ import {ApplicationContext} from './index';
14
15
  import {Radius, Spacing, Styles} from '../Consts';
15
16
  import {Icon} from '../Icon';
16
17
  import {Text} from '../Text';
@@ -207,6 +208,7 @@ const styles = StyleSheet.create({
207
208
  backgroundColor: 'white',
208
209
  borderTopRightRadius: 16,
209
210
  borderTopLeftRadius: 16,
211
+ maxHeight: Dimensions.get('window').height * 0.75,
210
212
  },
211
213
  header: {
212
214
  height: 50,
@@ -6,8 +6,8 @@ import {getOptions} from './utils';
6
6
  import {HeaderToolkitAction} from './Components';
7
7
 
8
8
  class Navigation {
9
- private instance: NavigationProp<any>;
10
- private readonly theme: Theme;
9
+ instance: NavigationProp<any>;
10
+ readonly theme: Theme;
11
11
 
12
12
  constructor(instance: any, theme: Theme) {
13
13
  this.instance = instance;
@@ -27,8 +27,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
27
27
  const heightHeader = useHeaderHeight();
28
28
 
29
29
  const data = {
30
- ...props.route.params,
31
30
  ...initialParams,
31
+ ...props.route.params,
32
32
  navigation,
33
33
  };
34
34
 
@@ -0,0 +1,128 @@
1
+ import {default as React, useContext, useEffect, useRef, useState} from 'react';
2
+ import {
3
+ Animated,
4
+ LayoutChangeEvent,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ } from 'react-native';
8
+ import {ApplicationContext} from '../Application';
9
+ import {Icon} from '../Icon';
10
+ import {Text} from '../Text';
11
+
12
+ export interface FloatingButtonProps {
13
+ label?: string;
14
+ onPress?: () => void;
15
+ position?: 'center' | 'right';
16
+ icon?: string;
17
+ iconColor?: string;
18
+ size?: 'small' | 'large';
19
+ animatedValue?: Animated.Value;
20
+ bottom?: number;
21
+ }
22
+
23
+ export const FloatingButton: React.FC<FloatingButtonProps> = ({
24
+ label = '',
25
+ onPress = () => {},
26
+ position = 'right',
27
+ icon = '',
28
+ iconColor = '',
29
+ size = 'small',
30
+ animatedValue,
31
+ bottom = 12,
32
+ }: FloatingButtonProps) => {
33
+ const {theme} = useContext(ApplicationContext);
34
+ const maxWidth = useRef(0);
35
+ const minWidth = size == 'small' ? 36 : 48;
36
+ const [widthAnimated, setWidthAnimated] = useState<Animated.Value>();
37
+ const lastOffset = useRef(0);
38
+ const debounce = useRef<any>();
39
+ const [showText, setShowText] = React.useState(true);
40
+
41
+ useEffect(() => {
42
+ if (!label) return;
43
+
44
+ const listener = animatedValue?.addListener(({value}) => {
45
+ clearTimeout(debounce.current);
46
+ debounce.current = setTimeout(() => {
47
+ const down = value > lastOffset.current;
48
+ lastOffset.current = value;
49
+ if (down) {
50
+ Animated.timing(widthAnimated!, {
51
+ toValue: minWidth,
52
+ duration: 300,
53
+ useNativeDriver: false,
54
+ }).start(() => setShowText(false));
55
+ } else {
56
+ Animated.timing(widthAnimated!, {
57
+ toValue: maxWidth.current,
58
+ duration: 300,
59
+ useNativeDriver: false,
60
+ }).start(() => setShowText(true));
61
+ }
62
+ }, 500);
63
+ });
64
+
65
+ return () => {
66
+ if (listener != null) {
67
+ animatedValue?.removeListener(listener);
68
+ }
69
+ };
70
+ }, [label, widthAnimated]);
71
+
72
+ const handleLayout = (event: LayoutChangeEvent) => {
73
+ const layout = event.nativeEvent.layout;
74
+ if (widthAnimated) return;
75
+ maxWidth.current = layout.width;
76
+ setWidthAnimated(new Animated.Value(layout.width));
77
+ };
78
+
79
+ return (
80
+ <Animated.View
81
+ onLayout={handleLayout}
82
+ style={[
83
+ styles.container,
84
+ {
85
+ right: position === 'right' ? 12 : undefined,
86
+ alignSelf: position === 'center' ? 'center' : 'flex-end',
87
+ width: widthAnimated,
88
+ height: size == 'small' ? 36 : 48,
89
+ backgroundColor: theme.colors.primary,
90
+ bottom,
91
+ },
92
+ ]}>
93
+ <TouchableOpacity
94
+ style={{flexDirection: 'row', alignItems: 'center'}}
95
+ onPress={onPress}>
96
+ <Icon
97
+ source={icon}
98
+ size={size === 'small' ? 12 : 24}
99
+ color={iconColor}
100
+ />
101
+ {showText && !!label && (
102
+ <Text
103
+ style={styles.label}
104
+ color="white"
105
+ typography="action_default_bold">
106
+ {label}
107
+ </Text>
108
+ )}
109
+ </TouchableOpacity>
110
+ </Animated.View>
111
+ );
112
+ };
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ flexDirection: 'row',
117
+ alignContent: 'center',
118
+ alignItems: 'center',
119
+ position: 'absolute',
120
+ borderRadius: 100,
121
+ paddingHorizontal: 12,
122
+ flexWrap: 'wrap',
123
+ alignSelf: 'flex-end',
124
+ },
125
+ label: {
126
+ marginLeft: 8,
127
+ },
128
+ });
package/Layout/Screen.tsx CHANGED
@@ -3,16 +3,13 @@ import React, {
3
3
  forwardRef,
4
4
  Fragment,
5
5
  ReactNode,
6
- useCallback,
7
6
  useContext,
8
7
  useEffect,
9
8
  useRef,
10
9
  } from 'react';
11
10
  import {
12
11
  Animated,
13
- Easing,
14
12
  KeyboardAvoidingView,
15
- LayoutChangeEvent,
16
13
  NativeScrollEvent,
17
14
  NativeSyntheticEvent,
18
15
  Platform,
@@ -29,15 +26,11 @@ import {
29
26
  import Navigation from '../Application/Navigation';
30
27
  import {AnimatedHeader, NavigationOptions} from '../Application/types';
31
28
  import {Colors, Spacing, Styles} from '../Consts';
32
- import {
33
- FloatingButton,
34
- FloatingButtonProps,
35
- } from '../FloatingButton/FloatingButton';
29
+ import {FloatingButton, FloatingButtonProps} from './FloatingButton';
36
30
  import {Image} from '../Image';
37
31
  import {Card, Section, validateChildren} from './index';
38
32
  import styles from './styles';
39
33
  import {HeaderType} from './types';
40
- import {debounce} from './utils';
41
34
 
42
35
  export interface ScreenProps extends ViewProps {
43
36
  children: React.ReactNode;
@@ -94,7 +87,12 @@ export interface ScreenProps extends ViewProps {
94
87
  /**
95
88
  * Optional. Props for the floating action button.
96
89
  */
97
- floatingButtonProps?: Omit<FloatingButtonProps, 'animatedValue'>;
90
+ floatingButtonProps?: Omit<FloatingButtonProps, 'animatedValue' | 'bottom'>;
91
+
92
+ /**
93
+ * Optional. If `true`, the screen content is using grid layout by span.
94
+ */
95
+ useGridLayout?: boolean;
98
96
  }
99
97
 
100
98
  const Screen = forwardRef(
@@ -112,24 +110,22 @@ const Screen = forwardRef(
112
110
  backgroundColor,
113
111
  headerType = 'default',
114
112
  floatingButtonProps,
113
+ useGridLayout = true,
115
114
  }: ScreenProps,
116
115
  ref,
117
116
  ) => {
118
117
  const animatedValue = useRef<Animated.Value | undefined>(undefined);
119
- const lastScrollPosition = useRef(0);
120
- const animatedFABValue = useRef(new Animated.Value(0)).current;
121
118
  const {theme} = useContext(ApplicationContext);
122
119
  const insets = useSafeAreaInsets();
123
120
  const heightHeader = useHeaderHeight();
124
- const layoutHeight = useRef(0);
125
- const contentHeight = useRef(0);
126
121
  const currentTint = useRef(Colors.black_01);
122
+ const isTab = navigation?.instance?.getState?.()?.type == 'tab';
123
+
127
124
  let styleAnimatedHeader: NavigationOptions;
128
125
  let handleScroll;
129
126
  let headerBackground: string | undefined = undefined;
130
127
  let Component: any = View;
131
128
  let keyboardVerticalOffset = heightHeader - 20;
132
- animatedValue.current = undefined;
133
129
 
134
130
  if (headerType == 'extended' || animatedHeader) {
135
131
  keyboardVerticalOffset = -20;
@@ -258,46 +254,12 @@ const Screen = forwardRef(
258
254
  });
259
255
  }
260
256
  }
261
- if (floatingButtonProps) {
262
- const currentScrollPosition = e.nativeEvent.contentOffset.y;
263
- const isScrollingDown =
264
- currentScrollPosition > lastScrollPosition.current;
265
- scrollViewProps?.onScroll?.(e);
266
-
267
- if (currentScrollPosition < 0) {
268
- return;
269
- }
270
- const isAtBottom =
271
- currentScrollPosition +
272
- layoutHeight.current +
273
- Math.min(insets.bottom, 21) +
274
- Spacing.S >=
275
- contentHeight.current;
276
- if (isAtBottom) {
277
- return;
278
- }
279
- animateFAB(isScrollingDown);
280
-
281
- lastScrollPosition.current = currentScrollPosition;
282
- }
283
257
  },
284
258
  },
285
259
  );
286
260
  }
287
261
  }
288
262
 
289
- const animateFAB = useCallback(
290
- debounce((isScrollingDown: boolean) => {
291
- Animated.timing(animatedFABValue, {
292
- toValue: isScrollingDown ? 1 : 0,
293
- duration: 100,
294
- useNativeDriver: false,
295
- easing: Easing.linear,
296
- }).start();
297
- }, 300),
298
- [],
299
- );
300
-
301
263
  /**
302
264
  * render top navigation banner
303
265
  */
@@ -351,6 +313,7 @@ const Screen = forwardRef(
351
313
  );
352
314
  }
353
315
  };
316
+
354
317
  return (
355
318
  <View style={[Styles.flex, {backgroundColor}]}>
356
319
  {!!headerBackground && (
@@ -373,39 +336,32 @@ const Screen = forwardRef(
373
336
  android: undefined,
374
337
  })}>
375
338
  {Header}
339
+
376
340
  <Component
377
341
  {...scrollViewProps}
378
342
  ref={ref}
379
343
  showsVerticalScrollIndicator={false}
380
344
  onScroll={handleScroll}
381
- onContentSizeChange={(_: number, height: number) => {
382
- contentHeight.current = height;
383
- }}
384
- onLayout={(event: LayoutChangeEvent) => {
385
- layoutHeight.current = event.nativeEvent.layout.height;
386
- }}
387
345
  style={Styles.flex}>
388
346
  <View
389
347
  style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
390
348
  {renderHeader()}
391
349
  </View>
392
- {renderContent(children)}
350
+ {useGridLayout ? renderContent(children) : children}
393
351
  </Component>
394
- <View>
395
- {floatingButtonProps && (
352
+
353
+ {floatingButtonProps && (
354
+ <View>
396
355
  <FloatingButton
397
- label={floatingButtonProps?.label}
398
- onPress={floatingButtonProps?.onPress}
399
- size={floatingButtonProps?.size}
400
- position={floatingButtonProps?.position}
401
- icon={floatingButtonProps?.icon}
402
- iconColor={floatingButtonProps?.iconColor}
403
- isBottomTab={floatingButtonProps?.isBottomTab}
404
- animatedValue={animatedFABValue}
405
- isFooter={!!Footer}
356
+ {...floatingButtonProps}
357
+ animatedValue={animatedValue.current}
358
+ bottom={
359
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
360
+ }
406
361
  />
407
- )}
408
- </View>
362
+ </View>
363
+ )}
364
+
409
365
  {Footer && (
410
366
  <View
411
367
  style={[
package/Layout/utils.ts CHANGED
@@ -51,20 +51,5 @@ const useGridSystem = () => {
51
51
 
52
52
  return {numberOfColumns};
53
53
  };
54
- function debounce<T extends (...args: any[]) => void>(
55
- func: T,
56
- wait: number,
57
- ): (...args: Parameters<T>) => void {
58
- let timeout: NodeJS.Timeout | null = null;
59
54
 
60
- return function (...args: Parameters<T>) {
61
- if (timeout !== null) {
62
- clearTimeout(timeout);
63
- }
64
- timeout = setTimeout(() => {
65
- func(...args);
66
- }, wait);
67
- };
68
- }
69
-
70
- export {debounce, useGridSystem, validateChildren};
55
+ export {useGridSystem, validateChildren};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.6-rc.3",
3
+ "version": "0.92.6-rc.4",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -1,154 +0,0 @@
1
- import {
2
- default as React,
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useRef,
7
- useState,
8
- } from 'react';
9
- import {
10
- Animated,
11
- LayoutChangeEvent,
12
- StyleSheet,
13
- TouchableOpacity,
14
- } from 'react-native';
15
- import {useSafeAreaInsets} from 'react-native-safe-area-context';
16
- import {ApplicationContext} from '../Application';
17
- import {Spacing} from '../Consts';
18
- import {Icon} from '../Icon';
19
- import {Text} from '../Text';
20
-
21
- export interface FloatingButtonProps {
22
- label?: string;
23
- onPress?: () => void;
24
- position?: 'center' | 'right';
25
- icon?: string;
26
- iconColor?: string;
27
- size?: 'small' | 'large';
28
- animatedValue: Animated.Value;
29
- isBottomTab?: boolean;
30
- isFooter?: boolean;
31
- }
32
-
33
- export const FloatingButton: React.FC<FloatingButtonProps> = ({
34
- label = '',
35
- onPress = () => {},
36
- position = 'right',
37
- icon = '',
38
- iconColor = '',
39
- size = 'small',
40
- animatedValue,
41
- isBottomTab = false,
42
- isFooter = false,
43
- }: FloatingButtonProps) => {
44
- const {theme} = useContext(ApplicationContext);
45
- const insets = useSafeAreaInsets();
46
- const [isExpanded, setIsExpanded] = React.useState(true);
47
- const [init, setInit] = useState(true);
48
- const width = size == 'small' ? 36 : 48;
49
- const widthAnim = useRef(new Animated.Value(width)).current;
50
- const [textWidth, setTextWidth] = useState(0);
51
-
52
- useEffect(() => {
53
- if (!label) return;
54
-
55
- const listener = animatedValue.addListener(({value}) => {
56
- if (value === 1) {
57
- setIsExpanded(false);
58
- collapseFab();
59
- } else {
60
- setIsExpanded(true);
61
- expandFab();
62
- }
63
- });
64
-
65
- return () => {
66
- animatedValue?.removeListener(listener);
67
- };
68
- }, [textWidth, label, init]);
69
-
70
- // If the text width is greater than 0 and it's not initialized then expand the fab
71
- useEffect(() => {
72
- if (textWidth > 0 && !init) {
73
- expandFab();
74
- }
75
- }, [textWidth]);
76
-
77
- const handleLayout = (event: LayoutChangeEvent) => {
78
- const {width} = event.nativeEvent.layout;
79
-
80
- if (init) {
81
- setTextWidth(width);
82
- setInit(false);
83
- }
84
- };
85
-
86
- const expandFab = useCallback(() => {
87
- Animated.timing(widthAnim, {
88
- toValue: width + textWidth + 8,
89
- duration: 200,
90
- useNativeDriver: false,
91
- }).start();
92
- }, [textWidth, widthAnim]);
93
-
94
- const collapseFab = useCallback(() => {
95
- Animated.timing(widthAnim, {
96
- toValue: width,
97
- duration: 200,
98
- useNativeDriver: false,
99
- }).start();
100
- }, [widthAnim]);
101
-
102
- return (
103
- <Animated.View
104
- style={[
105
- styles.container,
106
- {
107
- right: position === 'right' ? 12 : undefined,
108
- alignSelf: position === 'center' ? 'center' : 'flex-end',
109
- width: init ? undefined : widthAnim,
110
- height: width,
111
- backgroundColor: theme.colors.primary,
112
- bottom:
113
- isFooter || isBottomTab
114
- ? 12
115
- : Math.min(insets.bottom, 21) + Spacing.S,
116
- },
117
- ]}>
118
- <TouchableOpacity
119
- style={{flexDirection: 'row', alignItems: 'center'}}
120
- onPress={onPress}>
121
- <Icon
122
- source={icon}
123
- size={size === 'small' ? 12 : 24}
124
- color={iconColor}
125
- />
126
- {isExpanded && !!label && (
127
- <Text
128
- onLayout={handleLayout}
129
- style={styles.label}
130
- color="white"
131
- typography="action_default_bold">
132
- Hello
133
- </Text>
134
- )}
135
- </TouchableOpacity>
136
- </Animated.View>
137
- );
138
- };
139
-
140
- const styles = StyleSheet.create({
141
- container: {
142
- flexDirection: 'row',
143
- alignContent: 'center',
144
- alignItems: 'center',
145
- position: 'absolute',
146
- borderRadius: 100,
147
- paddingHorizontal: 12,
148
- flexWrap: 'wrap',
149
- alignSelf: 'flex-end',
150
- },
151
- label: {
152
- marginLeft: 8,
153
- },
154
- });