@momo-kits/foundation 1.0.9 → 1.0.11
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/Image/index.tsx +2 -2
- package/Input/Input.tsx +6 -8
- package/Input/InputMoney.tsx +139 -0
- package/Input/{SearchInput.tsx → InputSearch.tsx} +4 -3
- package/Input/{TextArea.tsx → InputTextArea.tsx} +8 -4
- package/Input/common.tsx +13 -0
- package/Input/index.tsx +15 -8
- package/Input/styles.ts +8 -2
- package/Input/utils.ts +63 -0
- package/Layout/ScreenContainer.tsx +4 -1
- package/Navigation/BottomTab.tsx +41 -0
- package/Navigation/Components.tsx +1 -12
- package/Navigation/ModalScreen.tsx +5 -1
- package/Navigation/NavigationButton.tsx +5 -0
- package/Navigation/index.ts +2 -0
- package/Navigation/types.ts +15 -2
- package/Popup/PopupPromotion.tsx +42 -26
- package/Popup/types.ts +1 -1
- package/Radio/index.tsx +20 -6
- package/{ContentLoader → Skeleton}/index.tsx +3 -5
- package/Skeleton/types.ts +3 -0
- package/index.ts +2 -2
- package/package.json +1 -1
- package/ContentLoader/types.ts +0 -3
- /package/{ContentLoader → Skeleton}/styles.ts +0 -0
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 {
|
|
6
|
+
import {Skeleton} from '../Skeleton';
|
|
7
7
|
import {Icon} from '../Icon';
|
|
8
8
|
import {Styles} from '../Consts';
|
|
9
9
|
import {FastImagePropsWithoutStyle} from './types';
|
|
@@ -24,7 +24,7 @@ const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
|
|
|
24
24
|
const renderContent = () => {
|
|
25
25
|
if (loading || fail) {
|
|
26
26
|
let content = (
|
|
27
|
-
<
|
|
27
|
+
<Skeleton style={[StyleSheet.absoluteFill, styles.image]} />
|
|
28
28
|
);
|
|
29
29
|
if (fail) {
|
|
30
30
|
content = (
|
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
|
|
|
@@ -27,6 +27,7 @@ const Input: FC<InputProps> = ({
|
|
|
27
27
|
disabled,
|
|
28
28
|
floatingIconColor,
|
|
29
29
|
iconColor,
|
|
30
|
+
required,
|
|
30
31
|
...props
|
|
31
32
|
}) => {
|
|
32
33
|
const {theme} = useContext(ApplicationContext);
|
|
@@ -35,17 +36,12 @@ const Input: FC<InputProps> = ({
|
|
|
35
36
|
|
|
36
37
|
const onClearText = () => {
|
|
37
38
|
inputRef?.current?.clear();
|
|
39
|
+
_onChangeText('');
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
const _onChangeText = (text: string) => {
|
|
41
43
|
onChangeText?.(text);
|
|
42
44
|
};
|
|
43
|
-
const getSizeStyle = () => {
|
|
44
|
-
if (size === 'small') {
|
|
45
|
-
return styles.smallContainer;
|
|
46
|
-
}
|
|
47
|
-
return styles.container;
|
|
48
|
-
};
|
|
49
45
|
|
|
50
46
|
const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
51
47
|
setFocused(true);
|
|
@@ -72,7 +68,7 @@ const Input: FC<InputProps> = ({
|
|
|
72
68
|
return (
|
|
73
69
|
<View
|
|
74
70
|
style={[
|
|
75
|
-
getSizeStyle(),
|
|
71
|
+
getSizeStyle(size),
|
|
76
72
|
getBorderColor(focused, errorMessage, disabled),
|
|
77
73
|
styles.inputWrapper,
|
|
78
74
|
{backgroundColor: theme.colors.background.surface},
|
|
@@ -81,6 +77,7 @@ const Input: FC<InputProps> = ({
|
|
|
81
77
|
floatingValue={floatingValue}
|
|
82
78
|
floatingIconColor={floatingIconColor}
|
|
83
79
|
disabled={disabled}
|
|
80
|
+
required={required}
|
|
84
81
|
floatingIcon={floatingIcon}
|
|
85
82
|
/>
|
|
86
83
|
<View style={styles.inputView}>
|
|
@@ -95,6 +92,7 @@ const Input: FC<InputProps> = ({
|
|
|
95
92
|
color: textColor,
|
|
96
93
|
},
|
|
97
94
|
]}
|
|
95
|
+
textBreakStrategy="highQuality"
|
|
98
96
|
value={value}
|
|
99
97
|
onChangeText={_onChangeText}
|
|
100
98
|
onFocus={_onFocus}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, {FC, useContext, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
NativeSyntheticEvent,
|
|
4
|
+
TextInput,
|
|
5
|
+
TextInputFocusEventData,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import {ApplicationContext} from '../Navigation';
|
|
10
|
+
import styles from './styles';
|
|
11
|
+
import {Image} from '../Image';
|
|
12
|
+
import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
|
|
13
|
+
import {InputMoneyProps} from './index';
|
|
14
|
+
import {Icon} from '../Icon';
|
|
15
|
+
import {formatMoneyToNumber, formatNumberToMoney} from './utils';
|
|
16
|
+
|
|
17
|
+
const InputMoney: FC<InputMoneyProps> = ({
|
|
18
|
+
onChangeText,
|
|
19
|
+
floatingValue,
|
|
20
|
+
floatingIcon,
|
|
21
|
+
size = 'small',
|
|
22
|
+
onBlur,
|
|
23
|
+
onFocus,
|
|
24
|
+
errorMessage,
|
|
25
|
+
icon,
|
|
26
|
+
disabled,
|
|
27
|
+
floatingIconColor,
|
|
28
|
+
iconColor,
|
|
29
|
+
required,
|
|
30
|
+
...props
|
|
31
|
+
}) => {
|
|
32
|
+
const {theme} = useContext(ApplicationContext);
|
|
33
|
+
const [focused, setFocused] = useState(false);
|
|
34
|
+
const inputRef = useRef<any>(null);
|
|
35
|
+
const [value, setValue] = useState('');
|
|
36
|
+
const onClearText = () => {
|
|
37
|
+
inputRef?.current?.clear();
|
|
38
|
+
setValue('');
|
|
39
|
+
onChangeText?.('');
|
|
40
|
+
};
|
|
41
|
+
const _onChangeText = (text: string) => {
|
|
42
|
+
if (text.length < value.length && value.indexOf(text) === 0) {
|
|
43
|
+
text = value.slice(0, -2) + 'đ';
|
|
44
|
+
}
|
|
45
|
+
const valueFormat = formatMoneyToNumber(text, 'đ');
|
|
46
|
+
setValue(formatNumberToMoney(valueFormat, 'đ'));
|
|
47
|
+
onChangeText?.(formatMoneyToNumber(text, 'đ').toString());
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
51
|
+
setFocused(true);
|
|
52
|
+
onFocus?.(e);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
56
|
+
setFocused(false);
|
|
57
|
+
onBlur?.(e);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const renderInputView = () => {
|
|
61
|
+
const disabledColor = theme.colors.text.disable;
|
|
62
|
+
let textColor = theme.colors.text.default;
|
|
63
|
+
let placeholderColor = theme.colors.text.hint;
|
|
64
|
+
let iconTintColor = iconColor;
|
|
65
|
+
|
|
66
|
+
if (disabled) {
|
|
67
|
+
textColor = disabledColor;
|
|
68
|
+
placeholderColor = disabledColor;
|
|
69
|
+
iconTintColor = disabledColor;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<View
|
|
74
|
+
style={[
|
|
75
|
+
getSizeStyle(size),
|
|
76
|
+
getBorderColor(focused, errorMessage, disabled),
|
|
77
|
+
styles.inputWrapper,
|
|
78
|
+
{backgroundColor: theme.colors.background.surface},
|
|
79
|
+
]}>
|
|
80
|
+
<FloatingView
|
|
81
|
+
floatingValue={floatingValue}
|
|
82
|
+
floatingIconColor={floatingIconColor}
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
required={required}
|
|
85
|
+
floatingIcon={floatingIcon}
|
|
86
|
+
/>
|
|
87
|
+
<View style={styles.inputView}>
|
|
88
|
+
<TextInput
|
|
89
|
+
{...props}
|
|
90
|
+
editable={!disabled}
|
|
91
|
+
textAlignVertical="top"
|
|
92
|
+
ref={inputRef}
|
|
93
|
+
keyboardType={'number-pad'}
|
|
94
|
+
style={[
|
|
95
|
+
styles.moneyInput,
|
|
96
|
+
{
|
|
97
|
+
color: textColor,
|
|
98
|
+
},
|
|
99
|
+
]}
|
|
100
|
+
value={value}
|
|
101
|
+
onChangeText={_onChangeText}
|
|
102
|
+
onFocus={_onFocus}
|
|
103
|
+
onBlur={_onBlur}
|
|
104
|
+
selectionColor={theme.colors.primary}
|
|
105
|
+
placeholderTextColor={placeholderColor}
|
|
106
|
+
placeholder={'0đ'}
|
|
107
|
+
/>
|
|
108
|
+
</View>
|
|
109
|
+
<View style={styles.iconView}>
|
|
110
|
+
{focused && (
|
|
111
|
+
<TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
|
|
112
|
+
<Icon
|
|
113
|
+
source="24_navigation_close_circle_full"
|
|
114
|
+
size={16}
|
|
115
|
+
color={theme.colors.text.hint}
|
|
116
|
+
/>
|
|
117
|
+
</TouchableOpacity>
|
|
118
|
+
)}
|
|
119
|
+
{icon && (
|
|
120
|
+
<Image
|
|
121
|
+
tintColor={iconTintColor}
|
|
122
|
+
source={{uri: icon}}
|
|
123
|
+
style={styles.icon}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</View>
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<View style={styles.wrapper}>
|
|
133
|
+
{renderInputView()}
|
|
134
|
+
<ErrorView errorMessage={errorMessage} />
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default InputMoney;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import {
|
|
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
|
|
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) => {
|
|
@@ -148,4 +149,4 @@ const SearchInput: FC<SearchInputProps> = ({
|
|
|
148
149
|
);
|
|
149
150
|
};
|
|
150
151
|
|
|
151
|
-
export default
|
|
152
|
+
export default InputSearch;
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
getBorderColor,
|
|
17
17
|
MAX_LENGTH,
|
|
18
18
|
} from './common';
|
|
19
|
-
import {
|
|
19
|
+
import {InputTextAreaProps} from './index';
|
|
20
20
|
import {Icon} from '../Icon';
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const InputTextArea: FC<InputTextAreaProps> = props => {
|
|
23
23
|
const {theme} = useContext(ApplicationContext);
|
|
24
24
|
const {
|
|
25
25
|
errorMessage,
|
|
@@ -35,6 +35,7 @@ const TextArea: FC<TextAreaProps> = props => {
|
|
|
35
35
|
height,
|
|
36
36
|
placeholder,
|
|
37
37
|
maxLength,
|
|
38
|
+
required,
|
|
38
39
|
} = props;
|
|
39
40
|
|
|
40
41
|
const [focused, setFocused] = useState(false);
|
|
@@ -43,6 +44,7 @@ const TextArea: FC<TextAreaProps> = props => {
|
|
|
43
44
|
|
|
44
45
|
const onClearText = () => {
|
|
45
46
|
inputRef?.current?.clear();
|
|
47
|
+
_onChangeText('');
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const _onChangeText = (text: string) => {
|
|
@@ -94,6 +96,7 @@ const TextArea: FC<TextAreaProps> = props => {
|
|
|
94
96
|
floatingIconColor={floatingIconColor}
|
|
95
97
|
disabled={disabled}
|
|
96
98
|
floatingIcon={floatingIcon}
|
|
99
|
+
required={required}
|
|
97
100
|
/>
|
|
98
101
|
<View style={styles.rowArea}>
|
|
99
102
|
<View style={styles.textAreaView}>
|
|
@@ -108,6 +111,7 @@ const TextArea: FC<TextAreaProps> = props => {
|
|
|
108
111
|
},
|
|
109
112
|
]}
|
|
110
113
|
maxLength={maxLength}
|
|
114
|
+
textBreakStrategy="highQuality"
|
|
111
115
|
multiline={true}
|
|
112
116
|
value={value}
|
|
113
117
|
onChangeText={_onChangeText}
|
|
@@ -141,8 +145,8 @@ const TextArea: FC<TextAreaProps> = props => {
|
|
|
141
145
|
);
|
|
142
146
|
};
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
InputTextArea.defaultProps = {
|
|
145
149
|
maxLength: MAX_LENGTH,
|
|
146
150
|
};
|
|
147
151
|
|
|
148
|
-
export default
|
|
152
|
+
export default InputTextArea;
|
package/Input/common.tsx
CHANGED
|
@@ -11,6 +11,7 @@ type FloatingViewProps = {
|
|
|
11
11
|
floatingIconColor?: string;
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
floatingIcon?: string;
|
|
14
|
+
required?: boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export const DEFAULT_HEIGHT = 112;
|
|
@@ -40,6 +41,12 @@ export const getBorderColor = (
|
|
|
40
41
|
return {borderColor};
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export const getSizeStyle = (size?: 'small' | 'large') => {
|
|
45
|
+
if (size === 'small') {
|
|
46
|
+
return styles.smallContainer;
|
|
47
|
+
}
|
|
48
|
+
return styles.container;
|
|
49
|
+
};
|
|
43
50
|
export const ErrorView: FC<{errorMessage?: string}> = ({errorMessage}) => {
|
|
44
51
|
const {theme} = useContext(ApplicationContext);
|
|
45
52
|
const errorColor = theme.colors.error.primary;
|
|
@@ -63,6 +70,7 @@ export const FloatingView: FC<FloatingViewProps> = ({
|
|
|
63
70
|
floatingIconColor,
|
|
64
71
|
disabled,
|
|
65
72
|
floatingIcon,
|
|
73
|
+
required,
|
|
66
74
|
}) => {
|
|
67
75
|
const {theme} = useContext(ApplicationContext);
|
|
68
76
|
|
|
@@ -85,6 +93,11 @@ export const FloatingView: FC<FloatingViewProps> = ({
|
|
|
85
93
|
]}>
|
|
86
94
|
<Text color={floatingTextColor} typography={'label_s'}>
|
|
87
95
|
{floatingValue}
|
|
96
|
+
{required && (
|
|
97
|
+
<Text typography={'label_s'} color={theme.colors.error.primary}>
|
|
98
|
+
*
|
|
99
|
+
</Text>
|
|
100
|
+
)}
|
|
88
101
|
</Text>
|
|
89
102
|
{floatingIcon && (
|
|
90
103
|
<Image
|
package/Input/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Input from './Input';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import InputTextArea from './InputTextArea';
|
|
3
|
+
import InputSearch from './InputSearch';
|
|
4
|
+
import InputMoney from './InputMoney';
|
|
4
5
|
import {TextInputProps} from 'react-native';
|
|
5
6
|
|
|
6
7
|
export interface InputProps extends TextInputProps {
|
|
@@ -12,20 +13,26 @@ export interface InputProps extends TextInputProps {
|
|
|
12
13
|
disabled?: boolean;
|
|
13
14
|
floatingIconColor?: string;
|
|
14
15
|
iconColor?: string;
|
|
16
|
+
required?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
type
|
|
19
|
+
type InputPropsWithoutSizeAndIcon = Omit<
|
|
20
|
+
InputProps,
|
|
21
|
+
'size' | 'icon' | 'iconColor'
|
|
22
|
+
>;
|
|
23
|
+
type InputPropsWithoutRequiredAndSize = Omit<InputProps, 'required' | 'size'>;
|
|
24
|
+
type InputPropsWithoutPlaceholder = Omit<InputProps, 'placeholder'>;
|
|
18
25
|
|
|
19
|
-
export interface
|
|
26
|
+
export interface InputTextAreaProps extends InputPropsWithoutSizeAndIcon {
|
|
20
27
|
height?: number;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
export interface
|
|
24
|
-
extends TextInputProps,
|
|
25
|
-
InputPropsWithoutSize {
|
|
30
|
+
export interface InputSearchProps extends InputPropsWithoutRequiredAndSize {
|
|
26
31
|
buttonText?: string;
|
|
27
32
|
showButtonText?: boolean;
|
|
28
33
|
showIcon?: boolean;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
export
|
|
36
|
+
export interface InputMoneyProps extends InputPropsWithoutPlaceholder {}
|
|
37
|
+
|
|
38
|
+
export {Input, InputTextArea, InputSearch, InputMoney};
|
package/Input/styles.ts
CHANGED
|
@@ -10,14 +10,12 @@ export default StyleSheet.create({
|
|
|
10
10
|
container: {
|
|
11
11
|
borderWidth: 1,
|
|
12
12
|
borderRadius: Radius.S,
|
|
13
|
-
marginBottom: Spacing.XS,
|
|
14
13
|
height: 56,
|
|
15
14
|
paddingVertical: 8,
|
|
16
15
|
},
|
|
17
16
|
smallContainer: {
|
|
18
17
|
borderRadius: Radius.S,
|
|
19
18
|
borderWidth: 1,
|
|
20
|
-
marginBottom: Spacing.XS,
|
|
21
19
|
height: 48,
|
|
22
20
|
},
|
|
23
21
|
floatingView: {
|
|
@@ -32,6 +30,7 @@ export default StyleSheet.create({
|
|
|
32
30
|
errorView: {
|
|
33
31
|
flexDirection: 'row',
|
|
34
32
|
alignItems: 'center',
|
|
33
|
+
marginTop: Spacing.XS,
|
|
35
34
|
},
|
|
36
35
|
inputView: {
|
|
37
36
|
justifyContent: 'space-between',
|
|
@@ -115,4 +114,11 @@ export default StyleSheet.create({
|
|
|
115
114
|
width: 1,
|
|
116
115
|
marginLeft: Spacing.XS,
|
|
117
116
|
},
|
|
117
|
+
moneyInput: {
|
|
118
|
+
width: '100%',
|
|
119
|
+
paddingLeft: Spacing.M,
|
|
120
|
+
height: '100%',
|
|
121
|
+
fontSize: 20,
|
|
122
|
+
fontWeight: 'bold',
|
|
123
|
+
},
|
|
118
124
|
});
|
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}>
|
|
54
|
+
<Component {...scrollViewProps} style={Styles.flex}>
|
|
55
|
+
{renderContent()}
|
|
56
|
+
</Component>
|
|
54
57
|
</KeyboardAvoidingView>
|
|
55
58
|
</SafeAreaView>
|
|
56
59
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, {FC} from 'react';
|
|
2
|
+
import {View} from 'react-native';
|
|
3
|
+
import {BottomTabItemProps, BottomTabProps} from './types';
|
|
4
|
+
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
|
|
5
|
+
import {Icon} from '@momo-kits/foundation';
|
|
6
|
+
|
|
7
|
+
const Tab = createBottomTabNavigator();
|
|
8
|
+
|
|
9
|
+
const BottomTab: FC<BottomTabProps> = ({tabs}) => {
|
|
10
|
+
return (
|
|
11
|
+
<Tab.Navigator>
|
|
12
|
+
{tabs.map((item, index) => {
|
|
13
|
+
return (
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
<Tab.Screen
|
|
16
|
+
key={`${item.label}-${index}`}
|
|
17
|
+
name={item.label}
|
|
18
|
+
component={item.component}
|
|
19
|
+
options={{
|
|
20
|
+
tabBarLabel: item.label,
|
|
21
|
+
tabBarBadge: item?.badgeLabel,
|
|
22
|
+
tabBarBadgeStyle: {
|
|
23
|
+
borderColor: 'white',
|
|
24
|
+
borderWidth: 2,
|
|
25
|
+
fontSize: 10,
|
|
26
|
+
lineHeight: 14,
|
|
27
|
+
fontWeight: 'bold',
|
|
28
|
+
alignSelf: 'center',
|
|
29
|
+
},
|
|
30
|
+
tabBarIcon: ({color, size}) => (
|
|
31
|
+
<Icon source={item.icon} color={color} size={size} />
|
|
32
|
+
),
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</Tab.Navigator>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
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={
|
|
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={[
|
package/Navigation/index.ts
CHANGED
|
@@ -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
|
};
|
package/Navigation/types.ts
CHANGED
|
@@ -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
|
+
};
|
package/Popup/PopupPromotion.tsx
CHANGED
|
@@ -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 {
|
|
7
|
-
import {
|
|
6
|
+
import {Radius} from '../Consts';
|
|
7
|
+
import {Icon} from '../Icon';
|
|
8
8
|
|
|
9
|
-
const PopupPromotion: React.FC<PopupPromotionProps> = ({image,
|
|
9
|
+
const PopupPromotion: React.FC<PopupPromotionProps> = ({image, onClose}) => {
|
|
10
10
|
const {theme, navigator} = useContext(ApplicationContext);
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param callback
|
|
13
|
+
* build close action
|
|
15
14
|
*/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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.
|
|
55
|
+
aspectRatio: 0.72,
|
|
44
56
|
},
|
|
45
|
-
|
|
57
|
+
iconCloseContainer: {
|
|
46
58
|
position: 'absolute',
|
|
47
|
-
bottom: 0,
|
|
48
59
|
width: '100%',
|
|
49
|
-
|
|
50
|
-
|
|
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
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
|
-
|
|
21
|
-
let
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
54
|
+
checkBoxStyle,
|
|
55
|
+
disabledStyle,
|
|
56
|
+
{marginRight: !!label ? Spacing.XS : 0},
|
|
43
57
|
]}
|
|
44
58
|
/>
|
|
45
59
|
{!!label && <Text typography={'description_default'}>{label}</Text>}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, {useContext, useEffect, useMemo, useRef} from 'react';
|
|
2
2
|
import {Animated, Platform, useWindowDimensions, View} from 'react-native';
|
|
3
3
|
import LinearGradient from 'react-native-linear-gradient';
|
|
4
|
-
import {
|
|
4
|
+
import {SkeletonTypes} from './types';
|
|
5
5
|
import {ApplicationContext} from '../Navigation';
|
|
6
6
|
import {Styles} from '../Consts';
|
|
7
7
|
import styles from './styles';
|
|
8
|
-
const
|
|
8
|
+
const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
|
|
9
9
|
const {width} = useWindowDimensions();
|
|
10
10
|
const {theme} = useContext(ApplicationContext);
|
|
11
11
|
const beginShimmerPosition = useRef(new Animated.Value(-1)).current;
|
|
@@ -62,6 +62,4 @@ const ContentLoader: React.FC<ContentLoaderTypes> = ({style}) => {
|
|
|
62
62
|
);
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export {ContentLoader};
|
|
65
|
+
export {Skeleton};
|
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 './
|
|
26
|
-
export * from './
|
|
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
package/ContentLoader/types.ts
DELETED
|
File without changes
|