@momo-kits/foundation 0.92.6-rc.1 → 0.92.6-rc.10

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.
@@ -10,7 +10,7 @@ import {
10
10
  import {Modalize} from 'react-native-modalize';
11
11
  import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
12
12
 
13
- import {ApplicationContext} from '.';
13
+ import {ApplicationContext} from './index';
14
14
  import {Radius, Spacing, Styles} from '../Consts';
15
15
  import {Icon} from '../Icon';
16
16
  import {Text} from '../Text';
@@ -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/Image/index.tsx CHANGED
@@ -2,7 +2,11 @@ import React, {useContext, useEffect, useRef, useState} from 'react';
2
2
  import {StyleSheet, View} from 'react-native';
3
3
  import FastImage, {Source} from 'react-native-fast-image';
4
4
  import styles from './styles';
5
- import {ApplicationContext, ComponentContext, MiniAppContext} from '../Application';
5
+ import {
6
+ ApplicationContext,
7
+ ComponentContext,
8
+ MiniAppContext,
9
+ } from '../Application';
6
10
  import {Skeleton} from '../Skeleton';
7
11
  import {Icon} from '../Icon';
8
12
  import {Styles} from '../Consts';
@@ -30,9 +34,9 @@ const Image: React.FC<ImageProps> = ({
30
34
  };
31
35
 
32
36
  useEffect(() => {
33
- if(app.enableAutoId){
37
+ if (app.enableAutoId) {
34
38
  component?.addChildren?.(test.accessibilityLabel, 'add');
35
- return () => component?.addChildren(test.accessibilityLabel, 'remove');
39
+ return () => component?.addChildren?.(test.accessibilityLabel, 'remove');
36
40
  }
37
41
  }, []);
38
42
 
@@ -57,37 +61,37 @@ const Image: React.FC<ImageProps> = ({
57
61
  };
58
62
 
59
63
  return (
60
- <FastImage
61
- {...rest}
62
- {...test}
63
- source={source}
64
- style={[styles.container, style]}
65
- onProgress={() => {
66
- error.current = false;
67
- if (status !== 'loading' && loading) {
68
- setStatus('loading');
69
- }
70
- }}
71
- onLoadStart={() => {
72
- error.current = false;
73
- }}
74
- onLoad={() => {
75
- error.current = false;
76
- }}
77
- onLoadEnd={() => {
78
- let current: Status = 'success';
79
- if (error.current) {
80
- current = 'error';
81
- }
82
- if (status !== current) {
83
- setStatus(current);
84
- }
85
- }}
86
- onError={() => {
87
- error.current = true;
88
- }}>
89
- {renderContent()}
90
- </FastImage>
64
+ <FastImage
65
+ {...rest}
66
+ {...test}
67
+ source={source}
68
+ style={[styles.container, style]}
69
+ onProgress={() => {
70
+ error.current = false;
71
+ if (status !== 'loading' && loading) {
72
+ setStatus('loading');
73
+ }
74
+ }}
75
+ onLoadStart={() => {
76
+ error.current = false;
77
+ }}
78
+ onLoad={() => {
79
+ error.current = false;
80
+ }}
81
+ onLoadEnd={() => {
82
+ let current: Status = 'success';
83
+ if (error.current) {
84
+ current = 'error';
85
+ }
86
+ if (status !== current) {
87
+ setStatus(current);
88
+ }
89
+ }}
90
+ onError={() => {
91
+ error.current = true;
92
+ }}>
93
+ {renderContent()}
94
+ </FastImage>
91
95
  );
92
96
  };
93
97
 
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,7 +48,9 @@ const Input = forwardRef(
42
48
  keyboardType,
43
49
  style,
44
50
  params,
51
+ hintText,
45
52
  accessibilityLabel,
53
+ editable = true,
46
54
  ...props
47
55
  }: InputProps,
48
56
  ref,
@@ -51,7 +59,7 @@ const Input = forwardRef(
51
59
  const [focused, setFocused] = useState(false);
52
60
  const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
53
61
  const [secureTextInput, setSecureTextInput] = useState(secureTextEntry);
54
- const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
62
+ const inputRef = useRef<TextInput | null>(null);
55
63
 
56
64
  const onClearText = () => {
57
65
  inputRef?.current?.clear();
@@ -73,6 +81,15 @@ const Input = forwardRef(
73
81
  onBlur?.(e);
74
82
  };
75
83
 
84
+ useImperativeHandle(ref, () => {
85
+ return {
86
+ clear: onClearText,
87
+ focus: () => inputRef.current?.focus(),
88
+ blur: () => inputRef.current?.blur(),
89
+ setText: (text: string) => _onChangeText(text),
90
+ };
91
+ });
92
+
76
93
  const renderIcon = (color: string | undefined) => {
77
94
  if (loading) {
78
95
  return <Loader type={'spinner'} color={color} style={styles.icon} />;
@@ -152,7 +169,7 @@ const Input = forwardRef(
152
169
  <TextInput
153
170
  {...props}
154
171
  accessibilityLabel={accessibilityLabel}
155
- editable={props.editable && !disabled}
172
+ editable={editable && !disabled}
156
173
  secureTextEntry={secure}
157
174
  textAlignVertical="center"
158
175
  ref={inputRef}
@@ -217,7 +234,11 @@ const Input = forwardRef(
217
234
  }}>
218
235
  <View style={[style, styles.wrapper]}>
219
236
  {renderInputView()}
220
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
237
+ <ErrorView
238
+ errorMessage={errorMessage}
239
+ errorSpacing={errorSpacing}
240
+ hintText={hintText}
241
+ />
221
242
  </View>
222
243
  </ComponentContext.Provider>
223
244
  );
@@ -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,20 +109,23 @@ 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
  ) => {
104
- const animatedValue = useRef<Animated.Value | undefined>(undefined);
117
+ const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
105
118
  const {theme} = useContext(ApplicationContext);
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 = {
@@ -176,12 +190,9 @@ const Screen = forwardRef(
176
190
  image={null}
177
191
  backgroundColor={theme.colors.background.surface}
178
192
  useBorder={true}
179
- animatedValue={animatedValue.current}
180
193
  />
181
194
  ),
182
- headerTitle: (props: any) => (
183
- <HeaderTitle {...props} animatedValue={animatedValue.current} />
184
- ),
195
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
185
196
  };
186
197
  break;
187
198
 
@@ -213,8 +224,7 @@ const Screen = forwardRef(
213
224
  if (scrollable) {
214
225
  Component = Animated.ScrollView;
215
226
  handleScroll = scrollViewProps?.onScroll;
216
- if (animatedHeader) {
217
- animatedValue.current = new Animated.Value(0);
227
+ if (animatedHeader || floatingButtonProps) {
218
228
  handleScroll = Animated.event(
219
229
  [
220
230
  {
@@ -224,9 +234,9 @@ const Screen = forwardRef(
224
234
  },
225
235
  ],
226
236
  {
227
- useNativeDriver: true,
237
+ useNativeDriver: false,
228
238
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
229
- if (animatedHeader.type == 'surface') {
239
+ if (animatedHeader?.type == 'surface') {
230
240
  const offsetY = e.nativeEvent.contentOffset.y;
231
241
  let color = Colors.black_01;
232
242
  if (offsetY > 50) {
@@ -239,7 +249,6 @@ const Screen = forwardRef(
239
249
  });
240
250
  }
241
251
  }
242
- scrollViewProps?.onScroll?.(e);
243
252
  },
244
253
  },
245
254
  );
@@ -322,6 +331,7 @@ const Screen = forwardRef(
322
331
  android: undefined,
323
332
  })}>
324
333
  {Header}
334
+
325
335
  <Component
326
336
  {...scrollViewProps}
327
337
  ref={ref}
@@ -332,12 +342,25 @@ const Screen = forwardRef(
332
342
  style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
333
343
  {renderHeader()}
334
344
  </View>
335
- {renderContent(children)}
345
+ {useGridLayout ? renderContent(children) : children}
336
346
  </Component>
347
+
348
+ {floatingButtonProps && (
349
+ <View>
350
+ <FloatingButton
351
+ {...floatingButtonProps}
352
+ animatedValue={animatedValue.current}
353
+ bottom={
354
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
355
+ }
356
+ />
357
+ </View>
358
+ )}
359
+
337
360
  {Footer && (
338
361
  <View
339
362
  style={[
340
- styles.footerContainer,
363
+ styles.shadow,
341
364
  {
342
365
  paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
343
366
  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,11 +1,9 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.6-rc.1",
3
+ "version": "0.92.6-rc.10",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
- "scripts": {
7
- "postinstall": "node verify.js"
8
- },
6
+ "scripts": {},
9
7
  "keywords": [
10
8
  "@momo-kits/foundation"
11
9
  ],