@umituz/react-native-animation
Version:
Universal animation system for React Native with react-native-reanimated. Provides declarative animations, gesture handling, and preset configurations.
110 lines (94 loc) • 3.16 kB
text/typescript
/**
* useModalAnimations Hook
* Single Responsibility: Manage modal overlay and container animations
* Uses react-native-animation's timing and spring animations
*/
import { useEffect, useMemo, useRef } from "react";
import { useAnimatedStyle } from "react-native-reanimated";
import { useReanimatedReady } from "./useReanimatedReady";
import { useTimingAnimation } from "./useTimingAnimation";
import { useSpringAnimation } from "./useSpringAnimation";
export interface ModalAnimationConfig {
overlayFadeDuration?: number;
modalScaleDamping?: number;
modalScaleStiffness?: number;
modalFadeDuration?: number;
}
const DEFAULT_MODAL_ANIMATION_CONFIG: Required<ModalAnimationConfig> = {
overlayFadeDuration: 300,
modalScaleDamping: 7,
modalScaleStiffness: 50,
modalFadeDuration: 300,
} as const;
export interface UseModalAnimationsReturn {
isReady: boolean;
overlayStyle: ReturnType<typeof useAnimatedStyle>;
modalStyle: ReturnType<typeof useAnimatedStyle>;
}
/**
* Hook for managing modal overlay and container animations
*/
export function useModalAnimations(
visible: boolean,
config?: ModalAnimationConfig,
): UseModalAnimationsReturn {
const isReanimatedReady = useReanimatedReady();
const overlayTiming = useTimingAnimation();
const modalTiming = useTimingAnimation();
const spring = useSpringAnimation();
const previousVisibleRef = useRef<boolean>(false);
const animationConfig = useMemo(
() => ({
...DEFAULT_MODAL_ANIMATION_CONFIG,
...config,
}),
[
config?.overlayFadeDuration,
config?.modalScaleDamping,
config?.modalScaleStiffness,
config?.modalFadeDuration,
],
);
useEffect(() => {
if (!isReanimatedReady) {
return;
}
// Only animate if visible state actually changed
if (visible === previousVisibleRef.current) {
return;
}
previousVisibleRef.current = visible;
if (visible) {
// Reset to initial state before animating
overlayTiming.opacity.value = 0;
modalTiming.opacity.value = 0;
spring.scale.value = 0;
// Start animations immediately
overlayTiming.fadeIn({ duration: animationConfig.overlayFadeDuration });
modalTiming.fadeIn({ duration: animationConfig.modalFadeDuration });
spring.scaleIn({
damping: animationConfig.modalScaleDamping,
stiffness: animationConfig.modalScaleStiffness,
});
} else {
overlayTiming.fadeOut({ duration: animationConfig.overlayFadeDuration });
modalTiming.fadeOut({ duration: animationConfig.modalFadeDuration });
spring.scaleOut({
damping: animationConfig.modalScaleDamping,
stiffness: animationConfig.modalScaleStiffness,
});
}
}, [visible, isReanimatedReady, animationConfig, overlayTiming, modalTiming, spring]);
const overlayStyle = useAnimatedStyle(() => ({
opacity: overlayTiming.opacity.value,
}));
const modalStyle = useAnimatedStyle(() => ({
opacity: modalTiming.opacity.value,
transform: [{ scale: spring.scale.value }],
}));
return {
isReady: isReanimatedReady,
overlayStyle,
modalStyle,
};
}