@momo-kits/foundation 0.77.8-beta.4 → 0.77.8-beta.6

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.
@@ -1,12 +1,6 @@
1
- import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useMemo,
6
- useRef,
7
- useState,
8
- } from 'react';
1
+ import React, {useContext, useEffect, useRef} from 'react';
9
2
  import {
3
+ Dimensions,
10
4
  KeyboardAvoidingView,
11
5
  Platform,
12
6
  Pressable,
@@ -91,10 +85,11 @@ const Modal: React.FC<ModalParams> = props => {
91
85
 
92
86
  const BottomSheet: React.FC<BottomSheetParams> = props => {
93
87
  const {theme, navigator} = useContext(ApplicationContext);
88
+ const {height} = Dimensions.get('window');
94
89
  const {
95
90
  screen,
96
91
  options,
97
- useSnap = true,
92
+ snapPoints = [height * 0.5, height * 0.9],
98
93
  }: BottomSheetParams = props.route.params;
99
94
  const Component = useRef(screen).current;
100
95
  const navigation = new Navigation(props.navigation, theme);
@@ -105,31 +100,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
105
100
 
106
101
  delete params.screen;
107
102
 
108
- const [contentHeight, setContentHeight] = useState(undefined);
109
- const snapPoints = useMemo(() => {
110
- if (useSnap) {
111
- return ['50%', '90%'];
112
- }
113
- return [contentHeight ?? '50%'];
114
- }, [contentHeight]);
115
-
116
103
  const bottomSheetRef = useRef<any>(null);
117
104
  const mountedRef = useRef(true);
118
105
 
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
106
  let backgroundColor = theme.colors.background.default;
134
107
  if (options?.surface) {
135
108
  backgroundColor = theme.colors.background.surface;
@@ -154,7 +127,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
154
127
  <SafeAreaView style={Styles.flex}>
155
128
  <BottomSheetModal
156
129
  ref={bottomSheetRef}
157
- snapPoints={snapPoints}
130
+ snapPoints={snapPoints?.map(item => item + 68)}
158
131
  onDismiss={() => {
159
132
  if (mountedRef.current) {
160
133
  navigator?.pop();
@@ -162,7 +135,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
162
135
  }}
163
136
  handleComponent={null}
164
137
  backdropComponent={backdropComponent}>
165
- <View onLayout={handleOnLayout} accessibilityLabel={'BottomSheet'}>
138
+ <View accessibilityLabel={'BottomSheet'}>
166
139
  <View style={[styles.sheetContainer, {backgroundColor}]}>
167
140
  <View style={styles.indicatorContainer}>
168
141
  <View
@@ -21,10 +21,12 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
21
21
  theme,
22
22
  options,
23
23
  maxApi,
24
+ localization,
24
25
  }) => {
25
26
  const navigationRef = React.useRef<NavigationContainerRef>(null);
26
27
  const navigator = useRef(new Navigator(navigationRef));
27
28
  const [showGrid, setShowGrid] = useState(false);
29
+ const [language, setLanguage] = useState<'vi' | 'en'>('vi');
28
30
 
29
31
  useEffect(() => {
30
32
  const subscription = DeviceEventEmitter.addListener(
@@ -38,13 +40,32 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
38
40
  };
39
41
  }, []);
40
42
 
43
+ useEffect(() => {
44
+ const listener = maxApi?.observer?.('language', (lang: 'vi' | 'en') => {
45
+ setLanguage(lang);
46
+ });
47
+ return () => {
48
+ listener?.();
49
+ };
50
+ }, []);
51
+
41
52
  navigator.current.maxApi = maxApi;
42
53
 
54
+ const translate = (key: string) => {
55
+ return localization?.[language]?.[key] || '';
56
+ };
57
+
43
58
  return (
44
59
  <SafeAreaProvider>
45
60
  <BottomSheetModalProvider>
46
61
  <ApplicationContext.Provider
47
- value={{theme, navigator: navigator.current, showGrid}}>
62
+ value={{
63
+ theme,
64
+ navigator: navigator.current,
65
+ showGrid,
66
+ language,
67
+ translate,
68
+ }}>
48
69
  <ReactNavigationContainer
49
70
  theme={{
50
71
  ...theme,
@@ -1,5 +1,5 @@
1
- import {Animated, ScrollViewProps, ViewProps} from 'react-native';
2
- import React, {ReactNode} from 'react';
1
+ import {Animated} from 'react-native';
2
+ import React from 'react';
3
3
  import Navigator from './Navigator';
4
4
  import {PopupNotifyProps} from '../Popup/types';
5
5
  import Navigation from './Navigation';
@@ -65,6 +65,17 @@ export type Context = {
65
65
  theme: Theme;
66
66
  navigator?: Navigator;
67
67
  showGrid?: boolean;
68
+ language: 'vi' | 'en';
69
+ translate: (key: string) => string;
70
+ };
71
+
72
+ export type LocalizationObject = {
73
+ vi: {
74
+ [key: string]: string;
75
+ };
76
+ en: {
77
+ [key: string]: string;
78
+ };
68
79
  };
69
80
 
70
81
  export type NavigationContainerProps = {
@@ -72,6 +83,7 @@ export type NavigationContainerProps = {
72
83
  options?: NavigationOptions;
73
84
  theme: Theme;
74
85
  maxApi?: any;
86
+ localization: LocalizationObject;
75
87
  };
76
88
 
77
89
  export type NavigationScreenProps = {
@@ -80,19 +92,6 @@ export type NavigationScreenProps = {
80
92
  navigation: Navigation;
81
93
  };
82
94
 
83
- export interface ScreenProps extends ViewProps {
84
- navigation?: Navigation;
85
- enableKeyboardAvoidingView?: boolean;
86
- headerBackground?: string;
87
- scrollable?: boolean;
88
- useAnimatedHeader?: boolean;
89
- useExtendedHeader?: boolean;
90
- scrollViewProps?: ScrollViewProps;
91
- footerComponent?: ReactNode;
92
- marginHeader?: number;
93
- backgroundColor?: string;
94
- }
95
-
96
95
  export type ScreenParams = {
97
96
  [key: string]: any;
98
97
  screen: Screen;
@@ -108,7 +107,7 @@ export interface ModalParams extends ScreenParams {
108
107
  export interface BottomSheetParams extends ScreenParams {
109
108
  [key: string]: any;
110
109
  onDismiss?: () => void;
111
- useSnap?: boolean;
110
+ snapPoints?: ReadonlyArray<number>;
112
111
  }
113
112
 
114
113
  export type NavigationButtonProps = {
package/Button/index.tsx CHANGED
@@ -11,6 +11,7 @@ import {Typography} from '../Text/types';
11
11
  import {Colors} from '../Consts';
12
12
  import styles from './styles';
13
13
  import {Icon} from '../Icon';
14
+ import {Loader} from '../Loader';
14
15
 
15
16
  export interface ButtonProps extends TouchableOpacityProps {
16
17
  /**
@@ -55,6 +56,11 @@ export interface ButtonProps extends TouchableOpacityProps {
55
56
  */
56
57
  useTintColor?: boolean;
57
58
 
59
+ /**
60
+ * If true, the icon of button will use be show indicator loading.
61
+ */
62
+ loading?: boolean;
63
+
58
64
  /**
59
65
  * A callback function that is called when the button is pressed.
60
66
  */
@@ -68,6 +74,7 @@ const Button: FC<ButtonProps> = ({
68
74
  full = true,
69
75
  iconRight,
70
76
  iconLeft,
77
+ loading = false,
71
78
  title = 'Button',
72
79
  onPress,
73
80
  ...rest
@@ -223,6 +230,19 @@ const Button: FC<ButtonProps> = ({
223
230
  const renderTrailing = () => {
224
231
  const marginLeft = getIconSpace();
225
232
  const color = useTintColor ? getTextColor() : undefined;
233
+ if (loading) {
234
+ const iconSize = getIconSize();
235
+ const color = getTextColor();
236
+ return (
237
+ <View
238
+ style={[
239
+ styles.trailing,
240
+ {width: iconSize, height: iconSize, marginLeft},
241
+ ]}>
242
+ <Loader type="spinner" color={color} />
243
+ </View>
244
+ );
245
+ }
226
246
  if (iconRight) {
227
247
  const iconSize = getIconSize();
228
248
  return (
package/Divider/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React, {useContext} from 'react';
2
2
  import {View} from 'react-native';
3
3
  import {ApplicationContext} from '../Application';
4
+ import {Spacing} from '../Consts';
4
5
 
5
6
  const Divider = () => {
6
7
  const {theme} = useContext(ApplicationContext);
@@ -11,6 +12,7 @@ const Divider = () => {
11
12
  height: 1,
12
13
  width: '100%',
13
14
  backgroundColor: theme.colors.border.default,
15
+ marginVertical: Spacing.XS,
14
16
  }}
15
17
  />
16
18
  );
package/Input/Input.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, {FC, useContext, useRef, useState} from 'react';
1
+ import React, {FC, forwardRef, useContext, useRef, useState} from 'react';
2
2
  import {
3
3
  NativeSyntheticEvent,
4
4
  TextInput,
@@ -12,125 +12,144 @@ import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
12
12
  import {InputProps} from './index';
13
13
  import {Icon} from '../Icon';
14
14
  import {checkTyping} from './utils';
15
+ import {Loader} from '../Loader';
15
16
 
16
- const Input: FC<InputProps> = ({
17
- value,
18
- onChangeText,
19
- floatingValue,
20
- floatingIcon,
21
- size,
22
- placeholder,
23
- onBlur,
24
- onFocus,
25
- errorMessage,
26
- icon,
27
- disabled,
28
- floatingIconColor,
29
- iconColor,
30
- required,
31
- errorSpacing,
32
- ...props
33
- }) => {
34
- const {theme} = useContext(ApplicationContext);
35
- const [focused, setFocused] = useState(false);
36
- const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
17
+ const Input: FC<InputProps> = forwardRef(
18
+ (
19
+ {
20
+ value,
21
+ onChangeText,
22
+ floatingValue,
23
+ floatingIcon,
24
+ size,
25
+ placeholder,
26
+ onBlur,
27
+ onFocus,
28
+ errorMessage,
29
+ icon,
30
+ disabled,
31
+ floatingIconColor,
32
+ iconColor,
33
+ required,
34
+ errorSpacing,
35
+ loading,
36
+ ...props
37
+ },
38
+ ref,
39
+ ) => {
40
+ const {theme} = useContext(ApplicationContext);
41
+ const [focused, setFocused] = useState(false);
42
+ const [haveValue, setHaveValue] = useState(!!value || !!props.defaultValue);
43
+ const inputRef = (ref as React.RefObject<TextInput>) ?? useRef<TextInput>();
37
44
 
38
- const inputRef = useRef<any>(null);
45
+ const onClearText = () => {
46
+ inputRef?.current?.clear();
47
+ _onChangeText('');
48
+ };
39
49
 
40
- const onClearText = () => {
41
- inputRef?.current?.clear();
42
- _onChangeText('');
43
- };
50
+ const _onChangeText = (text: string) => {
51
+ checkTyping(text, haveValue, setHaveValue);
52
+ onChangeText?.(text);
53
+ };
44
54
 
45
- const _onChangeText = (text: string) => {
46
- checkTyping(text, haveValue, setHaveValue);
47
- onChangeText?.(text);
48
- };
55
+ const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
56
+ setFocused(true);
57
+ onFocus?.(e);
58
+ };
49
59
 
50
- const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
51
- setFocused(true);
52
- onFocus?.(e);
53
- };
60
+ const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
61
+ setFocused(false);
62
+ onBlur?.(e);
63
+ };
54
64
 
55
- const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
56
- setFocused(false);
57
- onBlur?.(e);
58
- };
65
+ const renderIcon = (color: string | undefined) => {
66
+ if (loading) {
67
+ return <Loader type={'spinner'} color={color} />;
68
+ }
69
+ if (icon) {
70
+ return <Icon color={color} source={icon} size={24} />;
71
+ }
72
+ };
59
73
 
60
- const renderInputView = () => {
61
- const disabledColor = theme.colors.text.disable;
62
- let textColor = theme.colors.text.default;
63
- let placeholderColor = theme.colors.text.hint;
64
- let iconTintColor = iconColor;
74
+ const renderInputView = () => {
75
+ const disabledColor = theme.colors.text.disable;
76
+ let textColor = theme.colors.text.default;
77
+ let placeholderColor = theme.colors.text.hint;
78
+ let iconTintColor = iconColor;
65
79
 
66
- if (disabled) {
67
- textColor = disabledColor;
68
- placeholderColor = disabledColor;
69
- iconTintColor = disabledColor;
70
- }
80
+ if (disabled) {
81
+ textColor = disabledColor;
82
+ placeholderColor = disabledColor;
83
+ iconTintColor = disabledColor;
84
+ }
71
85
 
72
- return (
73
- <View
74
- style={[
75
- styles.inputWrapper,
76
- {backgroundColor: theme.colors.background.surface},
77
- getSizeStyle(size),
78
- getBorderColor(theme, focused, errorMessage, disabled),
79
- ]}>
80
- <FloatingView
81
- floatingValue={floatingValue}
82
- floatingIconColor={floatingIconColor}
83
- disabled={disabled}
84
- required={required}
85
- floatingIcon={floatingIcon}
86
- />
87
- <View style={styles.inputView}>
88
- <TextInput
89
- {...props}
90
- accessibilityLabel={`TextInput`}
91
- editable={!disabled}
92
- textAlignVertical="top"
93
- ref={inputRef}
94
- style={[
95
- styles.input,
96
- {
97
- color: textColor,
98
- fontFamily: `${theme.font}-Regular`,
99
- },
100
- ]}
101
- textBreakStrategy="highQuality"
102
- value={value}
103
- onChangeText={_onChangeText}
104
- onFocus={_onFocus}
105
- onBlur={_onBlur}
106
- placeholder={placeholder}
107
- selectionColor={theme.colors.primary}
108
- placeholderTextColor={placeholderColor}
86
+ return (
87
+ <View
88
+ style={[
89
+ styles.inputWrapper,
90
+ {backgroundColor: theme.colors.background.surface},
91
+ getSizeStyle(size),
92
+ getBorderColor(theme, focused, errorMessage, disabled),
93
+ ]}>
94
+ <FloatingView
95
+ floatingValue={floatingValue}
96
+ floatingIconColor={floatingIconColor}
97
+ disabled={disabled}
98
+ required={required}
99
+ floatingIcon={floatingIcon}
109
100
  />
101
+ <View style={styles.inputView}>
102
+ <TextInput
103
+ {...props}
104
+ accessibilityLabel={`TextInput`}
105
+ editable={!disabled}
106
+ textAlignVertical="top"
107
+ ref={inputRef}
108
+ style={[
109
+ styles.input,
110
+ {
111
+ color: textColor,
112
+ fontFamily: `${theme.font}-Regular`,
113
+ },
114
+ ]}
115
+ textBreakStrategy="highQuality"
116
+ value={value}
117
+ onChangeText={_onChangeText}
118
+ onFocus={_onFocus}
119
+ onBlur={_onBlur}
120
+ placeholder={placeholder}
121
+ selectionColor={theme.colors.primary}
122
+ placeholderTextColor={placeholderColor}
123
+ />
124
+ </View>
125
+ <View style={styles.iconView}>
126
+ {focused && haveValue && (
127
+ <TouchableOpacity
128
+ style={styles.iconWrapper}
129
+ onPress={onClearText}>
130
+ <Icon
131
+ source="24_navigation_close_circle_full"
132
+ size={16}
133
+ color={theme.colors.text.hint}
134
+ />
135
+ </TouchableOpacity>
136
+ )}
137
+ {renderIcon(iconTintColor)}
138
+ </View>
110
139
  </View>
111
- <View style={styles.iconView}>
112
- {focused && haveValue && (
113
- <TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
114
- <Icon
115
- source="24_navigation_close_circle_full"
116
- size={16}
117
- color={theme.colors.text.hint}
118
- />
119
- </TouchableOpacity>
120
- )}
121
- {!!icon && <Icon color={iconTintColor} source={icon} size={24} />}
122
- </View>
140
+ );
141
+ };
142
+
143
+ return (
144
+ <View
145
+ style={styles.wrapper}
146
+ accessibilityLabel={`Input/${floatingValue}`}>
147
+ {renderInputView()}
148
+ <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
123
149
  </View>
124
150
  );
125
- };
126
-
127
- return (
128
- <View style={styles.wrapper} accessibilityLabel={`Input/${floatingValue}`}>
129
- {renderInputView()}
130
- <ErrorView errorMessage={errorMessage} errorSpacing={errorSpacing} />
131
- </View>
132
- );
133
- };
151
+ },
152
+ );
134
153
 
135
154
  Input.defaultProps = {
136
155
  size: 'small',