@momo-kits/foundation 0.112.1-testing.20 → 0.113.0-rc.0
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/BottomTab/index.tsx +2 -1
- package/Application/NavigationContainer.tsx +11 -4
- package/Application/StackScreen.tsx +10 -2
- package/Application/index.ts +4 -2
- package/Application/types.ts +5 -0
- package/Button/index.tsx +11 -1
- package/Consts/colors+spacing+radius.ts +1 -31
- package/Image/index.tsx +11 -1
- package/Input/InputMoney.tsx +197 -2
- package/Layout/Screen.tsx +14 -1
- package/Skeleton/index.tsx +58 -3
- package/Text/index.tsx +27 -3
- package/package.json +1 -1
|
@@ -109,7 +109,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
109
109
|
initialRouteName,
|
|
110
110
|
floatingButton,
|
|
111
111
|
}) => {
|
|
112
|
-
const {theme} = useContext(ApplicationContext);
|
|
112
|
+
const {theme, navigator} = useContext(ApplicationContext);
|
|
113
113
|
const insets = useSafeAreaInsets();
|
|
114
114
|
|
|
115
115
|
useEffect(() => {
|
|
@@ -127,6 +127,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
127
127
|
state?: (e: any) => void;
|
|
128
128
|
} = {
|
|
129
129
|
tabPress: e => {
|
|
130
|
+
navigator?.maxApi?.triggerEventVibration?.('light');
|
|
130
131
|
listeners?.tabPress?.(e);
|
|
131
132
|
},
|
|
132
133
|
focus: e => {
|
|
@@ -16,7 +16,7 @@ import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
|
|
|
16
16
|
import {NavigationContainerProps} from './types';
|
|
17
17
|
import {ApplicationContext, MiniAppContext} from './index';
|
|
18
18
|
import Localize from './Localize';
|
|
19
|
-
import {defaultTheme
|
|
19
|
+
import {defaultTheme} from '../Consts';
|
|
20
20
|
|
|
21
21
|
const Stack = createStackNavigator();
|
|
22
22
|
|
|
@@ -36,6 +36,16 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
36
36
|
const [showGrid, setShowGrid] = useState(false);
|
|
37
37
|
const [currentContext, setCurrentContext] = useState({});
|
|
38
38
|
|
|
39
|
+
let headerBackground = context?.designConfig?.headerBar;
|
|
40
|
+
let headerGradient = theme.colors?.gradient;
|
|
41
|
+
|
|
42
|
+
if (theme.assets?.headerBackground) {
|
|
43
|
+
headerBackground = theme.assets?.headerBackground;
|
|
44
|
+
}
|
|
45
|
+
if (context?.designConfig?.headerGradient) {
|
|
46
|
+
headerGradient = context?.designConfig?.headerGradient;
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
/**
|
|
40
50
|
* inject data for navigator
|
|
41
51
|
*/
|
|
@@ -96,9 +106,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
96
106
|
});
|
|
97
107
|
};
|
|
98
108
|
|
|
99
|
-
const headerBackground = theme.assets?.headerBackground || Configs.headerBar;
|
|
100
|
-
const headerGradient = Configs?.headerGradient || theme.colors?.gradient;
|
|
101
|
-
|
|
102
109
|
navigator.current.setCurrentContext = setCurrentContext;
|
|
103
110
|
|
|
104
111
|
return (
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, {useContext, useEffect, useLayoutEffect, useRef} from 'react';
|
|
2
2
|
import {useHeaderHeight} from '@react-navigation/stack';
|
|
3
3
|
import {Alert, InteractionManager} from 'react-native';
|
|
4
|
-
import {ScreenParams} from './types';
|
|
4
|
+
import {ScreenParams, ScreenTrackingParams} from './types';
|
|
5
5
|
import Navigation from './Navigation';
|
|
6
6
|
import {ApplicationContext, MiniAppContext, ScreenContext} from './index';
|
|
7
7
|
import {GridSystem} from '../Layout';
|
|
8
|
-
|
|
8
|
+
import {version} from '../package.json';
|
|
9
9
|
const runAfterInteractions = InteractionManager.runAfterInteractions;
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -28,6 +28,7 @@ const StackScreen: React.FC<ScreenParams> = props => {
|
|
|
28
28
|
timeLoad: 0,
|
|
29
29
|
timeInteraction: 0,
|
|
30
30
|
widgets: [],
|
|
31
|
+
params: undefined,
|
|
31
32
|
});
|
|
32
33
|
const widgets = useRef<any>([]);
|
|
33
34
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -117,6 +118,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
|
|
|
117
118
|
state: 'load',
|
|
118
119
|
duration: timeLoad,
|
|
119
120
|
widgets: tracking.current.widgets,
|
|
121
|
+
params: tracking.current.params,
|
|
122
|
+
version: version,
|
|
120
123
|
});
|
|
121
124
|
navigator?.maxApi?.stopTrace?.(
|
|
122
125
|
tracking.current.traceIdLoad,
|
|
@@ -165,6 +168,8 @@ const StackScreen: React.FC<ScreenParams> = props => {
|
|
|
165
168
|
state: 'interaction',
|
|
166
169
|
duration: tracking.current.timeInteraction - timeLoad,
|
|
167
170
|
totalDuration: tracking.current.timeInteraction,
|
|
171
|
+
params: tracking.current.params,
|
|
172
|
+
version: version,
|
|
168
173
|
});
|
|
169
174
|
navigator?.maxApi?.stopTrace?.(
|
|
170
175
|
tracking.current.traceIdInteraction,
|
|
@@ -254,6 +259,9 @@ const StackScreen: React.FC<ScreenParams> = props => {
|
|
|
254
259
|
onScreenInteraction();
|
|
255
260
|
}, 2000);
|
|
256
261
|
},
|
|
262
|
+
onSetParams: (data: ScreenTrackingParams) => {
|
|
263
|
+
tracking.current.params = data;
|
|
264
|
+
},
|
|
257
265
|
}}>
|
|
258
266
|
<Component heightHeader={heightHeader} {...data} />
|
|
259
267
|
{showGrid && <GridSystem />}
|
package/Application/index.ts
CHANGED
|
@@ -16,15 +16,17 @@ import {setAutomationID} from './utils';
|
|
|
16
16
|
const Context = createContext({});
|
|
17
17
|
const ApplicationContext = createContext(defaultContext);
|
|
18
18
|
|
|
19
|
-
const MiniAppContext = (Platform as any).MiniAppContext ??
|
|
20
|
-
const ScreenContext = (Platform as any).ScreenContext ??
|
|
19
|
+
const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
|
|
20
|
+
const ScreenContext = (Platform as any).ScreenContext ?? Context;
|
|
21
21
|
const ComponentContext = (Platform as any).ComponentContext ?? Context;
|
|
22
|
+
const SkeletonContext = createContext({loading: false});
|
|
22
23
|
|
|
23
24
|
export {
|
|
24
25
|
ApplicationContext,
|
|
25
26
|
MiniAppContext,
|
|
26
27
|
ScreenContext,
|
|
27
28
|
ComponentContext,
|
|
29
|
+
SkeletonContext,
|
|
28
30
|
NavigationContainer,
|
|
29
31
|
Localize,
|
|
30
32
|
HeaderTitle,
|
package/Application/types.ts
CHANGED
|
@@ -117,6 +117,11 @@ export type ScreenParams = {
|
|
|
117
117
|
options?: NavigationOptions;
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
export type ScreenTrackingParams = {
|
|
121
|
+
value1?: any;
|
|
122
|
+
value2?: any;
|
|
123
|
+
};
|
|
124
|
+
|
|
120
125
|
export type ModalParams = {
|
|
121
126
|
[key: string]: any;
|
|
122
127
|
screen: React.ComponentType;
|
package/Button/index.tsx
CHANGED
|
@@ -7,13 +7,18 @@ import {
|
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
import LinearGradient from 'react-native-linear-gradient';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ApplicationContext,
|
|
12
|
+
ComponentContext,
|
|
13
|
+
SkeletonContext,
|
|
14
|
+
} from '../Application';
|
|
11
15
|
import {Text} from '../Text';
|
|
12
16
|
import {Typography} from '../Text/types';
|
|
13
17
|
import {Colors, Spacing} from '../Consts';
|
|
14
18
|
import styles from './styles';
|
|
15
19
|
import {Icon} from '../Icon';
|
|
16
20
|
import {Loader} from '../Loader';
|
|
21
|
+
import {Skeleton} from '../Skeleton';
|
|
17
22
|
|
|
18
23
|
const AnimationLinear = Animated.createAnimatedComponent(LinearGradient);
|
|
19
24
|
|
|
@@ -88,6 +93,7 @@ const Button: FC<ButtonProps> = ({
|
|
|
88
93
|
...rest
|
|
89
94
|
}) => {
|
|
90
95
|
const {theme, config} = useContext(ApplicationContext);
|
|
96
|
+
const skeleton = useContext(SkeletonContext);
|
|
91
97
|
const {gradient, color} = config?.navigationBar?.buttonColors ?? {};
|
|
92
98
|
let gradientPros;
|
|
93
99
|
let state = 'enabled';
|
|
@@ -289,6 +295,10 @@ const Button: FC<ButtonProps> = ({
|
|
|
289
295
|
full && {width: '100%'},
|
|
290
296
|
]);
|
|
291
297
|
|
|
298
|
+
if (skeleton?.loading) {
|
|
299
|
+
return <Skeleton style={[buttonStyle, {paddingHorizontal: 0}]} />;
|
|
300
|
+
}
|
|
301
|
+
|
|
292
302
|
return (
|
|
293
303
|
<ComponentContext.Provider
|
|
294
304
|
value={{
|
|
@@ -229,34 +229,4 @@ const Shadow = {
|
|
|
229
229
|
}),
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
headerGradient: undefined,
|
|
234
|
-
headerBar: undefined,
|
|
235
|
-
trustBanner: {
|
|
236
|
-
content: {
|
|
237
|
-
vi: 'Bảo mật thông tin & An toàn tài sản của bạn là ưu tiên hàng đầu của MoMo.',
|
|
238
|
-
en: "Your data security and money safety are MoMo's top priorities.",
|
|
239
|
-
},
|
|
240
|
-
subContent: {
|
|
241
|
-
vi: 'Tìm hiểu thêm',
|
|
242
|
-
en: 'Learn more',
|
|
243
|
-
},
|
|
244
|
-
pciImage: 'https://static.momocdn.net/app/img/kits/trustBanner/pci_dss.png',
|
|
245
|
-
sslImage: 'https://static.momocdn.net/app/img/kits/trustBanner/ssl.png',
|
|
246
|
-
momoImage:
|
|
247
|
-
'https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png',
|
|
248
|
-
urlConfig: 'login_and_security',
|
|
249
|
-
icons: [
|
|
250
|
-
'https://static.momocdn.net/app/img/kits/trustBanner/ic_viettinbank.png',
|
|
251
|
-
'https://static.momocdn.net/app/img/kits/trustBanner/ic_agribank.png',
|
|
252
|
-
'https://static.momocdn.net/app/img/kits/trustBanner/ic_vietcombank.png',
|
|
253
|
-
'https://static.momocdn.net/app/img/kits/trustBanner/ic_bidv.png',
|
|
254
|
-
],
|
|
255
|
-
titleWeb: {
|
|
256
|
-
vi: 'Thông tin',
|
|
257
|
-
en: 'Information',
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
export {Colors, Spacing, Radius, Shadow, Configs};
|
|
232
|
+
export {Colors, Spacing, Radius, Shadow};
|
package/Image/index.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React, {useContext, useRef, useState} from 'react';
|
|
|
2
2
|
import {StyleSheet, View} from 'react-native';
|
|
3
3
|
import FastImage, {Source} from 'react-native-fast-image';
|
|
4
4
|
import styles from './styles';
|
|
5
|
-
import {ApplicationContext} from '../Application';
|
|
5
|
+
import {ApplicationContext, SkeletonContext} from '../Application';
|
|
6
6
|
import {Skeleton} from '../Skeleton';
|
|
7
7
|
import {Icon} from '../Icon';
|
|
8
8
|
import {Styles} from '../Consts';
|
|
@@ -19,6 +19,7 @@ const Image: React.FC<ImageProps> = ({
|
|
|
19
19
|
...rest
|
|
20
20
|
}) => {
|
|
21
21
|
const {theme} = useContext(ApplicationContext);
|
|
22
|
+
const skeleton = useContext(SkeletonContext);
|
|
22
23
|
const error = useRef(false);
|
|
23
24
|
const [status, setStatus] = useState<Status>('success');
|
|
24
25
|
|
|
@@ -43,6 +44,15 @@ const Image: React.FC<ImageProps> = ({
|
|
|
43
44
|
</View>
|
|
44
45
|
);
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
if (skeleton.loading) {
|
|
49
|
+
return (
|
|
50
|
+
<View style={[StyleSheet.absoluteFill, Styles.flexCenter]}>
|
|
51
|
+
<Skeleton />
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
return children;
|
|
47
57
|
};
|
|
48
58
|
|
package/Input/InputMoney.tsx
CHANGED
|
@@ -25,8 +25,203 @@ import {
|
|
|
25
25
|
import {InputMoneyProps} from './index';
|
|
26
26
|
import styles from './styles';
|
|
27
27
|
import {formatMoneyToNumber, formatNumberToMoney} from './utils';
|
|
28
|
-
import {Input} from '@momo-kits/foundation';
|
|
29
28
|
|
|
30
|
-
const InputMoney =
|
|
29
|
+
const InputMoney = forwardRef(
|
|
30
|
+
(
|
|
31
|
+
{
|
|
32
|
+
onChangeText,
|
|
33
|
+
floatingValue,
|
|
34
|
+
floatingIcon,
|
|
35
|
+
size = 'small',
|
|
36
|
+
loading,
|
|
37
|
+
onBlur,
|
|
38
|
+
onFocus,
|
|
39
|
+
errorMessage,
|
|
40
|
+
icon,
|
|
41
|
+
iconColor,
|
|
42
|
+
onPressIcon,
|
|
43
|
+
trailing,
|
|
44
|
+
trailingColor,
|
|
45
|
+
onPressTrailing,
|
|
46
|
+
disabled = false,
|
|
47
|
+
floatingIconColor,
|
|
48
|
+
required = false,
|
|
49
|
+
errorSpacing,
|
|
50
|
+
style,
|
|
51
|
+
params,
|
|
52
|
+
defaultValue = '',
|
|
53
|
+
accessibilityLabel,
|
|
54
|
+
hintText,
|
|
55
|
+
value: _value,
|
|
56
|
+
onPressFloatingIcon,
|
|
57
|
+
placeholder = '0đ',
|
|
58
|
+
currency = 'đ',
|
|
59
|
+
showClearIcon = true,
|
|
60
|
+
...props
|
|
61
|
+
}: InputMoneyProps,
|
|
62
|
+
ref
|
|
63
|
+
) => {
|
|
64
|
+
const {theme} = useContext(ApplicationContext);
|
|
65
|
+
|
|
66
|
+
const [focused, setFocused] = useState(false);
|
|
67
|
+
const inputRef = useRef<TextInput>(null);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (_value) {
|
|
71
|
+
setValue(validateText(_value));
|
|
72
|
+
onChangeText?.(formatMoneyToNumber(_value, currency).toString());
|
|
73
|
+
}
|
|
74
|
+
}, [_value]);
|
|
75
|
+
|
|
76
|
+
const validateText = (text: string) => {
|
|
77
|
+
const valueFormat = formatMoneyToNumber(text, currency);
|
|
78
|
+
return formatNumberToMoney(valueFormat, currency);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const [value, setValue] = useState(
|
|
82
|
+
defaultValue ? validateText(defaultValue) : ''
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const onClearText = () => {
|
|
86
|
+
inputRef?.current?.clear();
|
|
87
|
+
setValue('');
|
|
88
|
+
onChangeText?.('');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const _onChangeText = (text: string) => {
|
|
92
|
+
if (text.length < value.length && value.indexOf(text) === 0) {
|
|
93
|
+
text = value.slice(0, -2) + currency;
|
|
94
|
+
}
|
|
95
|
+
setValue(validateText(text));
|
|
96
|
+
onChangeText?.(formatMoneyToNumber(text, currency).toString());
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
useImperativeHandle(ref, () => {
|
|
100
|
+
return {
|
|
101
|
+
clear: onClearText,
|
|
102
|
+
focus: () => inputRef.current?.focus(),
|
|
103
|
+
blur: () => inputRef.current?.blur(),
|
|
104
|
+
setText: _onChangeText,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
109
|
+
setFocused(true);
|
|
110
|
+
onFocus?.(e);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
114
|
+
setFocused(false);
|
|
115
|
+
onBlur?.(e);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const renderInputView = () => {
|
|
119
|
+
const disabledColor = theme.colors.text.disable;
|
|
120
|
+
let textColor = theme.colors.text.default;
|
|
121
|
+
let placeholderColor = theme.colors.text.hint;
|
|
122
|
+
let iconTintColor = trailingColor ?? iconColor;
|
|
123
|
+
|
|
124
|
+
if (disabled) {
|
|
125
|
+
textColor = disabledColor;
|
|
126
|
+
placeholderColor = disabledColor;
|
|
127
|
+
iconTintColor = disabledColor;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<View
|
|
132
|
+
style={[
|
|
133
|
+
styles.inputWrapper,
|
|
134
|
+
{backgroundColor: theme.colors.background.surface},
|
|
135
|
+
getSizeStyle(size),
|
|
136
|
+
getBorderColor(theme, focused, errorMessage, disabled),
|
|
137
|
+
]}>
|
|
138
|
+
<FloatingView
|
|
139
|
+
floatingValue={floatingValue}
|
|
140
|
+
floatingIconColor={floatingIconColor}
|
|
141
|
+
disabled={disabled}
|
|
142
|
+
required={required}
|
|
143
|
+
floatingIcon={floatingIcon}
|
|
144
|
+
onPress={onPressFloatingIcon}
|
|
145
|
+
/>
|
|
146
|
+
<View style={styles.inputView}>
|
|
147
|
+
<TextInput
|
|
148
|
+
{...props}
|
|
149
|
+
accessibilityLabel={accessibilityLabel}
|
|
150
|
+
editable={!disabled}
|
|
151
|
+
ref={inputRef}
|
|
152
|
+
keyboardType={'number-pad'}
|
|
153
|
+
allowFontScaling={false}
|
|
154
|
+
style={[
|
|
155
|
+
styles.moneyInput,
|
|
156
|
+
{
|
|
157
|
+
color: textColor,
|
|
158
|
+
},
|
|
159
|
+
]}
|
|
160
|
+
value={value}
|
|
161
|
+
onChangeText={_onChangeText}
|
|
162
|
+
onFocus={_onFocus}
|
|
163
|
+
onBlur={_onBlur}
|
|
164
|
+
selectionColor={theme.colors.primary}
|
|
165
|
+
placeholderTextColor={placeholderColor}
|
|
166
|
+
placeholder={placeholder}
|
|
167
|
+
/>
|
|
168
|
+
</View>
|
|
169
|
+
<View style={styles.iconView}>
|
|
170
|
+
{showClearIcon && focused && (
|
|
171
|
+
<TouchableOpacity
|
|
172
|
+
style={styles.iconWrapper}
|
|
173
|
+
onPress={onClearText}>
|
|
174
|
+
<Icon
|
|
175
|
+
source="24_navigation_close_circle_full"
|
|
176
|
+
size={16}
|
|
177
|
+
color={theme.colors.text.hint}
|
|
178
|
+
/>
|
|
179
|
+
</TouchableOpacity>
|
|
180
|
+
)}
|
|
181
|
+
<RenderTrailing
|
|
182
|
+
color={iconTintColor}
|
|
183
|
+
icon={icon}
|
|
184
|
+
trailing={trailing}
|
|
185
|
+
onPressTrailing={onPressTrailing}
|
|
186
|
+
onPressIcon={onPressIcon}
|
|
187
|
+
loading={loading}
|
|
188
|
+
/>
|
|
189
|
+
</View>
|
|
190
|
+
</View>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
let inputState = 'active';
|
|
195
|
+
|
|
196
|
+
if (value && value?.length > 0) {
|
|
197
|
+
inputState = 'filled';
|
|
198
|
+
}
|
|
199
|
+
if (errorMessage && errorMessage?.length > 0) {
|
|
200
|
+
inputState = 'error';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (disabled) {
|
|
204
|
+
inputState = 'disabled';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<ComponentContext.Provider
|
|
209
|
+
value={{
|
|
210
|
+
componentName: 'InputMoney',
|
|
211
|
+
params,
|
|
212
|
+
state: inputState,
|
|
213
|
+
}}>
|
|
214
|
+
<View style={[style, styles.wrapper]}>
|
|
215
|
+
{renderInputView()}
|
|
216
|
+
<ErrorView
|
|
217
|
+
errorMessage={errorMessage}
|
|
218
|
+
errorSpacing={errorSpacing}
|
|
219
|
+
hintText={hintText}
|
|
220
|
+
/>
|
|
221
|
+
</View>
|
|
222
|
+
</ComponentContext.Provider>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
);
|
|
31
226
|
|
|
32
227
|
export default InputMoney;
|
package/Layout/Screen.tsx
CHANGED
|
@@ -23,11 +23,12 @@ import {
|
|
|
23
23
|
ViewProps,
|
|
24
24
|
} from 'react-native';
|
|
25
25
|
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
26
|
-
import {ApplicationContext} from '../Application';
|
|
26
|
+
import {ApplicationContext, ScreenContext} from '../Application';
|
|
27
27
|
import Navigation from '../Application/Navigation';
|
|
28
28
|
import {
|
|
29
29
|
AnimatedHeader,
|
|
30
30
|
NavigationOptions,
|
|
31
|
+
ScreenTrackingParams,
|
|
31
32
|
SearchHeaderProps,
|
|
32
33
|
} from '../Application/types';
|
|
33
34
|
import {Colors, Spacing, Styles} from '../Consts';
|
|
@@ -148,6 +149,11 @@ export interface ScreenProps extends ViewProps {
|
|
|
148
149
|
* Optional. Custom headerBackground Image
|
|
149
150
|
*/
|
|
150
151
|
headerBackground?: string;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Optional. Custom tracking params
|
|
155
|
+
*/
|
|
156
|
+
trackingParams?: ScreenTrackingParams;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
const Screen = forwardRef(
|
|
@@ -174,11 +180,13 @@ const Screen = forwardRef(
|
|
|
174
180
|
animatedValue: customAnimatedValue,
|
|
175
181
|
headerBackground,
|
|
176
182
|
gradientColor,
|
|
183
|
+
trackingParams,
|
|
177
184
|
}: ScreenProps,
|
|
178
185
|
ref: any
|
|
179
186
|
) => {
|
|
180
187
|
const screenRef = useRef<View | ScrollView>();
|
|
181
188
|
const {theme} = useContext(ApplicationContext);
|
|
189
|
+
const screen: any = useContext(ScreenContext);
|
|
182
190
|
const insets = useSafeAreaInsets();
|
|
183
191
|
const heightHeader = useHeaderHeight();
|
|
184
192
|
const animatedValue = useRef<Animated.Value>(
|
|
@@ -195,6 +203,11 @@ const Screen = forwardRef(
|
|
|
195
203
|
keyboardOffset = -Math.min(insets.bottom, 21);
|
|
196
204
|
}
|
|
197
205
|
|
|
206
|
+
/**
|
|
207
|
+
* inject params for screen tracking
|
|
208
|
+
*/
|
|
209
|
+
screen?.onSetParams?.(trackingParams);
|
|
210
|
+
|
|
198
211
|
/**
|
|
199
212
|
* export options for screen
|
|
200
213
|
* @param headerType
|
package/Skeleton/index.tsx
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
LayoutAnimation,
|
|
6
|
+
Platform,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
UIManager,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
3
11
|
import LinearGradient from 'react-native-linear-gradient';
|
|
4
12
|
import {SkeletonTypes} from './types';
|
|
5
13
|
import {Colors, Styles} from '../Consts';
|
|
6
14
|
import styles from './styles';
|
|
15
|
+
import {SkeletonContext} from '../Application';
|
|
7
16
|
|
|
8
17
|
const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
|
|
9
18
|
const [width, setWidth] = useState(0);
|
|
@@ -24,7 +33,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
|
|
|
24
33
|
duration: 1000,
|
|
25
34
|
easing: Easing.linear,
|
|
26
35
|
useNativeDriver: Platform.OS !== 'web',
|
|
27
|
-
})
|
|
36
|
+
})
|
|
28
37
|
);
|
|
29
38
|
}, [beginShimmerPosition]);
|
|
30
39
|
|
|
@@ -69,4 +78,50 @@ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
|
|
|
69
78
|
);
|
|
70
79
|
};
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
/**
|
|
82
|
+
* SkeletonProvider component provides a context for managing the loading state
|
|
83
|
+
* and applies a layout animation when the loading state changes.
|
|
84
|
+
*
|
|
85
|
+
* @param {boolean} loading - Indicates whether the skeleton loading state is active.
|
|
86
|
+
* @param {React.ReactNode} children - The child components to be rendered within the provider.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* <SkeletonProvider loading={true}>
|
|
90
|
+
* <YourComponent />
|
|
91
|
+
* </SkeletonProvider>
|
|
92
|
+
*
|
|
93
|
+
* The layout animation is configured using LayoutAnimation.configureNext, which animates the opacity of the components over 500 milliseconds with an ease-in-ease-out effect.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
const SkeletonProvider: React.FC<{loading: boolean}> = ({
|
|
97
|
+
loading = true,
|
|
98
|
+
children,
|
|
99
|
+
}) => {
|
|
100
|
+
const [previous, setPrevious] = useState(loading);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (previous !== loading) {
|
|
104
|
+
if (Platform.OS === 'android') {
|
|
105
|
+
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
|
106
|
+
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
LayoutAnimation.configureNext({
|
|
110
|
+
duration: 500,
|
|
111
|
+
create: {
|
|
112
|
+
type: LayoutAnimation.Types.easeInEaseOut,
|
|
113
|
+
property: LayoutAnimation.Properties.opacity,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
setPrevious(loading);
|
|
117
|
+
}
|
|
118
|
+
}, [loading]);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<SkeletonContext.Provider value={{loading: previous}}>
|
|
122
|
+
{children}
|
|
123
|
+
</SkeletonContext.Provider>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export {Skeleton, SkeletonProvider};
|
package/Text/index.tsx
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import React, {useContext} from 'react';
|
|
2
|
-
import {Text as RNText, TextProps as RNTextProps} from 'react-native';
|
|
2
|
+
import {Text as RNText, TextProps as RNTextProps, View} from 'react-native';
|
|
3
3
|
import styles from './styles';
|
|
4
4
|
import {Typography, TypographyWeight} from './types';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ApplicationContext,
|
|
7
|
+
setAutomationID,
|
|
8
|
+
SkeletonContext,
|
|
9
|
+
} from '../Application';
|
|
6
10
|
import {scaleSize} from './utils';
|
|
11
|
+
import {Skeleton} from '../Skeleton';
|
|
7
12
|
|
|
8
13
|
const SFProText: TypographyWeight = {
|
|
9
14
|
100: 'Thin',
|
|
@@ -125,7 +130,7 @@ const Text: React.FC<TextProps> = ({
|
|
|
125
130
|
...rest
|
|
126
131
|
}) => {
|
|
127
132
|
const {theme} = useContext(ApplicationContext);
|
|
128
|
-
|
|
133
|
+
const skeleton = useContext(SkeletonContext);
|
|
129
134
|
const textStyle = getTypoStyle(typography, fontFamily);
|
|
130
135
|
|
|
131
136
|
if (deprecatedValues.includes(typography)) {
|
|
@@ -134,6 +139,25 @@ const Text: React.FC<TextProps> = ({
|
|
|
134
139
|
);
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
if (skeleton.loading) {
|
|
143
|
+
return (
|
|
144
|
+
<View style={style}>
|
|
145
|
+
<RNText
|
|
146
|
+
{...rest}
|
|
147
|
+
allowFontScaling={false}
|
|
148
|
+
style={[
|
|
149
|
+
textStyle,
|
|
150
|
+
{
|
|
151
|
+
color: color ?? theme.colors.text.default,
|
|
152
|
+
},
|
|
153
|
+
]}>
|
|
154
|
+
{children ?? ''}
|
|
155
|
+
</RNText>
|
|
156
|
+
<Skeleton style={{position: 'absolute'}} />
|
|
157
|
+
</View>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
137
161
|
return (
|
|
138
162
|
<RNText
|
|
139
163
|
{...rest}
|