@momo-kits/foundation 0.161.2-test.1 → 0.162.2-test.5
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/BottomSheet.tsx +4 -7
- package/Application/BottomTab/BottomTabBar.tsx +15 -15
- package/Application/BottomTab/index.tsx +56 -34
- package/Application/Components/BackgroundImageView.tsx +6 -22
- package/Application/Components/HeaderAnimated.tsx +29 -32
- package/Application/Components/HeaderBackground.tsx +26 -20
- package/Application/Components/HeaderExtendHeader.tsx +94 -114
- package/Application/Components/HeaderTitle.tsx +10 -39
- package/Application/Components/SearchHeader.tsx +21 -16
- package/Application/NavigationContainer.tsx +31 -2
- package/Application/ScaleSizeProvider.tsx +5 -3
- package/Application/WidgetContainer.tsx +1 -1
- package/Application/types.ts +6 -5
- package/Application/utils.tsx +3 -4
- package/Badge/BadgeDotAnimation.tsx +71 -53
- package/Button/index.tsx +4 -1
- package/Context/index.ts +6 -3
- package/Input/InputOTP.tsx +23 -38
- package/Layout/FloatingButton.tsx +59 -59
- package/Layout/Screen.tsx +61 -71
- package/Loader/ProgressBar.tsx +18 -20
- package/Pagination/Dot.tsx +2 -2
- package/Pagination/PaginationScroll.tsx +27 -31
- package/Skeleton/index.tsx +24 -32
- package/Text/utils.ts +11 -6
- package/package.json +1 -1
|
@@ -1,75 +1,93 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import Animated, {
|
|
4
|
-
cancelAnimation,
|
|
5
|
-
useAnimatedStyle,
|
|
6
|
-
useSharedValue,
|
|
7
|
-
withRepeat,
|
|
8
|
-
withSequence,
|
|
9
|
-
withSpring,
|
|
10
|
-
withTiming,
|
|
11
|
-
} from 'react-native-reanimated';
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Animated, View } from 'react-native';
|
|
12
3
|
import { BadgeDotProps } from './types';
|
|
13
4
|
import styles from './styles';
|
|
14
5
|
|
|
15
6
|
const DURATION = 500;
|
|
16
7
|
|
|
17
8
|
const BadgeDotAnimation = ({ size, style }: BadgeDotProps) => {
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
9
|
+
// Refs for animated values
|
|
10
|
+
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
11
|
+
const waveScaleAnim = useRef(new Animated.Value(1)).current;
|
|
12
|
+
const waveOpacityAnim = useRef(new Animated.Value(0)).current;
|
|
21
13
|
|
|
22
14
|
const dotStyle =
|
|
23
15
|
size === 'small' ? styles.dotAnimationSmall : styles.dotAnimation;
|
|
24
16
|
const waveStyle = size === 'small' ? styles.waveSmall : styles.wave;
|
|
25
17
|
|
|
26
18
|
useEffect(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
19
|
+
// Infinite loop animation for the scale and wave effect
|
|
20
|
+
const animation = Animated.loop(
|
|
21
|
+
Animated.parallel([
|
|
22
|
+
// Dot pulse animation
|
|
23
|
+
Animated.sequence([
|
|
24
|
+
Animated.spring(scaleAnim, {
|
|
25
|
+
toValue: 1, // Scale up slightly
|
|
26
|
+
friction: 5, // Controls the "bounciness" of the spring
|
|
27
|
+
tension: 30, // Controls the "stiffness" of the spring
|
|
28
|
+
useNativeDriver: true,
|
|
29
|
+
}),
|
|
30
|
+
Animated.spring(scaleAnim, {
|
|
31
|
+
toValue: 1.1,
|
|
32
|
+
friction: 5,
|
|
33
|
+
tension: 30,
|
|
34
|
+
useNativeDriver: true,
|
|
35
|
+
}),
|
|
36
|
+
]), // Wave animation
|
|
37
|
+
Animated.sequence([
|
|
38
|
+
Animated.timing(waveScaleAnim, {
|
|
39
|
+
toValue: 2.5,
|
|
40
|
+
duration: DURATION * 3,
|
|
41
|
+
useNativeDriver: true,
|
|
42
|
+
}),
|
|
43
|
+
Animated.timing(waveScaleAnim, {
|
|
44
|
+
toValue: 1, // Reset wave size
|
|
45
|
+
duration: 0,
|
|
46
|
+
useNativeDriver: true,
|
|
47
|
+
}),
|
|
48
|
+
]), // Wave opacity animation
|
|
49
|
+
Animated.sequence([
|
|
50
|
+
Animated.timing(waveOpacityAnim, {
|
|
51
|
+
toValue: 0.3, // Wave becomes visible
|
|
52
|
+
duration: DURATION * 2,
|
|
53
|
+
useNativeDriver: true,
|
|
54
|
+
}),
|
|
55
|
+
Animated.timing(waveOpacityAnim, {
|
|
56
|
+
toValue: 0, // Wave fades out
|
|
57
|
+
duration: DURATION,
|
|
58
|
+
useNativeDriver: true,
|
|
59
|
+
}),
|
|
60
|
+
]),
|
|
61
|
+
]),
|
|
52
62
|
);
|
|
63
|
+
animation.start();
|
|
64
|
+
|
|
53
65
|
return () => {
|
|
54
|
-
|
|
55
|
-
cancelAnimation(waveScaleAnim);
|
|
56
|
-
cancelAnimation(waveOpacityAnim);
|
|
66
|
+
animation.stop();
|
|
57
67
|
};
|
|
58
68
|
}, [scaleAnim, waveOpacityAnim, waveScaleAnim]);
|
|
59
69
|
|
|
60
|
-
const waveAnimatedStyle = useAnimatedStyle(() => ({
|
|
61
|
-
transform: [{ scale: waveScaleAnim.value }],
|
|
62
|
-
opacity: waveOpacityAnim.value,
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
const dotAnimatedStyle = useAnimatedStyle(() => ({
|
|
66
|
-
transform: [{ scale: scaleAnim.value }],
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
70
|
return (
|
|
70
71
|
<View style={[styles.dotAnimationContainer, style]}>
|
|
71
|
-
|
|
72
|
-
<Animated.View
|
|
72
|
+
{/* Wave Animation */}
|
|
73
|
+
<Animated.View
|
|
74
|
+
style={[
|
|
75
|
+
waveStyle,
|
|
76
|
+
{
|
|
77
|
+
transform: [{ scale: waveScaleAnim }],
|
|
78
|
+
opacity: waveOpacityAnim,
|
|
79
|
+
},
|
|
80
|
+
]}
|
|
81
|
+
/>
|
|
82
|
+
{/* Dot Animation */}
|
|
83
|
+
<Animated.View
|
|
84
|
+
style={[
|
|
85
|
+
dotStyle,
|
|
86
|
+
{
|
|
87
|
+
transform: [{ scale: scaleAnim }],
|
|
88
|
+
},
|
|
89
|
+
]}
|
|
90
|
+
/>
|
|
73
91
|
</View>
|
|
74
92
|
);
|
|
75
93
|
};
|
package/Button/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { FC, useContext, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Animated,
|
|
3
4
|
StyleSheet,
|
|
4
5
|
TouchableOpacity,
|
|
5
6
|
TouchableOpacityProps,
|
|
@@ -28,6 +29,8 @@ import Reanimated, {
|
|
|
28
29
|
withTiming,
|
|
29
30
|
} from 'react-native-reanimated';
|
|
30
31
|
|
|
32
|
+
const AnimationLinear = Animated.createAnimatedComponent(LinearGradient);
|
|
33
|
+
|
|
31
34
|
export interface ButtonProps extends TouchableOpacityProps {
|
|
32
35
|
/**
|
|
33
36
|
* Defines the visual style of the button.
|
|
@@ -356,7 +359,7 @@ const Button: FC<ButtonProps> = ({
|
|
|
356
359
|
{renderTitle()}
|
|
357
360
|
{renderIcon('right')}
|
|
358
361
|
{gradientPros && (
|
|
359
|
-
<
|
|
362
|
+
<AnimationLinear {...gradientPros} style={styles.gradientView} />
|
|
360
363
|
)}
|
|
361
364
|
{gradientPros && <View style={styles.strokeView} />}
|
|
362
365
|
</Reanimated.View>
|
package/Context/index.ts
CHANGED
|
@@ -9,9 +9,12 @@ const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
|
|
|
9
9
|
const ScreenContext = (Platform as any).ScreenContext ?? Context;
|
|
10
10
|
const ComponentContext = (Platform as any).ComponentContext ?? Context;
|
|
11
11
|
const SkeletonContext = createContext({ loading: false });
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Single source of truth: chứa cả flag OS lẫn rate user tự chỉnh.
|
|
13
|
+
export type FontScaleConfig = {
|
|
14
|
+
useOSFontScale?: boolean;
|
|
15
|
+
userScaleRate?: number;
|
|
16
|
+
};
|
|
17
|
+
const ScaleSizeContext = createContext<FontScaleConfig>({});
|
|
15
18
|
const TrackingScopeContext = createContext<{ scopeName?: string }>({
|
|
16
19
|
scopeName: undefined,
|
|
17
20
|
});
|
package/Input/InputOTP.tsx
CHANGED
|
@@ -8,22 +8,13 @@ import React, {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import {
|
|
11
|
+
Animated,
|
|
11
12
|
PixelRatio,
|
|
12
13
|
TextInput,
|
|
13
14
|
TextInputFocusEvent,
|
|
14
15
|
TouchableOpacity,
|
|
15
16
|
View,
|
|
16
17
|
} from 'react-native';
|
|
17
|
-
import Animated, {
|
|
18
|
-
cancelAnimation,
|
|
19
|
-
Easing,
|
|
20
|
-
useAnimatedStyle,
|
|
21
|
-
useSharedValue,
|
|
22
|
-
withDelay,
|
|
23
|
-
withRepeat,
|
|
24
|
-
withSequence,
|
|
25
|
-
withTiming,
|
|
26
|
-
} from 'react-native-reanimated';
|
|
27
18
|
import { useComponentId } from '../Application';
|
|
28
19
|
import { Spacing, Styles } from '../Consts';
|
|
29
20
|
import { useScaleSize, Text } from '../Text';
|
|
@@ -41,43 +32,37 @@ import {
|
|
|
41
32
|
const OTPCaret: FC<CaretProps> = ({ index, length }) => {
|
|
42
33
|
const DURATION = 300;
|
|
43
34
|
const { theme } = useContext(ApplicationContext);
|
|
44
|
-
const opacity =
|
|
35
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
45
36
|
|
|
46
37
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
DURATION
|
|
52
|
-
|
|
53
|
-
),
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
Animated.loop(
|
|
39
|
+
Animated.sequence([
|
|
40
|
+
Animated.timing(opacity, {
|
|
41
|
+
toValue: 1,
|
|
42
|
+
duration: DURATION,
|
|
43
|
+
useNativeDriver: true,
|
|
44
|
+
}),
|
|
45
|
+
Animated.delay(DURATION * 2),
|
|
46
|
+
Animated.timing(opacity, {
|
|
47
|
+
toValue: 0,
|
|
48
|
+
duration: DURATION,
|
|
49
|
+
useNativeDriver: true,
|
|
50
|
+
}),
|
|
51
|
+
]),
|
|
52
|
+
).start();
|
|
61
53
|
}, [opacity]);
|
|
62
|
-
|
|
63
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
64
|
-
opacity: opacity.value,
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
54
|
const spacingStyle = !isNaN(Number(length)) &&
|
|
68
55
|
index !== Number(length) - 1 && { marginRight: Spacing.L };
|
|
69
56
|
|
|
70
57
|
return (
|
|
71
58
|
<View style={[Styles.rowCenter, spacingStyle]}>
|
|
72
59
|
<Animated.View
|
|
73
|
-
style={
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
animatedStyle,
|
|
80
|
-
]}
|
|
60
|
+
style={{
|
|
61
|
+
height: useScaleSize(12),
|
|
62
|
+
width: 1,
|
|
63
|
+
backgroundColor: theme.colors.primary,
|
|
64
|
+
opacity,
|
|
65
|
+
}}
|
|
81
66
|
/>
|
|
82
67
|
<Text color={theme.colors.text.hint} typography={'body_default_regular'}>
|
|
83
68
|
-
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useState } from 'react';
|
|
2
1
|
import {
|
|
2
|
+
default as React,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import {
|
|
9
|
+
Animated,
|
|
3
10
|
LayoutChangeEvent,
|
|
4
11
|
StyleSheet,
|
|
5
12
|
TouchableOpacity,
|
|
6
13
|
View,
|
|
7
14
|
} from 'react-native';
|
|
8
|
-
import Animated, {
|
|
9
|
-
useAnimatedReaction,
|
|
10
|
-
useAnimatedStyle,
|
|
11
|
-
useSharedValue,
|
|
12
|
-
withTiming,
|
|
13
|
-
runOnJS,
|
|
14
|
-
type SharedValue,
|
|
15
|
-
} from 'react-native-reanimated';
|
|
16
15
|
import { ApplicationContext } from '../Context';
|
|
17
16
|
import { Icon } from '../Icon';
|
|
18
17
|
import { useScaleSize } from '../Text';
|
|
@@ -24,7 +23,7 @@ export interface FloatingButtonProps {
|
|
|
24
23
|
icon?: string;
|
|
25
24
|
iconColor?: string;
|
|
26
25
|
size?: 'small' | 'large';
|
|
27
|
-
animatedValue?:
|
|
26
|
+
animatedValue?: Animated.Value;
|
|
28
27
|
bottom?: number;
|
|
29
28
|
renderComponent?: () => React.ReactNode;
|
|
30
29
|
}
|
|
@@ -43,67 +42,68 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
43
42
|
const { theme } = useContext(ApplicationContext);
|
|
44
43
|
const scaledFontSize = useScaleSize(16);
|
|
45
44
|
const scaledLineHeight = useScaleSize(22);
|
|
46
|
-
const maxWidth =
|
|
45
|
+
const maxWidth = useRef(0);
|
|
47
46
|
const minWidth = size === 'small' ? 36 : 48;
|
|
48
|
-
const opacityAnimated =
|
|
49
|
-
const widthAnimated =
|
|
50
|
-
const lastOffset =
|
|
51
|
-
const lastDirection =
|
|
52
|
-
const [showText, setShowText] = useState(true);
|
|
47
|
+
const [opacityAnimated] = useState(new Animated.Value(0)); // Initial opacity set to 0
|
|
48
|
+
const [widthAnimated, setWidthAnimated] = useState<Animated.Value>();
|
|
49
|
+
const lastOffset = useRef(0);
|
|
50
|
+
const lastDirection = useRef<string>(null);
|
|
51
|
+
const [showText, setShowText] = React.useState(true);
|
|
53
52
|
|
|
54
53
|
useEffect(() => {
|
|
55
54
|
if (!label) return;
|
|
56
|
-
opacityAnimated.value = withTiming(1, { duration: 100 });
|
|
57
|
-
}, [label, opacityAnimated]);
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
Animated.timing(opacityAnimated, {
|
|
57
|
+
toValue: 1,
|
|
58
|
+
duration: 100,
|
|
59
|
+
useNativeDriver: true,
|
|
60
|
+
}).start();
|
|
61
|
+
|
|
62
|
+
const listener = animatedValue?.addListener(({ value }) => {
|
|
63
|
+
if (value !== lastOffset.current && value > 0) {
|
|
64
|
+
const direction = value > lastOffset.current ? 'down' : 'up';
|
|
65
|
+
lastOffset.current = value;
|
|
66
|
+
if (lastDirection.current !== direction) {
|
|
67
|
+
lastDirection.current = direction;
|
|
69
68
|
if (direction === 'down') {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
Animated.timing(opacityAnimated, {
|
|
70
|
+
toValue: 0,
|
|
71
|
+
duration: 100,
|
|
72
|
+
useNativeDriver: true,
|
|
73
|
+
}).start();
|
|
74
|
+
Animated.timing(widthAnimated!, {
|
|
75
|
+
toValue: minWidth,
|
|
76
|
+
duration: 100,
|
|
77
|
+
useNativeDriver: false,
|
|
78
|
+
}).start(() => setShowText(false));
|
|
78
79
|
} else {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
Animated.timing(opacityAnimated, {
|
|
81
|
+
toValue: 1,
|
|
82
|
+
duration: 100,
|
|
83
|
+
useNativeDriver: true,
|
|
84
|
+
}).start();
|
|
85
|
+
Animated.timing(widthAnimated!, {
|
|
86
|
+
toValue: maxWidth.current,
|
|
87
|
+
duration: 100,
|
|
88
|
+
useNativeDriver: false,
|
|
89
|
+
}).start(() => setShowText(true));
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
|
-
}
|
|
91
|
-
[label, animatedValue, minWidth],
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const containerStyle = useAnimatedStyle(() => ({
|
|
95
|
-
width: widthAnimated.value ?? undefined,
|
|
96
|
-
}));
|
|
93
|
+
});
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
return () => {
|
|
96
|
+
if (listener) {
|
|
97
|
+
animatedValue?.removeListener(listener);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}, [animatedValue, label, minWidth, opacityAnimated, widthAnimated]);
|
|
101
101
|
|
|
102
102
|
const handleLayout = (event: LayoutChangeEvent) => {
|
|
103
103
|
const layout = event.nativeEvent.layout;
|
|
104
|
-
if (widthAnimated
|
|
105
|
-
maxWidth.
|
|
106
|
-
|
|
104
|
+
if (widthAnimated) return;
|
|
105
|
+
maxWidth.current = layout.width;
|
|
106
|
+
setWidthAnimated(new Animated.Value(layout.width));
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
if (renderComponent) {
|
|
@@ -132,11 +132,11 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
132
132
|
{
|
|
133
133
|
right: position === 'right' ? 12 : undefined,
|
|
134
134
|
alignSelf: position === 'center' ? 'center' : 'flex-end',
|
|
135
|
+
width: widthAnimated,
|
|
135
136
|
height: size === 'small' ? 36 : 48,
|
|
136
137
|
backgroundColor: theme.colors.primary,
|
|
137
138
|
bottom,
|
|
138
139
|
},
|
|
139
|
-
containerStyle,
|
|
140
140
|
]}
|
|
141
141
|
>
|
|
142
142
|
<TouchableOpacity
|
|
@@ -156,9 +156,9 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
156
156
|
{
|
|
157
157
|
fontSize: scaledFontSize,
|
|
158
158
|
lineHeight: scaledLineHeight,
|
|
159
|
+
opacity: opacityAnimated,
|
|
159
160
|
color: 'white',
|
|
160
161
|
},
|
|
161
|
-
labelStyle,
|
|
162
162
|
]}
|
|
163
163
|
numberOfLines={1}
|
|
164
164
|
>
|