@hoddy-ui/core 1.1.0 → 1.1.2
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/README.md +972 -190
- package/index.ts +7 -3
- package/next/dist/index.d.mts +62 -7
- package/next/dist/index.d.ts +62 -7
- package/next/dist/index.js +225 -200
- package/next/dist/index.js.map +1 -1
- package/next/dist/index.mjs +223 -200
- package/next/dist/index.mjs.map +1 -1
- package/next/index.ts +1 -0
- package/next/package.json +1 -1
- package/package.json +1 -1
- package/src/Components/Animators/Animator.tsx +117 -0
- package/src/Components/Animators/README.md +137 -0
- package/src/Components/Animators/hooks/index.ts +8 -0
- package/src/Components/Animators/hooks/useAppState.ts +37 -0
- package/src/Components/Animators/hooks/useBlinkAnimation.ts +71 -0
- package/src/Components/Animators/hooks/useFadeAnimation.ts +50 -0
- package/src/Components/Animators/hooks/useFloatAnimation.ts +103 -0
- package/src/Components/Animators/hooks/useGrowAnimation.ts +54 -0
- package/src/Components/Animators/hooks/useRollAnimation.ts +86 -0
- package/src/Components/Animators/hooks/useSlideAnimation.ts +88 -0
- package/src/Components/Animators/hooks/useThrownUpAnimation.ts +82 -0
- package/src/Components/Locator.tsx +50 -22
- package/src/Components/Popup.tsx +16 -13
- package/src/Components/TextField.tsx +230 -220
- package/src/Components/Typography.tsx +0 -2
- package/src/config/KeyManager.ts +2 -0
- package/src/config/index.ts +2 -0
- package/src/theme/index.tsx +3 -1
- package/src/types.ts +57 -9
- package/src/Components/Animator.tsx +0 -43
package/next/index.ts
CHANGED
package/next/package.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
import { Animated } from "react-native";
|
|
3
|
+
import { AnimatorProps } from "../../types";
|
|
4
|
+
import { useBlinkAnimation } from "./hooks/useBlinkAnimation";
|
|
5
|
+
import { useFadeAnimation } from "./hooks/useFadeAnimation";
|
|
6
|
+
import { useFloatAnimation } from "./hooks/useFloatAnimation";
|
|
7
|
+
import { useGrowAnimation } from "./hooks/useGrowAnimation";
|
|
8
|
+
import { useRollAnimation } from "./hooks/useRollAnimation";
|
|
9
|
+
import { useSlideAnimation } from "./hooks/useSlideAnimation";
|
|
10
|
+
import { useThrownUpAnimation } from "./hooks/useThrownUpAnimation";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unified Animator component that handles multiple animation types with type-safe props.
|
|
14
|
+
*
|
|
15
|
+
* Each animation type only accepts its relevant props, ensuring type safety and better developer experience.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Fade animation - only accepts base props
|
|
19
|
+
* <Animator type="fade" duration={1000} closeAfter={3000}>
|
|
20
|
+
* <Text>This will fade in and out</Text>
|
|
21
|
+
* </Animator>
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Slide animation - only accepts direction and initialValue props
|
|
25
|
+
* <Animator type="slide" direction="up" duration={800} closeAfter={2000}>
|
|
26
|
+
* <View>This will slide up from bottom</View>
|
|
27
|
+
* </Animator>
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Grow animation - only accepts initialScale prop
|
|
31
|
+
* <Animator type="grow" initialScale={0.5} duration={600}>
|
|
32
|
+
* <Button>This will grow from 50% scale</Button>
|
|
33
|
+
* </Animator>
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Blink animation - only accepts blink-specific props
|
|
37
|
+
* <Animator type="blink" blinkDuration={1000} minOpacity={0.3}>
|
|
38
|
+
* <Icon>This will blink continuously</Icon>
|
|
39
|
+
* </Animator>
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // TypeScript will show errors for invalid prop combinations:
|
|
43
|
+
* // ❌ This will cause a TypeScript error:
|
|
44
|
+
* // <Animator type="fade" direction="up"> // direction is not valid for fade
|
|
45
|
+
* //
|
|
46
|
+
* // ✅ This is correct:
|
|
47
|
+
* // <Animator type="slide" direction="up">
|
|
48
|
+
*/
|
|
49
|
+
const Animator: FC<AnimatorProps> = (props) => {
|
|
50
|
+
const { children, type, duration, delay, closeAfter, style = {} } = props;
|
|
51
|
+
|
|
52
|
+
// Get animation style based on type
|
|
53
|
+
const getAnimationStyle = () => {
|
|
54
|
+
switch (type) {
|
|
55
|
+
case "fade":
|
|
56
|
+
return useFadeAnimation({ duration, delay, closeAfter });
|
|
57
|
+
|
|
58
|
+
case "grow":
|
|
59
|
+
return useGrowAnimation({
|
|
60
|
+
duration,
|
|
61
|
+
delay,
|
|
62
|
+
closeAfter,
|
|
63
|
+
initialScale: props.initialScale,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
case "slide":
|
|
67
|
+
return useSlideAnimation({
|
|
68
|
+
duration,
|
|
69
|
+
delay,
|
|
70
|
+
direction: props.direction,
|
|
71
|
+
closeAfter,
|
|
72
|
+
initialValue: props.initialValue,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
case "blink":
|
|
76
|
+
return useBlinkAnimation({
|
|
77
|
+
delay,
|
|
78
|
+
blinkDuration: props.blinkDuration,
|
|
79
|
+
minOpacity: props.minOpacity,
|
|
80
|
+
maxOpacity: props.maxOpacity,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
case "float":
|
|
84
|
+
return useFloatAnimation({
|
|
85
|
+
duration,
|
|
86
|
+
delay,
|
|
87
|
+
closeAfter,
|
|
88
|
+
closeDuration: props.closeDuration,
|
|
89
|
+
floatDistance: props.floatDistance,
|
|
90
|
+
floatDuration: props.floatDuration,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
case "roll":
|
|
94
|
+
return useRollAnimation({
|
|
95
|
+
duration,
|
|
96
|
+
delay,
|
|
97
|
+
closeAfter,
|
|
98
|
+
initialTranslateY: props.initialTranslateY,
|
|
99
|
+
initialRotate: props.initialRotate,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
case "thrownup":
|
|
103
|
+
return useThrownUpAnimation({ delay, closeAfter });
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
return { animatedStyle: {} };
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const { animatedStyle } = getAnimationStyle();
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Animated.View style={[style, animatedStyle]}>{children}</Animated.View>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default Animator;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Unified Animator Component
|
|
2
|
+
|
|
3
|
+
The `Animator` component provides a single interface for all animation types with generic props.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Single Component**: One component handles all animation types
|
|
8
|
+
- **Generic Props**: Consistent prop naming across all animations (e.g., `closeAfter` instead of animation-specific names)
|
|
9
|
+
- **Modular Hooks**: Animation logic is separated into individual custom hooks
|
|
10
|
+
- **Type Safety**: Full TypeScript support with proper typing
|
|
11
|
+
- **Performance**: Only the specified animation runs, others are not loaded
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Animator } from 'hoddy-ui';
|
|
17
|
+
|
|
18
|
+
// Fade animation
|
|
19
|
+
<Animator type="fade" duration={1000} closeAfter={3000}>
|
|
20
|
+
<Text>This will fade in and out</Text>
|
|
21
|
+
</Animator>
|
|
22
|
+
|
|
23
|
+
// Slide animation
|
|
24
|
+
<Animator type="slide" direction="up" duration={800} closeAfter={2000}>
|
|
25
|
+
<View>This will slide up from bottom</View>
|
|
26
|
+
</Animator>
|
|
27
|
+
|
|
28
|
+
// Grow animation
|
|
29
|
+
<Animator type="grow" initialScale={0.5} duration={600}>
|
|
30
|
+
<Button>This will grow from 50% scale</Button>
|
|
31
|
+
</Animator>
|
|
32
|
+
|
|
33
|
+
// Blink animation (continuous)
|
|
34
|
+
<Animator type="blink" blinkDuration={1000} minOpacity={0.3}>
|
|
35
|
+
<Icon>This will blink continuously</Icon>
|
|
36
|
+
</Animator>
|
|
37
|
+
|
|
38
|
+
// Float animation
|
|
39
|
+
<Animator type="float" floatDistance={20} floatDuration={2000} closeAfter={5000}>
|
|
40
|
+
<View>This will float up and down</View>
|
|
41
|
+
</Animator>
|
|
42
|
+
|
|
43
|
+
// Roll animation
|
|
44
|
+
<Animator type="roll" initialRotate="45deg" duration={800}>
|
|
45
|
+
<Image>This will roll and rotate</Image>
|
|
46
|
+
</Animator>
|
|
47
|
+
|
|
48
|
+
// Thrown up animation
|
|
49
|
+
<Animator type="thrownup" delay={500} closeAfter={4000}>
|
|
50
|
+
<Notification>This will spring up from bottom</Notification>
|
|
51
|
+
</Animator>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Generic Props
|
|
55
|
+
|
|
56
|
+
All animation types support these generic props:
|
|
57
|
+
|
|
58
|
+
- `type`: Animation type ("fade" | "grow" | "slide" | "blink" | "float" | "roll" | "thrownup")
|
|
59
|
+
- `duration`: Animation duration in milliseconds
|
|
60
|
+
- `delay`: Delay before animation starts
|
|
61
|
+
- `closeAfter`: Time after which the exit animation starts (null for no exit)
|
|
62
|
+
- `style`: Additional styles for the animated view
|
|
63
|
+
|
|
64
|
+
## Animation-Specific Props
|
|
65
|
+
|
|
66
|
+
### Slide Animation
|
|
67
|
+
|
|
68
|
+
- `direction`: "up" | "down" | "left" | "right"
|
|
69
|
+
- `initialValue`: Custom initial position value
|
|
70
|
+
|
|
71
|
+
### Grow Animation
|
|
72
|
+
|
|
73
|
+
- `initialScale`: Starting scale (default: 0)
|
|
74
|
+
|
|
75
|
+
### Blink Animation
|
|
76
|
+
|
|
77
|
+
- `blinkDuration`: Duration of one blink cycle
|
|
78
|
+
- `minOpacity`: Minimum opacity value
|
|
79
|
+
- `maxOpacity`: Maximum opacity value
|
|
80
|
+
|
|
81
|
+
### Float Animation
|
|
82
|
+
|
|
83
|
+
- `closeDuration`: Duration of exit animation
|
|
84
|
+
- `floatDistance`: Distance to float up/down
|
|
85
|
+
- `floatDuration`: Duration of one float cycle
|
|
86
|
+
|
|
87
|
+
### Roll Animation
|
|
88
|
+
|
|
89
|
+
- `initialTranslateY`: Initial vertical position
|
|
90
|
+
- `initialRotate`: Initial rotation value
|
|
91
|
+
|
|
92
|
+
## Custom Hooks
|
|
93
|
+
|
|
94
|
+
You can also use the animation hooks directly:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { useFadeAnimation, useSlideAnimation } from "hoddy-ui";
|
|
98
|
+
|
|
99
|
+
const MyComponent = () => {
|
|
100
|
+
const { animatedStyle } = useFadeAnimation({
|
|
101
|
+
duration: 800,
|
|
102
|
+
closeAfter: 2000,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Animated.View style={animatedStyle}>
|
|
107
|
+
<Text>Custom animated content</Text>
|
|
108
|
+
</Animated.View>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Migration from Old Components
|
|
114
|
+
|
|
115
|
+
Replace old individual animation components:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// Old way
|
|
119
|
+
<AnimatedFade fadeOutAfter={2000}>
|
|
120
|
+
<Text>Content</Text>
|
|
121
|
+
</AnimatedFade>
|
|
122
|
+
|
|
123
|
+
// New way
|
|
124
|
+
<Animator type="fade" closeAfter={2000}>
|
|
125
|
+
<Text>Content</Text>
|
|
126
|
+
</Animator>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Available Animation Types
|
|
130
|
+
|
|
131
|
+
1. **fade**: Simple fade in/out
|
|
132
|
+
2. **grow**: Scale-based growth animation
|
|
133
|
+
3. **slide**: Directional slide animations
|
|
134
|
+
4. **blink**: Continuous opacity blinking
|
|
135
|
+
5. **float**: Floating up/down motion with fade
|
|
136
|
+
6. **roll**: Combined rotation and translation
|
|
137
|
+
7. **thrownup**: Spring-based upward animation
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as useAppState } from "./useAppState";
|
|
2
|
+
export { useBlinkAnimation } from "./useBlinkAnimation";
|
|
3
|
+
export { useFadeAnimation } from "./useFadeAnimation";
|
|
4
|
+
export { useFloatAnimation } from "./useFloatAnimation";
|
|
5
|
+
export { useGrowAnimation } from "./useGrowAnimation";
|
|
6
|
+
export { useRollAnimation } from "./useRollAnimation";
|
|
7
|
+
export { useSlideAnimation } from "./useSlideAnimation";
|
|
8
|
+
export { useThrownUpAnimation } from "./useThrownUpAnimation";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { AppState, Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
const useAppState = () => {
|
|
5
|
+
const [isActive, setIsActive] = useState(true);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const handleAppStateChange = (nextAppState: string) => {
|
|
9
|
+
setIsActive(nextAppState === "active");
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let subscription: any;
|
|
13
|
+
let blurSub: any;
|
|
14
|
+
let focusSub: any;
|
|
15
|
+
|
|
16
|
+
if (Platform.OS === "android") {
|
|
17
|
+
blurSub = AppState.addEventListener("blur", () =>
|
|
18
|
+
handleAppStateChange("inactive")
|
|
19
|
+
);
|
|
20
|
+
focusSub = AppState.addEventListener("focus", () =>
|
|
21
|
+
handleAppStateChange("active")
|
|
22
|
+
);
|
|
23
|
+
} else {
|
|
24
|
+
subscription = AppState.addEventListener("change", handleAppStateChange);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
subscription?.remove();
|
|
29
|
+
blurSub?.remove();
|
|
30
|
+
focusSub?.remove();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
return { isActive };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default useAppState;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Easing, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseBlinkAnimationProps {
|
|
6
|
+
delay?: number;
|
|
7
|
+
blinkDuration?: number;
|
|
8
|
+
minOpacity?: number;
|
|
9
|
+
maxOpacity?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useBlinkAnimation = ({
|
|
13
|
+
delay = 0,
|
|
14
|
+
blinkDuration = 2000,
|
|
15
|
+
minOpacity = 0.5,
|
|
16
|
+
maxOpacity = 1,
|
|
17
|
+
}: UseBlinkAnimationProps = {}) => {
|
|
18
|
+
const opacity = useRef(new Animated.Value(maxOpacity)).current;
|
|
19
|
+
const { isActive } = useAppState();
|
|
20
|
+
const blinkAnim = useRef<Animated.CompositeAnimation | null>(null);
|
|
21
|
+
|
|
22
|
+
const startBlinking = () => {
|
|
23
|
+
blinkAnim.current = Animated.loop(
|
|
24
|
+
Animated.sequence([
|
|
25
|
+
Animated.timing(opacity, {
|
|
26
|
+
toValue: minOpacity,
|
|
27
|
+
duration: blinkDuration / 2,
|
|
28
|
+
easing: Easing.inOut(Easing.quad),
|
|
29
|
+
useNativeDriver: true,
|
|
30
|
+
}),
|
|
31
|
+
Animated.timing(opacity, {
|
|
32
|
+
toValue: maxOpacity,
|
|
33
|
+
duration: blinkDuration / 2,
|
|
34
|
+
easing: Easing.inOut(Easing.quad),
|
|
35
|
+
useNativeDriver: true,
|
|
36
|
+
}),
|
|
37
|
+
])
|
|
38
|
+
);
|
|
39
|
+
blinkAnim.current.start();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!isActive && Platform.OS === "ios") {
|
|
44
|
+
opacity.stopAnimation();
|
|
45
|
+
}
|
|
46
|
+
}, [isActive]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (delay > 0) {
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
startBlinking();
|
|
52
|
+
}, delay);
|
|
53
|
+
return () => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
opacity.stopAnimation();
|
|
56
|
+
blinkAnim.current?.stop();
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
startBlinking();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
opacity.stopAnimation();
|
|
64
|
+
blinkAnim.current?.stop();
|
|
65
|
+
};
|
|
66
|
+
}, [delay, blinkDuration, minOpacity, maxOpacity]);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
animatedStyle: { opacity },
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseFadeAnimationProps {
|
|
6
|
+
duration?: number;
|
|
7
|
+
delay?: number;
|
|
8
|
+
closeAfter?: number | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useFadeAnimation = ({
|
|
12
|
+
duration = 1000,
|
|
13
|
+
delay = 0,
|
|
14
|
+
closeAfter = 2000,
|
|
15
|
+
}: UseFadeAnimationProps = {}) => {
|
|
16
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
17
|
+
const { isActive } = useAppState();
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!isActive && Platform.OS === "ios") {
|
|
21
|
+
opacity.stopAnimation();
|
|
22
|
+
}
|
|
23
|
+
}, [isActive]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Fade-in animation
|
|
27
|
+
Animated.timing(opacity, {
|
|
28
|
+
toValue: 1,
|
|
29
|
+
duration,
|
|
30
|
+
delay,
|
|
31
|
+
useNativeDriver: true,
|
|
32
|
+
}).start(() => {
|
|
33
|
+
if (closeAfter) {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
Animated.timing(opacity, {
|
|
36
|
+
toValue: 0,
|
|
37
|
+
duration,
|
|
38
|
+
useNativeDriver: true,
|
|
39
|
+
}).start();
|
|
40
|
+
}, closeAfter);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return () => opacity.stopAnimation();
|
|
45
|
+
}, [opacity, duration, delay, closeAfter]);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
animatedStyle: { opacity },
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Easing, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseFloatAnimationProps {
|
|
6
|
+
duration?: number;
|
|
7
|
+
delay?: number;
|
|
8
|
+
closeAfter?: number | null;
|
|
9
|
+
closeDuration?: number;
|
|
10
|
+
floatDistance?: number;
|
|
11
|
+
floatDuration?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useFloatAnimation = ({
|
|
15
|
+
duration = 800,
|
|
16
|
+
delay = 0,
|
|
17
|
+
closeAfter = 2000,
|
|
18
|
+
closeDuration = 600,
|
|
19
|
+
floatDistance = 10,
|
|
20
|
+
floatDuration = 1200,
|
|
21
|
+
}: UseFloatAnimationProps = {}) => {
|
|
22
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
23
|
+
const translateY = useRef(new Animated.Value(0)).current;
|
|
24
|
+
const { isActive } = useAppState();
|
|
25
|
+
const floatAnim = useRef<Animated.CompositeAnimation | null>(null);
|
|
26
|
+
|
|
27
|
+
const startFloating = () => {
|
|
28
|
+
floatAnim.current = Animated.loop(
|
|
29
|
+
Animated.sequence([
|
|
30
|
+
Animated.timing(translateY, {
|
|
31
|
+
toValue: -floatDistance,
|
|
32
|
+
duration: floatDuration / 2,
|
|
33
|
+
easing: Easing.inOut(Easing.quad),
|
|
34
|
+
useNativeDriver: true,
|
|
35
|
+
}),
|
|
36
|
+
Animated.timing(translateY, {
|
|
37
|
+
toValue: floatDistance,
|
|
38
|
+
duration: floatDuration,
|
|
39
|
+
easing: Easing.inOut(Easing.quad),
|
|
40
|
+
useNativeDriver: true,
|
|
41
|
+
}),
|
|
42
|
+
Animated.timing(translateY, {
|
|
43
|
+
toValue: 0,
|
|
44
|
+
duration: floatDuration / 2,
|
|
45
|
+
easing: Easing.inOut(Easing.quad),
|
|
46
|
+
useNativeDriver: true,
|
|
47
|
+
}),
|
|
48
|
+
])
|
|
49
|
+
);
|
|
50
|
+
floatAnim.current.start();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!isActive && Platform.OS === "ios") {
|
|
55
|
+
opacity.stopAnimation();
|
|
56
|
+
translateY.stopAnimation();
|
|
57
|
+
}
|
|
58
|
+
}, [isActive]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
// Fade-in
|
|
62
|
+
Animated.timing(opacity, {
|
|
63
|
+
toValue: 1,
|
|
64
|
+
duration,
|
|
65
|
+
delay,
|
|
66
|
+
useNativeDriver: true,
|
|
67
|
+
}).start(() => {
|
|
68
|
+
startFloating();
|
|
69
|
+
|
|
70
|
+
if (closeAfter) {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
floatAnim.current?.stop();
|
|
73
|
+
|
|
74
|
+
Animated.timing(opacity, {
|
|
75
|
+
toValue: 0,
|
|
76
|
+
duration: closeDuration,
|
|
77
|
+
useNativeDriver: true,
|
|
78
|
+
}).start();
|
|
79
|
+
}, closeAfter);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
opacity.stopAnimation();
|
|
85
|
+
translateY.stopAnimation();
|
|
86
|
+
floatAnim.current?.stop();
|
|
87
|
+
};
|
|
88
|
+
}, [
|
|
89
|
+
duration,
|
|
90
|
+
delay,
|
|
91
|
+
closeAfter,
|
|
92
|
+
closeDuration,
|
|
93
|
+
floatDistance,
|
|
94
|
+
floatDuration,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
animatedStyle: {
|
|
99
|
+
opacity,
|
|
100
|
+
transform: [{ translateY }],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Easing, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseGrowAnimationProps {
|
|
6
|
+
duration?: number;
|
|
7
|
+
delay?: number;
|
|
8
|
+
closeAfter?: number | null;
|
|
9
|
+
initialScale?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useGrowAnimation = ({
|
|
13
|
+
duration = 500,
|
|
14
|
+
delay = 0,
|
|
15
|
+
closeAfter = 2000,
|
|
16
|
+
initialScale = 0,
|
|
17
|
+
}: UseGrowAnimationProps = {}) => {
|
|
18
|
+
const scale = useRef(new Animated.Value(initialScale)).current;
|
|
19
|
+
const { isActive } = useAppState();
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!isActive && Platform.OS === "ios") {
|
|
23
|
+
scale.stopAnimation();
|
|
24
|
+
}
|
|
25
|
+
}, [isActive]);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// Start grow-in animation with easing
|
|
29
|
+
Animated.timing(scale, {
|
|
30
|
+
toValue: 1,
|
|
31
|
+
duration,
|
|
32
|
+
delay,
|
|
33
|
+
easing: Easing.out(Easing.ease),
|
|
34
|
+
useNativeDriver: true,
|
|
35
|
+
}).start(() => {
|
|
36
|
+
if (closeAfter) {
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
Animated.timing(scale, {
|
|
39
|
+
toValue: initialScale,
|
|
40
|
+
duration,
|
|
41
|
+
easing: Easing.out(Easing.ease),
|
|
42
|
+
useNativeDriver: true,
|
|
43
|
+
}).start();
|
|
44
|
+
}, closeAfter);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return () => scale.stopAnimation();
|
|
49
|
+
}, [scale, duration, delay, closeAfter, initialScale]);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
animatedStyle: { transform: [{ scale }] },
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Easing, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseRollAnimationProps {
|
|
6
|
+
duration?: number;
|
|
7
|
+
delay?: number;
|
|
8
|
+
closeAfter?: number | null;
|
|
9
|
+
initialTranslateY?: number;
|
|
10
|
+
initialRotate?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useRollAnimation = ({
|
|
14
|
+
duration = 500,
|
|
15
|
+
delay = 0,
|
|
16
|
+
closeAfter = 2000,
|
|
17
|
+
initialTranslateY = 100,
|
|
18
|
+
initialRotate = "0deg",
|
|
19
|
+
}: UseRollAnimationProps = {}) => {
|
|
20
|
+
const translateY = useRef(new Animated.Value(initialTranslateY)).current;
|
|
21
|
+
const rotate = useRef(new Animated.Value(0.5)).current;
|
|
22
|
+
const { isActive } = useAppState();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!isActive && Platform.OS === "ios") {
|
|
26
|
+
rotate.stopAnimation();
|
|
27
|
+
translateY.stopAnimation();
|
|
28
|
+
}
|
|
29
|
+
}, [isActive]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
// Start roll-in animation with easing
|
|
33
|
+
Animated.parallel([
|
|
34
|
+
Animated.timing(translateY, {
|
|
35
|
+
toValue: 0.5,
|
|
36
|
+
duration,
|
|
37
|
+
delay,
|
|
38
|
+
easing: Easing.out(Easing.ease),
|
|
39
|
+
useNativeDriver: true,
|
|
40
|
+
}),
|
|
41
|
+
Animated.timing(rotate, {
|
|
42
|
+
toValue: 1,
|
|
43
|
+
duration,
|
|
44
|
+
delay,
|
|
45
|
+
easing: Easing.out(Easing.ease),
|
|
46
|
+
useNativeDriver: true,
|
|
47
|
+
}),
|
|
48
|
+
]).start(() => {
|
|
49
|
+
if (closeAfter) {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
Animated.parallel([
|
|
52
|
+
Animated.timing(translateY, {
|
|
53
|
+
toValue: initialTranslateY,
|
|
54
|
+
duration,
|
|
55
|
+
easing: Easing.out(Easing.ease),
|
|
56
|
+
useNativeDriver: true,
|
|
57
|
+
}),
|
|
58
|
+
Animated.timing(rotate, {
|
|
59
|
+
toValue: 0,
|
|
60
|
+
duration,
|
|
61
|
+
easing: Easing.out(Easing.ease),
|
|
62
|
+
useNativeDriver: true,
|
|
63
|
+
}),
|
|
64
|
+
]).start();
|
|
65
|
+
}, closeAfter);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
translateY.stopAnimation();
|
|
71
|
+
rotate.stopAnimation();
|
|
72
|
+
};
|
|
73
|
+
}, [translateY, rotate, duration, delay, closeAfter, initialTranslateY]);
|
|
74
|
+
|
|
75
|
+
// Interpolate the rotation value to degrees
|
|
76
|
+
const rotateInterpolation = rotate.interpolate({
|
|
77
|
+
inputRange: [0, 1],
|
|
78
|
+
outputRange: [initialRotate, "360deg"],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
animatedStyle: {
|
|
83
|
+
transform: [{ translateY }, { rotate: rotateInterpolation }],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|