@hoddy-ui/core 1.1.1 → 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 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 { Typography, Button, TextField, useColors } from "@hoddy-ui/core";
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
- <Typography variant="h4" color="primary" gutterBottom={20}>
98
- Welcome to Hoddy UI!
99
- </Typography>
100
-
101
- <TextField
102
- label="Email Address"
103
- variant="outlined"
104
- keyboardType="email-address"
105
- gutterBottom={16}
106
- />
107
-
108
- <Button
109
- title="Get Started"
110
- variant="contained"
111
- color="primary"
112
- onPress={() => console.log("Button pressed!")}
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 adding layout animations and transitions.
716
+ A unified component that provides a single interface for all animation types with generic props.
704
717
 
705
- **Props:**
718
+ **Features:**
706
719
 
707
- | Prop | Type | Default | Description |
708
- | --------------- | --------------------------------------------------------------------------- | ----------------- | ------------------------- |
709
- | `children` | `ReactNode` | - | Content to animate |
710
- | `type` | `"fade" \| "slideInLeft" \| "slideInRight" \| "slideInUp" \| "slideInDown"` | `"fade"` | Animation type |
711
- | `duration` | `number` | `500` | Animation duration (ms) |
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
- **Example:**
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
- <Animator type="slideInUp" duration={600} delay={200} animationType="spring">
720
- <Typography>This content will slide up</Typography>
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
@@ -8,20 +8,24 @@ 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
- export * from "./src/Components/OTPInput";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoddy-ui/core",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Core rich react native components written in typescript",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -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
- export interface AnimatorProps {
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
- };