@momo-kits/foundation 1.0.10 → 1.0.12

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.
package/Assets/icon.json CHANGED
@@ -3934,5 +3934,14 @@
3934
3934
  },
3935
3935
  "ic_error": {
3936
3936
  "uri": "https://img.mservice.com.vn/app/img/kits/error_mess_icon.png"
3937
+ },
3938
+ "media_fail": {
3939
+ "uri": "https://static.momocdn.net/app/img/kits/media_fail.png"
3940
+ },
3941
+ "ic_checked": {
3942
+ "uri": "https://img.mservice.com.vn/app/img/kits/checked_ic.png"
3943
+ },
3944
+ "ic_minus": {
3945
+ "uri": "https://img.mservice.com.vn/app/img/kits/minus.png"
3937
3946
  }
3938
3947
  }
package/Button/index.tsx CHANGED
@@ -11,6 +11,7 @@ import {Typography} from '../Text/types';
11
11
  import {Colors} from '../Consts';
12
12
  import styles from './styles';
13
13
  import {Image} from '../Image';
14
+ import {Icon} from '../Icon';
14
15
 
15
16
  export interface ButtonProps extends TouchableOpacityProps {
16
17
  type?:
@@ -179,11 +180,7 @@ const Button: FC<ButtonProps> = ({
179
180
  styles.leading,
180
181
  {width: iconSize, height: iconSize, marginRight},
181
182
  ]}>
182
- <Image
183
- tintColor={color}
184
- source={{uri: iconLeft}}
185
- style={{width: iconSize, height: iconSize}}
186
- />
183
+ <Icon color={color} source={iconLeft} size={iconSize} />
187
184
  </View>
188
185
  );
189
186
  }
@@ -203,11 +200,7 @@ const Button: FC<ButtonProps> = ({
203
200
  styles.trailing,
204
201
  {width: iconSize, height: iconSize, marginLeft},
205
202
  ]}>
206
- <Image
207
- tintColor={color}
208
- source={{uri: iconRight}}
209
- style={{width: iconSize, height: iconSize}}
210
- />
203
+ <Icon color={color} source={iconRight} size={iconSize} />
211
204
  </View>
212
205
  );
213
206
  }
@@ -2,13 +2,10 @@ import React, {FC, useContext} from 'react';
2
2
  import {TouchableOpacity, View} from 'react-native';
3
3
  import {CheckBoxProps} from './types';
4
4
  import styles from './styles';
5
- import Image from 'react-native-fast-image';
6
5
  import {ApplicationContext} from '../Navigation';
7
6
  import {Text} from '../Text';
7
+ import {Icon} from '../Icon';
8
8
 
9
- const IC_INDETERMINATED = 'https://img.mservice.com.vn/app/img/kits/minus.png';
10
- const IC_CHECKED_DEFAULT =
11
- 'https://img.mservice.com.vn/app/img/kits/checked_ic.png';
12
9
  const CheckBox: FC<CheckBoxProps> = props => {
13
10
  const {theme} = useContext(ApplicationContext);
14
11
  const {value, disabled, onChange, style, label, indeterminated} = props;
@@ -21,11 +18,7 @@ const CheckBox: FC<CheckBoxProps> = props => {
21
18
  ? theme.colors.primary
22
19
  : theme.colors.background.surface;
23
20
 
24
- let iconSource = value ? IC_CHECKED_DEFAULT : undefined;
25
-
26
- if (indeterminated) {
27
- iconSource = IC_INDETERMINATED;
28
- }
21
+ let iconSource = indeterminated ? 'ic_minus' : 'ic_checked';
29
22
 
30
23
  if (disabled) {
31
24
  borderColor = haveValue
@@ -47,7 +40,13 @@ const CheckBox: FC<CheckBoxProps> = props => {
47
40
  disabled={disabled}
48
41
  style={[style, styles.container]}>
49
42
  <View style={[checkboxStyle, styles.checkbox]}>
50
- <Image style={styles.icon} source={{uri: iconSource}} />
43
+ {haveValue && (
44
+ <Icon
45
+ color={theme.colors.background.surface}
46
+ size={20}
47
+ source={iconSource}
48
+ />
49
+ )}
51
50
  </View>
52
51
  <Text typography={'description_default'}>{label}</Text>
53
52
  </TouchableOpacity>
@@ -12,5 +12,4 @@ export default StyleSheet.create({
12
12
  alignItems: 'center',
13
13
  },
14
14
  container: {flexDirection: 'row'},
15
- icon: {width: 20, height: 20},
16
15
  });
package/Image/index.tsx CHANGED
@@ -3,7 +3,7 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native';
3
3
  import FastImage from 'react-native-fast-image';
4
4
  import styles from './styles';
5
5
  import {ApplicationContext} from '../Navigation';
6
- import {ContentLoader} from '../ContentLoader';
6
+ import {Skeleton} from '../Skeleton';
7
7
  import {Icon} from '../Icon';
8
8
  import {Styles} from '../Consts';
9
9
  import {FastImagePropsWithoutStyle} from './types';
@@ -24,12 +24,12 @@ const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
24
24
  const renderContent = () => {
25
25
  if (loading || fail) {
26
26
  let content = (
27
- <ContentLoader style={[StyleSheet.absoluteFill, styles.image]} />
27
+ <Skeleton style={[StyleSheet.absoluteFill, styles.image]} />
28
28
  );
29
29
  if (fail) {
30
30
  content = (
31
31
  <View style={Styles.flexCenter}>
32
- <Icon source="error-outline" />
32
+ <Icon source="media_fail" color={theme.colors.text.disable} />
33
33
  </View>
34
34
  );
35
35
  }
package/Input/Input.tsx CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  import {ApplicationContext} from '../Navigation';
10
10
  import styles from './styles';
11
11
  import {Image} from '../Image';
12
- import {ErrorView, FloatingView, getBorderColor} from './common';
12
+ import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
13
13
  import {InputProps} from './index';
14
14
  import {Icon} from '../Icon';
15
15
 
@@ -36,17 +36,12 @@ const Input: FC<InputProps> = ({
36
36
 
37
37
  const onClearText = () => {
38
38
  inputRef?.current?.clear();
39
+ _onChangeText('');
39
40
  };
40
41
 
41
42
  const _onChangeText = (text: string) => {
42
43
  onChangeText?.(text);
43
44
  };
44
- const getSizeStyle = () => {
45
- if (size === 'small') {
46
- return styles.smallContainer;
47
- }
48
- return styles.container;
49
- };
50
45
 
51
46
  const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
52
47
  setFocused(true);
@@ -73,7 +68,7 @@ const Input: FC<InputProps> = ({
73
68
  return (
74
69
  <View
75
70
  style={[
76
- getSizeStyle(),
71
+ getSizeStyle(size),
77
72
  getBorderColor(focused, errorMessage, disabled),
78
73
  styles.inputWrapper,
79
74
  {backgroundColor: theme.colors.background.surface},
@@ -118,11 +113,7 @@ const Input: FC<InputProps> = ({
118
113
  </TouchableOpacity>
119
114
  )}
120
115
  {icon && (
121
- <Image
122
- tintColor={iconTintColor}
123
- source={{uri: icon}}
124
- style={styles.icon}
125
- />
116
+ <Icon icon={iconTintColor} source={icon} style={styles.icon} />
126
117
  )}
127
118
  </View>
128
119
  </View>
@@ -9,15 +9,16 @@ import {
9
9
  import {ApplicationContext} from '../Navigation';
10
10
  import styles from './styles';
11
11
  import {Image} from '../Image';
12
- import {ErrorView, FloatingView, getBorderColor} from './common';
13
- import {MoneyInputProps} from './index';
12
+ import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
13
+ import {InputMoneyProps} from './index';
14
14
  import {Icon} from '../Icon';
15
+ import {formatMoneyToNumber, formatNumberToMoney} from './utils';
15
16
 
16
- const MoneyInput: FC<MoneyInputProps> = ({
17
+ const InputMoney: FC<InputMoneyProps> = ({
17
18
  onChangeText,
18
19
  floatingValue,
19
20
  floatingIcon,
20
- size,
21
+ size = 'small',
21
22
  onBlur,
22
23
  onFocus,
23
24
  errorMessage,
@@ -30,29 +31,20 @@ const MoneyInput: FC<MoneyInputProps> = ({
30
31
  }) => {
31
32
  const {theme} = useContext(ApplicationContext);
32
33
  const [focused, setFocused] = useState(false);
33
- const [value, setValue] = useState('');
34
34
  const inputRef = useRef<any>(null);
35
-
35
+ const [value, setValue] = useState('');
36
36
  const onClearText = () => {
37
37
  inputRef?.current?.clear();
38
- };
39
-
40
- const formatNumberWithDots = (num: string) => {
41
- return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
38
+ setValue('');
39
+ onChangeText?.('');
42
40
  };
43
41
  const _onChangeText = (text: string) => {
44
- let numericValue = text.replace(/[^0-9]/g, '');
45
- if (numericValue === '') numericValue = '0';
46
- const formattedNumber = formatNumberWithDots(numericValue);
47
- const formattedValue = `${formattedNumber}đ`;
48
- onChangeText?.(formattedValue);
49
- setValue(formattedValue);
50
- };
51
- const getSizeStyle = () => {
52
- if (size === 'small') {
53
- return styles.smallContainer;
42
+ if (text.length < value.length && value.indexOf(text) === 0) {
43
+ text = value.slice(0, -2) + 'đ';
54
44
  }
55
- return styles.container;
45
+ const valueFormat = formatMoneyToNumber(text, 'đ');
46
+ setValue(formatNumberToMoney(valueFormat, 'đ'));
47
+ onChangeText?.(formatMoneyToNumber(text, 'đ').toString());
56
48
  };
57
49
 
58
50
  const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
@@ -80,7 +72,7 @@ const MoneyInput: FC<MoneyInputProps> = ({
80
72
  return (
81
73
  <View
82
74
  style={[
83
- getSizeStyle(),
75
+ getSizeStyle(size),
84
76
  getBorderColor(focused, errorMessage, disabled),
85
77
  styles.inputWrapper,
86
78
  {backgroundColor: theme.colors.background.surface},
@@ -124,13 +116,7 @@ const MoneyInput: FC<MoneyInputProps> = ({
124
116
  />
125
117
  </TouchableOpacity>
126
118
  )}
127
- {icon && (
128
- <Image
129
- tintColor={iconTintColor}
130
- source={{uri: icon}}
131
- style={styles.icon}
132
- />
133
- )}
119
+ {icon && <Icon color={iconTintColor} source={icon} />}
134
120
  </View>
135
121
  </View>
136
122
  );
@@ -144,8 +130,4 @@ const MoneyInput: FC<MoneyInputProps> = ({
144
130
  );
145
131
  };
146
132
 
147
- MoneyInput.defaultProps = {
148
- size: 'small',
149
- };
150
-
151
- export default MoneyInput;
133
+ export default InputMoney;
@@ -6,7 +6,7 @@ import {
6
6
  TouchableOpacity,
7
7
  View,
8
8
  } from 'react-native';
9
- import {SearchInputProps} from './index';
9
+ import {InputSearchProps} from './index';
10
10
  import {getBorderColor} from './common';
11
11
  import {ApplicationContext} from '../Navigation';
12
12
  import styles from './styles';
@@ -14,7 +14,7 @@ import {Icon} from '../Icon';
14
14
  import {Image} from '../Image';
15
15
  import {Text} from '../Text';
16
16
 
17
- const SearchInput: FC<SearchInputProps> = ({
17
+ const InputSearch: FC<InputSearchProps> = ({
18
18
  errorMessage,
19
19
  disabled,
20
20
  placeholder,
@@ -47,6 +47,7 @@ const SearchInput: FC<SearchInputProps> = ({
47
47
 
48
48
  const onClearText = () => {
49
49
  inputRef?.current?.clear();
50
+ _onChangeText('');
50
51
  };
51
52
 
52
53
  const _onChangeText = (text: string) => {
@@ -111,11 +112,7 @@ const SearchInput: FC<SearchInputProps> = ({
111
112
  },
112
113
  ]}
113
114
  />
114
- <Image
115
- tintColor={iconTintColor}
116
- source={{uri: icon}}
117
- style={styles.iconSearchInput}
118
- />
115
+ <Icon color={iconTintColor} source={icon} />
119
116
  </View>
120
117
  )}
121
118
  </View>
@@ -148,4 +145,4 @@ const SearchInput: FC<SearchInputProps> = ({
148
145
  );
149
146
  };
150
147
 
151
- export default SearchInput;
148
+ export default InputSearch;
@@ -16,10 +16,10 @@ import {
16
16
  getBorderColor,
17
17
  MAX_LENGTH,
18
18
  } from './common';
19
- import {TextAreaProps} from './index';
19
+ import {InputTextAreaProps} from './index';
20
20
  import {Icon} from '../Icon';
21
21
 
22
- const TextArea: FC<TextAreaProps> = props => {
22
+ const InputTextArea: FC<InputTextAreaProps> = props => {
23
23
  const {theme} = useContext(ApplicationContext);
24
24
  const {
25
25
  errorMessage,
@@ -44,6 +44,7 @@ const TextArea: FC<TextAreaProps> = props => {
44
44
 
45
45
  const onClearText = () => {
46
46
  inputRef?.current?.clear();
47
+ _onChangeText('');
47
48
  };
48
49
 
49
50
  const _onChangeText = (text: string) => {
@@ -84,7 +85,7 @@ const TextArea: FC<TextAreaProps> = props => {
84
85
  <View
85
86
  style={[
86
87
  getBorderColor(focused, errorMessage, disabled),
87
- styles.textAreaWradpper,
88
+ styles.textAreaWrapper,
88
89
  {
89
90
  height: height || DEFAULT_HEIGHT,
90
91
  backgroundColor: theme.colors.background.surface,
@@ -144,8 +145,8 @@ const TextArea: FC<TextAreaProps> = props => {
144
145
  );
145
146
  };
146
147
 
147
- TextArea.defaultProps = {
148
+ InputTextArea.defaultProps = {
148
149
  maxLength: MAX_LENGTH,
149
150
  };
150
151
 
151
- export default TextArea;
152
+ export default InputTextArea;
package/Input/common.tsx CHANGED
@@ -41,6 +41,12 @@ export const getBorderColor = (
41
41
  return {borderColor};
42
42
  };
43
43
 
44
+ export const getSizeStyle = (size?: 'small' | 'large') => {
45
+ if (size === 'small') {
46
+ return styles.smallContainer;
47
+ }
48
+ return styles.container;
49
+ };
44
50
  export const ErrorView: FC<{errorMessage?: string}> = ({errorMessage}) => {
45
51
  const {theme} = useContext(ApplicationContext);
46
52
  const errorColor = theme.colors.error.primary;
@@ -94,9 +100,10 @@ export const FloatingView: FC<FloatingViewProps> = ({
94
100
  )}
95
101
  </Text>
96
102
  {floatingIcon && (
97
- <Image
98
- tintColor={floatingIconTintColor}
99
- source={{uri: floatingIcon}}
103
+ <Icon
104
+ color={floatingIconTintColor}
105
+ source={floatingIcon}
106
+ size={16}
100
107
  style={styles.floatingIcon}
101
108
  />
102
109
  )}
package/Input/index.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import Input from './Input';
2
- import TextArea from './TextArea';
3
- import SearchInput from './SearchInput';
4
- import MoneyInput from './MoneyInput';
2
+ import InputTextArea from './InputTextArea';
3
+ import InputSearch from './InputSearch';
4
+ import InputMoney from './InputMoney';
5
5
  import {TextInputProps} from 'react-native';
6
6
 
7
7
  export interface InputProps extends TextInputProps {
@@ -23,16 +23,16 @@ type InputPropsWithoutSizeAndIcon = Omit<
23
23
  type InputPropsWithoutRequiredAndSize = Omit<InputProps, 'required' | 'size'>;
24
24
  type InputPropsWithoutPlaceholder = Omit<InputProps, 'placeholder'>;
25
25
 
26
- export interface TextAreaProps extends InputPropsWithoutSizeAndIcon {
26
+ export interface InputTextAreaProps extends InputPropsWithoutSizeAndIcon {
27
27
  height?: number;
28
28
  }
29
29
 
30
- export interface SearchInputProps extends InputPropsWithoutRequiredAndSize {
30
+ export interface InputSearchProps extends InputPropsWithoutRequiredAndSize {
31
31
  buttonText?: string;
32
32
  showButtonText?: boolean;
33
33
  showIcon?: boolean;
34
34
  }
35
35
 
36
- export interface MoneyInputProps extends InputPropsWithoutPlaceholder {}
36
+ export interface InputMoneyProps extends InputPropsWithoutPlaceholder {}
37
37
 
38
- export {Input, TextArea, SearchInput, MoneyInput};
38
+ export {Input, InputTextArea, InputSearch, InputMoney};
package/Input/styles.ts CHANGED
@@ -25,7 +25,7 @@ export default StyleSheet.create({
25
25
  paddingHorizontal: Spacing.S,
26
26
  flexDirection: 'row',
27
27
  },
28
- floatingIcon: {width: 16, height: 16, marginLeft: Spacing.XS},
28
+ floatingIcon: {marginLeft: Spacing.XS},
29
29
  errorIcon: {marginRight: Spacing.XS},
30
30
  errorView: {
31
31
  flexDirection: 'row',
package/Input/utils.ts ADDED
@@ -0,0 +1,63 @@
1
+ const formatNumberToMoney = (number: number, currency = '') => {
2
+ if (!number || isNaN(number) || Number(number) === 0) {
3
+ return `0${currency}`;
4
+ }
5
+
6
+ const array = [];
7
+ let result = '';
8
+ let isNegative = false;
9
+
10
+ if (number < 0) {
11
+ isNegative = true;
12
+ }
13
+
14
+ const numberString = Math.abs(number).toString();
15
+ if (numberString.length < 3) {
16
+ return numberString + currency;
17
+ }
18
+
19
+ let count = 0;
20
+ for (let i = numberString.length - 1; i >= 0; i -= 1) {
21
+ count += 1;
22
+ if (numberString[i] === '.' || numberString[i] === ',') {
23
+ array.push(',');
24
+ count = 0;
25
+ } else {
26
+ array.push(numberString[i]);
27
+ }
28
+ if (count === 3 && i >= 1) {
29
+ array.push('.');
30
+ count = 0;
31
+ }
32
+ }
33
+
34
+ for (let i = array.length - 1; i >= 0; i -= 1) {
35
+ result += array[i];
36
+ }
37
+
38
+ if (isNegative) {
39
+ result = `-${result}`;
40
+ }
41
+
42
+ return result + currency;
43
+ };
44
+
45
+ const formatMoneyToNumber = (money: string, currencyUnit: string) => {
46
+ if (money && money.length > 0) {
47
+ const moneyString = money
48
+ .replace(currencyUnit, '')
49
+ .replace(/,/g, '')
50
+ .replace(/đ/g, '')
51
+ .replace(/\./g, '')
52
+ .replace(/ /g, '');
53
+ const number = Number(moneyString);
54
+ if (isNaN(number)) {
55
+ return 0;
56
+ }
57
+ return number;
58
+ }
59
+
60
+ return Number(money);
61
+ };
62
+
63
+ export {formatMoneyToNumber, formatNumberToMoney};
@@ -11,6 +11,7 @@ const ScreenContainer: React.FC<ScreenContainerProps> = ({
11
11
  edges,
12
12
  enableKeyboardAvoidingView,
13
13
  scrollable,
14
+ scrollViewProps,
14
15
  }) => {
15
16
  let Component: any = View;
16
17
  const headerHeight = useHeaderHeight();
@@ -50,7 +51,9 @@ const ScreenContainer: React.FC<ScreenContainerProps> = ({
50
51
  ios: 'padding',
51
52
  android: undefined,
52
53
  })}>
53
- <Component style={Styles.flex}>{renderContent()}</Component>
54
+ <Component {...scrollViewProps} style={Styles.flex}>
55
+ {renderContent()}
56
+ </Component>
54
57
  </KeyboardAvoidingView>
55
58
  </SafeAreaView>
56
59
  );
@@ -0,0 +1,40 @@
1
+ import React, {FC} from 'react';
2
+ import {BottomTabProps} from './types';
3
+ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
4
+ import {Icon} from '../Icon';
5
+
6
+ const Tab = createBottomTabNavigator();
7
+
8
+ const BottomTab: FC<BottomTabProps> = ({tabs}) => {
9
+ return (
10
+ <Tab.Navigator>
11
+ {tabs.map((item, index) => {
12
+ return (
13
+ // @ts-ignore
14
+ <Tab.Screen
15
+ key={`${item.label}-${index}`}
16
+ name={item.label}
17
+ component={item.component}
18
+ options={{
19
+ tabBarLabel: item.label,
20
+ tabBarBadge: item?.badgeLabel,
21
+ tabBarBadgeStyle: {
22
+ borderColor: 'white',
23
+ borderWidth: 2,
24
+ fontSize: 10,
25
+ lineHeight: 14,
26
+ fontWeight: 'bold',
27
+ alignSelf: 'center',
28
+ },
29
+ tabBarIcon: ({color, size}) => (
30
+ <Icon source={item.icon} color={color} size={size} />
31
+ ),
32
+ }}
33
+ />
34
+ );
35
+ })}
36
+ </Tab.Navigator>
37
+ );
38
+ };
39
+
40
+ export default BottomTab;
@@ -88,19 +88,9 @@ const HeaderLeft: React.FC<any> = ({tintColor}) => {
88
88
  }
89
89
  };
90
90
 
91
- let backgroundColor;
92
- if (tintColor != Colors.black_01) {
93
- backgroundColor = theme.colors.background.surface;
94
- }
95
-
96
91
  return (
97
92
  <View style={styles.headerLeft}>
98
- <NavigationButton
99
- icon="ic_back"
100
- tintColor={tintColor}
101
- backgroundColor={backgroundColor}
102
- onPress={goBack}
103
- />
93
+ <NavigationButton icon="ic_back" tintColor={tintColor} onPress={goBack} />
104
94
  </View>
105
95
  );
106
96
  };
@@ -206,7 +196,6 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
206
196
  <NavigationButton
207
197
  icon="addFavorite"
208
198
  tintColor={tintColor}
209
- backgroundColor={backgroundColor}
210
199
  onPress={() => {}}
211
200
  />
212
201
  <View
@@ -121,7 +121,11 @@ const BottomSheet: React.FC<BottomSheetParams> = ({navigation, route}) => {
121
121
  <BottomSheetModal
122
122
  ref={bottomSheetRef}
123
123
  snapPoints={snapPoints}
124
- onDismiss={navigation.pop}
124
+ onDismiss={() => {
125
+ if (mountedRef.current) {
126
+ navigation.pop();
127
+ }
128
+ }}
125
129
  handleComponent={null}
126
130
  backdropComponent={backdropComponent}>
127
131
  <View style={{paddingBottom: bottom}}>
@@ -3,6 +3,7 @@ import React, {useContext} from 'react';
3
3
  import {NavigationButtonProps} from './types';
4
4
  import {ApplicationContext} from './index';
5
5
  import {Icon} from '../Icon';
6
+ import {Colors} from '../Consts';
6
7
 
7
8
  const NavigationButton: React.FC<NavigationButtonProps> = ({
8
9
  icon,
@@ -12,6 +13,10 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
12
13
  useBorder = true,
13
14
  }) => {
14
15
  const {theme} = useContext(ApplicationContext);
16
+ if (!backgroundColor && tintColor != Colors.black_01) {
17
+ backgroundColor = theme.colors.background.surface;
18
+ }
19
+
15
20
  return (
16
21
  <TouchableOpacity
17
22
  style={[
@@ -2,6 +2,7 @@ import {NavigationContainer, ApplicationContext} from './NavigationContainer';
2
2
  import ScreenContainer from '../Layout/ScreenContainer';
3
3
  import NavigationButton from './NavigationButton';
4
4
  import {HeaderRightAction} from './Components';
5
+ import BottomTab from './BottomTab';
5
6
 
6
7
  export {
7
8
  NavigationContainer,
@@ -9,4 +10,5 @@ export {
9
10
  ScreenContainer,
10
11
  NavigationButton,
11
12
  HeaderRightAction,
13
+ BottomTab,
12
14
  };
@@ -1,5 +1,5 @@
1
- import {ViewProps} from 'react-native';
2
- import React from 'react';
1
+ import {ScrollViewProps, ViewProps} from 'react-native';
2
+ import React, {ComponentType, ReactElement} from 'react';
3
3
  import Navigator from './Navigator';
4
4
 
5
5
  export type Theme = {
@@ -72,6 +72,7 @@ export interface ScreenContainerProps extends ViewProps {
72
72
  edges?: any[];
73
73
  enableKeyboardAvoidingView?: boolean;
74
74
  scrollable: boolean;
75
+ scrollViewProps?: ScrollViewProps;
75
76
  }
76
77
 
77
78
  export type ScreenParams = {
@@ -121,3 +122,15 @@ export type TitleCustomProps = {
121
122
  tintColor?: string;
122
123
  content?: React.ReactNode;
123
124
  };
125
+
126
+ export type BottomTabItemProps = {
127
+ label: string;
128
+ icon: string;
129
+ showDot?: boolean;
130
+ badgeLabel?: string;
131
+ component: ComponentType<any> | undefined;
132
+ };
133
+
134
+ export type BottomTabProps = {
135
+ tabs: BottomTabItemProps[];
136
+ };
@@ -1,21 +1,40 @@
1
1
  import React, {useContext} from 'react';
2
- import {StyleSheet, View} from 'react-native';
2
+ import {StyleSheet, TouchableOpacity, View} from 'react-native';
3
3
  import {PopupPromotionProps} from './types';
4
4
  import {ApplicationContext} from '../Navigation';
5
5
  import {Image} from '../Image';
6
- import {Button} from '../Button';
7
- import {Spacing} from '../Consts';
6
+ import {Radius} from '../Consts';
7
+ import {Icon} from '../Icon';
8
8
 
9
- const PopupPromotion: React.FC<PopupPromotionProps> = ({image, primary}) => {
9
+ const PopupPromotion: React.FC<PopupPromotionProps> = ({image, onClose}) => {
10
10
  const {theme, navigator} = useContext(ApplicationContext);
11
11
 
12
12
  /**
13
- * on action popup
14
- * @param callback
13
+ * build close action
15
14
  */
16
- const onAction = (callback?: () => void) => {
17
- navigator?.pop();
18
- callback?.();
15
+ const buildCloseIcon = () => {
16
+ return (
17
+ <View style={styles.iconCloseContainer}>
18
+ <TouchableOpacity
19
+ onPress={() => {
20
+ navigator?.pop();
21
+ onClose?.();
22
+ }}
23
+ style={[
24
+ styles.iconClose,
25
+ {
26
+ backgroundColor: theme.colors.text.default,
27
+ borderColor: theme.colors.background.surface,
28
+ },
29
+ ]}>
30
+ <Icon
31
+ source="navigation_close"
32
+ color={theme.colors.background.surface}
33
+ size={14}
34
+ />
35
+ </TouchableOpacity>
36
+ </View>
37
+ );
19
38
  };
20
39
 
21
40
  return (
@@ -26,37 +45,34 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({image, primary}) => {
26
45
  uri: image,
27
46
  }}
28
47
  />
29
- <View style={styles.actionContainer}>
30
- <Button
31
- title={primary?.title}
32
- onPress={() => {
33
- onAction(primary?.onPress);
34
- }}
35
- />
36
- </View>
48
+ {buildCloseIcon()}
37
49
  </>
38
50
  );
39
51
  };
40
52
 
41
53
  const styles = StyleSheet.create({
42
54
  container: {
43
- aspectRatio: 0.66,
55
+ aspectRatio: 0.72,
44
56
  },
45
- actionContainer: {
57
+ iconCloseContainer: {
46
58
  position: 'absolute',
47
- bottom: 0,
48
59
  width: '100%',
49
- paddingVertical: Spacing.XL,
50
- paddingHorizontal: Spacing.L,
60
+ flexDirection: 'row',
61
+ justifyContent: 'center',
62
+ bottom: -36,
63
+ },
64
+ iconClose: {
65
+ width: 20,
66
+ height: 20,
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ borderRadius: Radius.M,
70
+ borderWidth: 2,
51
71
  },
52
72
  });
53
73
 
54
74
  PopupPromotion.defaultProps = {
55
75
  image: 'https://google.com',
56
- primary: {
57
- title: 'Primary',
58
- onPress: () => {},
59
- },
60
76
  };
61
77
 
62
78
  export default PopupPromotion;
package/Popup/types.ts CHANGED
@@ -11,7 +11,7 @@ export type PopupNotifyProps = {
11
11
 
12
12
  export type PopupPromotionProps = {
13
13
  image: string;
14
- primary: PopupAction;
14
+ onClose?: () => void;
15
15
  };
16
16
 
17
17
  type PopupAction = {
package/Radio/index.tsx CHANGED
@@ -17,12 +17,24 @@ const Radio: FC<RadioProps> = ({
17
17
  }) => {
18
18
  const {theme} = useContext(ApplicationContext);
19
19
  const selected = value === groupValue;
20
- const borderWidth = selected ? 6 : 2;
21
- let borderColor = selected ? theme.colors.primary : theme.colors.text.default;
20
+ let disabledStyle = {};
21
+ let checkBoxStyle = {
22
+ borderWidth: 2,
23
+ borderColor: theme.colors.text.default,
24
+ };
25
+
26
+ if (selected) {
27
+ checkBoxStyle = {
28
+ borderWidth: 6,
29
+ borderColor: theme.colors.primary,
30
+ };
31
+ }
22
32
  if (disabled) {
23
- borderColor = selected
24
- ? theme.colors.background.tonal
25
- : theme.colors.text.disable;
33
+ disabledStyle = {
34
+ borderColor: selected
35
+ ? theme.colors.background.tonal
36
+ : theme.colors.text.disable,
37
+ };
26
38
  }
27
39
 
28
40
  return (
@@ -39,7 +51,9 @@ const Radio: FC<RadioProps> = ({
39
51
  <View
40
52
  style={[
41
53
  styles.radio,
42
- {borderWidth, borderColor, marginRight: !!label ? Spacing.XS : 0},
54
+ checkBoxStyle,
55
+ disabledStyle,
56
+ {marginRight: !!label ? Spacing.XS : 0},
43
57
  ]}
44
58
  />
45
59
  {!!label && <Text typography={'description_default'}>{label}</Text>}
@@ -0,0 +1,73 @@
1
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
2
+ import {Animated, Easing, Platform, StyleSheet, View} from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+ import {SkeletonTypes} from './types';
5
+ import {Styles} from '../Consts';
6
+ import styles from './styles';
7
+ import {Colors} from '@momo-kits/foundation';
8
+
9
+ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
10
+ const [width, setWidth] = useState(0);
11
+ const PRIMARY_COLOR = Colors.black_05;
12
+ const HIGHLIGHT_COLOR1 = Colors.black_05;
13
+ const HIGHLIGHT_COLOR2 = Colors.black_03;
14
+ const beginShimmerPosition = useRef(new Animated.Value(0)).current;
15
+
16
+ const shimmerColors = [HIGHLIGHT_COLOR1, HIGHLIGHT_COLOR2, HIGHLIGHT_COLOR1];
17
+ const linearTranslate = beginShimmerPosition.interpolate({
18
+ inputRange: [0, 1],
19
+ outputRange: [-width, width],
20
+ });
21
+ const animatedValue = useMemo(() => {
22
+ return Animated.loop(
23
+ Animated.timing(beginShimmerPosition, {
24
+ toValue: 1,
25
+ duration: 1000,
26
+ easing: Easing.linear,
27
+ useNativeDriver: Platform.OS !== 'web',
28
+ }),
29
+ );
30
+ }, [beginShimmerPosition]);
31
+
32
+ useEffect(() => {
33
+ animatedValue.start();
34
+ return () => {
35
+ animatedValue.stop();
36
+ };
37
+ }, [animatedValue]);
38
+
39
+ const onLayout = (newWidth: number) => {
40
+ if (newWidth !== width) {
41
+ setWidth(newWidth);
42
+ }
43
+ };
44
+ return (
45
+ <View style={[styles.container, style]}>
46
+ <View
47
+ onLayout={e => onLayout(e.nativeEvent.layout.width)}
48
+ style={[Styles.flex, {backgroundColor: PRIMARY_COLOR}]}>
49
+ <Animated.View
50
+ style={{
51
+ transform: [{translateX: linearTranslate}],
52
+ width: '60%',
53
+ height: '100%',
54
+ }}>
55
+ <LinearGradient
56
+ style={[StyleSheet.absoluteFill]}
57
+ colors={shimmerColors}
58
+ start={{
59
+ x: 0,
60
+ y: 0,
61
+ }}
62
+ end={{
63
+ x: 1,
64
+ y: 0,
65
+ }}
66
+ />
67
+ </Animated.View>
68
+ </View>
69
+ </View>
70
+ );
71
+ };
72
+
73
+ export {Skeleton};
@@ -0,0 +1,3 @@
1
+ export type SkeletonTypes = {
2
+ style?: object;
3
+ };
package/index.ts CHANGED
@@ -22,8 +22,8 @@ export * from './Icon/types';
22
22
  export * from './IconButton';
23
23
  export * from './Image';
24
24
  export * from './Image/types';
25
- export * from './ContentLoader';
26
- export * from './ContentLoader/types';
25
+ export * from './Skeleton';
26
+ export * from './Skeleton/types';
27
27
  export * from './CheckBox';
28
28
  export * from './CheckBox/types';
29
29
  export * from './Radio';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -1,67 +0,0 @@
1
- import React, {useContext, useEffect, useMemo, useRef} from 'react';
2
- import {Animated, Platform, useWindowDimensions, View} from 'react-native';
3
- import LinearGradient from 'react-native-linear-gradient';
4
- import {ContentLoaderTypes} from './types';
5
- import {ApplicationContext} from '../Navigation';
6
- import {Styles} from '../Consts';
7
- import styles from './styles';
8
- const ContentLoader: React.FC<ContentLoaderTypes> = ({style}) => {
9
- const {width} = useWindowDimensions();
10
- const {theme} = useContext(ApplicationContext);
11
- const beginShimmerPosition = useRef(new Animated.Value(-1)).current;
12
-
13
- const shimmerColors = [
14
- theme.colors.text.disable + '40',
15
- theme.colors.text.disable + '80',
16
- theme.colors.text.disable,
17
- ];
18
- const location = [0.3, 0.5, 0.7];
19
- const linearTranslate = beginShimmerPosition.interpolate({
20
- inputRange: [-1, 1],
21
- outputRange: [0, width],
22
- });
23
- const animatedValue = useMemo(() => {
24
- return Animated.loop(
25
- Animated.timing(beginShimmerPosition, {
26
- toValue: 1,
27
- delay: 0,
28
- duration: 1000,
29
- useNativeDriver: Platform.OS !== 'web',
30
- }),
31
- );
32
- }, [beginShimmerPosition]);
33
-
34
- useEffect(() => {
35
- animatedValue.start();
36
- return () => {
37
- animatedValue.stop();
38
- };
39
- }, [animatedValue]);
40
-
41
- return (
42
- <View style={[styles.container, style]}>
43
- <View style={[Styles.flex, {backgroundColor: shimmerColors[0]}]}>
44
- <Animated.View
45
- style={[Styles.flex, {transform: [{translateX: linearTranslate}]}]}>
46
- <LinearGradient
47
- colors={shimmerColors}
48
- style={[Styles.flex, {width: width}]}
49
- start={{
50
- x: -1,
51
- y: 0.5,
52
- }}
53
- end={{
54
- x: 2,
55
- y: 0.5,
56
- }}
57
- locations={location}
58
- />
59
- </Animated.View>
60
- </View>
61
- </View>
62
- );
63
- };
64
-
65
- ContentLoader.defaultProps = {};
66
-
67
- export {ContentLoader};
@@ -1,3 +0,0 @@
1
- export type ContentLoaderTypes = {
2
- style?: object;
3
- };
File without changes