{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "animated-overlay", "type": "registry:ui", "title": "Animated Overlay", "description": "Shared pop-in/pop-out animated backdrop + content.", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json" ], "files": [ { "path": "registry/shared/ui/animated-overlay.tsx", "type": "registry:ui", "target": "components/ui/animated-overlay.tsx", "content": "import { useCallback, useEffect, useRef, type ReactNode } from 'react';\nimport {\n Animated,\n Pressable,\n StyleSheet,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\n\nexport type OverlayAnimationType =\n | 'scale'\n | 'slideUp'\n | 'slideDown'\n | 'fade'\n | 'none';\n\nconst ENTER_MS = 200;\nconst EXIT_MS = 160;\n\nexport interface UseAnimatedOverlayOptions {\n animationType?: OverlayAnimationType;\n /** Duration override (ms) for both enter and exit. */\n duration?: number;\n /** Backdrop max opacity (0–1). */\n backdropOpacity?: number;\n /** Called when the enter (true) or exit (false) animation finishes. */\n onAnimationEnd?: (entering: boolean) => void;\n}\n\n/**\n * Reusable pop-in / pop-out animation for overlays (modal, sheet, dialog).\n * Built on RN `Animated` — no reanimated dep.\n *\n * Returns an animated `progress` value plus ready-made `contentStyle`\n * (opacity + scale/translate) and `backdropStyle` (fade to backdropOpacity).\n */\nexport function useAnimatedOverlay({\n animationType = 'scale',\n duration,\n backdropOpacity = 0.5,\n onAnimationEnd,\n}: UseAnimatedOverlayOptions = {}) {\n const progress = useRef(new Animated.Value(0)).current;\n\n const setVisible = useCallback(\n (visible: boolean) => {\n if (animationType === 'none') {\n progress.setValue(visible ? 1 : 0);\n onAnimationEnd?.(visible);\n return;\n }\n Animated.timing(progress, {\n toValue: visible ? 1 : 0,\n duration: duration ?? (visible ? ENTER_MS : EXIT_MS),\n useNativeDriver: true,\n }).start(({ finished }) => {\n if (finished) onAnimationEnd?.(visible);\n });\n },\n [progress, animationType, duration, onAnimationEnd]\n );\n\n const contentStyle: StyleProp = {\n opacity: progress,\n transform: [\n animationType === 'slideUp' || animationType === 'slideDown'\n ? {\n translateY: progress.interpolate({\n inputRange: [0, 1],\n outputRange: [animationType === 'slideUp' ? 24 : -24, 0],\n }),\n }\n : {\n scale: progress.interpolate({\n inputRange: [0, 1],\n outputRange: [0.96, 1],\n }),\n },\n ],\n };\n\n const backdropStyle: StyleProp = {\n opacity: Animated.multiply(progress, backdropOpacity),\n };\n\n return { progress, setVisible, contentStyle, backdropStyle };\n}\n\nexport interface AnimatedOverlayProps {\n visible: boolean;\n animationType?: OverlayAnimationType;\n /** Duration override (ms). */\n duration?: number;\n children?: ReactNode;\n className?: string;\n backdropColor?: string;\n backdropOpacity?: number;\n showBackdrop?: boolean;\n onBackdropPress?: () => void;\n /** Called when the enter (true) or exit (false) animation finishes. */\n onAnimationEnd?: (entering: boolean) => void;\n style?: StyleProp;\n backdropStyle?: StyleProp;\n /** Extra style merged after the animated content style. */\n contentStyle?: StyleProp;\n}\n\n/**\n * AnimatedOverlay — fade-in backdrop + pop-in content wrapper.\n * Pair with RN `Modal transparent`; callers can defer unmount until\n * `onAnimationEnd(false)` fires so the exit animation plays.\n */\nexport function AnimatedOverlay({\n visible,\n animationType = 'scale',\n duration,\n children,\n className,\n backdropColor = '#000',\n backdropOpacity = 0.5,\n showBackdrop = true,\n onBackdropPress,\n onAnimationEnd,\n style,\n backdropStyle,\n contentStyle,\n}: AnimatedOverlayProps) {\n const {\n setVisible,\n contentStyle: animContent,\n backdropStyle: animBackdrop,\n } = useAnimatedOverlay({\n animationType,\n duration,\n backdropOpacity,\n onAnimationEnd,\n });\n\n useEffect(() => {\n setVisible(visible);\n }, [visible, setVisible]);\n\n return (\n \n {showBackdrop && (\n \n \n \n )}\n \n {children}\n \n \n );\n}\n\nconst styles = StyleSheet.create({\n content: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n" } ] }