@hoddy-ui/core 1.1.1 → 1.1.3
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 +230 -31
- package/index.ts +8 -4
- 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/types.ts +56 -9
- package/src/Components/Animator.tsx +0 -43
package/README.md
CHANGED
|
@@ -87,30 +87,43 @@ export default function Root() {
|
|
|
87
87
|
```tsx
|
|
88
88
|
import React from "react";
|
|
89
89
|
import { View } from "react-native";
|
|
90
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
Typography,
|
|
92
|
+
Button,
|
|
93
|
+
TextField,
|
|
94
|
+
Animator,
|
|
95
|
+
useColors,
|
|
96
|
+
useFadeAnimation,
|
|
97
|
+
} from "@hoddy-ui/core";
|
|
91
98
|
|
|
92
99
|
export default function HomeScreen() {
|
|
93
100
|
const colors = useColors();
|
|
94
101
|
|
|
95
102
|
return (
|
|
96
103
|
<View style={{ padding: 20, backgroundColor: colors.white[1] }}>
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
<Animator type="fade" duration={1000}>
|
|
105
|
+
<Typography variant="h4" color="primary" gutterBottom={20}>
|
|
106
|
+
Welcome to Hoddy UI!
|
|
107
|
+
</Typography>
|
|
108
|
+
</Animator>
|
|
109
|
+
|
|
110
|
+
<Animator type="slide" direction="up" delay={200}>
|
|
111
|
+
<TextField
|
|
112
|
+
label="Email Address"
|
|
113
|
+
variant="outlined"
|
|
114
|
+
keyboardType="email-address"
|
|
115
|
+
gutterBottom={16}
|
|
116
|
+
/>
|
|
117
|
+
</Animator>
|
|
118
|
+
|
|
119
|
+
<Animator type="grow" delay={400}>
|
|
120
|
+
<Button
|
|
121
|
+
title="Get Started"
|
|
122
|
+
variant="contained"
|
|
123
|
+
color="primary"
|
|
124
|
+
onPress={() => console.log("Button pressed!")}
|
|
125
|
+
/>
|
|
126
|
+
</Animator>
|
|
114
127
|
</View>
|
|
115
128
|
);
|
|
116
129
|
}
|
|
@@ -700,24 +713,139 @@ A loading indicator component with customizable appearance.
|
|
|
700
713
|
|
|
701
714
|
### Animator
|
|
702
715
|
|
|
703
|
-
A component for
|
|
716
|
+
A unified component that provides a single interface for all animation types with generic props.
|
|
704
717
|
|
|
705
|
-
**
|
|
718
|
+
**Features:**
|
|
706
719
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
| `delay` | `number` | `100` | Animation delay (ms) |
|
|
713
|
-
| `animationType` | `"easeInEaseOut" \| "linear" \| "spring"` | `"easeInEaseOut"` | Animation timing function |
|
|
714
|
-
| `style` | `ViewStyle` | `{}` | Container style overrides |
|
|
720
|
+
- **Single Component**: One component handles all animation types
|
|
721
|
+
- **Generic Props**: Consistent prop naming across all animations (e.g., `closeAfter` instead of animation-specific names)
|
|
722
|
+
- **Modular Hooks**: Animation logic is separated into individual custom hooks
|
|
723
|
+
- **Type Safety**: Full TypeScript support with proper typing
|
|
724
|
+
- **Performance**: Only the specified animation runs, others are not loaded
|
|
715
725
|
|
|
716
|
-
**
|
|
726
|
+
**Generic Props:**
|
|
727
|
+
|
|
728
|
+
All animation types support these generic props:
|
|
729
|
+
|
|
730
|
+
| Prop | Type | Default | Description |
|
|
731
|
+
| ------------ | --------------------------------------------------------------------------- | ------- | ------------------------------------------------------------- |
|
|
732
|
+
| `type` | `"fade" \| "grow" \| "slide" \| "blink" \| "float" \| "roll" \| "thrownup"` | - | Animation type |
|
|
733
|
+
| `duration` | `number` | `500` | Animation duration in milliseconds |
|
|
734
|
+
| `delay` | `number` | `0` | Delay before animation starts |
|
|
735
|
+
| `closeAfter` | `number \| null` | `null` | Time after which the exit animation starts (null for no exit) |
|
|
736
|
+
| `style` | `ViewStyle` | `{}` | Additional styles for the animated view |
|
|
737
|
+
|
|
738
|
+
**Animation-Specific Props:**
|
|
739
|
+
|
|
740
|
+
**Slide Animation:**
|
|
741
|
+
|
|
742
|
+
- `direction`: `"up" \| "down" \| "left" \| "right"`
|
|
743
|
+
- `initialValue`: Custom initial position value
|
|
744
|
+
|
|
745
|
+
**Grow Animation:**
|
|
746
|
+
|
|
747
|
+
- `initialScale`: Starting scale (default: 0)
|
|
748
|
+
|
|
749
|
+
**Blink Animation:**
|
|
750
|
+
|
|
751
|
+
- `blinkDuration`: Duration of one blink cycle
|
|
752
|
+
- `minOpacity`: Minimum opacity value
|
|
753
|
+
- `maxOpacity`: Maximum opacity value
|
|
754
|
+
|
|
755
|
+
**Float Animation:**
|
|
756
|
+
|
|
757
|
+
- `closeDuration`: Duration of exit animation
|
|
758
|
+
- `floatDistance`: Distance to float up/down
|
|
759
|
+
- `floatDuration`: Duration of one float cycle
|
|
760
|
+
|
|
761
|
+
**Roll Animation:**
|
|
762
|
+
|
|
763
|
+
- `initialTranslateY`: Initial vertical position
|
|
764
|
+
- `initialRotate`: Initial rotation value
|
|
765
|
+
|
|
766
|
+
**Examples:**
|
|
767
|
+
|
|
768
|
+
```tsx
|
|
769
|
+
// Fade animation
|
|
770
|
+
<Animator type="fade" duration={1000} closeAfter={3000}>
|
|
771
|
+
<Text>This will fade in and out</Text>
|
|
772
|
+
</Animator>
|
|
773
|
+
|
|
774
|
+
// Slide animation
|
|
775
|
+
<Animator type="slide" direction="up" duration={800} closeAfter={2000}>
|
|
776
|
+
<View>This will slide up from bottom</View>
|
|
777
|
+
</Animator>
|
|
778
|
+
|
|
779
|
+
// Grow animation
|
|
780
|
+
<Animator type="grow" initialScale={0.5} duration={600}>
|
|
781
|
+
<Button>This will grow from 50% scale</Button>
|
|
782
|
+
</Animator>
|
|
783
|
+
|
|
784
|
+
// Blink animation (continuous)
|
|
785
|
+
<Animator type="blink" blinkDuration={1000} minOpacity={0.3}>
|
|
786
|
+
<Icon>This will blink continuously</Icon>
|
|
787
|
+
</Animator>
|
|
788
|
+
|
|
789
|
+
// Float animation
|
|
790
|
+
<Animator type="float" floatDistance={20} floatDuration={2000} closeAfter={5000}>
|
|
791
|
+
<View>This will float up and down</View>
|
|
792
|
+
</Animator>
|
|
793
|
+
|
|
794
|
+
// Roll animation
|
|
795
|
+
<Animator type="roll" initialRotate="45deg" duration={800}>
|
|
796
|
+
<Image>This will roll and rotate</Image>
|
|
797
|
+
</Animator>
|
|
798
|
+
|
|
799
|
+
// Thrown up animation
|
|
800
|
+
<Animator type="thrownup" delay={500} closeAfter={4000}>
|
|
801
|
+
<Notification>This will spring up from bottom</Notification>
|
|
802
|
+
</Animator>
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
**Available Animation Types:**
|
|
806
|
+
|
|
807
|
+
1. **fade**: Simple fade in/out
|
|
808
|
+
2. **grow**: Scale-based growth animation
|
|
809
|
+
3. **slide**: Directional slide animations
|
|
810
|
+
4. **blink**: Continuous opacity blinking
|
|
811
|
+
5. **float**: Floating up/down motion with fade
|
|
812
|
+
6. **roll**: Combined rotation and translation
|
|
813
|
+
7. **thrownup**: Spring-based upward animation
|
|
814
|
+
|
|
815
|
+
**Using Animation Hooks Directly:**
|
|
816
|
+
|
|
817
|
+
You can also use the animation hooks directly for custom implementations:
|
|
818
|
+
|
|
819
|
+
```tsx
|
|
820
|
+
import { useFadeAnimation, useSlideAnimation } from "@hoddy-ui/core";
|
|
821
|
+
|
|
822
|
+
const MyComponent = () => {
|
|
823
|
+
const { animatedStyle } = useFadeAnimation({
|
|
824
|
+
duration: 800,
|
|
825
|
+
closeAfter: 2000,
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
return (
|
|
829
|
+
<Animated.View style={animatedStyle}>
|
|
830
|
+
<Text>Custom animated content</Text>
|
|
831
|
+
</Animated.View>
|
|
832
|
+
);
|
|
833
|
+
};
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**Migration from Old Components:**
|
|
837
|
+
|
|
838
|
+
Replace old individual animation components:
|
|
717
839
|
|
|
718
840
|
```tsx
|
|
719
|
-
|
|
720
|
-
|
|
841
|
+
// Old way
|
|
842
|
+
<AnimatedFade fadeOutAfter={2000}>
|
|
843
|
+
<Text>Content</Text>
|
|
844
|
+
</AnimatedFade>
|
|
845
|
+
|
|
846
|
+
// New way
|
|
847
|
+
<Animator type="fade" closeAfter={2000}>
|
|
848
|
+
<Text>Content</Text>
|
|
721
849
|
</Animator>
|
|
722
850
|
```
|
|
723
851
|
|
|
@@ -803,6 +931,77 @@ function MyScreen() {
|
|
|
803
931
|
}
|
|
804
932
|
```
|
|
805
933
|
|
|
934
|
+
### Animation Hooks
|
|
935
|
+
|
|
936
|
+
Access animation logic directly for custom implementations:
|
|
937
|
+
|
|
938
|
+
#### useFadeAnimation
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
import { useFadeAnimation } from "@hoddy-ui/core";
|
|
942
|
+
|
|
943
|
+
function FadeComponent() {
|
|
944
|
+
const { animatedStyle } = useFadeAnimation({
|
|
945
|
+
duration: 800,
|
|
946
|
+
closeAfter: 2000,
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
return (
|
|
950
|
+
<Animated.View style={animatedStyle}>
|
|
951
|
+
<Text>This will fade in and out</Text>
|
|
952
|
+
</Animated.View>
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
#### useSlideAnimation
|
|
958
|
+
|
|
959
|
+
```tsx
|
|
960
|
+
import { useSlideAnimation } from "@hoddy-ui/core";
|
|
961
|
+
|
|
962
|
+
function SlideComponent() {
|
|
963
|
+
const { animatedStyle } = useSlideAnimation({
|
|
964
|
+
direction: "up",
|
|
965
|
+
duration: 600,
|
|
966
|
+
initialValue: 100,
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
return (
|
|
970
|
+
<Animated.View style={animatedStyle}>
|
|
971
|
+
<Text>This will slide up</Text>
|
|
972
|
+
</Animated.View>
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
#### useGrowAnimation
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
import { useGrowAnimation } from "@hoddy-ui/core";
|
|
981
|
+
|
|
982
|
+
function GrowComponent() {
|
|
983
|
+
const { animatedStyle } = useGrowAnimation({
|
|
984
|
+
duration: 500,
|
|
985
|
+
initialScale: 0.5,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
return (
|
|
989
|
+
<Animated.View style={animatedStyle}>
|
|
990
|
+
<Text>This will grow from 50% scale</Text>
|
|
991
|
+
</Animated.View>
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
#### Other Animation Hooks
|
|
997
|
+
|
|
998
|
+
- `useBlinkAnimation` - For continuous blinking effects
|
|
999
|
+
- `useFloatAnimation` - For floating up/down motion
|
|
1000
|
+
- `useRollAnimation` - For rotation and translation effects
|
|
1001
|
+
- `useThrownUpAnimation` - For spring-based upward animations
|
|
1002
|
+
|
|
1003
|
+
All animation hooks accept similar configuration objects with properties like `duration`, `delay`, and animation-specific options.
|
|
1004
|
+
|
|
806
1005
|
## 🎯 Advanced Usage
|
|
807
1006
|
|
|
808
1007
|
### Custom Theme Provider
|
package/index.ts
CHANGED
|
@@ -3,25 +3,29 @@ import { initialize } from "./src/config";
|
|
|
3
3
|
// Components
|
|
4
4
|
export { default as AdaptiveStatusBar } from "./src/Components/AdaptiveStatusBar";
|
|
5
5
|
export { default as AlertX } from "./src/Components/AlertX";
|
|
6
|
-
export * from "./src/Components/Animator";
|
|
6
|
+
export * from "./src/Components/Animators/Animator";
|
|
7
7
|
export { default as Avatar } from "./src/Components/Avatar";
|
|
8
8
|
export * from "./src/Components/Button";
|
|
9
9
|
export { default as Button } from "./src/Components/Button";
|
|
10
10
|
export * from "./src/Components/Checkbox";
|
|
11
|
+
export * from "./src/Components/Divider";
|
|
11
12
|
export * from "./src/Components/FlashMessage";
|
|
12
13
|
export * from "./src/Components/FormWrapper";
|
|
13
|
-
export * from "./src/Components/StarRating";
|
|
14
14
|
export * from "./src/Components/Grid";
|
|
15
15
|
export * from "./src/Components/Locator";
|
|
16
|
+
export * from "./src/Components/OTPInput";
|
|
16
17
|
export * from "./src/Components/Popup";
|
|
17
18
|
export * from "./src/Components/SafeAreaView";
|
|
18
|
-
export * from "./src/Components/Divider";
|
|
19
19
|
export { default as SelectMenu } from "./src/Components/SelectMenu";
|
|
20
20
|
export { default as Spinner } from "./src/Components/Spinner";
|
|
21
|
+
export * from "./src/Components/StarRating";
|
|
21
22
|
export * from "./src/Components/TextField";
|
|
22
23
|
export { default as TextField } from "./src/Components/TextField";
|
|
23
24
|
export { default as Typography } from "./src/Components/Typography";
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
// Animation hooks
|
|
27
|
+
export * from "./src/Components/Animators/hooks";
|
|
28
|
+
|
|
25
29
|
// Others
|
|
26
30
|
// export * from "./src/config";
|
|
27
31
|
export * from "./src/hooks";
|
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
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Dimensions, Easing, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
const { width, height } = Dimensions.get("window");
|
|
6
|
+
|
|
7
|
+
interface UseSlideAnimationProps {
|
|
8
|
+
duration?: number;
|
|
9
|
+
delay?: number;
|
|
10
|
+
direction?: "up" | "down" | "left" | "right";
|
|
11
|
+
closeAfter?: number | null;
|
|
12
|
+
initialValue?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getInitialPosition = (direction: string) => {
|
|
16
|
+
switch (direction) {
|
|
17
|
+
case "up":
|
|
18
|
+
return height;
|
|
19
|
+
case "down":
|
|
20
|
+
return -height;
|
|
21
|
+
case "left":
|
|
22
|
+
return width;
|
|
23
|
+
case "right":
|
|
24
|
+
return -width;
|
|
25
|
+
default:
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const useSlideAnimation = ({
|
|
31
|
+
duration = 1000,
|
|
32
|
+
delay = 0,
|
|
33
|
+
direction = "up",
|
|
34
|
+
closeAfter,
|
|
35
|
+
initialValue,
|
|
36
|
+
}: UseSlideAnimationProps = {}) => {
|
|
37
|
+
const translateValue = useRef(new Animated.Value(0)).current;
|
|
38
|
+
const { isActive } = useAppState();
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!isActive && Platform.OS === "ios") {
|
|
42
|
+
translateValue.stopAnimation();
|
|
43
|
+
}
|
|
44
|
+
}, [isActive]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const initialPosition = initialValue || getInitialPosition(direction);
|
|
48
|
+
translateValue.setValue(initialPosition);
|
|
49
|
+
|
|
50
|
+
// Slide-in animation with ease-out effect
|
|
51
|
+
Animated.timing(translateValue, {
|
|
52
|
+
toValue: 0,
|
|
53
|
+
duration,
|
|
54
|
+
delay,
|
|
55
|
+
easing: Easing.out(Easing.ease),
|
|
56
|
+
useNativeDriver: true,
|
|
57
|
+
}).start();
|
|
58
|
+
|
|
59
|
+
if (closeAfter) {
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
Animated.timing(translateValue, {
|
|
62
|
+
toValue: initialPosition,
|
|
63
|
+
duration,
|
|
64
|
+
easing: Easing.out(Easing.ease),
|
|
65
|
+
useNativeDriver: true,
|
|
66
|
+
}).start();
|
|
67
|
+
}, closeAfter + duration + delay);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
translateValue.stopAnimation();
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
translateValue.stopAnimation();
|
|
77
|
+
};
|
|
78
|
+
}, [translateValue, duration, delay, direction, closeAfter]);
|
|
79
|
+
|
|
80
|
+
const slideStyle =
|
|
81
|
+
direction === "up" || direction === "down"
|
|
82
|
+
? { transform: [{ translateY: translateValue }] }
|
|
83
|
+
: { transform: [{ translateX: translateValue }] };
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
animatedStyle: slideStyle,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Platform } from "react-native";
|
|
3
|
+
import useAppState from "./useAppState";
|
|
4
|
+
|
|
5
|
+
interface UseThrownUpAnimationProps {
|
|
6
|
+
delay?: number;
|
|
7
|
+
closeAfter?: number | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useThrownUpAnimation = ({
|
|
11
|
+
delay = 0,
|
|
12
|
+
closeAfter = 3000,
|
|
13
|
+
}: UseThrownUpAnimationProps = {}) => {
|
|
14
|
+
const translateY = useRef(new Animated.Value(600)).current;
|
|
15
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
16
|
+
const isUnmounting = useRef(false);
|
|
17
|
+
const { isActive } = useAppState();
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!isActive && Platform.OS === "ios") {
|
|
21
|
+
translateY.stopAnimation();
|
|
22
|
+
opacity.stopAnimation();
|
|
23
|
+
}
|
|
24
|
+
}, [isActive]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// Animate up and fade in when component mounts
|
|
28
|
+
Animated.parallel([
|
|
29
|
+
Animated.spring(translateY, {
|
|
30
|
+
toValue: 0,
|
|
31
|
+
velocity: 1,
|
|
32
|
+
tension: 0.001,
|
|
33
|
+
friction: 2,
|
|
34
|
+
useNativeDriver: true,
|
|
35
|
+
delay,
|
|
36
|
+
}),
|
|
37
|
+
Animated.timing(opacity, {
|
|
38
|
+
toValue: 1,
|
|
39
|
+
duration: 500,
|
|
40
|
+
useNativeDriver: true,
|
|
41
|
+
delay,
|
|
42
|
+
}),
|
|
43
|
+
]).start();
|
|
44
|
+
|
|
45
|
+
// Start timer to animate out after duration
|
|
46
|
+
let timer: NodeJS.Timeout | null = null;
|
|
47
|
+
if (closeAfter) {
|
|
48
|
+
timer = setTimeout(() => {
|
|
49
|
+
if (!isUnmounting.current) {
|
|
50
|
+
Animated.parallel([
|
|
51
|
+
Animated.spring(translateY, {
|
|
52
|
+
toValue: 800,
|
|
53
|
+
velocity: 1,
|
|
54
|
+
tension: 10,
|
|
55
|
+
friction: 7,
|
|
56
|
+
useNativeDriver: true,
|
|
57
|
+
}),
|
|
58
|
+
Animated.timing(opacity, {
|
|
59
|
+
toValue: 0,
|
|
60
|
+
duration: 500,
|
|
61
|
+
useNativeDriver: true,
|
|
62
|
+
}),
|
|
63
|
+
]).start();
|
|
64
|
+
}
|
|
65
|
+
}, closeAfter);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
if (timer) clearTimeout(timer);
|
|
70
|
+
translateY.stopAnimation();
|
|
71
|
+
opacity.stopAnimation();
|
|
72
|
+
isUnmounting.current = true;
|
|
73
|
+
};
|
|
74
|
+
}, [translateY, opacity, delay, closeAfter]);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
animatedStyle: {
|
|
78
|
+
transform: [{ translateY }],
|
|
79
|
+
opacity,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { ReactNode } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
NativeScrollEvent,
|
|
4
4
|
NativeSyntheticEvent,
|
|
5
|
-
Text,
|
|
6
5
|
TextInputProps,
|
|
7
6
|
TextProps,
|
|
8
7
|
TextStyle,
|
|
@@ -79,14 +78,7 @@ export interface AvatarProps {
|
|
|
79
78
|
size?: number;
|
|
80
79
|
style?: ViewStyle;
|
|
81
80
|
}
|
|
82
|
-
|
|
83
|
-
style?: ViewStyle;
|
|
84
|
-
duration?: number;
|
|
85
|
-
children: ReactNode;
|
|
86
|
-
delay?: number;
|
|
87
|
-
animationType?: "easeInEaseOut" | "linear" | "spring";
|
|
88
|
-
type?: "fade" | "slideInLeft" | "slideInRight" | "slideInUp" | "slideInDown";
|
|
89
|
-
}
|
|
81
|
+
|
|
90
82
|
export interface ButtonProps {
|
|
91
83
|
color?: colorTypes;
|
|
92
84
|
variant?: "text" | "outlined" | "contained";
|
|
@@ -325,3 +317,58 @@ export interface DividerProps {
|
|
|
325
317
|
style?: ViewStyle;
|
|
326
318
|
height?: number;
|
|
327
319
|
}
|
|
320
|
+
|
|
321
|
+
export type AnimationType =
|
|
322
|
+
| "fade"
|
|
323
|
+
| "grow"
|
|
324
|
+
| "slide"
|
|
325
|
+
| "blink"
|
|
326
|
+
| "float"
|
|
327
|
+
| "roll"
|
|
328
|
+
| "thrownup";
|
|
329
|
+
|
|
330
|
+
// Base props that are common to all animations
|
|
331
|
+
interface BaseAnimatorProps {
|
|
332
|
+
children: ReactNode;
|
|
333
|
+
duration?: number;
|
|
334
|
+
delay?: number;
|
|
335
|
+
closeAfter?: number | null;
|
|
336
|
+
style?: ViewStyle;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Type-specific animation props using discriminated unions
|
|
340
|
+
export type AnimatorProps =
|
|
341
|
+
| (BaseAnimatorProps & {
|
|
342
|
+
type: "fade";
|
|
343
|
+
// No additional props for fade animation
|
|
344
|
+
})
|
|
345
|
+
| (BaseAnimatorProps & {
|
|
346
|
+
type: "grow";
|
|
347
|
+
initialScale?: number;
|
|
348
|
+
})
|
|
349
|
+
| (BaseAnimatorProps & {
|
|
350
|
+
type: "slide";
|
|
351
|
+
direction?: "up" | "down" | "left" | "right";
|
|
352
|
+
initialValue?: number;
|
|
353
|
+
})
|
|
354
|
+
| (BaseAnimatorProps & {
|
|
355
|
+
type: "blink";
|
|
356
|
+
blinkDuration?: number;
|
|
357
|
+
minOpacity?: number;
|
|
358
|
+
maxOpacity?: number;
|
|
359
|
+
})
|
|
360
|
+
| (BaseAnimatorProps & {
|
|
361
|
+
type: "float";
|
|
362
|
+
closeDuration?: number;
|
|
363
|
+
floatDistance?: number;
|
|
364
|
+
floatDuration?: number;
|
|
365
|
+
})
|
|
366
|
+
| (BaseAnimatorProps & {
|
|
367
|
+
type: "roll";
|
|
368
|
+
initialTranslateY?: number;
|
|
369
|
+
initialRotate?: string;
|
|
370
|
+
})
|
|
371
|
+
| (BaseAnimatorProps & {
|
|
372
|
+
type: "thrownup";
|
|
373
|
+
// No additional props for thrownup animation
|
|
374
|
+
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { useFocusEffect } from "@react-navigation/native";
|
|
2
|
-
import React, { FC, useCallback, useState } from "react";
|
|
3
|
-
import { LayoutAnimation, View } from "react-native";
|
|
4
|
-
import { ScaledSheet } from "react-native-size-matters";
|
|
5
|
-
import { AnimatorProps } from "../types";
|
|
6
|
-
|
|
7
|
-
export const Animator: FC<AnimatorProps> = ({
|
|
8
|
-
style = {},
|
|
9
|
-
duration = 500,
|
|
10
|
-
children,
|
|
11
|
-
delay = 100,
|
|
12
|
-
animationType = "easeInEaseOut",
|
|
13
|
-
type = "fade",
|
|
14
|
-
}) => {
|
|
15
|
-
const [play, setPlay] = useState(false);
|
|
16
|
-
const toggleAnimation = () => {
|
|
17
|
-
setPlay(false);
|
|
18
|
-
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
LayoutAnimation.configureNext({
|
|
21
|
-
...LayoutAnimation.Presets[animationType],
|
|
22
|
-
duration,
|
|
23
|
-
});
|
|
24
|
-
setPlay(true);
|
|
25
|
-
}, delay);
|
|
26
|
-
};
|
|
27
|
-
const styles = ScaledSheet.create({
|
|
28
|
-
root: {
|
|
29
|
-
opacity: play ? 1 : 0,
|
|
30
|
-
left: type === "slideInLeft" ? (!play ? -200 : 0) : undefined,
|
|
31
|
-
right: type === "slideInRight" ? (!play ? -200 : 0) : undefined,
|
|
32
|
-
bottom: type === "slideInUp" ? (!play ? -100 : 0) : undefined,
|
|
33
|
-
top: type === "slideInDown" ? (!play ? -100 : 0) : undefined,
|
|
34
|
-
...style,
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
useFocusEffect(
|
|
38
|
-
useCallback(() => {
|
|
39
|
-
toggleAnimation();
|
|
40
|
-
}, [])
|
|
41
|
-
);
|
|
42
|
-
return <View style={styles.root}>{play && children}</View>;
|
|
43
|
-
};
|