@hoddy-ui/core 1.1.4 → 2.5.0
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 +95 -16
- package/next/package.json +3 -3
- package/package.json +6 -4
- package/src/Components/Animators/Animator.tsx +1 -1
- package/src/Components/Animators/hooks/useAppState.ts +4 -11
- package/src/Components/Animators/hooks/useBlinkAnimation.ts +31 -24
- package/src/Components/Animators/hooks/useFadeAnimation.ts +30 -26
- package/src/Components/Animators/hooks/useFloatAnimation.ts +67 -57
- package/src/Components/Animators/hooks/useGrowAnimation.ts +40 -28
- package/src/Components/Animators/hooks/useRollAnimation.ts +73 -57
- package/src/Components/Animators/hooks/useSlideAnimation.ts +41 -29
- package/src/Components/Animators/hooks/useThrownUpAnimation.ts +40 -44
- package/src/Components/OTPInput.tsx +0 -4
- package/src/Components/Popup.tsx +93 -76
- package/src/types.ts +3 -0
- package/src/Components/Animators/README.md +0 -137
package/README.md
CHANGED
|
@@ -27,15 +27,17 @@ yarn add @hoddy-ui/core
|
|
|
27
27
|
Install the required peer dependencies:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install @expo/vector-icons @react-native-async-storage/async-storage @react-navigation/native expo-navigation-bar expo-system-ui react-native-safe-area-context react-native-size-matters
|
|
30
|
+
npm install @expo/vector-icons @react-native-async-storage/async-storage @react-navigation/native expo-navigation-bar expo-system-ui react-native-safe-area-context react-native-size-matters react-native-reanimated
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Or with yarn:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
yarn add @expo/vector-icons @react-native-async-storage/async-storage @react-navigation/native expo-navigation-bar expo-system-ui react-native-safe-area-context react-native-size-matters
|
|
36
|
+
yarn add @expo/vector-icons @react-native-async-storage/async-storage @react-navigation/native expo-navigation-bar expo-system-ui react-native-safe-area-context react-native-size-matters react-native-reanimated
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
**Important**: Make sure to follow the [react-native-reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/3.x/fundamentals/getting-started) for platform-specific setup as it requires additional configuration for iOS and Android.
|
|
40
|
+
|
|
39
41
|
## 🚀 Quick Start
|
|
40
42
|
|
|
41
43
|
### Basic Setup
|
|
@@ -257,6 +259,7 @@ const colors = {
|
|
|
257
259
|
|
|
258
260
|
- **`TextField`** - Material Design text input with variants
|
|
259
261
|
- **`TextField2`** - Alternative text field design
|
|
262
|
+
- **`OTPInput`** - One-Time Password input with auto-advance and paste support
|
|
260
263
|
- **`Locator`** - Location picker with Google Maps integration
|
|
261
264
|
|
|
262
265
|
### Interactive Elements
|
|
@@ -433,6 +436,57 @@ A Material Design text input component with comprehensive features.
|
|
|
433
436
|
/>
|
|
434
437
|
```
|
|
435
438
|
|
|
439
|
+
### OTPInput
|
|
440
|
+
|
|
441
|
+
A specialized input component for One-Time Password entry with auto-advance, paste support, and backspace handling.
|
|
442
|
+
|
|
443
|
+
**Props:**
|
|
444
|
+
|
|
445
|
+
| Prop | Type | Default | Description |
|
|
446
|
+
| ---------- | ------------------------------------- | ------------ | -------------------------------- |
|
|
447
|
+
| `length` | `number` | `6` | Number of OTP digits |
|
|
448
|
+
| `onChange` | `(value: string) => void` | - | Change handler for OTP value |
|
|
449
|
+
| `value` | `string` | `""` | Current OTP value |
|
|
450
|
+
| `variant` | `"outlined" \| "text" \| "contained"` | `"outlined"` | Input variant style |
|
|
451
|
+
| `spacing` | `number` | `1` | Spacing between input boxes |
|
|
452
|
+
| `size` | `number` | `45` | Size of each input box in pixels |
|
|
453
|
+
|
|
454
|
+
**Features:**
|
|
455
|
+
|
|
456
|
+
- **Auto-advance**: Automatically moves to next input after digit entry
|
|
457
|
+
- **Paste support**: Handles pasting of complete OTP codes
|
|
458
|
+
- **Backspace handling**: Moves to previous input on backspace
|
|
459
|
+
- **Number-only input**: Automatically filters non-numeric characters
|
|
460
|
+
- **Customizable styling**: Supports different variants and sizes
|
|
461
|
+
|
|
462
|
+
**Example:**
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
const [otp, setOtp] = useState('');
|
|
466
|
+
|
|
467
|
+
<OTPInput
|
|
468
|
+
length={6}
|
|
469
|
+
value={otp}
|
|
470
|
+
onChange={setOtp}
|
|
471
|
+
variant="outlined"
|
|
472
|
+
size={50}
|
|
473
|
+
spacing={2}
|
|
474
|
+
/>
|
|
475
|
+
|
|
476
|
+
// Usage with form validation
|
|
477
|
+
<OTPInput
|
|
478
|
+
length={4}
|
|
479
|
+
value={otp}
|
|
480
|
+
onChange={(value) => {
|
|
481
|
+
setOtp(value);
|
|
482
|
+
if (value.length === 4) {
|
|
483
|
+
verifyOTP(value);
|
|
484
|
+
}
|
|
485
|
+
}}
|
|
486
|
+
variant="contained"
|
|
487
|
+
/>
|
|
488
|
+
```
|
|
489
|
+
|
|
436
490
|
### Locator
|
|
437
491
|
|
|
438
492
|
A location picker component with Google Maps integration.
|
|
@@ -674,6 +728,8 @@ A modal component for overlays, dialogs, and bottom sheets.
|
|
|
674
728
|
| `sheet` | `boolean \| number` | `false` | Bottom sheet mode/height |
|
|
675
729
|
| `bare` | `boolean` | `false` | Hide header and padding |
|
|
676
730
|
| `keyboardVerticalOffset` | `number` | - | Keyboard avoidance offset |
|
|
731
|
+
| `onModalShow` | `() => void` | - | Callback when modal shows |
|
|
732
|
+
| `onModalHide` | `() => void` | - | Callback when modal hides |
|
|
677
733
|
| `style` | `ViewStyle` | `{}` | Container style overrides |
|
|
678
734
|
|
|
679
735
|
**Example:**
|
|
@@ -684,6 +740,8 @@ A modal component for overlays, dialogs, and bottom sheets.
|
|
|
684
740
|
onClose={() => setIsOpen(false)}
|
|
685
741
|
title="Confirm Action"
|
|
686
742
|
sheet={400}
|
|
743
|
+
onModalShow={() => console.log("Modal is now visible")}
|
|
744
|
+
onModalHide={() => console.log("Modal is now hidden")}
|
|
687
745
|
>
|
|
688
746
|
<Typography>Are you sure you want to delete this item?</Typography>
|
|
689
747
|
<Button title="Delete" color="error" />
|
|
@@ -713,7 +771,7 @@ A loading indicator component with customizable appearance.
|
|
|
713
771
|
|
|
714
772
|
### Animator
|
|
715
773
|
|
|
716
|
-
A unified component that provides a single interface for all animation types with generic props.
|
|
774
|
+
A unified component that provides a single interface for all animation types with generic props. Built with **react-native-reanimated** for optimal performance and smooth animations.
|
|
717
775
|
|
|
718
776
|
**Features:**
|
|
719
777
|
|
|
@@ -721,7 +779,12 @@ A unified component that provides a single interface for all animation types wit
|
|
|
721
779
|
- **Generic Props**: Consistent prop naming across all animations (e.g., `closeAfter` instead of animation-specific names)
|
|
722
780
|
- **Modular Hooks**: Animation logic is separated into individual custom hooks
|
|
723
781
|
- **Type Safety**: Full TypeScript support with proper typing
|
|
724
|
-
- **Performance**:
|
|
782
|
+
- **Performance**: Built with react-native-reanimated for native performance
|
|
783
|
+
- **Smooth Animations**: Runs on the UI thread for 60fps animations
|
|
784
|
+
|
|
785
|
+
**Requirements:**
|
|
786
|
+
|
|
787
|
+
Requires `react-native-reanimated` as peer dependencies. Make sure to follow the [react-native-reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/3.x/fundamentals/getting-started) for platform-specific setup.
|
|
725
788
|
|
|
726
789
|
**Generic Props:**
|
|
727
790
|
|
|
@@ -804,20 +867,23 @@ All animation types support these generic props:
|
|
|
804
867
|
|
|
805
868
|
**Available Animation Types:**
|
|
806
869
|
|
|
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
|
|
870
|
+
1. **fade**: Simple fade in/out using opacity (native performance)
|
|
871
|
+
2. **grow**: Scale-based growth animation with easing
|
|
872
|
+
3. **slide**: Directional slide animations from screen edges
|
|
873
|
+
4. **blink**: Continuous opacity blinking with repeat
|
|
874
|
+
5. **float**: Floating up/down motion with fade effects
|
|
875
|
+
6. **roll**: Combined rotation and translation effects
|
|
876
|
+
7. **thrownup**: Spring-based upward animation with physics
|
|
877
|
+
|
|
878
|
+
All animations run on the UI thread for optimal performance and smooth 60fps animations.
|
|
814
879
|
|
|
815
880
|
**Using Animation Hooks Directly:**
|
|
816
881
|
|
|
817
|
-
You can also use the animation hooks directly for custom implementations:
|
|
882
|
+
You can also use the animation hooks directly for custom implementations with react-native-reanimated:
|
|
818
883
|
|
|
819
884
|
```tsx
|
|
820
885
|
import { useFadeAnimation, useSlideAnimation } from "@hoddy-ui/core";
|
|
886
|
+
import Animated from "react-native-reanimated";
|
|
821
887
|
|
|
822
888
|
const MyComponent = () => {
|
|
823
889
|
const { animatedStyle } = useFadeAnimation({
|
|
@@ -833,7 +899,17 @@ const MyComponent = () => {
|
|
|
833
899
|
};
|
|
834
900
|
```
|
|
835
901
|
|
|
836
|
-
**
|
|
902
|
+
**Performance Benefits:**
|
|
903
|
+
|
|
904
|
+
With react-native-reanimated, all animations:
|
|
905
|
+
|
|
906
|
+
- **Run on the UI thread** for better performance
|
|
907
|
+
- **Maintain 60fps** even during JavaScript thread blocking
|
|
908
|
+
- **Use native drivers** automatically
|
|
909
|
+
- **Support worklets** for complex animation logic
|
|
910
|
+
- **Provide smooth gestures** and interactions
|
|
911
|
+
|
|
912
|
+
**Migration from Legacy API:**
|
|
837
913
|
|
|
838
914
|
Replace old individual animation components:
|
|
839
915
|
|
|
@@ -933,12 +1009,13 @@ function MyScreen() {
|
|
|
933
1009
|
|
|
934
1010
|
### Animation Hooks
|
|
935
1011
|
|
|
936
|
-
Access animation logic directly for custom implementations:
|
|
1012
|
+
Access animation logic directly for custom implementations using react-native-reanimated:
|
|
937
1013
|
|
|
938
1014
|
#### useFadeAnimation
|
|
939
1015
|
|
|
940
1016
|
```tsx
|
|
941
1017
|
import { useFadeAnimation } from "@hoddy-ui/core";
|
|
1018
|
+
import Animated from "react-native-reanimated";
|
|
942
1019
|
|
|
943
1020
|
function FadeComponent() {
|
|
944
1021
|
const { animatedStyle } = useFadeAnimation({
|
|
@@ -958,6 +1035,7 @@ function FadeComponent() {
|
|
|
958
1035
|
|
|
959
1036
|
```tsx
|
|
960
1037
|
import { useSlideAnimation } from "@hoddy-ui/core";
|
|
1038
|
+
import Animated from "react-native-reanimated";
|
|
961
1039
|
|
|
962
1040
|
function SlideComponent() {
|
|
963
1041
|
const { animatedStyle } = useSlideAnimation({
|
|
@@ -978,6 +1056,7 @@ function SlideComponent() {
|
|
|
978
1056
|
|
|
979
1057
|
```tsx
|
|
980
1058
|
import { useGrowAnimation } from "@hoddy-ui/core";
|
|
1059
|
+
import Animated from "react-native-reanimated";
|
|
981
1060
|
|
|
982
1061
|
function GrowComponent() {
|
|
983
1062
|
const { animatedStyle } = useGrowAnimation({
|
|
@@ -1000,7 +1079,7 @@ function GrowComponent() {
|
|
|
1000
1079
|
- `useRollAnimation` - For rotation and translation effects
|
|
1001
1080
|
- `useThrownUpAnimation` - For spring-based upward animations
|
|
1002
1081
|
|
|
1003
|
-
All animation hooks accept similar configuration objects with properties like `duration`, `delay`, and animation-specific options.
|
|
1082
|
+
All animation hooks accept similar configuration objects with properties like `duration`, `delay`, and animation-specific options. They return `animatedStyle` objects that work with `Animated.View` from react-native-reanimated for optimal performance.
|
|
1004
1083
|
|
|
1005
1084
|
## 🎯 Advanced Usage
|
|
1006
1085
|
|
|
@@ -1110,4 +1189,4 @@ MIT © [Hoddy Inc](https://github.com/kinghoddy)
|
|
|
1110
1189
|
|
|
1111
1190
|
---
|
|
1112
1191
|
|
|
1113
|
-
**Need help?** [Open an issue](https://github.com/kinghoddy/hoddy-ui/issues) or check out our [examples](../../test/
|
|
1192
|
+
**Need help?** [Open an issue](https://github.com/kinghoddy/hoddy-ui/issues) or check out our [examples](../../test/my-app).
|
package/next/package.json
CHANGED
|
@@ -19,17 +19,17 @@
|
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@expo/vector-icons": ">=13.0.0",
|
|
22
|
-
"@types/react": "
|
|
22
|
+
"@types/react": ">=18.2.6",
|
|
23
23
|
"@types/react-native": "^0.72.0",
|
|
24
24
|
"expo-haptics": ">=12.4.0",
|
|
25
25
|
"expo-location": ">=15.1.1",
|
|
26
26
|
"expo-navigation-bar": ">=2.1.1",
|
|
27
27
|
"expo-router": ">=1.0.0",
|
|
28
28
|
"expo-system-ui": ">=2.2.1",
|
|
29
|
-
"react": "
|
|
29
|
+
"react": ">=18.2.0",
|
|
30
30
|
"react-native": ">=0.71.8",
|
|
31
31
|
"react-native-safe-area-context": ">=4.5.3",
|
|
32
|
-
"typescript": "
|
|
32
|
+
"typescript": ">=5.0.4"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"react-native",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoddy-ui/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Core rich react native components written in typescript",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"private": false,
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@expo/vector-icons": ">=13.0.0",
|
|
16
|
-
"@react-navigation/native": ">=6.1.
|
|
16
|
+
"@react-navigation/native": ">=6.1.6",
|
|
17
17
|
"@types/react": ">=18.2.6",
|
|
18
18
|
"@types/react-native": ">=0.72.0",
|
|
19
19
|
"expo-haptics": ">=12.4.0",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"expo-system-ui": ">=2.2.1",
|
|
23
23
|
"react": ">=18.2.0",
|
|
24
24
|
"react-native": ">=0.71.8",
|
|
25
|
+
"react-native-reanimated": ">=3.17.4",
|
|
25
26
|
"react-native-safe-area-context": ">=4.5.3",
|
|
26
27
|
"typescript": ">=5.0.4"
|
|
27
28
|
},
|
|
@@ -34,9 +35,10 @@
|
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"@react-native-async-storage/async-storage": "^1.18.1",
|
|
37
|
-
"react-native-size-matters": "^0.4.
|
|
38
|
+
"react-native-size-matters": "^0.4.2"
|
|
38
39
|
},
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"access": "public"
|
|
41
|
-
}
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {}
|
|
42
44
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
|
-
import
|
|
2
|
+
import Animated from "react-native-reanimated";
|
|
3
3
|
import { AnimatorProps } from "../../types";
|
|
4
4
|
import { useBlinkAnimation } from "./hooks/useBlinkAnimation";
|
|
5
5
|
import { useFadeAnimation } from "./hooks/useFadeAnimation";
|
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|
|
2
2
|
import { AppState, Platform } from "react-native";
|
|
3
3
|
|
|
4
4
|
const useAppState = () => {
|
|
5
|
-
const [isActive, setIsActive] = useState(
|
|
5
|
+
const [isActive, setIsActive] = useState(AppState.currentState === "active");
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
const handleAppStateChange = (nextAppState: string) => {
|
|
@@ -10,24 +10,17 @@ const useAppState = () => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
let subscription: any;
|
|
13
|
-
let blurSub: any;
|
|
14
|
-
let focusSub: any;
|
|
15
13
|
|
|
16
14
|
if (Platform.OS === "android") {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
focusSub = AppState.addEventListener("focus", () =>
|
|
21
|
-
handleAppStateChange("active")
|
|
22
|
-
);
|
|
15
|
+
// For Android, use the change event which covers both blur and focus
|
|
16
|
+
subscription = AppState.addEventListener("change", handleAppStateChange);
|
|
23
17
|
} else {
|
|
18
|
+
// For iOS, use the change event as well
|
|
24
19
|
subscription = AppState.addEventListener("change", handleAppStateChange);
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
return () => {
|
|
28
23
|
subscription?.remove();
|
|
29
|
-
blurSub?.remove();
|
|
30
|
-
focusSub?.remove();
|
|
31
24
|
};
|
|
32
25
|
}, []);
|
|
33
26
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { useEffect
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
Easing,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withRepeat,
|
|
8
|
+
withSequence,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from "react-native-reanimated";
|
|
3
11
|
import useAppState from "./useAppState";
|
|
4
12
|
|
|
5
13
|
interface UseBlinkAnimationProps {
|
|
@@ -15,57 +23,56 @@ export const useBlinkAnimation = ({
|
|
|
15
23
|
minOpacity = 0.5,
|
|
16
24
|
maxOpacity = 1,
|
|
17
25
|
}: UseBlinkAnimationProps = {}) => {
|
|
18
|
-
const opacity =
|
|
26
|
+
const opacity = useSharedValue(maxOpacity);
|
|
19
27
|
const { isActive } = useAppState();
|
|
20
|
-
|
|
28
|
+
|
|
29
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
30
|
+
return {
|
|
31
|
+
opacity: opacity.value,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
21
34
|
|
|
22
35
|
const startBlinking = () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
toValue: minOpacity,
|
|
36
|
+
opacity.value = withRepeat(
|
|
37
|
+
withSequence(
|
|
38
|
+
withTiming(minOpacity, {
|
|
27
39
|
duration: blinkDuration / 2,
|
|
28
40
|
easing: Easing.inOut(Easing.quad),
|
|
29
|
-
useNativeDriver: true,
|
|
30
41
|
}),
|
|
31
|
-
|
|
32
|
-
toValue: maxOpacity,
|
|
42
|
+
withTiming(maxOpacity, {
|
|
33
43
|
duration: blinkDuration / 2,
|
|
34
44
|
easing: Easing.inOut(Easing.quad),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
})
|
|
46
|
+
),
|
|
47
|
+
-1,
|
|
48
|
+
false
|
|
38
49
|
);
|
|
39
|
-
blinkAnim.current.start();
|
|
40
50
|
};
|
|
41
51
|
|
|
42
52
|
useEffect(() => {
|
|
43
53
|
if (!isActive && Platform.OS === "ios") {
|
|
44
|
-
opacity.
|
|
54
|
+
opacity.value = maxOpacity;
|
|
55
|
+
return;
|
|
45
56
|
}
|
|
46
|
-
}, [isActive]);
|
|
47
57
|
|
|
48
|
-
useEffect(() => {
|
|
49
58
|
if (delay > 0) {
|
|
50
59
|
const timer = setTimeout(() => {
|
|
51
60
|
startBlinking();
|
|
52
61
|
}, delay);
|
|
53
62
|
return () => {
|
|
54
63
|
clearTimeout(timer);
|
|
55
|
-
opacity.
|
|
56
|
-
blinkAnim.current?.stop();
|
|
64
|
+
opacity.value = maxOpacity;
|
|
57
65
|
};
|
|
58
66
|
} else {
|
|
59
67
|
startBlinking();
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
return () => {
|
|
63
|
-
opacity.
|
|
64
|
-
blinkAnim.current?.stop();
|
|
71
|
+
opacity.value = maxOpacity;
|
|
65
72
|
};
|
|
66
|
-
}, [delay, blinkDuration, minOpacity, maxOpacity]);
|
|
73
|
+
}, [delay, blinkDuration, minOpacity, maxOpacity, isActive]);
|
|
67
74
|
|
|
68
75
|
return {
|
|
69
|
-
animatedStyle
|
|
76
|
+
animatedStyle,
|
|
70
77
|
};
|
|
71
78
|
};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { useEffect
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withDelay,
|
|
7
|
+
withTiming,
|
|
8
|
+
} from "react-native-reanimated";
|
|
3
9
|
import useAppState from "./useAppState";
|
|
4
10
|
|
|
5
11
|
interface UseFadeAnimationProps {
|
|
@@ -11,40 +17,38 @@ interface UseFadeAnimationProps {
|
|
|
11
17
|
export const useFadeAnimation = ({
|
|
12
18
|
duration = 1000,
|
|
13
19
|
delay = 0,
|
|
14
|
-
closeAfter =
|
|
20
|
+
closeAfter = null,
|
|
15
21
|
}: UseFadeAnimationProps = {}) => {
|
|
16
|
-
const opacity =
|
|
22
|
+
const opacity = useSharedValue(0);
|
|
17
23
|
const { isActive } = useAppState();
|
|
18
24
|
|
|
25
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
26
|
+
return {
|
|
27
|
+
opacity: opacity.value,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
19
31
|
useEffect(() => {
|
|
20
32
|
if (!isActive && Platform.OS === "ios") {
|
|
21
|
-
opacity.
|
|
33
|
+
opacity.value = 0;
|
|
34
|
+
return;
|
|
22
35
|
}
|
|
23
|
-
}, [isActive]);
|
|
24
36
|
|
|
25
|
-
useEffect(() => {
|
|
26
37
|
// Fade-in animation
|
|
27
|
-
|
|
28
|
-
toValue: 1,
|
|
29
|
-
duration,
|
|
38
|
+
opacity.value = withDelay(
|
|
30
39
|
delay,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return () => opacity.stopAnimation();
|
|
45
|
-
}, [opacity, duration, delay, closeAfter]);
|
|
40
|
+
withTiming(1, { duration }, () => {
|
|
41
|
+
if (closeAfter) {
|
|
42
|
+
// Schedule fade-out after closeAfter duration
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
opacity.value = withTiming(0, { duration });
|
|
45
|
+
}, closeAfter);
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
}, [opacity, duration, delay, closeAfter, isActive]);
|
|
46
50
|
|
|
47
51
|
return {
|
|
48
|
-
animatedStyle
|
|
52
|
+
animatedStyle,
|
|
49
53
|
};
|
|
50
54
|
};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
Easing,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withDelay,
|
|
8
|
+
withRepeat,
|
|
9
|
+
withSequence,
|
|
10
|
+
withTiming,
|
|
11
|
+
} from "react-native-reanimated";
|
|
3
12
|
import useAppState from "./useAppState";
|
|
4
13
|
|
|
5
14
|
interface UseFloatAnimationProps {
|
|
@@ -14,76 +23,79 @@ interface UseFloatAnimationProps {
|
|
|
14
23
|
export const useFloatAnimation = ({
|
|
15
24
|
duration = 800,
|
|
16
25
|
delay = 0,
|
|
17
|
-
closeAfter =
|
|
26
|
+
closeAfter = null,
|
|
18
27
|
closeDuration = 600,
|
|
19
28
|
floatDistance = 10,
|
|
20
29
|
floatDuration = 1200,
|
|
21
30
|
}: UseFloatAnimationProps = {}) => {
|
|
22
|
-
const opacity =
|
|
23
|
-
const translateY =
|
|
31
|
+
const opacity = useSharedValue(0);
|
|
32
|
+
const translateY = useSharedValue(0);
|
|
24
33
|
const { isActive } = useAppState();
|
|
25
|
-
const
|
|
34
|
+
const isFloating = useRef(false);
|
|
35
|
+
|
|
36
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
37
|
+
return {
|
|
38
|
+
opacity: opacity.value,
|
|
39
|
+
transform: [{ translateY: translateY.value }],
|
|
40
|
+
};
|
|
41
|
+
});
|
|
26
42
|
|
|
27
43
|
const startFloating = () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
if (!isFloating.current) {
|
|
45
|
+
isFloating.current = true;
|
|
46
|
+
translateY.value = withRepeat(
|
|
47
|
+
withSequence(
|
|
48
|
+
withTiming(-floatDistance, {
|
|
49
|
+
duration: floatDuration / 2,
|
|
50
|
+
easing: Easing.inOut(Easing.quad),
|
|
51
|
+
}),
|
|
52
|
+
withTiming(floatDistance, {
|
|
53
|
+
duration: floatDuration,
|
|
54
|
+
easing: Easing.inOut(Easing.quad),
|
|
55
|
+
}),
|
|
56
|
+
withTiming(0, {
|
|
57
|
+
duration: floatDuration / 2,
|
|
58
|
+
easing: Easing.inOut(Easing.quad),
|
|
59
|
+
})
|
|
60
|
+
),
|
|
61
|
+
-1,
|
|
62
|
+
false
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stopFloating = () => {
|
|
68
|
+
isFloating.current = false;
|
|
69
|
+
translateY.value = withTiming(0, { duration: 200 });
|
|
51
70
|
};
|
|
52
71
|
|
|
53
72
|
useEffect(() => {
|
|
54
73
|
if (!isActive && Platform.OS === "ios") {
|
|
55
|
-
opacity.
|
|
56
|
-
translateY.
|
|
74
|
+
opacity.value = 0;
|
|
75
|
+
translateY.value = 0;
|
|
76
|
+
isFloating.current = false;
|
|
77
|
+
return;
|
|
57
78
|
}
|
|
58
|
-
}, [isActive]);
|
|
59
79
|
|
|
60
|
-
useEffect(() => {
|
|
61
80
|
// Fade-in
|
|
62
|
-
|
|
63
|
-
toValue: 1,
|
|
64
|
-
duration,
|
|
81
|
+
opacity.value = withDelay(
|
|
65
82
|
delay,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
startFloating();
|
|
83
|
+
withTiming(1, { duration }, () => {
|
|
84
|
+
startFloating();
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}).start();
|
|
79
|
-
}, closeAfter);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
86
|
+
if (closeAfter) {
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
stopFloating();
|
|
89
|
+
opacity.value = withTiming(0, { duration: closeDuration });
|
|
90
|
+
}, closeAfter);
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
);
|
|
82
94
|
|
|
83
95
|
return () => {
|
|
84
|
-
opacity.
|
|
85
|
-
translateY.
|
|
86
|
-
|
|
96
|
+
opacity.value = 0;
|
|
97
|
+
translateY.value = 0;
|
|
98
|
+
isFloating.current = false;
|
|
87
99
|
};
|
|
88
100
|
}, [
|
|
89
101
|
duration,
|
|
@@ -92,12 +104,10 @@ export const useFloatAnimation = ({
|
|
|
92
104
|
closeDuration,
|
|
93
105
|
floatDistance,
|
|
94
106
|
floatDuration,
|
|
107
|
+
isActive,
|
|
95
108
|
]);
|
|
96
109
|
|
|
97
110
|
return {
|
|
98
|
-
animatedStyle
|
|
99
|
-
opacity,
|
|
100
|
-
transform: [{ translateY }],
|
|
101
|
-
},
|
|
111
|
+
animatedStyle,
|
|
102
112
|
};
|
|
103
113
|
};
|