@momo-kits/foundation 0.79.6 → 0.80.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.
@@ -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
- <TouchableOpacity
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
- </TouchableOpacity>
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 style={styles.headerBackground} source={{uri: headerImage}} />
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
- <TouchableOpacity
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
- </TouchableOpacity>
401
+ </Touchable>
396
402
  <View style={[styles.divider, {backgroundColor: tintColor}]} />
397
- <TouchableOpacity
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
- </TouchableOpacity>
409
+ </Touchable>
406
410
  </View>
407
411
  </View>
408
412
  );
@@ -1,11 +1,4 @@
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,
@@ -13,8 +6,12 @@ import {
13
6
  StyleSheet,
14
7
  View,
15
8
  } from 'react-native';
16
- import {SafeAreaView} from 'react-native-safe-area-context';
17
- import {BottomSheetBackdrop, BottomSheetModal} from '@gorhom/bottom-sheet';
9
+ import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
10
+ import {
11
+ BottomSheetBackdrop,
12
+ BottomSheetModal,
13
+ BottomSheetView,
14
+ } from '@gorhom/bottom-sheet';
18
15
  import Navigation from './Navigation';
19
16
  import {BottomSheetParams, ModalParams} from './types';
20
17
  import {Radius, Spacing, Styles} from '../Consts';
@@ -90,12 +87,9 @@ const Modal: React.FC<ModalParams> = props => {
90
87
  };
91
88
 
92
89
  const BottomSheet: React.FC<BottomSheetParams> = props => {
90
+ const insets = useSafeAreaInsets();
93
91
  const {theme, navigator} = useContext(ApplicationContext);
94
- const {
95
- screen,
96
- options,
97
- useSnap = true,
98
- }: BottomSheetParams = props.route.params;
92
+ const {screen, options, snapPoint}: BottomSheetParams = props.route.params;
99
93
  const Component = useRef(screen).current;
100
94
  const navigation = new Navigation(props.navigation, theme);
101
95
  const params = {
@@ -105,31 +99,16 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
105
99
 
106
100
  delete params.screen;
107
101
 
108
- const [contentHeight, setContentHeight] = useState(undefined);
109
102
  const snapPoints = useMemo(() => {
110
- if (useSnap) {
111
- return ['50%', '90%'];
103
+ if (snapPoint) {
104
+ return [snapPoint];
112
105
  }
113
- return [contentHeight ?? '50%'];
114
- }, [contentHeight]);
106
+ return ['50%', '70%', '90%'];
107
+ }, []);
115
108
 
116
109
  const bottomSheetRef = useRef<any>(null);
117
110
  const mountedRef = useRef(true);
118
111
 
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
112
  let backgroundColor = theme.colors.background.default;
134
113
  if (options?.surface) {
135
114
  backgroundColor = theme.colors.background.surface;
@@ -162,8 +141,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
162
141
  }}
163
142
  handleComponent={null}
164
143
  backdropComponent={backdropComponent}>
165
- <View onLayout={handleOnLayout} accessibilityLabel={'BottomSheet'}>
166
- <View style={[styles.sheetContainer, {backgroundColor}]}>
144
+ <BottomSheetView accessibilityLabel={'BottomSheet'}>
145
+ <View
146
+ style={[
147
+ styles.sheetContainer,
148
+ {
149
+ backgroundColor,
150
+ paddingBottom: Math.min(insets.bottom, 20) + Spacing.S,
151
+ },
152
+ ]}>
167
153
  <View style={styles.indicatorContainer}>
168
154
  <View
169
155
  style={[
@@ -222,7 +208,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
222
208
  }}
223
209
  />
224
210
  </View>
225
- </View>
211
+ </BottomSheetView>
226
212
  </BottomSheetModal>
227
213
  </SafeAreaView>
228
214
  );
@@ -255,11 +241,12 @@ const styles = StyleSheet.create({
255
241
  indicatorContainer: {
256
242
  width: '100%',
257
243
  paddingTop: Spacing.S,
244
+ paddingBottom: Spacing.XS,
258
245
  alignItems: 'center',
259
246
  justifyContent: 'center',
260
247
  },
261
248
  indicator: {
262
- width: 64,
249
+ width: 40,
263
250
  height: 4,
264
251
  borderRadius: Radius.S,
265
252
  },
@@ -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
- return localization?.[language]?.[key] || key;
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, useEffect} from 'react';
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
- useEffect(() => {
19
+ useLayoutEffect(() => {
20
20
  if (options) {
21
21
  navigation.setOptions(options);
22
22
  }
@@ -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
- useSnap?: boolean;
110
+ snapPoint?: '30%' | '50%' | '70%' | '90%';
111
111
  }
112
112
 
113
113
  export type NavigationButtonProps = {
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
- image,
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
- {!!image && <Image style={imageStyle} source={{uri: image}} />}
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
- image?: string;
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 {Input, InputTextArea, InputSearch, InputMoney};
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
- image: {
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
- setImmediate(() => {
111
- navigation?.setOptions({
112
- animatedValue: animatedValue.current,
113
- headerTransparent: headerTransparent,
114
- ...title,
115
- });
110
+ navigation?.setOptions({
111
+ animatedValue: animatedValue.current,
112
+ headerTransparent: headerTransparent,
113
+ ...title,
116
114
  });
117
115
  }
118
116
  }, [useExtendedHeader, useAnimatedHeader]);
@@ -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
- | React.ReactElement<any, string | React.JSXElementConstructor<any>>
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
- return childrenTypes.some(childrenType => child?.type === childrenType);
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
- results = flattenChildren.filter((child: React.ReactElement) => {
41
- if (validateType(child)) {
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, useContext} from 'react';
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 ? theme.colors.primary : Colors.black_07;
12
+ let backgroundColor = value ? Colors.green_03 : Colors.black_07;
15
13
  if (disabled) {
16
- backgroundColor = value ? theme.colors.background.tonal : Colors.black_05;
14
+ backgroundColor = value ? Colors.green_09 : Colors.black_05;
17
15
  }
18
16
 
19
17
  return (
@@ -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};
@@ -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
@@ -45,3 +45,5 @@ export * from './Pagination';
45
45
  export * from './Pagination/types';
46
46
  export * from './Loader';
47
47
  export * from './Loader/types';
48
+ export * from './Title';
49
+ export * from './Title/types';
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.79.6",
3
+ "version": "0.80.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "postinstall": "node verify.js"
8
8
  },
9
9
  "keywords": [
10
10
  "@momo-kits/foundation"
package/verify.js ADDED
@@ -0,0 +1,22 @@
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 (packageVersion > iOSAppTarget || packageVersion > androidAppTarget) {
15
+ throw new Error(
16
+ `Package ${packageInfo.name} version: ${packageInfo.version} require deploymentTarget ${packageVersion}`,
17
+ );
18
+ }
19
+ } catch (error) {
20
+ console.error(`Installation failed: ${error.message}`);
21
+ process.exit(1);
22
+ }
Binary file