@momo-kits/foundation 0.79.6 → 0.80.2-beta.1
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/Application/Components.tsx +16 -12
- package/Application/ModalScreen.tsx +31 -38
- package/Application/NavigationContainer.tsx +10 -2
- package/Application/StackScreen.tsx +2 -2
- package/Application/types.ts +1 -1
- package/Consts/theme.ts +3 -0
- package/Input/Input.tsx +11 -4
- package/Input/InputDropDown.tsx +127 -0
- package/Input/InputOTP.tsx +213 -0
- package/Input/common.tsx +4 -1
- package/Input/index.tsx +116 -7
- package/Input/styles.ts +26 -9
- package/Layout/Screen.tsx +4 -6
- package/Layout/Section.tsx +4 -1
- package/Layout/utils.ts +20 -37
- package/Switch/index.tsx +3 -5
- package/Title/index.tsx +89 -0
- package/Title/styles.ts +46 -0
- package/Title/types.ts +11 -0
- package/index.ts +2 -0
- package/package.json +3 -3
- package/verify.js +24 -0
- package/Assets/header-background.png +0 -0
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
Alert,
|
|
4
4
|
Animated,
|
|
5
5
|
DeviceEventEmitter,
|
|
6
|
+
Platform,
|
|
6
7
|
StatusBar,
|
|
7
8
|
StyleSheet,
|
|
8
|
-
TouchableOpacity,
|
|
9
9
|
View,
|
|
10
|
+
TouchableOpacity as TouchableRN,
|
|
10
11
|
} from 'react-native';
|
|
12
|
+
import {TouchableOpacity as TouchableGesture} from 'react-native-gesture-handler';
|
|
11
13
|
import {ApplicationContext} from './index';
|
|
12
14
|
import {Colors, Radius, Spacing, Styles} from '../Consts';
|
|
13
15
|
import {Image} from '../Image';
|
|
@@ -22,6 +24,8 @@ import {scaleSize, Text} from '../Text';
|
|
|
22
24
|
import {Icon} from '../Icon';
|
|
23
25
|
import {PopupNotify} from '../Popup';
|
|
24
26
|
|
|
27
|
+
const Touchable = Platform.OS === 'android' ? TouchableGesture : TouchableRN;
|
|
28
|
+
|
|
25
29
|
const styles = StyleSheet.create({
|
|
26
30
|
navigationButton: {
|
|
27
31
|
height: 28,
|
|
@@ -101,7 +105,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
|
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
return (
|
|
104
|
-
<
|
|
108
|
+
<Touchable
|
|
105
109
|
accessibilityLabel={`NavigationButton/${accessibilityLabel}`}
|
|
106
110
|
style={[
|
|
107
111
|
styles.navigationButton,
|
|
@@ -116,7 +120,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
|
|
|
116
120
|
color={tintColor ?? theme.colors.text.default}
|
|
117
121
|
size={20}
|
|
118
122
|
/>
|
|
119
|
-
</
|
|
123
|
+
</Touchable>
|
|
120
124
|
);
|
|
121
125
|
};
|
|
122
126
|
|
|
@@ -225,7 +229,11 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
225
229
|
barStyle={headerImage || theme.dark ? 'light-content' : 'dark-content'}
|
|
226
230
|
/>
|
|
227
231
|
{!!headerImage && (
|
|
228
|
-
<Image
|
|
232
|
+
<Image
|
|
233
|
+
style={styles.headerBackground}
|
|
234
|
+
source={{uri: headerImage}}
|
|
235
|
+
loading={false}
|
|
236
|
+
/>
|
|
229
237
|
)}
|
|
230
238
|
</Animated.View>
|
|
231
239
|
);
|
|
@@ -388,21 +396,17 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
|
|
|
388
396
|
borderWidth: backgroundColor ? 0.5 : 0,
|
|
389
397
|
},
|
|
390
398
|
]}>
|
|
391
|
-
<
|
|
392
|
-
accessibilityLabel={'Toolkit/More'}
|
|
393
|
-
onPress={onMore}>
|
|
399
|
+
<Touchable accessibilityLabel={'Toolkit/More'} onPress={onMore}>
|
|
394
400
|
<Icon color={tintColor} source="navigation_more_horiz" size={20} />
|
|
395
|
-
</
|
|
401
|
+
</Touchable>
|
|
396
402
|
<View style={[styles.divider, {backgroundColor: tintColor}]} />
|
|
397
|
-
<
|
|
398
|
-
accessibilityLabel={`Toolkit/Close`}
|
|
399
|
-
onPress={onClose}>
|
|
403
|
+
<Touchable accessibilityLabel={`Toolkit/Close`} onPress={onClose}>
|
|
400
404
|
<Icon
|
|
401
405
|
color={tintColor}
|
|
402
406
|
source="16_navigation_close_circle"
|
|
403
407
|
size={20}
|
|
404
408
|
/>
|
|
405
|
-
</
|
|
409
|
+
</Touchable>
|
|
406
410
|
</View>
|
|
407
411
|
</View>
|
|
408
412
|
);
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useContext,
|
|
4
|
-
useEffect,
|
|
5
|
-
useMemo,
|
|
6
|
-
useRef,
|
|
7
|
-
useState,
|
|
8
|
-
} from 'react';
|
|
1
|
+
import React, {useContext, useEffect, useMemo, useRef} from 'react';
|
|
9
2
|
import {
|
|
10
3
|
KeyboardAvoidingView,
|
|
11
4
|
Platform,
|
|
12
5
|
Pressable,
|
|
13
6
|
StyleSheet,
|
|
14
7
|
View,
|
|
8
|
+
Keyboard,
|
|
15
9
|
} from 'react-native';
|
|
16
|
-
import {SafeAreaView} from 'react-native-safe-area-context';
|
|
17
|
-
import {
|
|
10
|
+
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
11
|
+
import {
|
|
12
|
+
BottomSheetBackdrop,
|
|
13
|
+
BottomSheetModal,
|
|
14
|
+
BottomSheetView,
|
|
15
|
+
} from '@gorhom/bottom-sheet';
|
|
18
16
|
import Navigation from './Navigation';
|
|
19
17
|
import {BottomSheetParams, ModalParams} from './types';
|
|
20
18
|
import {Radius, Spacing, Styles} from '../Consts';
|
|
@@ -90,12 +88,9 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
90
88
|
};
|
|
91
89
|
|
|
92
90
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
91
|
+
const insets = useSafeAreaInsets();
|
|
93
92
|
const {theme, navigator} = useContext(ApplicationContext);
|
|
94
|
-
const {
|
|
95
|
-
screen,
|
|
96
|
-
options,
|
|
97
|
-
useSnap = true,
|
|
98
|
-
}: BottomSheetParams = props.route.params;
|
|
93
|
+
const {screen, options, snapPoint}: BottomSheetParams = props.route.params;
|
|
99
94
|
const Component = useRef(screen).current;
|
|
100
95
|
const navigation = new Navigation(props.navigation, theme);
|
|
101
96
|
const params = {
|
|
@@ -105,40 +100,30 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
105
100
|
|
|
106
101
|
delete params.screen;
|
|
107
102
|
|
|
108
|
-
const [contentHeight, setContentHeight] = useState(undefined);
|
|
109
103
|
const snapPoints = useMemo(() => {
|
|
110
|
-
if (
|
|
111
|
-
return [
|
|
104
|
+
if (snapPoint) {
|
|
105
|
+
return [snapPoint];
|
|
112
106
|
}
|
|
113
|
-
return [
|
|
114
|
-
}, [
|
|
107
|
+
return ['50%', '70%', '90%'];
|
|
108
|
+
}, []);
|
|
115
109
|
|
|
116
110
|
const bottomSheetRef = useRef<any>(null);
|
|
117
111
|
const mountedRef = useRef(true);
|
|
118
112
|
|
|
119
|
-
let handleOnLayout;
|
|
120
|
-
if (!useSnap) {
|
|
121
|
-
handleOnLayout = useCallback(
|
|
122
|
-
({
|
|
123
|
-
nativeEvent: {
|
|
124
|
-
layout: {height},
|
|
125
|
-
},
|
|
126
|
-
}) => {
|
|
127
|
-
setContentHeight(height);
|
|
128
|
-
},
|
|
129
|
-
[],
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
113
|
let backgroundColor = theme.colors.background.default;
|
|
134
114
|
if (options?.surface) {
|
|
135
115
|
backgroundColor = theme.colors.background.surface;
|
|
136
116
|
}
|
|
137
117
|
|
|
138
118
|
useEffect(() => {
|
|
119
|
+
const keyboardDidShow = Keyboard.addListener('keyboardDidShow', () =>
|
|
120
|
+
bottomSheetRef.current?.expand(),
|
|
121
|
+
);
|
|
122
|
+
|
|
139
123
|
bottomSheetRef.current?.present();
|
|
140
124
|
return () => {
|
|
141
125
|
mountedRef.current = false;
|
|
126
|
+
keyboardDidShow.remove();
|
|
142
127
|
};
|
|
143
128
|
}, []);
|
|
144
129
|
|
|
@@ -162,8 +147,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
162
147
|
}}
|
|
163
148
|
handleComponent={null}
|
|
164
149
|
backdropComponent={backdropComponent}>
|
|
165
|
-
<
|
|
166
|
-
<View
|
|
150
|
+
<BottomSheetView accessibilityLabel={'BottomSheet'}>
|
|
151
|
+
<View
|
|
152
|
+
style={[
|
|
153
|
+
styles.sheetContainer,
|
|
154
|
+
{
|
|
155
|
+
backgroundColor,
|
|
156
|
+
paddingBottom: Math.min(insets.bottom, 20) + Spacing.S,
|
|
157
|
+
},
|
|
158
|
+
]}>
|
|
167
159
|
<View style={styles.indicatorContainer}>
|
|
168
160
|
<View
|
|
169
161
|
style={[
|
|
@@ -222,7 +214,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
222
214
|
}}
|
|
223
215
|
/>
|
|
224
216
|
</View>
|
|
225
|
-
</
|
|
217
|
+
</BottomSheetView>
|
|
226
218
|
</BottomSheetModal>
|
|
227
219
|
</SafeAreaView>
|
|
228
220
|
);
|
|
@@ -255,11 +247,12 @@ const styles = StyleSheet.create({
|
|
|
255
247
|
indicatorContainer: {
|
|
256
248
|
width: '100%',
|
|
257
249
|
paddingTop: Spacing.S,
|
|
250
|
+
paddingBottom: Spacing.XS,
|
|
258
251
|
alignItems: 'center',
|
|
259
252
|
justifyContent: 'center',
|
|
260
253
|
},
|
|
261
254
|
indicator: {
|
|
262
|
-
width:
|
|
255
|
+
width: 40,
|
|
263
256
|
height: 4,
|
|
264
257
|
borderRadius: Radius.S,
|
|
265
258
|
},
|
|
@@ -10,9 +10,10 @@ import StackScreen from './StackScreen';
|
|
|
10
10
|
import ModalScreen from './ModalScreen';
|
|
11
11
|
import Navigator from './Navigator';
|
|
12
12
|
import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
|
|
13
|
-
import {NavigationContainerProps} from './types';
|
|
13
|
+
import {LocalizationObject, NavigationContainerProps} from './types';
|
|
14
14
|
import {ApplicationContext} from './index';
|
|
15
15
|
import {DeviceEventEmitter} from 'react-native';
|
|
16
|
+
import kitLocalization from '../Assets/language.json';
|
|
16
17
|
|
|
17
18
|
const Stack = createStackNavigator();
|
|
18
19
|
|
|
@@ -41,6 +42,9 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
41
42
|
}, []);
|
|
42
43
|
|
|
43
44
|
useEffect(() => {
|
|
45
|
+
maxApi?.getLanguage?.((lang: 'vi' | 'en') => {
|
|
46
|
+
setLanguage(lang);
|
|
47
|
+
});
|
|
44
48
|
const listener = maxApi?.observer?.('language', (lang: 'vi' | 'en') => {
|
|
45
49
|
setLanguage(lang);
|
|
46
50
|
});
|
|
@@ -52,7 +56,11 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
52
56
|
navigator.current.maxApi = maxApi;
|
|
53
57
|
|
|
54
58
|
const translate = (key: string) => {
|
|
55
|
-
|
|
59
|
+
const mergedLocalization: LocalizationObject = {
|
|
60
|
+
vi: {...localization?.vi, ...kitLocalization.vi},
|
|
61
|
+
en: {...localization?.en, ...kitLocalization.en},
|
|
62
|
+
};
|
|
63
|
+
return mergedLocalization?.[language]?.[key] || key;
|
|
56
64
|
};
|
|
57
65
|
|
|
58
66
|
return (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {useContext,
|
|
1
|
+
import React, {useContext, useLayoutEffect} from 'react';
|
|
2
2
|
import {ScreenParams} from './types';
|
|
3
3
|
import Navigation from './Navigation';
|
|
4
4
|
import {ApplicationContext} from './index';
|
|
@@ -16,7 +16,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
|
|
|
16
16
|
|
|
17
17
|
delete params.screen;
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
20
|
if (options) {
|
|
21
21
|
navigation.setOptions(options);
|
|
22
22
|
}
|
package/Application/types.ts
CHANGED
|
@@ -107,7 +107,7 @@ export interface ModalParams extends ScreenParams {
|
|
|
107
107
|
export interface BottomSheetParams extends ScreenParams {
|
|
108
108
|
[key: string]: any;
|
|
109
109
|
onDismiss?: () => void;
|
|
110
|
-
|
|
110
|
+
snapPoint?: '30%' | '50%' | '70%' | '90%';
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export type NavigationButtonProps = {
|
package/Consts/theme.ts
CHANGED
package/Input/Input.tsx
CHANGED
|
@@ -13,7 +13,6 @@ import {InputProps} from './index';
|
|
|
13
13
|
import {Icon} from '../Icon';
|
|
14
14
|
import {checkTyping} from './utils';
|
|
15
15
|
import {Loader} from '../Loader';
|
|
16
|
-
import {Image} from '../Image';
|
|
17
16
|
|
|
18
17
|
const Input = forwardRef(
|
|
19
18
|
(
|
|
@@ -34,7 +33,8 @@ const Input = forwardRef(
|
|
|
34
33
|
required,
|
|
35
34
|
errorSpacing,
|
|
36
35
|
loading,
|
|
37
|
-
|
|
36
|
+
leadingIcon,
|
|
37
|
+
leadingIconColor,
|
|
38
38
|
...props
|
|
39
39
|
}: InputProps,
|
|
40
40
|
ref,
|
|
@@ -78,7 +78,6 @@ const Input = forwardRef(
|
|
|
78
78
|
let textColor = theme.colors.text.default;
|
|
79
79
|
let placeholderColor = theme.colors.text.hint;
|
|
80
80
|
let iconTintColor = iconColor;
|
|
81
|
-
const imageStyle = size === 'small' ? styles.imageSmall : styles.image;
|
|
82
81
|
|
|
83
82
|
if (disabled) {
|
|
84
83
|
textColor = disabledColor;
|
|
@@ -102,7 +101,15 @@ const Input = forwardRef(
|
|
|
102
101
|
floatingIcon={floatingIcon}
|
|
103
102
|
/>
|
|
104
103
|
<View style={styles.inputView}>
|
|
105
|
-
{!!
|
|
104
|
+
{!!leadingIcon && (
|
|
105
|
+
<View style={styles.leadingIconContainer}>
|
|
106
|
+
<Icon
|
|
107
|
+
color={leadingIconColor}
|
|
108
|
+
source={leadingIcon}
|
|
109
|
+
size={size === 'small' ? 24 : 32}
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
112
|
+
)}
|
|
106
113
|
<TextInput
|
|
107
114
|
{...props}
|
|
108
115
|
accessibilityLabel={`TextInput`}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, {forwardRef, useContext, useRef} from 'react';
|
|
2
|
+
import {TextInput, TouchableOpacity, View} from 'react-native';
|
|
3
|
+
import {ApplicationContext} from '../Application';
|
|
4
|
+
import styles from './styles';
|
|
5
|
+
import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
|
|
6
|
+
import {InputDropDownProps} from './index';
|
|
7
|
+
import {Icon} from '../Icon';
|
|
8
|
+
import {Loader} from '../Loader';
|
|
9
|
+
import {Styles} from '../Consts';
|
|
10
|
+
|
|
11
|
+
const InputDropDown = forwardRef(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
value,
|
|
15
|
+
floatingValue,
|
|
16
|
+
floatingIcon,
|
|
17
|
+
size,
|
|
18
|
+
onPress,
|
|
19
|
+
placeholder,
|
|
20
|
+
errorMessage,
|
|
21
|
+
icon = 'arrow_chevron_down_small',
|
|
22
|
+
disabled,
|
|
23
|
+
floatingIconColor,
|
|
24
|
+
iconColor,
|
|
25
|
+
required,
|
|
26
|
+
errorSpacing,
|
|
27
|
+
loading,
|
|
28
|
+
leadingIcon,
|
|
29
|
+
leadingIconColor,
|
|
30
|
+
...props
|
|
31
|
+
}: InputDropDownProps,
|
|
32
|
+
ref,
|
|
33
|
+
) => {
|
|
34
|
+
const {theme} = useContext(ApplicationContext);
|
|
35
|
+
const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
|
|
36
|
+
const full = !!value || !!placeholder;
|
|
37
|
+
const renderIcon = (color: string | undefined) => {
|
|
38
|
+
if (loading) {
|
|
39
|
+
return <Loader type={'spinner'} color={color} />;
|
|
40
|
+
}
|
|
41
|
+
if (icon) {
|
|
42
|
+
return <Icon color={color} source={icon} size={24} />;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const renderInputView = () => {
|
|
47
|
+
const disabledColor = theme.colors.text.disable;
|
|
48
|
+
let textColor = theme.colors.text.default;
|
|
49
|
+
let placeholderColor = theme.colors.text.hint;
|
|
50
|
+
let iconTintColor = iconColor;
|
|
51
|
+
|
|
52
|
+
if (disabled) {
|
|
53
|
+
textColor = disabledColor;
|
|
54
|
+
placeholderColor = disabledColor;
|
|
55
|
+
iconTintColor = disabledColor;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<View
|
|
60
|
+
style={[
|
|
61
|
+
styles.inputWrapper,
|
|
62
|
+
{backgroundColor: theme.colors.background.surface},
|
|
63
|
+
getSizeStyle(size),
|
|
64
|
+
getBorderColor(theme, false, errorMessage, disabled),
|
|
65
|
+
]}>
|
|
66
|
+
<FloatingView
|
|
67
|
+
floatingValue={floatingValue}
|
|
68
|
+
floatingIconColor={floatingIconColor}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
required={required}
|
|
71
|
+
floatingIcon={floatingIcon}
|
|
72
|
+
/>
|
|
73
|
+
<View style={full ? styles.inputView : Styles.row}>
|
|
74
|
+
{!!leadingIcon && (
|
|
75
|
+
<View style={styles.leadingIconContainer}>
|
|
76
|
+
<Icon
|
|
77
|
+
color={leadingIconColor}
|
|
78
|
+
source={leadingIcon}
|
|
79
|
+
size={size === 'small' ? 24 : 32}
|
|
80
|
+
/>
|
|
81
|
+
</View>
|
|
82
|
+
)}
|
|
83
|
+
{full && (
|
|
84
|
+
<TextInput
|
|
85
|
+
{...props}
|
|
86
|
+
onPressIn={onPress}
|
|
87
|
+
accessibilityLabel={`TextInput`}
|
|
88
|
+
editable={false}
|
|
89
|
+
textAlignVertical="top"
|
|
90
|
+
ref={inputRef}
|
|
91
|
+
style={[
|
|
92
|
+
styles.input,
|
|
93
|
+
{
|
|
94
|
+
color: textColor,
|
|
95
|
+
fontFamily: `${theme.font}-Regular`,
|
|
96
|
+
},
|
|
97
|
+
]}
|
|
98
|
+
textBreakStrategy="highQuality"
|
|
99
|
+
value={value}
|
|
100
|
+
placeholder={placeholder}
|
|
101
|
+
selectionColor={theme.colors.primary}
|
|
102
|
+
placeholderTextColor={placeholderColor}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</View>
|
|
106
|
+
<View style={styles.iconView}>{renderIcon(iconTintColor)}</View>
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
onPress={onPress}
|
|
114
|
+
style={full && styles.wrapper}
|
|
115
|
+
accessibilityLabel={`InputDropDown/${floatingValue}`}>
|
|
116
|
+
{renderInputView()}
|
|
117
|
+
<ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
|
|
118
|
+
</TouchableOpacity>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
InputDropDown.defaultProps = {
|
|
124
|
+
size: 'small',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default InputDropDown;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, {FC, useContext, useEffect, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
KeyboardType,
|
|
5
|
+
NativeSyntheticEvent,
|
|
6
|
+
TextInput,
|
|
7
|
+
TextInputFocusEventData,
|
|
8
|
+
TouchableOpacity,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import {CaretProps, InputOTPProps} from './index';
|
|
12
|
+
import {scaleSize, Text} from '../Text';
|
|
13
|
+
import styles from './styles';
|
|
14
|
+
import {ErrorView, getBorderColor} from './common';
|
|
15
|
+
import {ApplicationContext} from '../Application';
|
|
16
|
+
import {Spacing} from '../Consts';
|
|
17
|
+
|
|
18
|
+
const OTPCaret: FC<CaretProps> = ({index, length}) => {
|
|
19
|
+
const DURATION = 300;
|
|
20
|
+
const {theme} = useContext(ApplicationContext);
|
|
21
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
Animated.loop(
|
|
25
|
+
Animated.sequence([
|
|
26
|
+
Animated.timing(opacity, {
|
|
27
|
+
toValue: 1,
|
|
28
|
+
duration: DURATION,
|
|
29
|
+
useNativeDriver: true,
|
|
30
|
+
}),
|
|
31
|
+
Animated.delay(DURATION * 2),
|
|
32
|
+
Animated.timing(opacity, {
|
|
33
|
+
toValue: 0,
|
|
34
|
+
duration: DURATION,
|
|
35
|
+
useNativeDriver: true,
|
|
36
|
+
}),
|
|
37
|
+
]),
|
|
38
|
+
).start();
|
|
39
|
+
}, []);
|
|
40
|
+
const spacingStyle = !isNaN(Number(length)) &&
|
|
41
|
+
index !== Number(length) - 1 && {marginRight: Spacing.L};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<View style={[{flexDirection: 'row', alignItems: 'center'}, spacingStyle]}>
|
|
45
|
+
<Animated.View
|
|
46
|
+
style={{
|
|
47
|
+
height: scaleSize(12),
|
|
48
|
+
width: 1,
|
|
49
|
+
backgroundColor: theme.colors.primary,
|
|
50
|
+
opacity,
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
<Text color={theme.colors.text.hint} typography={'paragraph_default'}>
|
|
54
|
+
-
|
|
55
|
+
</Text>
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const InputOTP: FC<InputOTPProps> = ({
|
|
61
|
+
length,
|
|
62
|
+
errorMessage,
|
|
63
|
+
errorSpacing,
|
|
64
|
+
floatingValue,
|
|
65
|
+
floatingIconColor,
|
|
66
|
+
floatingIcon,
|
|
67
|
+
placeholder,
|
|
68
|
+
onChangeText,
|
|
69
|
+
onFocus,
|
|
70
|
+
onBlur,
|
|
71
|
+
dataType = 'number',
|
|
72
|
+
...props
|
|
73
|
+
}) => {
|
|
74
|
+
const MAX_LENGTH = 10;
|
|
75
|
+
const [value, setValue] = useState('');
|
|
76
|
+
const [focused, setFocused] = useState(false);
|
|
77
|
+
const inputRef = useRef<TextInput>(null);
|
|
78
|
+
const {theme} = useContext(ApplicationContext);
|
|
79
|
+
|
|
80
|
+
const onFocusInput = () => {
|
|
81
|
+
setFocused(true);
|
|
82
|
+
inputRef.current?.focus();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const onBlurInput = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
86
|
+
setFocused(false);
|
|
87
|
+
onBlur?.(e);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const _onChangeText = (text: string) => {
|
|
91
|
+
if (text.length > MAX_LENGTH) return;
|
|
92
|
+
if (dataType === 'number' && isNaN(Number(text))) return;
|
|
93
|
+
if (
|
|
94
|
+
!isNaN(Number(text)) &&
|
|
95
|
+
value.length >= Number(length) &&
|
|
96
|
+
text.length >= value.length
|
|
97
|
+
)
|
|
98
|
+
return;
|
|
99
|
+
|
|
100
|
+
setValue(text);
|
|
101
|
+
onChangeText?.(text);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const renderInputs = (length: number) => {
|
|
105
|
+
const TextInputs = [];
|
|
106
|
+
for (let i = 0; i < length; i++) {
|
|
107
|
+
TextInputs.push(
|
|
108
|
+
<View>
|
|
109
|
+
{focused && value.length === i ? (
|
|
110
|
+
<OTPCaret index={i} length={length} />
|
|
111
|
+
) : (
|
|
112
|
+
<Text
|
|
113
|
+
style={i !== length - 1 && {marginRight: Spacing.L}}
|
|
114
|
+
color={
|
|
115
|
+
!!value[i] ? theme.colors.text.default : theme.colors.text.hint
|
|
116
|
+
}
|
|
117
|
+
typography={!!value[i] ? 'title_s' : 'paragraph_default'}>
|
|
118
|
+
{value[i] || '-'}
|
|
119
|
+
</Text>
|
|
120
|
+
)}
|
|
121
|
+
</View>,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return TextInputs;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const renderUnidentifiedInputs = () => {
|
|
128
|
+
const splitValue = value.split('');
|
|
129
|
+
|
|
130
|
+
if (!focused && value.length === 0 && !!placeholder) {
|
|
131
|
+
return (
|
|
132
|
+
<Text color={theme.colors.text.hint} typography={'description_s'}>
|
|
133
|
+
{placeholder}
|
|
134
|
+
</Text>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<View style={{flexDirection: 'row'}}>
|
|
140
|
+
{splitValue.map((letter, index) => {
|
|
141
|
+
return (
|
|
142
|
+
<Text
|
|
143
|
+
style={
|
|
144
|
+
(index !== value.length - 1 ||
|
|
145
|
+
(focused && value.length !== MAX_LENGTH)) && {
|
|
146
|
+
marginRight: Spacing.L,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
typography={'title_s'}>
|
|
150
|
+
{letter}
|
|
151
|
+
</Text>
|
|
152
|
+
);
|
|
153
|
+
})}
|
|
154
|
+
{focused && value.length !== MAX_LENGTH && <OTPCaret />}
|
|
155
|
+
</View>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const renderInputView = () => {
|
|
160
|
+
let placeholderColor = theme.colors.text.hint;
|
|
161
|
+
const keyboardType: KeyboardType =
|
|
162
|
+
dataType === 'number' ? 'number-pad' : 'default';
|
|
163
|
+
return (
|
|
164
|
+
<View
|
|
165
|
+
style={[
|
|
166
|
+
styles.otpInput,
|
|
167
|
+
{backgroundColor: theme.colors.background.surface},
|
|
168
|
+
getBorderColor(theme, focused, errorMessage),
|
|
169
|
+
]}>
|
|
170
|
+
{!!floatingValue && (
|
|
171
|
+
<View
|
|
172
|
+
style={[
|
|
173
|
+
styles.otpFloatingView,
|
|
174
|
+
{backgroundColor: theme.colors.background.surface},
|
|
175
|
+
]}>
|
|
176
|
+
<Text typography={'description_s'}>{floatingValue}</Text>
|
|
177
|
+
</View>
|
|
178
|
+
)}
|
|
179
|
+
<View style={styles.otpInputsView}>
|
|
180
|
+
{length ? renderInputs(length) : renderUnidentifiedInputs()}
|
|
181
|
+
</View>
|
|
182
|
+
<View style={styles.inputView}>
|
|
183
|
+
<TextInput
|
|
184
|
+
{...props}
|
|
185
|
+
accessibilityLabel={`TextInput`}
|
|
186
|
+
ref={inputRef}
|
|
187
|
+
value={value}
|
|
188
|
+
onChangeText={_onChangeText}
|
|
189
|
+
keyboardType={keyboardType}
|
|
190
|
+
onFocus={onFocusInput}
|
|
191
|
+
onBlur={onBlurInput}
|
|
192
|
+
style={{opacity: 0}}
|
|
193
|
+
selectionColor={theme.colors.primary}
|
|
194
|
+
placeholderTextColor={placeholderColor}
|
|
195
|
+
/>
|
|
196
|
+
</View>
|
|
197
|
+
</View>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<TouchableOpacity
|
|
203
|
+
activeOpacity={1}
|
|
204
|
+
onPress={onFocusInput}
|
|
205
|
+
style={styles.otpWrapper}
|
|
206
|
+
accessibilityLabel={`Input/${floatingValue}`}>
|
|
207
|
+
{renderInputView()}
|
|
208
|
+
<ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
|
|
209
|
+
</TouchableOpacity>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default InputOTP;
|
package/Input/common.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {View} from 'react-native';
|
|
1
|
+
import {View, ViewStyle} from 'react-native';
|
|
2
2
|
import styles from './styles';
|
|
3
3
|
import {Text} from '../Text';
|
|
4
4
|
import React, {FC, useContext} from 'react';
|
|
@@ -13,6 +13,7 @@ type FloatingViewProps = {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
floatingIcon?: string;
|
|
15
15
|
required?: boolean;
|
|
16
|
+
style?: ViewStyle;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export const DEFAULT_HEIGHT = 112;
|
|
@@ -81,6 +82,7 @@ export const FloatingView: FC<FloatingViewProps> = ({
|
|
|
81
82
|
disabled,
|
|
82
83
|
floatingIcon,
|
|
83
84
|
required,
|
|
85
|
+
style,
|
|
84
86
|
}) => {
|
|
85
87
|
const {theme} = useContext(ApplicationContext);
|
|
86
88
|
|
|
@@ -100,6 +102,7 @@ export const FloatingView: FC<FloatingViewProps> = ({
|
|
|
100
102
|
backgroundColor: theme.colors.background.surface,
|
|
101
103
|
zIndex: 10,
|
|
102
104
|
},
|
|
105
|
+
style,
|
|
103
106
|
]}>
|
|
104
107
|
<Text color={floatingTextColor} typography={'label_s'}>
|
|
105
108
|
{floatingValue}
|
package/Input/index.tsx
CHANGED
|
@@ -2,6 +2,8 @@ import Input from './Input';
|
|
|
2
2
|
import InputTextArea from './InputTextArea';
|
|
3
3
|
import InputSearch from './InputSearch';
|
|
4
4
|
import InputMoney from './InputMoney';
|
|
5
|
+
import InputOTP from './InputOTP';
|
|
6
|
+
import InputDropDown from './InputDropDown';
|
|
5
7
|
import {TextInputProps} from 'react-native';
|
|
6
8
|
|
|
7
9
|
export interface InputProps extends TextInputProps {
|
|
@@ -30,6 +32,11 @@ export interface InputProps extends TextInputProps {
|
|
|
30
32
|
*/
|
|
31
33
|
icon?: string;
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Optional. Represents the color of the icon in the Input component.
|
|
37
|
+
*/
|
|
38
|
+
iconColor?: string;
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* Optional. If `true`, the user won't be able to interact with the Input component.
|
|
35
42
|
* Defaults to `false` if not provided.
|
|
@@ -41,11 +48,6 @@ export interface InputProps extends TextInputProps {
|
|
|
41
48
|
*/
|
|
42
49
|
floatingIconColor?: string;
|
|
43
50
|
|
|
44
|
-
/**
|
|
45
|
-
* Optional. Represents the color of the icon in the Input component.
|
|
46
|
-
*/
|
|
47
|
-
iconColor?: string;
|
|
48
|
-
|
|
49
51
|
/**
|
|
50
52
|
* Optional. If `true`, the Input component is marked as required,
|
|
51
53
|
* indicating that the user must provide a value before submitting a form.
|
|
@@ -65,13 +67,22 @@ export interface InputProps extends TextInputProps {
|
|
|
65
67
|
*/
|
|
66
68
|
loading?: boolean;
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Optional. Represents the leading icon in the Input component.
|
|
72
|
+
*/
|
|
73
|
+
leadingIcon?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Optional. Represents the color of the leading icon in the Input component.
|
|
77
|
+
*/
|
|
78
|
+
leadingIconColor?: string;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
type InputPropsWithoutSizeAndIcon = Omit<
|
|
72
82
|
InputProps,
|
|
73
83
|
'size' | 'icon' | 'iconColor'
|
|
74
84
|
>;
|
|
85
|
+
|
|
75
86
|
type InputPropsOmitForSearch = Omit<
|
|
76
87
|
InputProps,
|
|
77
88
|
'required' | 'size' | 'errorMessage' | 'disabled'
|
|
@@ -109,4 +120,102 @@ export interface InputSearchProps extends InputPropsOmitForSearch {
|
|
|
109
120
|
|
|
110
121
|
export interface InputMoneyProps extends InputPropsWithoutPlaceholder {}
|
|
111
122
|
|
|
112
|
-
export
|
|
123
|
+
export type OTPInputLength = 2 | 4 | 6 | 8 | 10;
|
|
124
|
+
|
|
125
|
+
export interface InputOTPProps extends InputPropsWithoutSizeAndIcon {
|
|
126
|
+
length?: OTPInputLength;
|
|
127
|
+
dataType?: 'string' | 'number';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type CaretProps = {
|
|
131
|
+
index?: number;
|
|
132
|
+
length?: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type InputDropDownProps = {
|
|
136
|
+
/**
|
|
137
|
+
* Optional. Defines the size of the Input component.
|
|
138
|
+
*/
|
|
139
|
+
size?: 'small' | 'large';
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Optional. Defines the value of InputDropDown.
|
|
143
|
+
*/
|
|
144
|
+
value?: string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Optional. Defines the placeholder of InputDropDown.
|
|
148
|
+
*/
|
|
149
|
+
placeholder?: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Optional. Defines the onPress of InputDropDown.
|
|
153
|
+
*/
|
|
154
|
+
onPress?: () => void;
|
|
155
|
+
/**
|
|
156
|
+
* Optional. Represents the value for the floating title in the Input component.
|
|
157
|
+
*/
|
|
158
|
+
floatingValue?: string;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Optional. Represents the name or key of the floating icon to be displayed in the Input component.
|
|
162
|
+
*/
|
|
163
|
+
floatingIcon?: string;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Optional. Represents the error message to be displayed below the Input component when there is an error.
|
|
167
|
+
*/
|
|
168
|
+
errorMessage?: string;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Optional. Represents the name or key of the icon to be displayed in the Input component.
|
|
172
|
+
*/
|
|
173
|
+
icon?: string;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Optional. Represents the color of the icon in the Input component.
|
|
177
|
+
*/
|
|
178
|
+
iconColor?: string;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Optional. If `true`, the user won't be able to interact with the Input component.
|
|
182
|
+
* Defaults to `false` if not provided.
|
|
183
|
+
*/
|
|
184
|
+
disabled?: boolean;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Optional. Represents the color of the floating icon in the Input component.
|
|
188
|
+
*/
|
|
189
|
+
floatingIconColor?: string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Optional. If `true`, the Input component is marked as required,
|
|
193
|
+
* indicating that the user must provide a value before submitting a form.
|
|
194
|
+
* Defaults to `false` if not provided.
|
|
195
|
+
*/
|
|
196
|
+
required?: boolean;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Optional. If `true`,
|
|
200
|
+
* include spacing when Input not have error message.
|
|
201
|
+
* Defaults to `false` if not provided.
|
|
202
|
+
*/
|
|
203
|
+
errorSpacing?: boolean;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* If true, the icon of input will use be show indicator loading.
|
|
207
|
+
*/
|
|
208
|
+
loading?: boolean;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Optional. Represents the leading icon in the Input component.
|
|
212
|
+
*/
|
|
213
|
+
leadingIcon?: string;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Optional. Represents the color of the leading icon in the Input component.
|
|
217
|
+
*/
|
|
218
|
+
leadingIconColor?: string;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export {Input, InputTextArea, InputSearch, InputMoney, InputOTP, InputDropDown};
|
package/Input/styles.ts
CHANGED
|
@@ -42,17 +42,10 @@ export default StyleSheet.create({
|
|
|
42
42
|
flexDirection: 'row',
|
|
43
43
|
alignItems: 'center',
|
|
44
44
|
},
|
|
45
|
-
|
|
46
|
-
width: 32,
|
|
47
|
-
height: 32,
|
|
48
|
-
marginLeft: Spacing.M,
|
|
49
|
-
borderRadius: Radius.XS,
|
|
50
|
-
},
|
|
51
|
-
imageSmall: {
|
|
52
|
-
width: 24,
|
|
53
|
-
height: 24,
|
|
45
|
+
leadingIconContainer: {
|
|
54
46
|
marginLeft: Spacing.M,
|
|
55
47
|
borderRadius: Radius.XS,
|
|
48
|
+
overflow: 'hidden',
|
|
56
49
|
},
|
|
57
50
|
iconView: {
|
|
58
51
|
flexDirection: 'row',
|
|
@@ -142,4 +135,28 @@ export default StyleSheet.create({
|
|
|
142
135
|
paddingVertical: Spacing.S,
|
|
143
136
|
fontWeight: 'bold',
|
|
144
137
|
},
|
|
138
|
+
|
|
139
|
+
//OTP
|
|
140
|
+
otpWrapper: {
|
|
141
|
+
width: '100%',
|
|
142
|
+
marginTop: Spacing.M,
|
|
143
|
+
},
|
|
144
|
+
otpInput: {
|
|
145
|
+
height: scaleSize(56),
|
|
146
|
+
borderRadius: Radius.S,
|
|
147
|
+
borderWidth: 1,
|
|
148
|
+
},
|
|
149
|
+
otpFloatingView: {
|
|
150
|
+
position: 'absolute',
|
|
151
|
+
top: -Spacing.S,
|
|
152
|
+
alignSelf: 'center',
|
|
153
|
+
paddingHorizontal: Spacing.S,
|
|
154
|
+
},
|
|
155
|
+
otpInputsView: {
|
|
156
|
+
width: '100%',
|
|
157
|
+
height: '100%',
|
|
158
|
+
flexDirection: 'row',
|
|
159
|
+
alignItems: 'center',
|
|
160
|
+
justifyContent: 'center',
|
|
161
|
+
},
|
|
145
162
|
});
|
package/Layout/Screen.tsx
CHANGED
|
@@ -107,12 +107,10 @@ const Screen: React.FC<ScreenProps> = ({
|
|
|
107
107
|
if (useExtendedHeader) {
|
|
108
108
|
title = {headerTitle: HeaderTitle};
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
...title,
|
|
115
|
-
});
|
|
110
|
+
navigation?.setOptions({
|
|
111
|
+
animatedValue: animatedValue.current,
|
|
112
|
+
headerTransparent: headerTransparent,
|
|
113
|
+
...title,
|
|
116
114
|
});
|
|
117
115
|
}
|
|
118
116
|
}, [useExtendedHeader, useAnimatedHeader]);
|
package/Layout/Section.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {useContext} from 'react';
|
|
1
|
+
import React, {Fragment, useContext} from 'react';
|
|
2
2
|
import {Dimensions, View, ViewProps} from 'react-native';
|
|
3
3
|
import {useGridSystem, validateChildren} from './utils';
|
|
4
4
|
import {GridContextProps} from './types';
|
|
@@ -135,6 +135,9 @@ const Section: React.FC<SectionProps> = ({
|
|
|
135
135
|
}
|
|
136
136
|
} else {
|
|
137
137
|
element.push(children);
|
|
138
|
+
if (children?.type == Fragment) {
|
|
139
|
+
return renderView(children?.props.children);
|
|
140
|
+
}
|
|
138
141
|
}
|
|
139
142
|
return element;
|
|
140
143
|
};
|
package/Layout/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Alert, Dimensions} from 'react-native';
|
|
2
|
-
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import React, {Fragment, ReactElement, useEffect, useState} from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* validate children type
|
|
@@ -7,53 +7,36 @@ import React, {useEffect, useState} from 'react';
|
|
|
7
7
|
* @param childrenTypes
|
|
8
8
|
*/
|
|
9
9
|
const validateChildren = (
|
|
10
|
-
children:
|
|
11
|
-
|
|
12
|
-
| string
|
|
13
|
-
| number
|
|
14
|
-
| {}
|
|
15
|
-
| Iterable<React.ReactNode>
|
|
16
|
-
| React.ReactPortal
|
|
17
|
-
| null
|
|
18
|
-
| undefined
|
|
19
|
-
| boolean,
|
|
20
|
-
childrenTypes: Array<React.FC<any>>,
|
|
10
|
+
children: React.ReactNode | React.ReactNode[],
|
|
11
|
+
childrenTypes: Array<React.FC>,
|
|
21
12
|
) => {
|
|
22
13
|
if (!__DEV__) return children;
|
|
23
14
|
|
|
24
|
-
let results: React.ReactElement[] = [];
|
|
25
15
|
const validateType = (child: React.ReactElement) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const alertWrongType = () => {
|
|
30
|
-
Alert.alert(
|
|
31
|
-
'Component Kits',
|
|
32
|
-
`Wrong children type for this component, expected are ${childrenTypes
|
|
33
|
-
.map(type => type.displayName)
|
|
34
|
-
.join(', ')}.`,
|
|
16
|
+
const valid = childrenTypes.some(
|
|
17
|
+
childrenType => child?.type === childrenType || child?.type == Fragment,
|
|
35
18
|
);
|
|
19
|
+
if (!valid) {
|
|
20
|
+
Alert.alert(
|
|
21
|
+
'Component Kits',
|
|
22
|
+
`Wrong children type for this component, expected are ${childrenTypes
|
|
23
|
+
.map(type => type.displayName)
|
|
24
|
+
.join(', ')}.`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return valid;
|
|
36
28
|
};
|
|
37
29
|
|
|
38
30
|
if (Array.isArray(children)) {
|
|
39
31
|
const flattenChildren: React.ReactElement[] = children.flat();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return child;
|
|
43
|
-
} else {
|
|
44
|
-
alertWrongType();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
} else {
|
|
48
|
-
const singleChild: any = children;
|
|
49
|
-
if (validateType(singleChild)) {
|
|
50
|
-
results = [singleChild];
|
|
51
|
-
} else {
|
|
52
|
-
alertWrongType();
|
|
32
|
+
for (const child of flattenChildren) {
|
|
33
|
+
validateType(child);
|
|
53
34
|
}
|
|
35
|
+
return flattenChildren;
|
|
36
|
+
} else {
|
|
37
|
+
validateType(children as ReactElement);
|
|
38
|
+
return children;
|
|
54
39
|
}
|
|
55
|
-
|
|
56
|
-
return results;
|
|
57
40
|
};
|
|
58
41
|
|
|
59
42
|
const useGridSystem = () => {
|
package/Switch/index.tsx
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import React, {FC
|
|
1
|
+
import React, {FC} from 'react';
|
|
2
2
|
import {TouchableOpacity, View} from 'react-native';
|
|
3
3
|
import {SwitchProps} from './types';
|
|
4
4
|
import styles from './styles';
|
|
5
|
-
import {ApplicationContext} from '../Application';
|
|
6
5
|
import {Colors} from '../Consts';
|
|
7
6
|
|
|
8
7
|
const Switch: FC<SwitchProps> = props => {
|
|
9
|
-
const {theme} = useContext(ApplicationContext);
|
|
10
8
|
const {value, onChange, disabled, style} = props;
|
|
11
9
|
const circleBackgroundColor = value ? Colors.black_01 : Colors.black_03;
|
|
12
10
|
const circleAlign = value ? 'flex-end' : 'flex-start';
|
|
13
11
|
|
|
14
|
-
let backgroundColor = value ?
|
|
12
|
+
let backgroundColor = value ? Colors.green_03 : Colors.black_07;
|
|
15
13
|
if (disabled) {
|
|
16
|
-
backgroundColor = value ?
|
|
14
|
+
backgroundColor = value ? Colors.green_09 : Colors.black_05;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
return (
|
package/Title/index.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, {FC, useContext} from 'react';
|
|
2
|
+
import {Text as RNText, TouchableOpacity, View} from 'react-native';
|
|
3
|
+
import {TitleProps} from './types';
|
|
4
|
+
import {Icon} from '../Icon';
|
|
5
|
+
import styles from './styles';
|
|
6
|
+
import {ApplicationContext} from '../Application';
|
|
7
|
+
import {Colors, Spacing} from '../Consts';
|
|
8
|
+
import {Button} from '../Button';
|
|
9
|
+
import {scaleSize, Text} from '../Text';
|
|
10
|
+
|
|
11
|
+
const Title: FC<TitleProps> = ({
|
|
12
|
+
size = 'large',
|
|
13
|
+
type = 'section',
|
|
14
|
+
onPressAction,
|
|
15
|
+
actionTitle,
|
|
16
|
+
title = 'Title',
|
|
17
|
+
titleFlex = true,
|
|
18
|
+
showAction = true,
|
|
19
|
+
icon,
|
|
20
|
+
description,
|
|
21
|
+
}) => {
|
|
22
|
+
const {theme} = useContext(ApplicationContext);
|
|
23
|
+
const flexStyle = titleFlex ? {flex: 1} : {};
|
|
24
|
+
const typeStyle =
|
|
25
|
+
type === 'screen' ? styles.screen : styles[`${type}_${size}`];
|
|
26
|
+
const actionColor = titleFlex
|
|
27
|
+
? theme.colors.primary
|
|
28
|
+
: theme.colors.text.default;
|
|
29
|
+
const actionBackground = titleFlex ? Colors.pink_08 + '4D' : Colors.black_06;
|
|
30
|
+
const actionSize = titleFlex ? scaleSize(22) : scaleSize(18);
|
|
31
|
+
const titleWeight = type === 'screen' ? 'Semibold' : 'Bold';
|
|
32
|
+
const marginStyle = type === 'section' ? {marginTop: Spacing.M} : {};
|
|
33
|
+
|
|
34
|
+
const renderAction = () => {
|
|
35
|
+
return !!actionTitle && titleFlex ? (
|
|
36
|
+
<Button
|
|
37
|
+
onPress={() => onPressAction?.()}
|
|
38
|
+
type={'text'}
|
|
39
|
+
full={false}
|
|
40
|
+
size={'small'}
|
|
41
|
+
title={actionTitle}
|
|
42
|
+
/>
|
|
43
|
+
) : (
|
|
44
|
+
<TouchableOpacity
|
|
45
|
+
style={[
|
|
46
|
+
{backgroundColor: actionBackground},
|
|
47
|
+
styles.actionIcon,
|
|
48
|
+
{width: actionSize, height: actionSize},
|
|
49
|
+
]}
|
|
50
|
+
onPress={onPressAction}>
|
|
51
|
+
<Icon
|
|
52
|
+
size={actionSize}
|
|
53
|
+
color={actionColor}
|
|
54
|
+
source={'arrow_chevron_right_small'}
|
|
55
|
+
/>
|
|
56
|
+
</TouchableOpacity>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<View style={styles.wrapper}>
|
|
62
|
+
<View style={[styles.container, marginStyle]}>
|
|
63
|
+
{!!icon && type !== 'screen' && (
|
|
64
|
+
<Icon style={styles.icon} source={icon} />
|
|
65
|
+
)}
|
|
66
|
+
<RNText
|
|
67
|
+
style={[
|
|
68
|
+
typeStyle,
|
|
69
|
+
flexStyle,
|
|
70
|
+
styles.title,
|
|
71
|
+
{
|
|
72
|
+
fontFamily: `${theme.font}-${titleWeight}`,
|
|
73
|
+
},
|
|
74
|
+
]}>
|
|
75
|
+
{title}
|
|
76
|
+
</RNText>
|
|
77
|
+
{showAction && type !== 'screen' && renderAction()}
|
|
78
|
+
</View>
|
|
79
|
+
{!!description && !!icon && type !== 'screen' && (
|
|
80
|
+
<Text typography={'description_s'} color={theme.colors.text.secondary}>
|
|
81
|
+
{description}
|
|
82
|
+
</Text>
|
|
83
|
+
)}
|
|
84
|
+
</View>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export {Title};
|
|
89
|
+
export type {TitleProps};
|
package/Title/styles.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {StyleSheet} from 'react-native';
|
|
2
|
+
import {scaleSize} from '../Text';
|
|
3
|
+
import {Radius, Spacing} from '../Consts';
|
|
4
|
+
|
|
5
|
+
export default StyleSheet.create({
|
|
6
|
+
card_small: {
|
|
7
|
+
fontSize: scaleSize(16),
|
|
8
|
+
lineHeight: scaleSize(22),
|
|
9
|
+
},
|
|
10
|
+
card_large: {
|
|
11
|
+
fontSize: scaleSize(18),
|
|
12
|
+
lineHeight: scaleSize(26),
|
|
13
|
+
},
|
|
14
|
+
section_small: {
|
|
15
|
+
fontSize: scaleSize(20),
|
|
16
|
+
lineHeight: scaleSize(28),
|
|
17
|
+
},
|
|
18
|
+
section_large: {
|
|
19
|
+
fontSize: scaleSize(24),
|
|
20
|
+
lineHeight: scaleSize(34),
|
|
21
|
+
},
|
|
22
|
+
screen: {
|
|
23
|
+
fontSize: scaleSize(36),
|
|
24
|
+
lineHeight: scaleSize(44),
|
|
25
|
+
},
|
|
26
|
+
title: {
|
|
27
|
+
marginRight: Spacing.XS,
|
|
28
|
+
},
|
|
29
|
+
container: {
|
|
30
|
+
flexDirection: 'row',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
width: '100%',
|
|
33
|
+
},
|
|
34
|
+
actionIcon: {
|
|
35
|
+
borderRadius: Radius.M,
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
},
|
|
39
|
+
icon: {
|
|
40
|
+
marginRight: Spacing.XS,
|
|
41
|
+
borderRadius: Radius.XS,
|
|
42
|
+
},
|
|
43
|
+
wrapper: {
|
|
44
|
+
width: '100%',
|
|
45
|
+
},
|
|
46
|
+
});
|
package/Title/types.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type TitleProps = {
|
|
2
|
+
type?: 'screen' | 'section' | 'card';
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
size?: 'large' | 'small';
|
|
6
|
+
icon?: string;
|
|
7
|
+
titleFlex?: boolean;
|
|
8
|
+
actionTitle?: string;
|
|
9
|
+
onPressAction?: () => void;
|
|
10
|
+
showAction?: boolean;
|
|
11
|
+
};
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momo-kits/foundation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.2-beta.1",
|
|
4
4
|
"description": "React Native Component Kits",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
7
|
+
"postinstall": "node verify.js"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"@momo-kits/foundation"
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
},
|
|
43
43
|
"author": "@momo-kits/foundation",
|
|
44
44
|
"license": "ISC"
|
|
45
|
-
}
|
|
45
|
+
}
|
package/verify.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
const package = path.join(`${__dirname}`, 'package.json');
|
|
6
|
+
const packageInfo = JSON.parse(fs.readFileSync(package, 'utf8'));
|
|
7
|
+
const packageVersion = parseInt(packageInfo?.version.split('.')?.[1]);
|
|
8
|
+
|
|
9
|
+
const app = path.join(`${__dirname}/../../../`, 'app.json');
|
|
10
|
+
const appInfo = JSON.parse(fs.readFileSync(app, 'utf8'));
|
|
11
|
+
const iOSAppTarget = appInfo?.client?.ios.deploymentTarget;
|
|
12
|
+
const androidAppTarget = appInfo?.client?.android.deploymentTarget;
|
|
13
|
+
|
|
14
|
+
if (appInfo.name === 'MoMoPlatform') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (packageVersion > iOSAppTarget || packageVersion > androidAppTarget) {
|
|
19
|
+
throw `Package ${packageInfo.name} version: ${packageInfo.version} require deploymentTarget ${packageVersion}`;
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error(`Installation failed: ${error.message}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
Binary file
|