@momo-kits/foundation 0.92.6-rc.1 → 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
 
@@ -120,7 +120,7 @@ export interface BottomSheetParams extends ScreenParams {
120
120
  [key: string]: any;
121
121
  onDismiss?: () => void;
122
122
  footerComponent?: React.ReactNode;
123
- flatListProps: FlatListProps<any>;
123
+ flatListProps?: FlatListProps<any>;
124
124
  }
125
125
 
126
126
  export type NavigationButtonProps = {
package/Icon/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {ImageStyle} from 'react-native-fast-image';
2
+ import {StyleProp} from "react-native";
2
3
 
3
4
  export type IconProps = {
4
5
  /**
@@ -23,5 +24,5 @@ export type IconProps = {
23
24
  * Optional. Used to apply custom styling to the Icon component.
24
25
  * Accepts an object of style properties.
25
26
  */
26
- style?: ImageStyle;
27
+ style?: StyleProp<ImageStyle>;
27
28
  };
package/Input/Input.tsx CHANGED
@@ -1,4 +1,10 @@
1
- import React, {forwardRef, useContext, useRef, useState} from 'react';
1
+ import React, {
2
+ forwardRef,
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import {
3
9
  NativeSyntheticEvent,
4
10
  Platform,
@@ -8,12 +14,12 @@ import {
8
14
  View,
9
15
  } from 'react-native';
10
16
  import {ApplicationContext, ComponentContext} from '../Application';
11
- import styles from './styles';
17
+ import {Icon} from '../Icon';
18
+ import {Loader} from '../Loader';
12
19
  import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
13
20
  import {InputProps} from './index';
14
- import {Icon} from '../Icon';
21
+ import styles from './styles';
15
22
  import {checkTyping} from './utils';
16
- import {Loader} from '../Loader';
17
23
 
18
24
  const Input = forwardRef(
19
25
  (
@@ -42,6 +48,7 @@ const Input = forwardRef(
42
48
  keyboardType,
43
49
  style,
44
50
  params,
51
+ hintText,
45
52
  accessibilityLabel,
46
53
  ...props
47
54
  }: InputProps,
@@ -51,7 +58,7 @@ const Input = forwardRef(
51
58
  const [focused, setFocused] = useState(false);
52
59
  const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
53
60
  const [secureTextInput, setSecureTextInput] = useState(secureTextEntry);
54
- const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
61
+ const inputRef = useRef<TextInput | null>(null);
55
62
 
56
63
  const onClearText = () => {
57
64
  inputRef?.current?.clear();
@@ -73,6 +80,15 @@ const Input = forwardRef(
73
80
  onBlur?.(e);
74
81
  };
75
82
 
83
+ useImperativeHandle(ref, () => {
84
+ return {
85
+ clear: onClearText,
86
+ focus: () => inputRef.current?.focus(),
87
+ blur: () => inputRef.current?.blur(),
88
+ setText: (text: string) => _onChangeText(text),
89
+ };
90
+ });
91
+
76
92
  const renderIcon = (color: string | undefined) => {
77
93
  if (loading) {
78
94
  return <Loader type={'spinner'} color={color} style={styles.icon} />;
@@ -217,7 +233,11 @@ const Input = forwardRef(
217
233
  }}>
218
234
  <View style={[style, styles.wrapper]}>
219
235
  {renderInputView()}
220
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
236
+ <ErrorView
237
+ errorMessage={errorMessage}
238
+ errorSpacing={errorSpacing}
239
+ hintText={hintText}
240
+ />
221
241
  </View>
222
242
  </ComponentContext.Provider>
223
243
  );
@@ -1,12 +1,12 @@
1
1
  import React, {useContext} from 'react';
2
2
  import {TextInput, TouchableOpacity, View} from 'react-native';
3
3
  import {ApplicationContext, ComponentContext} from '../Application';
4
- import styles from './styles';
5
- import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
6
- import {InputDropDownProps} from './index';
4
+ import {Styles} from '../Consts';
7
5
  import {Icon} from '../Icon';
8
6
  import {Loader} from '../Loader';
9
- import {Styles} from '../Consts';
7
+ import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
8
+ import {InputDropDownProps} from './index';
9
+ import styles from './styles';
10
10
 
11
11
  const InputDropDown = ({
12
12
  value,
@@ -28,6 +28,7 @@ const InputDropDown = ({
28
28
  style,
29
29
  params,
30
30
  accessibilityLabel,
31
+ hintText,
31
32
  ...props
32
33
  }: InputDropDownProps) => {
33
34
  const {theme} = useContext(ApplicationContext);
@@ -118,7 +119,11 @@ const InputDropDown = ({
118
119
  onPress={onPress}
119
120
  style={[style, full && styles.wrapper]}>
120
121
  {renderInputView()}
121
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
122
+ <ErrorView
123
+ errorMessage={errorMessage}
124
+ errorSpacing={errorSpacing}
125
+ hintText={hintText}
126
+ />
122
127
  </TouchableOpacity>
123
128
  </ComponentContext.Provider>
124
129
  );
@@ -13,10 +13,10 @@ import {
13
13
  View,
14
14
  } from 'react-native';
15
15
  import {ApplicationContext, ComponentContext} from '../Application';
16
- import styles from './styles';
16
+ import {Icon} from '../Icon';
17
17
  import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
18
18
  import {InputMoneyProps} from './index';
19
- import {Icon} from '../Icon';
19
+ import styles from './styles';
20
20
  import {formatMoneyToNumber, formatNumberToMoney} from './utils';
21
21
 
22
22
  const InputMoney = forwardRef(
@@ -39,13 +39,14 @@ const InputMoney = forwardRef(
39
39
  params,
40
40
  defaultValue = '',
41
41
  accessibilityLabel,
42
+ hintText,
42
43
  ...props
43
44
  }: InputMoneyProps,
44
45
  ref,
45
46
  ) => {
46
47
  const {theme} = useContext(ApplicationContext);
47
48
  const [focused, setFocused] = useState(false);
48
- const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
49
+ const inputRef = useRef<TextInput>(null);
49
50
 
50
51
  const validateText = (text: string) => {
51
52
  const valueFormat = formatMoneyToNumber(text, 'đ');
@@ -72,6 +73,10 @@ const InputMoney = forwardRef(
72
73
 
73
74
  useImperativeHandle(ref, () => {
74
75
  return {
76
+ clear: onClearText,
77
+ focus: () => inputRef.current?.focus(),
78
+ blur: () => inputRef.current?.blur(),
79
+ changeText: (text: string) => _onChangeText(text),
75
80
  setText: _onChangeText,
76
81
  };
77
82
  });
@@ -177,7 +182,11 @@ const InputMoney = forwardRef(
177
182
  }}>
178
183
  <View style={[style, styles.wrapper]}>
179
184
  {renderInputView()}
180
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
185
+ <ErrorView
186
+ errorMessage={errorMessage}
187
+ errorSpacing={errorSpacing}
188
+ hintText={hintText}
189
+ />
181
190
  </View>
182
191
  </ComponentContext.Provider>
183
192
  );
@@ -15,12 +15,12 @@ import {
15
15
  TouchableOpacity,
16
16
  View,
17
17
  } from 'react-native';
18
- import {CaretProps, InputOTPProps} from './index';
19
- import {scaleSize, Text} from '../Text';
20
- import styles from './styles';
21
- import {ErrorView, getBorderColor} from './common';
22
18
  import {ApplicationContext, ComponentContext} from '../Application';
23
19
  import {Spacing} from '../Consts';
20
+ import {scaleSize, Text} from '../Text';
21
+ import {ErrorView, getBorderColor} from './common';
22
+ import {CaretProps, InputOTPProps} from './index';
23
+ import styles from './styles';
24
24
 
25
25
  const OTPCaret: FC<CaretProps> = ({index, length}) => {
26
26
  const DURATION = 300;
@@ -81,6 +81,7 @@ const InputOTP = forwardRef(
81
81
  params,
82
82
  style,
83
83
  accessibilityLabel,
84
+ hintText,
84
85
  ...props
85
86
  }: InputOTPProps,
86
87
  ref,
@@ -88,13 +89,16 @@ const InputOTP = forwardRef(
88
89
  const MAX_LENGTH = 10;
89
90
  const [value, setValue] = useState('');
90
91
  const [focused, setFocused] = useState(false);
91
- const inputRef = useRef<TextInput>(null);
92
+ const inputRef = useRef<TextInput | null>(null);
92
93
  const {theme} = useContext(ApplicationContext);
93
94
 
94
95
  useImperativeHandle(ref, () => ({
95
96
  onChangeText: (text: string) => {
96
97
  _onChangeText(text);
97
98
  },
99
+ focus: () => inputRef.current?.focus(),
100
+ blur: () => inputRef.current?.blur(),
101
+ setText: (text: string) => _onChangeText(text),
98
102
  }));
99
103
 
100
104
  const onFocusInput = () => {
@@ -239,7 +243,11 @@ const InputOTP = forwardRef(
239
243
  onPress={onFocusInput}
240
244
  style={[style, styles.otpWrapper]}>
241
245
  {renderInputView()}
242
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
246
+ <ErrorView
247
+ errorMessage={errorMessage}
248
+ errorSpacing={errorSpacing}
249
+ hintText={hintText}
250
+ />
243
251
  </TouchableOpacity>
244
252
  </ComponentContext.Provider>
245
253
  );
@@ -1,4 +1,10 @@
1
- import React, {forwardRef, useContext, useRef, useState} from 'react';
1
+ import React, {
2
+ forwardRef,
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import {
3
9
  NativeSyntheticEvent,
4
10
  TextInput,
@@ -6,11 +12,11 @@ import {
6
12
  TouchableOpacity,
7
13
  View,
8
14
  } from 'react-native';
9
- import {InputSearchProps} from './index';
10
15
  import {ApplicationContext, ComponentContext} from '../Application';
11
- import styles from './styles';
12
16
  import {Icon} from '../Icon';
13
17
  import {Text} from '../Text';
18
+ import {InputSearchProps} from './index';
19
+ import styles from './styles';
14
20
  import {checkTyping} from './utils';
15
21
 
16
22
  const InputSearch = forwardRef(
@@ -38,7 +44,7 @@ const InputSearch = forwardRef(
38
44
  const {theme} = useContext(ApplicationContext);
39
45
  const [focused, setFocused] = useState(false);
40
46
  const [haveValue, setHaveValue] = useState(!!value || !!defaultValue);
41
- const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
47
+ const inputRef = useRef<TextInput | null>(null);
42
48
 
43
49
  const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
44
50
  setFocused(true);
@@ -60,6 +66,15 @@ const InputSearch = forwardRef(
60
66
  onChangeText?.(text);
61
67
  };
62
68
 
69
+ useImperativeHandle(ref, () => {
70
+ return {
71
+ clear: onClearText,
72
+ focus: () => inputRef.current?.focus(),
73
+ blur: () => inputRef.current?.blur(),
74
+ setText: (text: string) => _onChangeText(text),
75
+ };
76
+ });
77
+
63
78
  const renderInputView = () => {
64
79
  return (
65
80
  <TextInput
@@ -1,4 +1,10 @@
1
- import React, {forwardRef, useContext, useRef, useState} from 'react';
1
+ import React, {
2
+ forwardRef,
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import {
3
9
  NativeSyntheticEvent,
4
10
  TextInput,
@@ -6,7 +12,8 @@ import {
6
12
  TouchableOpacity,
7
13
  View,
8
14
  } from 'react-native';
9
- import styles from './styles';
15
+ import {ApplicationContext} from '../Application';
16
+ import {Icon} from '../Icon';
10
17
  import {Text} from '../Text';
11
18
  import {
12
19
  DEFAULT_HEIGHT,
@@ -16,8 +23,7 @@ import {
16
23
  MAX_LENGTH,
17
24
  } from './common';
18
25
  import {InputTextAreaProps} from './index';
19
- import {Icon} from '../Icon';
20
- import {ApplicationContext} from '../Application';
26
+ import styles from './styles';
21
27
 
22
28
  const InputTextArea = forwardRef(
23
29
  (
@@ -39,6 +45,7 @@ const InputTextArea = forwardRef(
39
45
  errorSpacing,
40
46
  style,
41
47
  accessibilityLabel,
48
+ hintText,
42
49
  ...props
43
50
  }: InputTextAreaProps,
44
51
  ref,
@@ -46,7 +53,7 @@ const InputTextArea = forwardRef(
46
53
  const {theme} = useContext(ApplicationContext);
47
54
  const [focused, setFocused] = useState(false);
48
55
  const [inputValue, setInputValue] = useState(defaultValue ?? '');
49
- const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
56
+ const inputRef = useRef<TextInput | null>(null);
50
57
 
51
58
  const onClearText = () => {
52
59
  inputRef?.current?.clear();
@@ -68,6 +75,15 @@ const InputTextArea = forwardRef(
68
75
  onBlur?.(e);
69
76
  };
70
77
 
78
+ useImperativeHandle(ref, () => {
79
+ return {
80
+ clear: onClearText,
81
+ focus: () => inputRef.current?.focus(),
82
+ blur: () => inputRef.current?.blur(),
83
+ setText: (text: string) => _onChangeText(text),
84
+ };
85
+ });
86
+
71
87
  const renderCountingView = () => {
72
88
  return (
73
89
  <View style={styles.countingView}>
@@ -151,7 +167,11 @@ const InputTextArea = forwardRef(
151
167
  return (
152
168
  <View style={[style, styles.wrapper]}>
153
169
  {renderInputView()}
154
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
170
+ <ErrorView
171
+ errorMessage={errorMessage}
172
+ errorSpacing={errorSpacing}
173
+ hintText={hintText}
174
+ />
155
175
  </View>
156
176
  );
157
177
  },
package/Input/common.tsx CHANGED
@@ -1,11 +1,11 @@
1
- import {View, ViewStyle} from 'react-native';
2
- import styles from './styles';
3
- import {scaleSize, Text} from '../Text';
4
1
  import React, {FC, useContext} from 'react';
2
+ import {View, ViewStyle} from 'react-native';
5
3
  import {ApplicationContext} from '../Application';
6
- import {Icon} from '../Icon';
7
- import {Styles} from '../Consts';
8
4
  import {Theme} from '../Application/types';
5
+ import {Styles} from '../Consts';
6
+ import {Icon} from '../Icon';
7
+ import {scaleSize, Text} from '../Text';
8
+ import styles from './styles';
9
9
 
10
10
  type FloatingViewProps = {
11
11
  floatingValue?: string;
@@ -16,6 +16,13 @@ type FloatingViewProps = {
16
16
  style?: ViewStyle;
17
17
  };
18
18
 
19
+ export interface InputRef {
20
+ focus: () => void;
21
+ blur: () => void;
22
+ clear: () => void;
23
+ setText: (text: string) => void;
24
+ }
25
+
19
26
  export const DEFAULT_HEIGHT = scaleSize(104);
20
27
  export const MAX_LENGTH = 300;
21
28
 
@@ -48,24 +55,31 @@ export const getSizeStyle = (size?: 'small' | 'large') => {
48
55
  }
49
56
  return styles.container;
50
57
  };
51
- export const ErrorView: FC<{errorMessage?: string; errorSpacing?: boolean}> = ({
52
- errorMessage,
53
- errorSpacing,
54
- }) => {
58
+ export const ErrorView: FC<{
59
+ errorMessage?: string;
60
+ errorSpacing?: boolean;
61
+ hintText?: string;
62
+ }> = ({errorMessage, errorSpacing, hintText}) => {
55
63
  const {theme} = useContext(ApplicationContext);
56
64
  const errorColor = theme.colors.error.primary;
57
- if (errorMessage) {
65
+ const hintColor = theme.colors.text.hint;
66
+ const hintTextDefault = hintText ?? 'Không thể chỉnh sửa';
67
+ if (errorMessage || hintText) {
58
68
  return (
59
69
  <View style={styles.errorView}>
60
70
  <View style={styles.errorIcon}>
61
- <Icon color={errorColor} source="ic_error" size={16} />
71
+ <Icon
72
+ color={errorMessage ? errorColor : hintColor}
73
+ source="ic_error"
74
+ size={16}
75
+ />
62
76
  </View>
63
77
  <Text
64
78
  style={Styles.flex}
65
- color={errorColor}
79
+ color={errorMessage ? errorColor : hintColor}
66
80
  numberOfLines={2}
67
81
  typography={'description_default_regular'}>
68
- {errorMessage}
82
+ {errorMessage ?? hintTextDefault}
69
83
  </Text>
70
84
  </View>
71
85
  );
package/Input/index.tsx CHANGED
@@ -1,10 +1,10 @@
1
+ import {TextInputProps, ViewStyle} from 'react-native';
1
2
  import Input from './Input';
2
- import InputTextArea from './InputTextArea';
3
- import InputSearch from './InputSearch';
3
+ import InputDropDown from './InputDropDown';
4
4
  import InputMoney from './InputMoney';
5
5
  import InputOTP from './InputOTP';
6
- import InputDropDown from './InputDropDown';
7
- import {TextInputProps, ViewStyle} from 'react-native';
6
+ import InputSearch from './InputSearch';
7
+ import InputTextArea from './InputTextArea';
8
8
 
9
9
  export interface InputProps extends TextInputProps {
10
10
  /**
@@ -77,11 +77,25 @@ export interface InputProps extends TextInputProps {
77
77
  */
78
78
  leadingIconColor?: string;
79
79
 
80
+ /**
81
+ * Optional. Represents the font weight of the text in the Input component.
82
+ */
80
83
  fontWeight?: 'Regular' | 'Bold';
81
84
 
85
+ /**
86
+ * Optional. callback function to be called when the icon is pressed.
87
+ */
82
88
  onPressIcon?: () => void;
83
89
 
90
+ /**
91
+ * Optional. params for element tracking.
92
+ */
84
93
  params?: any;
94
+
95
+ /**
96
+ * Optional. Represents text below the Input component.
97
+ */
98
+ hintText?: string;
85
99
  }
86
100
 
87
101
  type InputPropsWithoutSizeAndIcon = Omit<
@@ -267,4 +281,4 @@ export interface InputDropDownProps extends InputProps {
267
281
  style?: ViewStyle | ViewStyle[];
268
282
  }
269
283
 
270
- export {Input, InputTextArea, InputSearch, InputMoney, InputOTP, InputDropDown};
284
+ export {Input, InputDropDown, InputMoney, InputOTP, InputSearch, InputTextArea};
@@ -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
@@ -1,3 +1,12 @@
1
+ import {useHeaderHeight} from '@react-navigation/stack';
2
+ import React, {
3
+ forwardRef,
4
+ Fragment,
5
+ ReactNode,
6
+ useContext,
7
+ useEffect,
8
+ useRef,
9
+ } from 'react';
1
10
  import {
2
11
  Animated,
3
12
  KeyboardAvoidingView,
@@ -8,27 +17,19 @@ import {
8
17
  View,
9
18
  ViewProps,
10
19
  } from 'react-native';
11
- import {useHeaderHeight} from '@react-navigation/stack';
12
20
  import {useSafeAreaInsets} from 'react-native-safe-area-context';
13
- import React, {
14
- forwardRef,
15
- Fragment,
16
- ReactNode,
17
- useContext,
18
- useEffect,
19
- useRef,
20
- } from 'react';
21
- import {Colors, Spacing, Styles} from '../Consts';
22
- import {Card, Section, validateChildren} from './index';
23
21
  import {
24
22
  ApplicationContext,
25
23
  HeaderBackground,
26
24
  HeaderTitle,
27
25
  } from '../Application';
28
26
  import Navigation from '../Application/Navigation';
29
- import styles from './styles';
30
27
  import {AnimatedHeader, NavigationOptions} from '../Application/types';
28
+ import {Colors, Spacing, Styles} from '../Consts';
29
+ import {FloatingButton, FloatingButtonProps} from './FloatingButton';
31
30
  import {Image} from '../Image';
31
+ import {Card, Section, validateChildren} from './index';
32
+ import styles from './styles';
32
33
  import {HeaderType} from './types';
33
34
 
34
35
  export interface ScreenProps extends ViewProps {
@@ -82,6 +83,16 @@ export interface ScreenProps extends ViewProps {
82
83
  * Optional. layout card offset overlap header banner.
83
84
  */
84
85
  layoutOffset?: -8 | -24 | -56;
86
+
87
+ /**
88
+ * Optional. Props for the floating action button.
89
+ */
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;
85
96
  }
86
97
 
87
98
  const Screen = forwardRef(
@@ -98,6 +109,8 @@ const Screen = forwardRef(
98
109
  layoutOffset = -56,
99
110
  backgroundColor,
100
111
  headerType = 'default',
112
+ floatingButtonProps,
113
+ useGridLayout = true,
101
114
  }: ScreenProps,
102
115
  ref,
103
116
  ) => {
@@ -106,12 +119,13 @@ const Screen = forwardRef(
106
119
  const insets = useSafeAreaInsets();
107
120
  const heightHeader = useHeaderHeight();
108
121
  const currentTint = useRef(Colors.black_01);
122
+ const isTab = navigation?.instance?.getState?.()?.type == 'tab';
123
+
109
124
  let styleAnimatedHeader: NavigationOptions;
110
125
  let handleScroll;
111
126
  let headerBackground: string | undefined = undefined;
112
127
  let Component: any = View;
113
128
  let keyboardVerticalOffset = heightHeader - 20;
114
- animatedValue.current = undefined;
115
129
 
116
130
  if (headerType == 'extended' || animatedHeader) {
117
131
  keyboardVerticalOffset = -20;
@@ -155,7 +169,7 @@ const Screen = forwardRef(
155
169
 
156
170
  useEffect(() => {
157
171
  if (!animatedHeader) {
158
- let headerOptions: NavigationOptions = {};
172
+ let headerOptions: NavigationOptions;
159
173
  switch (headerType) {
160
174
  case 'none':
161
175
  headerOptions = {
@@ -213,8 +227,9 @@ const Screen = forwardRef(
213
227
  if (scrollable) {
214
228
  Component = Animated.ScrollView;
215
229
  handleScroll = scrollViewProps?.onScroll;
216
- if (animatedHeader) {
230
+ if (animatedHeader || floatingButtonProps) {
217
231
  animatedValue.current = new Animated.Value(0);
232
+
218
233
  handleScroll = Animated.event(
219
234
  [
220
235
  {
@@ -224,9 +239,9 @@ const Screen = forwardRef(
224
239
  },
225
240
  ],
226
241
  {
227
- useNativeDriver: true,
242
+ useNativeDriver: false,
228
243
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
229
- if (animatedHeader.type == 'surface') {
244
+ if (animatedHeader?.type == 'surface') {
230
245
  const offsetY = e.nativeEvent.contentOffset.y;
231
246
  let color = Colors.black_01;
232
247
  if (offsetY > 50) {
@@ -239,7 +254,6 @@ const Screen = forwardRef(
239
254
  });
240
255
  }
241
256
  }
242
- scrollViewProps?.onScroll?.(e);
243
257
  },
244
258
  },
245
259
  );
@@ -322,6 +336,7 @@ const Screen = forwardRef(
322
336
  android: undefined,
323
337
  })}>
324
338
  {Header}
339
+
325
340
  <Component
326
341
  {...scrollViewProps}
327
342
  ref={ref}
@@ -332,12 +347,25 @@ const Screen = forwardRef(
332
347
  style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
333
348
  {renderHeader()}
334
349
  </View>
335
- {renderContent(children)}
350
+ {useGridLayout ? renderContent(children) : children}
336
351
  </Component>
352
+
353
+ {floatingButtonProps && (
354
+ <View>
355
+ <FloatingButton
356
+ {...floatingButtonProps}
357
+ animatedValue={animatedValue.current}
358
+ bottom={
359
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
360
+ }
361
+ />
362
+ </View>
363
+ )}
364
+
337
365
  {Footer && (
338
366
  <View
339
367
  style={[
340
- styles.footerContainer,
368
+ styles.shadow,
341
369
  {
342
370
  paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
343
371
  backgroundColor: theme.colors.background.surface,
package/Layout/styles.ts CHANGED
@@ -80,17 +80,21 @@ export default StyleSheet.create({
80
80
  paddingHorizontal: undefined,
81
81
  paddingVertical: undefined,
82
82
  },
83
- footerContainer: {
83
+ shadow: {
84
84
  paddingTop: Spacing.S,
85
85
  ...Platform.select({
86
- android: {
87
- elevation: 4,
88
- },
89
86
  ios: {
87
+ shadowColor: Colors.black_17,
88
+ shadowOffset: {
89
+ width: 2,
90
+ height: 2,
91
+ },
92
+ shadowOpacity: 0.25,
93
+ shadowRadius: 10,
94
+ },
95
+ android: {
90
96
  shadowColor: Colors.black_20,
91
- shadowOffset: {width: 0, height: -2},
92
- shadowOpacity: 0.1,
93
- shadowRadius: 3,
97
+ elevation: 24,
94
98
  },
95
99
  }),
96
100
  },
package/Layout/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {Dimensions} from 'react-native';
2
1
  import React, {Fragment, ReactElement, useEffect, useState} from 'react';
2
+ import {Dimensions} from 'react-native';
3
3
 
4
4
  /**
5
5
  * validate children type
@@ -52,4 +52,4 @@ const useGridSystem = () => {
52
52
  return {numberOfColumns};
53
53
  };
54
54
 
55
- export {validateChildren, useGridSystem};
55
+ export {useGridSystem, validateChildren};
@@ -1,12 +1,12 @@
1
1
  import React, {useContext, useEffect, useRef, useState} from 'react';
2
2
  import {ScrollView, StyleSheet, TouchableOpacity, View} from 'react-native';
3
- import {PopupNotifyProps} from './types';
4
- import {Radius, Spacing, Styles} from '../Consts';
5
- import {Image} from '../Image';
6
- import {Text} from '../Text';
7
3
  import {ApplicationContext, MiniAppContext} from '../Application';
8
4
  import {Button} from '../Button';
5
+ import {Radius, Spacing, Styles} from '../Consts';
9
6
  import {Icon} from '../Icon';
7
+ import {Image} from '../Image';
8
+ import {Text} from '../Text';
9
+ import {PopupNotifyProps} from './types';
10
10
 
11
11
  const PopupNotify: React.FC<PopupNotifyProps> = ({
12
12
  id,
@@ -23,14 +23,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
23
23
  const {theme, navigator, translate} = useContext(ApplicationContext);
24
24
  const [scrollContent, setScrollContent] = useState(false);
25
25
  const closeAction = useRef('touch_outside');
26
-
27
26
  let descriptionStyle = {};
28
27
  let Description: any = View;
29
28
  if (scrollContent) {
30
29
  Description = ScrollView;
31
30
  descriptionStyle = {height: 80};
32
31
  }
33
-
34
32
  /**
35
33
  * tracking
36
34
  */
@@ -160,30 +158,32 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
160
158
  * build close action
161
159
  */
162
160
  const buildCloseIcon = () => {
163
- return (
164
- <TouchableOpacity
165
- accessibilityLabel={'ic_popup_close'}
166
- style={styles.iconCloseContainer}
167
- onPress={() => {
168
- closeAction.current = 'icon_close';
169
- onAction(onIconClose);
170
- }}>
171
- <View
172
- style={[
173
- styles.iconClose,
174
- {
175
- backgroundColor: theme.colors.text.default,
176
- borderColor: theme.colors.background.surface,
177
- },
178
- ]}>
179
- <Icon
180
- source="navigation_close"
181
- color={theme.colors.background.surface}
182
- size={14}
183
- />
184
- </View>
185
- </TouchableOpacity>
186
- );
161
+ if (typeof onIconClose === 'function') {
162
+ return (
163
+ <TouchableOpacity
164
+ accessibilityLabel={'ic_popup_close'}
165
+ style={styles.iconCloseContainer}
166
+ onPress={() => {
167
+ closeAction.current = 'icon_close';
168
+ onAction(onIconClose);
169
+ }}>
170
+ <View
171
+ style={[
172
+ styles.iconClose,
173
+ {
174
+ backgroundColor: theme.colors.text.default,
175
+ borderColor: theme.colors.background.surface,
176
+ },
177
+ ]}>
178
+ <Icon
179
+ source="navigation_close"
180
+ color={theme.colors.background.surface}
181
+ size={14}
182
+ />
183
+ </View>
184
+ </TouchableOpacity>
185
+ );
186
+ }
187
187
  };
188
188
 
189
189
  return (
package/Popup/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import {TextStyle} from 'react-native';
2
1
  import React from 'react';
3
2
 
4
3
  export type PopupNotifyProps = {
@@ -79,9 +78,4 @@ type PopupAction = {
79
78
  onPress: () => void;
80
79
  };
81
80
 
82
- export type HighlightText = {
83
- text: string;
84
- style: TextStyle;
85
- };
86
-
87
81
  type PopupActionDirection = 'row' | 'column' | 'auto';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.6-rc.1",
3
+ "version": "0.92.6-rc.4",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {