@momo-kits/foundation 0.79.6-beta.10 → 0.79.6-beta.14
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/ModalScreen.tsx +2 -1
- package/Input/InputOTP.tsx +171 -0
- package/Input/OTPCaret.tsx +50 -0
- package/Input/common.tsx +4 -1
- package/Input/index.tsx +13 -1
- package/Input/styles.ts +24 -0
- package/Switch/index.tsx +8 -2
- package/package.json +1 -1
|
@@ -241,11 +241,12 @@ const styles = StyleSheet.create({
|
|
|
241
241
|
indicatorContainer: {
|
|
242
242
|
width: '100%',
|
|
243
243
|
paddingTop: Spacing.S,
|
|
244
|
+
paddingBottom: Spacing.XS,
|
|
244
245
|
alignItems: 'center',
|
|
245
246
|
justifyContent: 'center',
|
|
246
247
|
},
|
|
247
248
|
indicator: {
|
|
248
|
-
width:
|
|
249
|
+
width: 40,
|
|
249
250
|
height: 4,
|
|
250
251
|
borderRadius: Radius.S,
|
|
251
252
|
},
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import React, {FC, useContext, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
KeyboardType,
|
|
4
|
+
NativeSyntheticEvent,
|
|
5
|
+
TextInput,
|
|
6
|
+
TextInputFocusEventData,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {InputOTPProps} from './index';
|
|
11
|
+
import {Text} from '../Text';
|
|
12
|
+
import styles from './styles';
|
|
13
|
+
import {ErrorView, getBorderColor} from './common';
|
|
14
|
+
import {ApplicationContext} from '../Application';
|
|
15
|
+
import {Spacing} from '../Consts';
|
|
16
|
+
import OTPCaret from './OTPCaret';
|
|
17
|
+
|
|
18
|
+
const InputOTP: FC<InputOTPProps> = ({
|
|
19
|
+
length,
|
|
20
|
+
errorMessage,
|
|
21
|
+
errorSpacing,
|
|
22
|
+
floatingValue,
|
|
23
|
+
floatingIconColor,
|
|
24
|
+
floatingIcon,
|
|
25
|
+
placeholder,
|
|
26
|
+
onChangeText,
|
|
27
|
+
onFocus,
|
|
28
|
+
onBlur,
|
|
29
|
+
dataType = 'number',
|
|
30
|
+
...props
|
|
31
|
+
}) => {
|
|
32
|
+
const MAX_LENGTH = 10;
|
|
33
|
+
const [value, setValue] = useState('');
|
|
34
|
+
const [focused, setFocused] = useState(false);
|
|
35
|
+
const inputRef = useRef<TextInput>(null);
|
|
36
|
+
const {theme} = useContext(ApplicationContext);
|
|
37
|
+
|
|
38
|
+
const onFocusInput = () => {
|
|
39
|
+
setFocused(true);
|
|
40
|
+
inputRef.current?.focus();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const onBlurInput = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
44
|
+
setFocused(false);
|
|
45
|
+
onBlur?.(e);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const _onChangeText = (text: string) => {
|
|
49
|
+
if (text.length > MAX_LENGTH) return;
|
|
50
|
+
if (dataType === 'number' && isNaN(Number(text))) return;
|
|
51
|
+
if (
|
|
52
|
+
!isNaN(Number(text)) &&
|
|
53
|
+
value.length >= Number(length) &&
|
|
54
|
+
text.length >= value.length
|
|
55
|
+
)
|
|
56
|
+
return;
|
|
57
|
+
|
|
58
|
+
setValue(text);
|
|
59
|
+
onChangeText?.(text);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const renderInputs = (length: number) => {
|
|
63
|
+
const TextInputs = [];
|
|
64
|
+
for (let i = 0; i < length; i++) {
|
|
65
|
+
TextInputs.push(
|
|
66
|
+
<View>
|
|
67
|
+
{focused && value.length === i ? (
|
|
68
|
+
<OTPCaret index={i} length={length} />
|
|
69
|
+
) : (
|
|
70
|
+
<Text
|
|
71
|
+
style={i !== length - 1 && {marginRight: Spacing.L}}
|
|
72
|
+
color={
|
|
73
|
+
!!value[i] ? theme.colors.text.default : theme.colors.text.hint
|
|
74
|
+
}
|
|
75
|
+
typography={!!value[i] ? 'title_s' : 'paragraph_default'}>
|
|
76
|
+
{value[i] || '-'}
|
|
77
|
+
</Text>
|
|
78
|
+
)}
|
|
79
|
+
</View>,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return TextInputs;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const renderUnidentifiedInputs = () => {
|
|
86
|
+
const splitValue = value.split('');
|
|
87
|
+
|
|
88
|
+
if (!focused && value.length === 0 && !!placeholder) {
|
|
89
|
+
return (
|
|
90
|
+
<Text color={theme.colors.text.hint} typography={'description_s'}>
|
|
91
|
+
{placeholder}
|
|
92
|
+
</Text>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View style={{flexDirection: 'row'}}>
|
|
98
|
+
{splitValue.map((letter, index) => {
|
|
99
|
+
return (
|
|
100
|
+
<Text
|
|
101
|
+
style={
|
|
102
|
+
(index !== value.length - 1 ||
|
|
103
|
+
(focused && value.length !== MAX_LENGTH)) && {
|
|
104
|
+
marginRight: Spacing.L,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
typography={'title_s'}>
|
|
108
|
+
{letter}
|
|
109
|
+
</Text>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
{focused && value.length !== MAX_LENGTH && <OTPCaret />}
|
|
113
|
+
</View>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const renderInputView = () => {
|
|
118
|
+
let placeholderColor = theme.colors.text.hint;
|
|
119
|
+
const keyboardType: KeyboardType =
|
|
120
|
+
dataType === 'number' ? 'number-pad' : 'default';
|
|
121
|
+
return (
|
|
122
|
+
<View
|
|
123
|
+
style={[
|
|
124
|
+
styles.otpInput,
|
|
125
|
+
{backgroundColor: theme.colors.background.surface},
|
|
126
|
+
getBorderColor(theme, focused, errorMessage),
|
|
127
|
+
]}>
|
|
128
|
+
{!!floatingValue && (
|
|
129
|
+
<View
|
|
130
|
+
style={[
|
|
131
|
+
styles.otpFloatingView,
|
|
132
|
+
{backgroundColor: theme.colors.background.surface},
|
|
133
|
+
]}>
|
|
134
|
+
<Text typography={'description_s'}>{floatingValue}</Text>
|
|
135
|
+
</View>
|
|
136
|
+
)}
|
|
137
|
+
<View style={styles.otpInputsView}>
|
|
138
|
+
{length ? renderInputs(length) : renderUnidentifiedInputs()}
|
|
139
|
+
</View>
|
|
140
|
+
<View style={styles.inputView}>
|
|
141
|
+
<TextInput
|
|
142
|
+
{...props}
|
|
143
|
+
accessibilityLabel={`TextInput`}
|
|
144
|
+
ref={inputRef}
|
|
145
|
+
value={value}
|
|
146
|
+
onChangeText={_onChangeText}
|
|
147
|
+
keyboardType={keyboardType}
|
|
148
|
+
onFocus={onFocusInput}
|
|
149
|
+
onBlur={onBlurInput}
|
|
150
|
+
style={{opacity: 0}}
|
|
151
|
+
selectionColor={theme.colors.primary}
|
|
152
|
+
placeholderTextColor={placeholderColor}
|
|
153
|
+
/>
|
|
154
|
+
</View>
|
|
155
|
+
</View>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<TouchableOpacity
|
|
161
|
+
activeOpacity={1}
|
|
162
|
+
onPress={onFocusInput}
|
|
163
|
+
style={styles.otpWrapper}
|
|
164
|
+
accessibilityLabel={`Input/${floatingValue}`}>
|
|
165
|
+
{renderInputView()}
|
|
166
|
+
<ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
|
|
167
|
+
</TouchableOpacity>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default InputOTP;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, {FC, useContext, useEffect, useRef} from 'react';
|
|
2
|
+
import {Animated, View} from 'react-native';
|
|
3
|
+
import {Spacing} from '../Consts';
|
|
4
|
+
import {scaleSize, Text} from '../Text';
|
|
5
|
+
import {ApplicationContext} from '../Application';
|
|
6
|
+
import {CaretProps} from './index';
|
|
7
|
+
|
|
8
|
+
const OtpCaret: FC<CaretProps> = ({index, length}) => {
|
|
9
|
+
const DURATION = 300;
|
|
10
|
+
const {theme} = useContext(ApplicationContext);
|
|
11
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
Animated.loop(
|
|
15
|
+
Animated.sequence([
|
|
16
|
+
Animated.timing(opacity, {
|
|
17
|
+
toValue: 1,
|
|
18
|
+
duration: DURATION,
|
|
19
|
+
useNativeDriver: true,
|
|
20
|
+
}),
|
|
21
|
+
Animated.delay(DURATION * 2),
|
|
22
|
+
Animated.timing(opacity, {
|
|
23
|
+
toValue: 0,
|
|
24
|
+
duration: DURATION,
|
|
25
|
+
useNativeDriver: true,
|
|
26
|
+
}),
|
|
27
|
+
]),
|
|
28
|
+
).start();
|
|
29
|
+
}, []);
|
|
30
|
+
const spacingStyle = !isNaN(Number(length)) &&
|
|
31
|
+
index !== Number(length) - 1 && {marginRight: Spacing.L};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={[{flexDirection: 'row', alignItems: 'center'}, spacingStyle]}>
|
|
35
|
+
<Animated.View
|
|
36
|
+
style={{
|
|
37
|
+
height: scaleSize(12),
|
|
38
|
+
width: 1,
|
|
39
|
+
backgroundColor: theme.colors.primary,
|
|
40
|
+
opacity,
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
<Text color={theme.colors.text.hint} typography={'paragraph_default'}>
|
|
44
|
+
-
|
|
45
|
+
</Text>
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default OtpCaret;
|
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,7 @@ 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';
|
|
5
6
|
import {TextInputProps} from 'react-native';
|
|
6
7
|
|
|
7
8
|
export interface InputProps extends TextInputProps {
|
|
@@ -109,4 +110,15 @@ export interface InputSearchProps extends InputPropsOmitForSearch {
|
|
|
109
110
|
|
|
110
111
|
export interface InputMoneyProps extends InputPropsWithoutPlaceholder {}
|
|
111
112
|
|
|
112
|
-
export
|
|
113
|
+
export type OTPInputLength = 2 | 4 | 6 | 8 | 10;
|
|
114
|
+
|
|
115
|
+
export interface InputOTPProps extends InputPropsWithoutSizeAndIcon {
|
|
116
|
+
length?: OTPInputLength;
|
|
117
|
+
dataType?: 'string' | 'number';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type CaretProps = {
|
|
121
|
+
index?: number;
|
|
122
|
+
length?: number;
|
|
123
|
+
};
|
|
124
|
+
export {Input, InputTextArea, InputSearch, InputMoney, InputOTP};
|
package/Input/styles.ts
CHANGED
|
@@ -142,4 +142,28 @@ export default StyleSheet.create({
|
|
|
142
142
|
paddingVertical: Spacing.S,
|
|
143
143
|
fontWeight: 'bold',
|
|
144
144
|
},
|
|
145
|
+
|
|
146
|
+
//OTP
|
|
147
|
+
otpWrapper: {
|
|
148
|
+
width: '100%',
|
|
149
|
+
marginTop: Spacing.M,
|
|
150
|
+
},
|
|
151
|
+
otpInput: {
|
|
152
|
+
height: scaleSize(56),
|
|
153
|
+
borderRadius: Radius.S,
|
|
154
|
+
borderWidth: 1,
|
|
155
|
+
},
|
|
156
|
+
otpFloatingView: {
|
|
157
|
+
position: 'absolute',
|
|
158
|
+
top: -Spacing.S,
|
|
159
|
+
alignSelf: 'center',
|
|
160
|
+
paddingHorizontal: Spacing.S,
|
|
161
|
+
},
|
|
162
|
+
otpInputsView: {
|
|
163
|
+
width: '100%',
|
|
164
|
+
height: '100%',
|
|
165
|
+
flexDirection: 'row',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
justifyContent: 'center',
|
|
168
|
+
},
|
|
145
169
|
});
|
package/Switch/index.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, {FC} from 'react';
|
|
2
|
-
import {TouchableOpacity, View} from 'react-native';
|
|
2
|
+
import {NativeModules, TouchableOpacity, View} from 'react-native';
|
|
3
3
|
import {SwitchProps} from './types';
|
|
4
4
|
import styles from './styles';
|
|
5
5
|
import {Colors} from '../Consts';
|
|
6
6
|
|
|
7
|
+
const {HapticModule} = NativeModules;
|
|
7
8
|
const Switch: FC<SwitchProps> = props => {
|
|
8
9
|
const {value, onChange, disabled, style} = props;
|
|
9
10
|
const circleBackgroundColor = value ? Colors.black_01 : Colors.black_03;
|
|
@@ -14,11 +15,16 @@ const Switch: FC<SwitchProps> = props => {
|
|
|
14
15
|
backgroundColor = value ? Colors.green_09 : Colors.black_05;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
const onChangeValue = () => {
|
|
19
|
+
HapticModule?.triggerHaptic?.('medium');
|
|
20
|
+
onChange?.(!value);
|
|
21
|
+
};
|
|
22
|
+
|
|
17
23
|
return (
|
|
18
24
|
<TouchableOpacity
|
|
19
25
|
accessibilityLabel={'Switch'}
|
|
20
26
|
disabled={disabled}
|
|
21
|
-
onPress={
|
|
27
|
+
onPress={onChangeValue}
|
|
22
28
|
style={[
|
|
23
29
|
style,
|
|
24
30
|
styles.container,
|