UNPKG

react-native-reanimated-modal

Version:

A lightweight and performant modal component. Designed for smooth animations, flexibility, and minimal footprint.

203 lines (184 loc) 5.62 kB
import type { ReactNode } from 'react'; import type { ModalAnimation, ModalAnimationConfigUnion, ModalSwipeConfig, ModalBackdropConfig, SwipeDirection, FadeAnimationConfig, SlideAnimationConfig, ScaleAnimationConfig, } from './types'; import type { SpringConfig } from 'react-native-reanimated/lib/typescript/animation/spring'; /** * Default values and configurations. */ export const DEFAULT_MODAL_ANIMATION_DURATION = 300; export const DEFAULT_MODAL_SCALE_FACTOR = 0.8; export const DEFAULT_MODAL_SWIPE_THRESHOLD = 100; export const DEFAULT_MODAL_BOUNCE_OPACITY_THRESHOLD = 0.05; export const DEFAULT_MODAL_SWIPE_DIRECTION: SwipeDirection = 'down'; /** * Default backdrop configuration. */ export const DEFAULT_MODAL_BACKDROP_CONFIG: ModalBackdropConfig = { enabled: true, color: 'black', opacity: 0.7, } as const; export const DEFAULT_MODAL_BOUNCE_SPRING_CONFIG: SpringConfig = { dampingRatio: 0.5, duration: 700, } as const; /** * Default animation configurations. */ export const DEFAULT_MODAL_ANIMATION_CONFIGS = { fade: { type: 'fade', duration: DEFAULT_MODAL_ANIMATION_DURATION, } as FadeAnimationConfig, slide: { type: 'slide', duration: DEFAULT_MODAL_ANIMATION_DURATION, direction: DEFAULT_MODAL_SWIPE_DIRECTION, } as SlideAnimationConfig, scale: { type: 'scale', duration: DEFAULT_MODAL_ANIMATION_DURATION, scaleFactor: DEFAULT_MODAL_SCALE_FACTOR, } as ScaleAnimationConfig, } as const; /** * Default swipe configuration. */ export const DEFAULT_MODAL_SWIPE_CONFIG: ModalSwipeConfig = { enabled: true, directions: [DEFAULT_MODAL_SWIPE_DIRECTION], threshold: DEFAULT_MODAL_SWIPE_THRESHOLD, bounceSpringConfig: DEFAULT_MODAL_BOUNCE_SPRING_CONFIG, bounceOpacityThreshold: DEFAULT_MODAL_BOUNCE_OPACITY_THRESHOLD, }; /** * Normalizes animation configuration by providing defaults for missing properties. * @param config - Partial animation configuration or animation type string. * @returns Complete animation configuration with defaults applied. */ export function normalizeAnimationConfig( config: Partial<ModalAnimationConfigUnion> | ModalAnimation | undefined = {} ): ModalAnimationConfigUnion { // Handle string type (legacy support) if (typeof config === 'string') { return DEFAULT_MODAL_ANIMATION_CONFIGS[ config as keyof typeof DEFAULT_MODAL_ANIMATION_CONFIGS ]; } const type = (config as any)?.type || 'fade'; const defaultConfig = DEFAULT_MODAL_ANIMATION_CONFIGS[ type as keyof typeof DEFAULT_MODAL_ANIMATION_CONFIGS ]; return { ...defaultConfig, ...config, } as ModalAnimationConfigUnion; } /** * Normalizes backdrop configuration by providing defaults for missing properties. * @param backdrop - Backdrop configuration. * @returns Normalized backdrop information with enabled flag and config. */ export function normalizeBackdropConfig( backdrop: | ModalBackdropConfig | ReactNode | false = DEFAULT_MODAL_BACKDROP_CONFIG ): { enabled: boolean; isCustom: boolean; config: ModalBackdropConfig; customRenderer?: ReactNode; } { // false - no backdrop if (backdrop === false) { return { enabled: false, isCustom: false, config: { ...DEFAULT_MODAL_BACKDROP_CONFIG, enabled: false }, }; } // ReactNode - custom renderer if (backdrop && typeof backdrop === 'object' && 'type' in backdrop) { return { enabled: true, isCustom: true, config: DEFAULT_MODAL_BACKDROP_CONFIG, customRenderer: backdrop, }; } // object or undefined - use config with defaults const config = { ...DEFAULT_MODAL_BACKDROP_CONFIG, ...(backdrop as ModalBackdropConfig), }; return { enabled: config.enabled !== false, isCustom: false, config, }; } /** * Normalizes swipe configuration by providing defaults for missing properties. * @param config - Partial swipe configuration. * @returns Complete swipe configuration with defaults applied. */ export function normalizeSwipeConfig( config: ModalSwipeConfig | false = {} ): ModalSwipeConfig { if (config === false) return { enabled: false }; return { ...DEFAULT_MODAL_SWIPE_CONFIG, ...config, }; } /** * Extracts swipe directions from swipe config or animation config fallback. */ export function getSwipeDirections( swipeConfig: ModalSwipeConfig, animationConfig?: ModalAnimationConfigUnion, fallback: SwipeDirection | SwipeDirection[] = DEFAULT_MODAL_SWIPE_DIRECTION ): SwipeDirection[] { // If swipe config has directions, use them if (swipeConfig.directions && swipeConfig.directions.length > 0) { return swipeConfig.directions; } // Fallback to animation config for slide animations if ( animationConfig && animationConfig.type === 'slide' && animationConfig.direction ) { if (typeof animationConfig.direction === 'string') { return [animationConfig.direction]; } const endDirections = animationConfig.direction.end; return Array.isArray(endDirections) ? endDirections : [endDirections]; } return Array.isArray(fallback) ? fallback : [fallback]; } /** * Gets the slide-in direction from animation config. */ export function getSlideInDirection( animationConfig: ModalAnimationConfigUnion, fallback: SwipeDirection = DEFAULT_MODAL_SWIPE_DIRECTION ): SwipeDirection { if (animationConfig.type === 'slide' && animationConfig.direction) { if (typeof animationConfig.direction === 'string') { return animationConfig.direction; } return animationConfig.direction.start; } return fallback; }