@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.
@@ -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
- const scaleAnim = useSharedValue(1);
19
- const waveScaleAnim = useSharedValue(1);
20
- const waveOpacityAnim = useSharedValue(0);
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
- const springCfg = { damping: 5, stiffness: 30, mass: 1 };
28
-
29
- scaleAnim.value = withRepeat(
30
- withSequence(
31
- withSpring(1, springCfg),
32
- withSpring(1.1, springCfg),
33
- ),
34
- -1,
35
- false,
36
- );
37
- waveScaleAnim.value = withRepeat(
38
- withSequence(
39
- withTiming(2.5, { duration: DURATION * 3 }),
40
- withTiming(1, { duration: 0 }),
41
- ),
42
- -1,
43
- false,
44
- );
45
- waveOpacityAnim.value = withRepeat(
46
- withSequence(
47
- withTiming(0.3, { duration: DURATION * 2 }),
48
- withTiming(0, { duration: DURATION }),
49
- ),
50
- -1,
51
- false,
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
- cancelAnimation(scaleAnim);
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
- <Animated.View style={[waveStyle, waveAnimatedStyle]} />
72
- <Animated.View style={[dotStyle, dotAnimatedStyle]} />
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
- <LinearGradient {...gradientPros} style={styles.gradientView} />
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
- const ScaleSizeContext = createContext<{ scaleSizeMaxRate?: number }>({
13
- scaleSizeMaxRate: undefined,
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
  });
@@ -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 = useSharedValue(0);
35
+ const opacity = useRef(new Animated.Value(0)).current;
45
36
 
46
37
  useEffect(() => {
47
- opacity.value = withRepeat(
48
- withSequence(
49
- withTiming(1, { duration: DURATION, easing: Easing.linear }),
50
- withDelay(
51
- DURATION * 2,
52
- withTiming(0, { duration: DURATION, easing: Easing.linear }),
53
- ),
54
- ),
55
- -1,
56
- false,
57
- );
58
- return () => {
59
- cancelAnimation(opacity);
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
- height: useScaleSize(12),
76
- width: 1,
77
- backgroundColor: theme.colors.primary,
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?: SharedValue<number>;
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 = useSharedValue(0);
45
+ const maxWidth = useRef(0);
47
46
  const minWidth = size === 'small' ? 36 : 48;
48
- const opacityAnimated = useSharedValue(0);
49
- const widthAnimated = useSharedValue<number | null>(null);
50
- const lastOffset = useSharedValue(0);
51
- const lastDirection = useSharedValue<string | null>(null);
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
- useAnimatedReaction(
60
- () => animatedValue?.value ?? 0,
61
- (value) => {
62
- 'worklet';
63
- if (!label || !animatedValue) return;
64
- if (value !== lastOffset.value && value > 0) {
65
- const direction = value > lastOffset.value ? 'down' : 'up';
66
- lastOffset.value = value;
67
- if (lastDirection.value !== direction) {
68
- lastDirection.value = direction;
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
- opacityAnimated.value = withTiming(0, { duration: 100 });
71
- widthAnimated.value = withTiming(
72
- minWidth,
73
- { duration: 100 },
74
- (finished) => {
75
- if (finished) runOnJS(setShowText)(false);
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
- opacityAnimated.value = withTiming(1, { duration: 100 });
80
- widthAnimated.value = withTiming(
81
- maxWidth.value,
82
- { duration: 100 },
83
- (finished) => {
84
- if (finished) runOnJS(setShowText)(true);
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
- const labelStyle = useAnimatedStyle(() => ({
99
- opacity: opacityAnimated.value,
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.value != null) return;
105
- maxWidth.value = layout.width;
106
- widthAnimated.value = layout.width;
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
  >