@momo-kits/foundation 0.92.20 → 0.92.21-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 +86 -38
- package/Application/Navigation.ts +0 -13
- package/Application/types.ts +16 -6
- package/Application/utils.tsx +0 -9
- package/Button/index.tsx +0 -1
- package/Icon/types.ts +1 -0
- package/Input/InputDropDown.tsx +1 -0
- package/Input/InputMoney.tsx +3 -3
- package/Input/InputOTP.tsx +4 -4
- package/Input/InputSearch.tsx +166 -147
- package/Input/TextTyping.tsx +108 -0
- package/Input/index.tsx +11 -1
- package/Layout/AnimatedInputSearch.tsx +58 -0
- package/Layout/Screen.tsx +109 -25
- package/Layout/styles.ts +0 -6
- package/Layout/types.ts +20 -1
- package/Title/index.tsx +9 -6
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
|
|
2
|
+
FC,
|
|
3
3
|
useCallback,
|
|
4
4
|
useContext,
|
|
5
5
|
useEffect,
|
|
6
|
+
useRef,
|
|
6
7
|
useState,
|
|
7
8
|
} from 'react';
|
|
8
9
|
import {
|
|
@@ -83,11 +84,6 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
|
|
|
83
84
|
|
|
84
85
|
const HeaderTitle: React.FC<any> = props => {
|
|
85
86
|
const {theme} = useContext(ApplicationContext);
|
|
86
|
-
const opacity = props.animatedValue?.interpolate({
|
|
87
|
-
inputRange: [0, 200],
|
|
88
|
-
outputRange: [0, 1],
|
|
89
|
-
extrapolate: 'clamp',
|
|
90
|
-
});
|
|
91
87
|
|
|
92
88
|
return (
|
|
93
89
|
<Animated.Text
|
|
@@ -95,7 +91,11 @@ const HeaderTitle: React.FC<any> = props => {
|
|
|
95
91
|
accessibilityLabel={'title_navigation_header'}
|
|
96
92
|
style={[
|
|
97
93
|
styles.title,
|
|
98
|
-
{
|
|
94
|
+
{
|
|
95
|
+
fontFamily: `${theme.font}-Bold`,
|
|
96
|
+
color: props.tintColor,
|
|
97
|
+
opacity: props.titleOpacity,
|
|
98
|
+
},
|
|
99
99
|
]}
|
|
100
100
|
/>
|
|
101
101
|
);
|
|
@@ -113,7 +113,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
|
|
|
113
113
|
useEffect(() => {
|
|
114
114
|
const backHandler = BackHandler.addEventListener(
|
|
115
115
|
'hardwareBackPress',
|
|
116
|
-
goBackSafe
|
|
116
|
+
goBackSafe
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
return () => backHandler.remove();
|
|
@@ -263,35 +263,40 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
|
|
|
263
263
|
return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
|
|
264
264
|
};
|
|
265
265
|
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
'HeaderRightAction contains element type of NavigationButton, Please check again.',
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
return child;
|
|
275
|
-
};
|
|
266
|
+
const HeaderRight: FC<any> = ({type, ...props}) => {
|
|
267
|
+
let buttons = props?.buttons || [];
|
|
268
|
+
if (!type) {
|
|
269
|
+
return <View />;
|
|
270
|
+
}
|
|
276
271
|
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
return (
|
|
281
|
-
<View key={`HeaderRightAction ${index}`} style={styles.headerButton}>
|
|
282
|
-
{React.cloneElement(validateType(child), {...restProps})}
|
|
283
|
-
</View>
|
|
284
|
-
);
|
|
285
|
-
});
|
|
272
|
+
if (type === 'iconButton') {
|
|
273
|
+
if (buttons?.length > 3) {
|
|
274
|
+
buttons = buttons.slice(0, 3);
|
|
286
275
|
}
|
|
287
|
-
|
|
288
276
|
return (
|
|
289
|
-
<View style={styles.
|
|
290
|
-
{
|
|
277
|
+
<View style={styles.headerRightButton}>
|
|
278
|
+
{buttons?.map?.(
|
|
279
|
+
({icon, onPress}: NavigationButtonProps, index: number) => {
|
|
280
|
+
return (
|
|
281
|
+
<View
|
|
282
|
+
key={`HeaderRightAction ${index}`}
|
|
283
|
+
style={{
|
|
284
|
+
marginLeft: index !== 0 ? Spacing.S : 0,
|
|
285
|
+
}}>
|
|
286
|
+
<NavigationButton
|
|
287
|
+
icon={icon}
|
|
288
|
+
onPress={onPress}
|
|
289
|
+
tintColor={props?.tintColor}
|
|
290
|
+
/>
|
|
291
|
+
</View>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
)}
|
|
291
295
|
</View>
|
|
292
296
|
);
|
|
293
|
-
}
|
|
294
|
-
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return <HeaderToolkitAction {...props} />;
|
|
295
300
|
};
|
|
296
301
|
|
|
297
302
|
const HeaderToolkitAction: React.FC<any> = ({
|
|
@@ -327,7 +332,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
327
332
|
(config: any) => {
|
|
328
333
|
navigator.toolkitConfig = config;
|
|
329
334
|
setToolConfig(navigator?.toolkitConfig);
|
|
330
|
-
}
|
|
335
|
+
}
|
|
331
336
|
);
|
|
332
337
|
};
|
|
333
338
|
|
|
@@ -340,7 +345,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
340
345
|
const {item} = res;
|
|
341
346
|
navigator?.toolkitCallback?.(item);
|
|
342
347
|
getToolkitConfig();
|
|
343
|
-
}
|
|
348
|
+
}
|
|
344
349
|
);
|
|
345
350
|
};
|
|
346
351
|
|
|
@@ -357,7 +362,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
357
362
|
navigator?.maxApi?.dispatchFunction?.(
|
|
358
363
|
'dismiss',
|
|
359
364
|
undefined,
|
|
360
|
-
undefined
|
|
365
|
+
undefined
|
|
361
366
|
);
|
|
362
367
|
},
|
|
363
368
|
}}
|
|
@@ -388,7 +393,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
388
393
|
[pinTool?.key],
|
|
389
394
|
() => {
|
|
390
395
|
getToolkitConfig();
|
|
391
|
-
}
|
|
396
|
+
}
|
|
392
397
|
);
|
|
393
398
|
navigator?.toolkitCallback?.(pinTool.key);
|
|
394
399
|
}}
|
|
@@ -405,6 +410,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
405
410
|
{
|
|
406
411
|
backgroundColor: backgroundColor ?? '#00000066',
|
|
407
412
|
borderWidth: backgroundColor ? 0.5 : 0,
|
|
413
|
+
marginLeft: renderPinnedTool() ? Spacing.S : 0,
|
|
408
414
|
},
|
|
409
415
|
]}>
|
|
410
416
|
<TouchableOpacity
|
|
@@ -470,6 +476,42 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
470
476
|
);
|
|
471
477
|
};
|
|
472
478
|
|
|
479
|
+
const ExtendBackground: FC<{
|
|
480
|
+
headerBackground: string;
|
|
481
|
+
animatedValue: Animated.Value;
|
|
482
|
+
heightHeader: number;
|
|
483
|
+
}> = ({headerBackground, animatedValue, heightHeader}) => {
|
|
484
|
+
const heightAnimated = useRef(new Animated.Value(0));
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
const listener = animatedValue.addListener(({value}) => {
|
|
487
|
+
heightAnimated.current.setValue(value);
|
|
488
|
+
});
|
|
489
|
+
return () => {
|
|
490
|
+
animatedValue?.removeListener(listener);
|
|
491
|
+
};
|
|
492
|
+
}, []);
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<>
|
|
496
|
+
<Image
|
|
497
|
+
source={{
|
|
498
|
+
uri: headerBackground,
|
|
499
|
+
}}
|
|
500
|
+
style={styles.extendedHeader}
|
|
501
|
+
/>
|
|
502
|
+
<Animated.View
|
|
503
|
+
style={{
|
|
504
|
+
height: heightAnimated.current.interpolate({
|
|
505
|
+
inputRange: [0, 100],
|
|
506
|
+
outputRange: [heightHeader, heightHeader - 52],
|
|
507
|
+
extrapolate: 'clamp',
|
|
508
|
+
}),
|
|
509
|
+
}}
|
|
510
|
+
/>
|
|
511
|
+
</>
|
|
512
|
+
);
|
|
513
|
+
};
|
|
514
|
+
|
|
473
515
|
const styles = StyleSheet.create({
|
|
474
516
|
navigationButton: {
|
|
475
517
|
height: 28,
|
|
@@ -509,7 +551,6 @@ const styles = StyleSheet.create({
|
|
|
509
551
|
paddingRight: Spacing.M,
|
|
510
552
|
},
|
|
511
553
|
toolkitContainer: {
|
|
512
|
-
marginLeft: Spacing.S,
|
|
513
554
|
padding: Spacing.XS,
|
|
514
555
|
height: 28,
|
|
515
556
|
borderRadius: 14,
|
|
@@ -541,6 +582,12 @@ const styles = StyleSheet.create({
|
|
|
541
582
|
top: -Spacing.XS,
|
|
542
583
|
right: -Spacing.XS,
|
|
543
584
|
},
|
|
585
|
+
extendedHeader: {
|
|
586
|
+
aspectRatio: 1.75,
|
|
587
|
+
position: 'absolute',
|
|
588
|
+
width: '100%',
|
|
589
|
+
height: 210,
|
|
590
|
+
},
|
|
544
591
|
});
|
|
545
592
|
|
|
546
593
|
export {
|
|
@@ -548,8 +595,9 @@ export {
|
|
|
548
595
|
HeaderTitle,
|
|
549
596
|
HeaderLeft,
|
|
550
597
|
HeaderBackground,
|
|
551
|
-
HeaderRightAction,
|
|
552
598
|
HeaderToolkitAction,
|
|
553
599
|
HeaderCustom,
|
|
554
600
|
HeaderAnimated,
|
|
601
|
+
HeaderRight,
|
|
602
|
+
ExtendBackground,
|
|
555
603
|
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import {NavigationProp} from '@react-navigation/native';
|
|
3
2
|
import {NavigationOptions, Theme} from './types';
|
|
4
|
-
import {HeaderRightAction} from './index';
|
|
5
3
|
import {getOptions} from './utils';
|
|
6
|
-
import {HeaderToolkitAction} from './Components';
|
|
7
4
|
|
|
8
5
|
class Navigation {
|
|
9
6
|
instance: NavigationProp<any>;
|
|
@@ -19,16 +16,6 @@ class Navigation {
|
|
|
19
16
|
* @param params
|
|
20
17
|
*/
|
|
21
18
|
private filterParams = (params: NavigationOptions) => {
|
|
22
|
-
if (params.headerRight) {
|
|
23
|
-
const headerRight = params.headerRight?.({}) as React.ReactElement;
|
|
24
|
-
const type = headerRight?.type;
|
|
25
|
-
if (type !== HeaderRightAction && type !== HeaderToolkitAction) {
|
|
26
|
-
console.warn(
|
|
27
|
-
'headerRight contains element type of HeaderRightAction | HeaderToolkitAction, Please check again.',
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
19
|
if (params.headerLeft) {
|
|
33
20
|
console.warn('headerLeft not allow override render by design system!');
|
|
34
21
|
delete params.headerLeft;
|
package/Application/types.ts
CHANGED
|
@@ -144,19 +144,29 @@ export type RuntimeToolType = {
|
|
|
144
144
|
key: string;
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
-
export interface NavigationOptions
|
|
147
|
+
export interface NavigationOptions
|
|
148
|
+
extends Omit<StackNavigationOptions, 'headerRight'> {
|
|
148
149
|
preventBack?: PopupNotifyProps;
|
|
149
150
|
onPressLeftHeader?: () => void;
|
|
150
151
|
surface?: boolean;
|
|
151
152
|
hiddenBack?: boolean;
|
|
152
153
|
customTitle?: TitleCustomProps;
|
|
153
|
-
|
|
154
|
-
pinnedTool?: PinnedToolType;
|
|
155
|
-
runtimeTools?: RuntimeToolType[];
|
|
156
|
-
preventClose?: PopupNotifyProps;
|
|
157
|
-
};
|
|
154
|
+
headerRight?: HeaderRightToolkit | HeaderRightActions | Element;
|
|
158
155
|
}
|
|
159
156
|
|
|
157
|
+
export type HeaderRightToolkit = {
|
|
158
|
+
type?: 'toolkit';
|
|
159
|
+
pinnedTool?: PinnedToolType;
|
|
160
|
+
runtimeTools?: RuntimeToolType[];
|
|
161
|
+
preventClose?: PopupNotifyProps;
|
|
162
|
+
useMore?: boolean;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type HeaderRightActions = {
|
|
166
|
+
type?: 'iconButton';
|
|
167
|
+
buttons: NavigationButtonProps[];
|
|
168
|
+
};
|
|
169
|
+
|
|
160
170
|
export interface HeaderBackProps extends NavigationButtonProps {
|
|
161
171
|
preventBack?: PopupNotifyProps;
|
|
162
172
|
onPressLeftHeader?: () => void;
|
package/Application/utils.tsx
CHANGED
|
@@ -122,15 +122,6 @@ const getOptions = (params: NavigationOptions, theme: Theme) => {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
if (params.toolkitParams) {
|
|
126
|
-
options = {
|
|
127
|
-
...options,
|
|
128
|
-
headerRight: (props: any) => {
|
|
129
|
-
return <HeaderToolkitAction {...params.toolkitParams} {...props} />;
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
125
|
return {
|
|
135
126
|
...params,
|
|
136
127
|
...options,
|
package/Button/index.tsx
CHANGED
package/Icon/types.ts
CHANGED
package/Input/InputDropDown.tsx
CHANGED
package/Input/InputMoney.tsx
CHANGED
|
@@ -44,7 +44,7 @@ const InputMoney = forwardRef(
|
|
|
44
44
|
value: _value,
|
|
45
45
|
...props
|
|
46
46
|
}: InputMoneyProps,
|
|
47
|
-
ref
|
|
47
|
+
ref
|
|
48
48
|
) => {
|
|
49
49
|
const {theme} = useContext(ApplicationContext);
|
|
50
50
|
const [focused, setFocused] = useState(false);
|
|
@@ -63,7 +63,7 @@ const InputMoney = forwardRef(
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const [value, setValue] = useState(
|
|
66
|
-
defaultValue ? validateText(defaultValue) : ''
|
|
66
|
+
defaultValue ? validateText(defaultValue) : ''
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
const onClearText = () => {
|
|
@@ -198,7 +198,7 @@ const InputMoney = forwardRef(
|
|
|
198
198
|
</View>
|
|
199
199
|
</ComponentContext.Provider>
|
|
200
200
|
);
|
|
201
|
-
}
|
|
201
|
+
}
|
|
202
202
|
);
|
|
203
203
|
|
|
204
204
|
export default InputMoney;
|
package/Input/InputOTP.tsx
CHANGED
|
@@ -41,7 +41,7 @@ const OTPCaret: FC<CaretProps> = ({index, length}) => {
|
|
|
41
41
|
duration: DURATION,
|
|
42
42
|
useNativeDriver: true,
|
|
43
43
|
}),
|
|
44
|
-
])
|
|
44
|
+
])
|
|
45
45
|
).start();
|
|
46
46
|
}, []);
|
|
47
47
|
const spacingStyle = !isNaN(Number(length)) &&
|
|
@@ -81,7 +81,7 @@ const InputOTP = forwardRef(
|
|
|
81
81
|
hintText,
|
|
82
82
|
...props
|
|
83
83
|
}: InputOTPProps,
|
|
84
|
-
ref
|
|
84
|
+
ref
|
|
85
85
|
) => {
|
|
86
86
|
const MAX_LENGTH = 10;
|
|
87
87
|
const [value, setValue] = useState('');
|
|
@@ -143,7 +143,7 @@ const InputOTP = forwardRef(
|
|
|
143
143
|
{value[i] || '-'}
|
|
144
144
|
</Text>
|
|
145
145
|
)}
|
|
146
|
-
</View
|
|
146
|
+
</View>
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
return TextInputs;
|
|
@@ -248,7 +248,7 @@ const InputOTP = forwardRef(
|
|
|
248
248
|
</TouchableOpacity>
|
|
249
249
|
</ComponentContext.Provider>
|
|
250
250
|
);
|
|
251
|
-
}
|
|
251
|
+
}
|
|
252
252
|
);
|
|
253
253
|
|
|
254
254
|
export default InputOTP;
|
package/Input/InputSearch.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
forwardRef,
|
|
3
|
+
ForwardRefRenderFunction,
|
|
3
4
|
useContext,
|
|
4
5
|
useImperativeHandle,
|
|
5
6
|
useRef,
|
|
@@ -15,177 +16,195 @@ import {
|
|
|
15
16
|
import {ApplicationContext, ComponentContext} from '../Application';
|
|
16
17
|
import {Icon} from '../Icon';
|
|
17
18
|
import {Text} from '../Text';
|
|
18
|
-
import {InputSearchProps} from './index';
|
|
19
|
+
import {InputSearchProps, InputSearchRef} from './index';
|
|
19
20
|
import styles from './styles';
|
|
20
21
|
import {checkTyping} from './utils';
|
|
22
|
+
import TextTyping from './TextTyping';
|
|
21
23
|
|
|
22
|
-
const InputSearch
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
const InputSearch: ForwardRefRenderFunction<
|
|
25
|
+
InputSearchRef,
|
|
26
|
+
InputSearchProps
|
|
27
|
+
> = (
|
|
28
|
+
{
|
|
29
|
+
placeholder,
|
|
30
|
+
onFocus,
|
|
31
|
+
onBlur,
|
|
32
|
+
iconColor,
|
|
33
|
+
value,
|
|
34
|
+
onChangeText,
|
|
35
|
+
icon,
|
|
36
|
+
buttonText = 'Hủy',
|
|
37
|
+
showButtonText = true,
|
|
38
|
+
style,
|
|
39
|
+
defaultValue,
|
|
40
|
+
onPressButtonText,
|
|
41
|
+
params,
|
|
42
|
+
hasColorBG = false,
|
|
43
|
+
accessibilityLabel,
|
|
44
|
+
typingData = [],
|
|
45
|
+
onPress,
|
|
46
|
+
...props
|
|
47
|
+
},
|
|
48
|
+
ref
|
|
49
|
+
) => {
|
|
50
|
+
const {theme} = useContext(ApplicationContext);
|
|
51
|
+
const [focused, setFocused] = useState(false);
|
|
52
|
+
const [haveValue, setHaveValue] = useState(!!value || !!defaultValue);
|
|
53
|
+
const inputRef = useRef<TextInput | null>(null);
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
56
|
+
setFocused(true);
|
|
57
|
+
onFocus?.(e);
|
|
58
|
+
};
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
61
|
+
setFocused(false);
|
|
62
|
+
onBlur?.(e);
|
|
63
|
+
};
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
const onClearText = () => {
|
|
66
|
+
inputRef?.current?.clear();
|
|
67
|
+
_onChangeText('');
|
|
68
|
+
};
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
const _onChangeText = (text: string) => {
|
|
71
|
+
checkTyping(text, haveValue, setHaveValue);
|
|
72
|
+
onChangeText?.(text);
|
|
73
|
+
};
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
useImperativeHandle(ref, () => {
|
|
76
|
+
return {
|
|
77
|
+
clear: onClearText,
|
|
78
|
+
focus: () => inputRef.current?.focus(),
|
|
79
|
+
blur: () => inputRef.current?.blur(),
|
|
80
|
+
setText: (text: string) => _onChangeText(text),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
const renderInputView = () => {
|
|
85
|
+
if (typingData?.length > 0) {
|
|
79
86
|
return (
|
|
80
|
-
<
|
|
81
|
-
{...props}
|
|
82
|
-
accessibilityLabel={accessibilityLabel}
|
|
83
|
-
textAlignVertical="center"
|
|
84
|
-
ref={inputRef}
|
|
87
|
+
<TouchableOpacity
|
|
85
88
|
style={[
|
|
86
89
|
styles.searchInput,
|
|
87
90
|
{
|
|
88
|
-
|
|
91
|
+
justifyContent: 'center',
|
|
89
92
|
},
|
|
90
93
|
]}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
placeholder={placeholder}
|
|
96
|
-
selectionColor={theme.colors.primary}
|
|
97
|
-
placeholderTextColor={theme.colors.text.hint}
|
|
98
|
-
/>
|
|
94
|
+
onPress={onPress}
|
|
95
|
+
activeOpacity={onPress ? 0.6 : 1}>
|
|
96
|
+
<TextTyping data={typingData} />
|
|
97
|
+
</TouchableOpacity>
|
|
99
98
|
);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const renderIconView = () => {
|
|
103
|
-
return (
|
|
104
|
-
<View
|
|
105
|
-
style={{
|
|
106
|
-
flexDirection: 'row',
|
|
107
|
-
}}>
|
|
108
|
-
{focused && haveValue && (
|
|
109
|
-
<TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
|
|
110
|
-
<Icon
|
|
111
|
-
source="24_navigation_close_circle_full"
|
|
112
|
-
size={16}
|
|
113
|
-
color={theme.colors.text.hint}
|
|
114
|
-
/>
|
|
115
|
-
</TouchableOpacity>
|
|
116
|
-
)}
|
|
117
|
-
{!!icon && (
|
|
118
|
-
<View style={{flexDirection: 'row'}}>
|
|
119
|
-
<View
|
|
120
|
-
style={[
|
|
121
|
-
styles.divider,
|
|
122
|
-
{
|
|
123
|
-
backgroundColor: theme.colors.primary,
|
|
124
|
-
},
|
|
125
|
-
]}
|
|
126
|
-
/>
|
|
127
|
-
<Icon
|
|
128
|
-
color={iconColor}
|
|
129
|
-
source={icon}
|
|
130
|
-
style={styles.iconSearchInput}
|
|
131
|
-
/>
|
|
132
|
-
</View>
|
|
133
|
-
)}
|
|
134
|
-
</View>
|
|
135
|
-
);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
let inputState = 'empty';
|
|
139
|
-
|
|
140
|
-
if (focused) {
|
|
141
|
-
inputState = 'focus';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (value && value?.length > 0) {
|
|
145
|
-
inputState = 'filled';
|
|
146
99
|
}
|
|
147
100
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
101
|
+
return (
|
|
102
|
+
<TextInput
|
|
103
|
+
{...props}
|
|
104
|
+
accessibilityLabel={accessibilityLabel}
|
|
105
|
+
textAlignVertical="center"
|
|
106
|
+
ref={inputRef}
|
|
107
|
+
style={[
|
|
108
|
+
styles.searchInput,
|
|
109
|
+
{
|
|
110
|
+
color: theme.colors.text.default,
|
|
111
|
+
},
|
|
112
|
+
]}
|
|
113
|
+
value={value}
|
|
114
|
+
onChangeText={_onChangeText}
|
|
115
|
+
onFocus={_onFocus}
|
|
116
|
+
onBlur={_onBlur}
|
|
117
|
+
placeholder={placeholder}
|
|
118
|
+
selectionColor={theme.colors.primary}
|
|
119
|
+
placeholderTextColor={theme.colors.text.hint}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
151
123
|
|
|
124
|
+
const renderIconView = () => {
|
|
152
125
|
return (
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
params,
|
|
157
|
-
state: inputState,
|
|
158
|
-
componentId: accessibilityLabel,
|
|
126
|
+
<View
|
|
127
|
+
style={{
|
|
128
|
+
flexDirection: 'row',
|
|
159
129
|
}}>
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
style={[
|
|
163
|
-
styles.searchInputWrapper,
|
|
164
|
-
{
|
|
165
|
-
backgroundColor,
|
|
166
|
-
},
|
|
167
|
-
]}>
|
|
130
|
+
{focused && haveValue && (
|
|
131
|
+
<TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
|
|
168
132
|
<Icon
|
|
169
|
-
source=
|
|
170
|
-
size={
|
|
133
|
+
source="24_navigation_close_circle_full"
|
|
134
|
+
size={16}
|
|
171
135
|
color={theme.colors.text.hint}
|
|
172
136
|
/>
|
|
173
|
-
|
|
174
|
-
|
|
137
|
+
</TouchableOpacity>
|
|
138
|
+
)}
|
|
139
|
+
{!!icon && (
|
|
140
|
+
<View style={{flexDirection: 'row'}}>
|
|
141
|
+
<View
|
|
142
|
+
style={[
|
|
143
|
+
styles.divider,
|
|
144
|
+
{
|
|
145
|
+
backgroundColor: theme.colors.primary,
|
|
146
|
+
},
|
|
147
|
+
]}
|
|
148
|
+
/>
|
|
149
|
+
<Icon
|
|
150
|
+
color={iconColor}
|
|
151
|
+
source={icon}
|
|
152
|
+
style={styles.iconSearchInput}
|
|
153
|
+
/>
|
|
175
154
|
</View>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<Text
|
|
179
|
-
typography={'action_default_bold'}
|
|
180
|
-
style={styles.textButton}>
|
|
181
|
-
{buttonText}
|
|
182
|
-
</Text>
|
|
183
|
-
</TouchableOpacity>
|
|
184
|
-
)}
|
|
185
|
-
</View>
|
|
186
|
-
</ComponentContext.Provider>
|
|
155
|
+
)}
|
|
156
|
+
</View>
|
|
187
157
|
);
|
|
188
|
-
}
|
|
189
|
-
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
let inputState = 'empty';
|
|
161
|
+
|
|
162
|
+
if (focused) {
|
|
163
|
+
inputState = 'focus';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (value && value?.length > 0) {
|
|
167
|
+
inputState = 'filled';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const backgroundColor = hasColorBG
|
|
171
|
+
? theme.colors.background.default
|
|
172
|
+
: theme.colors.background.surface;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<ComponentContext.Provider
|
|
176
|
+
value={{
|
|
177
|
+
componentName: 'InputSearch',
|
|
178
|
+
params,
|
|
179
|
+
state: inputState,
|
|
180
|
+
componentId: accessibilityLabel,
|
|
181
|
+
}}>
|
|
182
|
+
<View style={[style, styles.searchInputContainer]}>
|
|
183
|
+
<View
|
|
184
|
+
style={[
|
|
185
|
+
styles.searchInputWrapper,
|
|
186
|
+
{
|
|
187
|
+
backgroundColor,
|
|
188
|
+
},
|
|
189
|
+
]}>
|
|
190
|
+
<Icon
|
|
191
|
+
source={'navigation_search'}
|
|
192
|
+
size={24}
|
|
193
|
+
color={theme.colors.text.hint}
|
|
194
|
+
/>
|
|
195
|
+
{renderInputView()}
|
|
196
|
+
{renderIconView()}
|
|
197
|
+
</View>
|
|
198
|
+
{showButtonText && (
|
|
199
|
+
<TouchableOpacity onPress={onPressButtonText}>
|
|
200
|
+
<Text typography={'action_default_bold'} style={styles.textButton}>
|
|
201
|
+
{buttonText}
|
|
202
|
+
</Text>
|
|
203
|
+
</TouchableOpacity>
|
|
204
|
+
)}
|
|
205
|
+
</View>
|
|
206
|
+
</ComponentContext.Provider>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
190
209
|
|
|
191
|
-
export default InputSearch;
|
|
210
|
+
export default forwardRef(InputSearch);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, {FC, memo, useEffect, useRef} from 'react';
|
|
2
|
+
import {StyleSheet, TextInput, TextStyle} from 'react-native';
|
|
3
|
+
import {Colors} from '../Consts';
|
|
4
|
+
import {scaleSize} from '../Text';
|
|
5
|
+
|
|
6
|
+
export interface TextTypingProps {
|
|
7
|
+
data: string[];
|
|
8
|
+
timeDelayEnd?: number;
|
|
9
|
+
timeDelayNextChar?: number;
|
|
10
|
+
inputStyle?: TextStyle;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TextTyping: FC<TextTypingProps> = ({
|
|
14
|
+
data = [],
|
|
15
|
+
timeDelayEnd = 2000,
|
|
16
|
+
timeDelayNextChar = 20,
|
|
17
|
+
inputStyle = {},
|
|
18
|
+
}) => {
|
|
19
|
+
const textRef = useRef<TextInput>(null);
|
|
20
|
+
|
|
21
|
+
const currentIndex = useRef<number>(0);
|
|
22
|
+
const currentCharIndex = useRef<number>(0);
|
|
23
|
+
const currentText = useRef('');
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
playAnimation();
|
|
28
|
+
}, 1000);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const playAnimation = () => {
|
|
32
|
+
const handleResetList = () => {
|
|
33
|
+
currentIndex.current = 0;
|
|
34
|
+
handleAnimation();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleAnimation = () => {
|
|
38
|
+
const listChar = data?.[currentIndex.current]?.split('') || [];
|
|
39
|
+
listChar.length !== 0 && showChar(listChar?.[currentCharIndex.current]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
currentIndex.current >= data?.length
|
|
43
|
+
? handleResetList()
|
|
44
|
+
: handleAnimation();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const showChar = (char?: string) => {
|
|
48
|
+
const handleResetString = () => {
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
reset();
|
|
51
|
+
}, timeDelayEnd);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleShowChar = () => {
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
textRef.current?.setNativeProps?.({
|
|
57
|
+
text: currentText.current + char,
|
|
58
|
+
});
|
|
59
|
+
currentText.current += char;
|
|
60
|
+
currentCharIndex.current++;
|
|
61
|
+
playAnimation();
|
|
62
|
+
}, timeDelayNextChar);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
currentCharIndex.current >= (data?.[currentIndex.current]?.length || 0)
|
|
66
|
+
? handleResetString()
|
|
67
|
+
: handleShowChar();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const reset = () => {
|
|
71
|
+
const handleNextString = () => {
|
|
72
|
+
currentIndex.current++;
|
|
73
|
+
playAnimation();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleReset = () => {
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
textRef.current?.setNativeProps?.({
|
|
79
|
+
text: currentText.current.slice(0, -1),
|
|
80
|
+
});
|
|
81
|
+
currentText.current = currentText.current.slice(0, -1);
|
|
82
|
+
currentCharIndex.current--;
|
|
83
|
+
reset();
|
|
84
|
+
}, timeDelayNextChar);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
currentCharIndex.current <= 0 ? handleNextString() : handleReset();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<TextInput
|
|
92
|
+
style={[styles.inputStyle, inputStyle]}
|
|
93
|
+
ref={textRef}
|
|
94
|
+
editable={false}
|
|
95
|
+
autoCorrect={false}
|
|
96
|
+
pointerEvents={'none'}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default memo(TextTyping);
|
|
102
|
+
|
|
103
|
+
const styles = StyleSheet.create({
|
|
104
|
+
inputStyle: {
|
|
105
|
+
color: Colors.black_12,
|
|
106
|
+
fontSize: scaleSize(12),
|
|
107
|
+
},
|
|
108
|
+
});
|
package/Input/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {TextInputProps, ViewStyle} from 'react-native';
|
|
1
|
+
import {GestureResponderEvent, TextInputProps, ViewStyle} from 'react-native';
|
|
2
2
|
import Input from './Input';
|
|
3
3
|
import InputDropDown from './InputDropDown';
|
|
4
4
|
import InputMoney from './InputMoney';
|
|
@@ -143,6 +143,9 @@ export interface InputSearchProps extends TextInputProps {
|
|
|
143
143
|
* Optional. Represents the style of the InputSearch component.
|
|
144
144
|
*/
|
|
145
145
|
params?: any;
|
|
146
|
+
|
|
147
|
+
typingData?: string[];
|
|
148
|
+
onPress?: (e: GestureResponderEvent) => void;
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
export interface InputMoneyProps extends Omit<InputProps, 'placeholder'> {}
|
|
@@ -282,4 +285,11 @@ export interface InputDropDownProps extends InputProps {
|
|
|
282
285
|
style?: ViewStyle | ViewStyle[];
|
|
283
286
|
}
|
|
284
287
|
|
|
288
|
+
export type InputSearchRef = {
|
|
289
|
+
clear: () => void;
|
|
290
|
+
focus: () => void | undefined;
|
|
291
|
+
blur: () => void | undefined;
|
|
292
|
+
setText: (text: string) => void;
|
|
293
|
+
};
|
|
294
|
+
|
|
285
295
|
export {Input, InputDropDown, InputMoney, InputOTP, InputSearch, InputTextArea};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, {FC, useEffect, useRef} from 'react';
|
|
2
|
+
import {InputSearch} from '../Input';
|
|
3
|
+
import {Animated, Dimensions} from 'react-native';
|
|
4
|
+
import {AnimatedInputSearchProps} from './types';
|
|
5
|
+
import {Spacing} from '../Consts';
|
|
6
|
+
import {scaleSize} from '../Text';
|
|
7
|
+
|
|
8
|
+
const {width: screenWidth} = Dimensions.get('window');
|
|
9
|
+
const SCREEN_PADDING = 12;
|
|
10
|
+
const BACK_WIDTH = scaleSize(28);
|
|
11
|
+
|
|
12
|
+
const AnimatedInputSearch: FC<AnimatedInputSearchProps> = ({
|
|
13
|
+
animatedValue,
|
|
14
|
+
headerRightWidth,
|
|
15
|
+
inputSearchProps,
|
|
16
|
+
inputRef,
|
|
17
|
+
}) => {
|
|
18
|
+
const widthAnimated = useRef(new Animated.Value(0));
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const listener = animatedValue.addListener(({value}) => {
|
|
22
|
+
widthAnimated.current.setValue(value);
|
|
23
|
+
});
|
|
24
|
+
return () => {
|
|
25
|
+
animatedValue?.removeListener(listener);
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const translateX = widthAnimated.current.interpolate({
|
|
30
|
+
inputRange: [0, 100],
|
|
31
|
+
outputRange: [SCREEN_PADDING, BACK_WIDTH + SCREEN_PADDING * 2],
|
|
32
|
+
extrapolate: 'clamp',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Animated.View
|
|
37
|
+
style={{
|
|
38
|
+
transform: [{translateX}],
|
|
39
|
+
marginVertical: Spacing.S,
|
|
40
|
+
width: widthAnimated.current.interpolate({
|
|
41
|
+
inputRange: [0, 100],
|
|
42
|
+
outputRange: [
|
|
43
|
+
screenWidth - SCREEN_PADDING * 2,
|
|
44
|
+
screenWidth - SCREEN_PADDING * 3 - BACK_WIDTH - headerRightWidth,
|
|
45
|
+
],
|
|
46
|
+
extrapolate: 'clamp',
|
|
47
|
+
}),
|
|
48
|
+
}}>
|
|
49
|
+
<InputSearch
|
|
50
|
+
ref={inputRef}
|
|
51
|
+
{...inputSearchProps}
|
|
52
|
+
showButtonText={false}
|
|
53
|
+
/>
|
|
54
|
+
</Animated.View>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default AnimatedInputSearch;
|
package/Layout/Screen.tsx
CHANGED
|
@@ -2,14 +2,17 @@ import {useHeaderHeight} from '@react-navigation/stack';
|
|
|
2
2
|
import React, {
|
|
3
3
|
forwardRef,
|
|
4
4
|
Fragment,
|
|
5
|
+
LegacyRef,
|
|
5
6
|
ReactNode,
|
|
6
7
|
useContext,
|
|
7
8
|
useEffect,
|
|
8
9
|
useRef,
|
|
10
|
+
useState,
|
|
9
11
|
} from 'react';
|
|
10
12
|
import {
|
|
11
13
|
Animated,
|
|
12
14
|
KeyboardAvoidingView,
|
|
15
|
+
LayoutChangeEvent,
|
|
13
16
|
NativeScrollEvent,
|
|
14
17
|
NativeSyntheticEvent,
|
|
15
18
|
Platform,
|
|
@@ -24,13 +27,20 @@ import {
|
|
|
24
27
|
HeaderTitle,
|
|
25
28
|
} from '../Application';
|
|
26
29
|
import Navigation from '../Application/Navigation';
|
|
27
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
AnimatedHeader,
|
|
32
|
+
HeaderRightActions,
|
|
33
|
+
HeaderRightToolkit,
|
|
34
|
+
NavigationOptions,
|
|
35
|
+
} from '../Application/types';
|
|
28
36
|
import {Colors, Spacing, Styles} from '../Consts';
|
|
29
37
|
import {FloatingButton, FloatingButtonProps} from './FloatingButton';
|
|
30
|
-
import {Image} from '../Image';
|
|
31
38
|
import {Card, Section, validateChildren} from './index';
|
|
32
39
|
import styles from './styles';
|
|
33
40
|
import {HeaderType} from './types';
|
|
41
|
+
import AnimatedInputSearch from './AnimatedInputSearch';
|
|
42
|
+
import {InputSearchProps, InputSearchRef} from '../Input';
|
|
43
|
+
import {ExtendBackground, HeaderRight} from '../Application/Components';
|
|
34
44
|
|
|
35
45
|
export interface ScreenProps extends ViewProps {
|
|
36
46
|
/**
|
|
@@ -101,8 +111,15 @@ export interface ScreenProps extends ViewProps {
|
|
|
101
111
|
* Optional. If `true`, the screen content is using grid layout by span.
|
|
102
112
|
*/
|
|
103
113
|
useGridLayout?: boolean;
|
|
114
|
+
|
|
115
|
+
inputSearchProps?: InputSearchProps;
|
|
116
|
+
|
|
117
|
+
headerRight?: HeaderRightActions | HeaderRightToolkit | CustomHeaderRight;
|
|
118
|
+
inputSearchRef?: LegacyRef<InputSearchRef>;
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
type CustomHeaderRight = (props: any) => Element;
|
|
122
|
+
|
|
106
123
|
const Screen = forwardRef(
|
|
107
124
|
(
|
|
108
125
|
{
|
|
@@ -120,26 +137,31 @@ const Screen = forwardRef(
|
|
|
120
137
|
floatingButtonProps,
|
|
121
138
|
useGridLayout = true,
|
|
122
139
|
keyboardVerticalOffset,
|
|
140
|
+
inputSearchProps,
|
|
141
|
+
headerRight,
|
|
142
|
+
inputSearchRef,
|
|
123
143
|
}: ScreenProps,
|
|
124
|
-
ref
|
|
144
|
+
ref
|
|
125
145
|
) => {
|
|
126
146
|
const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
|
|
127
147
|
const {theme} = useContext(ApplicationContext);
|
|
128
148
|
const insets = useSafeAreaInsets();
|
|
129
149
|
const heightHeader = useHeaderHeight();
|
|
150
|
+
const scrollViewRef = useRef<any>(ref);
|
|
130
151
|
const currentTint = useRef(Colors.black_01);
|
|
131
|
-
const isTab = navigation?.instance?.getState?.()?.type
|
|
152
|
+
const isTab = navigation?.instance?.getState?.()?.type === 'tab';
|
|
153
|
+
const [headerRightWidth, setHeaderRightWidth] = useState(0);
|
|
132
154
|
|
|
133
155
|
let styleAnimatedHeader: NavigationOptions;
|
|
134
156
|
let handleScroll;
|
|
135
157
|
let headerBackground: string | undefined = undefined;
|
|
136
158
|
let Component: any = View;
|
|
137
159
|
let keyboardOffset = heightHeader - 20;
|
|
138
|
-
if (headerType
|
|
160
|
+
if (headerType === 'extended' || animatedHeader) {
|
|
139
161
|
keyboardOffset = -20;
|
|
140
162
|
}
|
|
141
163
|
|
|
142
|
-
if (headerType
|
|
164
|
+
if (headerType === 'extended') {
|
|
143
165
|
headerBackground = theme.assets?.headerBackground;
|
|
144
166
|
}
|
|
145
167
|
|
|
@@ -157,7 +179,7 @@ const Screen = forwardRef(
|
|
|
157
179
|
<HeaderTitle {...props} animatedValue={animatedValue.current} />
|
|
158
180
|
),
|
|
159
181
|
};
|
|
160
|
-
if (animatedHeader.type
|
|
182
|
+
if (animatedHeader.type === 'surface') {
|
|
161
183
|
styleAnimatedHeader = {
|
|
162
184
|
...styleAnimatedHeader,
|
|
163
185
|
headerBackground: (props: any) => (
|
|
@@ -180,6 +202,7 @@ const Screen = forwardRef(
|
|
|
180
202
|
}),
|
|
181
203
|
};
|
|
182
204
|
}
|
|
205
|
+
|
|
183
206
|
navigation?.setOptions(styleAnimatedHeader);
|
|
184
207
|
}
|
|
185
208
|
}, [animatedHeader]);
|
|
@@ -187,6 +210,22 @@ const Screen = forwardRef(
|
|
|
187
210
|
useEffect(() => {
|
|
188
211
|
if (!animatedHeader) {
|
|
189
212
|
let headerOptions: NavigationOptions;
|
|
213
|
+
|
|
214
|
+
const interpolateConfig: Animated.InterpolationConfigType =
|
|
215
|
+
inputSearchProps
|
|
216
|
+
? {
|
|
217
|
+
inputRange: [0, 100],
|
|
218
|
+
outputRange: [1, 0],
|
|
219
|
+
extrapolate: 'clamp',
|
|
220
|
+
}
|
|
221
|
+
: {
|
|
222
|
+
inputRange: [0, 200],
|
|
223
|
+
outputRange: [0, 1],
|
|
224
|
+
extrapolate: 'clamp',
|
|
225
|
+
};
|
|
226
|
+
const titleOpacity =
|
|
227
|
+
animatedValue.current.interpolate(interpolateConfig);
|
|
228
|
+
|
|
190
229
|
switch (headerType) {
|
|
191
230
|
case 'none':
|
|
192
231
|
headerOptions = {
|
|
@@ -214,7 +253,9 @@ const Screen = forwardRef(
|
|
|
214
253
|
headerTransparent: true,
|
|
215
254
|
headerTintColor: Colors.black_01,
|
|
216
255
|
headerBackground: () => null,
|
|
217
|
-
headerTitle: (props: any) =>
|
|
256
|
+
headerTitle: (props: any) => (
|
|
257
|
+
<HeaderTitle {...props} titleOpacity={titleOpacity} />
|
|
258
|
+
),
|
|
218
259
|
};
|
|
219
260
|
break;
|
|
220
261
|
|
|
@@ -230,13 +271,33 @@ const Screen = forwardRef(
|
|
|
230
271
|
}
|
|
231
272
|
}, [headerType]);
|
|
232
273
|
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (headerRight) {
|
|
276
|
+
navigation?.setOptions({
|
|
277
|
+
headerRight: (props: any) => (
|
|
278
|
+
<View onLayout={onHeaderRightLayout}>
|
|
279
|
+
{typeof headerRight === 'function' ? (
|
|
280
|
+
headerRight(props)
|
|
281
|
+
) : (
|
|
282
|
+
<HeaderRight {...props} {...headerRight} />
|
|
283
|
+
)}
|
|
284
|
+
</View>
|
|
285
|
+
),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}, [headerRight]);
|
|
289
|
+
|
|
290
|
+
const onHeaderRightLayout = (e: LayoutChangeEvent) => {
|
|
291
|
+
setHeaderRightWidth(e.nativeEvent.layout.width);
|
|
292
|
+
};
|
|
293
|
+
|
|
233
294
|
/**
|
|
234
295
|
* animated when use scroll && animated value
|
|
235
296
|
*/
|
|
236
297
|
if (scrollable) {
|
|
237
298
|
Component = Animated.ScrollView;
|
|
238
299
|
handleScroll = scrollViewProps?.onScroll;
|
|
239
|
-
if (animatedHeader || floatingButtonProps?.icon) {
|
|
300
|
+
if (animatedHeader || floatingButtonProps?.icon || inputSearchProps) {
|
|
240
301
|
handleScroll = Animated.event(
|
|
241
302
|
[
|
|
242
303
|
{
|
|
@@ -249,13 +310,13 @@ const Screen = forwardRef(
|
|
|
249
310
|
useNativeDriver: true,
|
|
250
311
|
listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
251
312
|
scrollViewProps?.onScroll?.(e);
|
|
252
|
-
if (animatedHeader?.type
|
|
313
|
+
if (animatedHeader?.type === 'surface') {
|
|
253
314
|
const offsetY = e.nativeEvent.contentOffset.y;
|
|
254
315
|
let color = Colors.black_01;
|
|
255
316
|
if (offsetY > 50) {
|
|
256
317
|
color = theme.colors.text.default;
|
|
257
318
|
}
|
|
258
|
-
if (color
|
|
319
|
+
if (color !== currentTint.current) {
|
|
259
320
|
currentTint.current = color;
|
|
260
321
|
navigation?.setOptions({
|
|
261
322
|
headerTintColor: color,
|
|
@@ -263,11 +324,26 @@ const Screen = forwardRef(
|
|
|
263
324
|
}
|
|
264
325
|
}
|
|
265
326
|
},
|
|
266
|
-
}
|
|
327
|
+
}
|
|
267
328
|
);
|
|
268
329
|
}
|
|
269
330
|
}
|
|
270
331
|
|
|
332
|
+
const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
333
|
+
const offsetY = e.nativeEvent.contentOffset.y;
|
|
334
|
+
|
|
335
|
+
if (inputSearchProps && offsetY < 100 && offsetY > 0) {
|
|
336
|
+
Animated.timing(animatedValue.current, {
|
|
337
|
+
toValue: 0,
|
|
338
|
+
useNativeDriver: true,
|
|
339
|
+
duration: 300,
|
|
340
|
+
}).start();
|
|
341
|
+
scrollViewRef.current?.scrollTo({y: 0, animated: true});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
scrollViewProps?.onScrollEndDrag?.(e);
|
|
345
|
+
};
|
|
346
|
+
|
|
271
347
|
/**
|
|
272
348
|
* render top navigation banner
|
|
273
349
|
*/
|
|
@@ -287,7 +363,7 @@ const Screen = forwardRef(
|
|
|
287
363
|
if (Array.isArray(results)) {
|
|
288
364
|
return results.map((item, index) => {
|
|
289
365
|
const space = item?.props?.useMargin === false ? 0 : Spacing.M;
|
|
290
|
-
if (item?.type
|
|
366
|
+
if (item?.type === Fragment) {
|
|
291
367
|
return renderContent(item?.props?.children);
|
|
292
368
|
}
|
|
293
369
|
if (item) {
|
|
@@ -307,7 +383,7 @@ const Screen = forwardRef(
|
|
|
307
383
|
} else {
|
|
308
384
|
const item: any = children;
|
|
309
385
|
const space = item?.props?.useMargin === false ? 0 : Spacing.M;
|
|
310
|
-
if (item?.type
|
|
386
|
+
if (item?.type === Fragment) {
|
|
311
387
|
return renderContent(item?.props?.children);
|
|
312
388
|
}
|
|
313
389
|
return (
|
|
@@ -327,15 +403,21 @@ const Screen = forwardRef(
|
|
|
327
403
|
return (
|
|
328
404
|
<View style={[Styles.flex, {backgroundColor}]}>
|
|
329
405
|
{!!headerBackground && (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
406
|
+
<ExtendBackground
|
|
407
|
+
headerBackground={headerBackground}
|
|
408
|
+
heightHeader={heightHeader}
|
|
409
|
+
animatedValue={animatedValue.current}
|
|
410
|
+
/>
|
|
411
|
+
)}
|
|
412
|
+
{inputSearchProps && (
|
|
413
|
+
<AnimatedInputSearch
|
|
414
|
+
animatedValue={animatedValue.current}
|
|
415
|
+
headerRightWidth={headerRightWidth}
|
|
416
|
+
headerType={headerType}
|
|
417
|
+
heightHeader={heightHeader}
|
|
418
|
+
inputSearchProps={inputSearchProps}
|
|
419
|
+
inputRef={inputSearchRef}
|
|
420
|
+
/>
|
|
339
421
|
)}
|
|
340
422
|
<KeyboardAvoidingView
|
|
341
423
|
style={Styles.flex}
|
|
@@ -349,9 +431,11 @@ const Screen = forwardRef(
|
|
|
349
431
|
|
|
350
432
|
<Component
|
|
351
433
|
{...scrollViewProps}
|
|
352
|
-
ref={
|
|
434
|
+
ref={scrollViewRef}
|
|
353
435
|
showsVerticalScrollIndicator={false}
|
|
354
436
|
onScroll={handleScroll}
|
|
437
|
+
onScrollEndDrag={handleScrollEnd}
|
|
438
|
+
scrollEventThrottle={16}
|
|
355
439
|
style={Styles.flex}>
|
|
356
440
|
<View
|
|
357
441
|
style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
|
|
@@ -387,7 +471,7 @@ const Screen = forwardRef(
|
|
|
387
471
|
</KeyboardAvoidingView>
|
|
388
472
|
</View>
|
|
389
473
|
);
|
|
390
|
-
}
|
|
474
|
+
}
|
|
391
475
|
);
|
|
392
476
|
|
|
393
477
|
export default Screen;
|
package/Layout/styles.ts
CHANGED
|
@@ -2,12 +2,6 @@ import {Platform, StyleSheet} from 'react-native';
|
|
|
2
2
|
import {Colors, Radius, Spacing} from '../Consts';
|
|
3
3
|
|
|
4
4
|
export default StyleSheet.create({
|
|
5
|
-
extendedHeader: {
|
|
6
|
-
aspectRatio: 1.75,
|
|
7
|
-
position: 'absolute',
|
|
8
|
-
width: '100%',
|
|
9
|
-
height: 210,
|
|
10
|
-
},
|
|
11
5
|
screenBanner: {
|
|
12
6
|
width: '100%',
|
|
13
7
|
},
|
package/Layout/types.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import {Animated} from 'react-native';
|
|
2
|
+
import {InputSearchProps, InputSearchRef} from '../Input';
|
|
3
|
+
import {LegacyRef} from 'react';
|
|
4
|
+
|
|
5
|
+
export type HeaderType = 'default' | 'surface' | 'extended' | 'search' | 'none';
|
|
2
6
|
|
|
3
7
|
export type GridContextProps = {
|
|
4
8
|
/**
|
|
@@ -21,3 +25,18 @@ export type GridContextProps = {
|
|
|
21
25
|
*/
|
|
22
26
|
getSizeSpan: (span: number) => number;
|
|
23
27
|
};
|
|
28
|
+
|
|
29
|
+
export type AnimatedInputSearchProps = {
|
|
30
|
+
animatedValue: Animated.Value;
|
|
31
|
+
headerRightWidth: number;
|
|
32
|
+
heightHeader: number;
|
|
33
|
+
headerType: HeaderType;
|
|
34
|
+
inputSearchProps?: InputSearchProps;
|
|
35
|
+
inputRef?: LegacyRef<InputSearchRef>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ExtendHeaderProps = {
|
|
39
|
+
headerBackground: string;
|
|
40
|
+
animatedValue: Animated.Value;
|
|
41
|
+
heightHeader: number;
|
|
42
|
+
};
|
package/Title/index.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import React, {FC, useContext, useState} from 'react';
|
|
2
2
|
import {Text as RNText, TouchableOpacity, View, ViewStyle} from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
Typography,
|
|
5
|
+
Colors,
|
|
6
|
+
Icon,
|
|
7
|
+
Badge,
|
|
8
|
+
ApplicationContext,
|
|
9
|
+
scaleSize,
|
|
10
|
+
Text,
|
|
11
|
+
} from '@momo-kits/foundation';
|
|
8
12
|
import styles from './styles';
|
|
9
13
|
import {TitleProps} from './types';
|
|
10
|
-
import {Typography} from '../Text/types';
|
|
11
14
|
|
|
12
15
|
const Title: FC<TitleProps> = ({
|
|
13
16
|
title = 'Title',
|