AnimatedDialog
Dialog/Modal with 4 variants (default, fullscreen, bottom-sheet, minimal) and smooth animations.
9:41
signal_cellular_altwifibattery_full
Installation
$
Usage
Example.tsx
import { useState } from 'react';import { View, Button } from 'react-native';import { AnimatedDialog } from './components/AnimatedDialog';export default function App() {const [visible, setVisible] = useState(false);return (<View><Button title="Open Dialog" onPress={() => setVisible(true)} /><AnimatedDialogvisible={visible}onClose={() => setVisible(false)}title="Confirm Action"message="Are you sure you want to proceed?"animation="scale"actions={[{ label: 'Cancel', onPress: () => setVisible(false), variant: 'secondary' },{ label: 'Confirm', onPress: () => { /* do action */ setVisible(false); } },]}/></View>);}
Full Component Code
AnimatedDialog.tsx
1import React, { useEffect, useState } from 'react';2import { View, Text, StyleSheet, TouchableOpacity, Modal, Pressable } from 'react-native';3import Animated, { useSharedValue, useAnimatedStyle, withTiming, withSpring, interpolate, runOnJS } from 'react-native-reanimated';45type DialogVariant = 'default' | 'fullscreen' | 'bottom-sheet' | 'minimal';6type DialogAnimation = 'fade' | 'scale' | 'slide-up' | 'slide-down';78interface DialogAction {9 label: string;10 onPress: () => void;11 variant?: 'primary' | 'secondary' | 'destructive';12}1314interface AnimatedDialogProps {15 visible: boolean;16 onClose: () => void;17 title?: string;18 message?: string;19 children?: React.ReactNode;20 variant?: DialogVariant;21 animation?: DialogAnimation;22 actions?: DialogAction[];23 primaryColor?: string;24}2526export const AnimatedDialog: React.FC<AnimatedDialogProps> = ({27 visible, onClose, title, message, children,28 variant = 'default', animation = 'scale',29 actions = [], primaryColor = '#cf350b',30}) => {31 const [isVisible, setIsVisible] = useState(visible);32 const progress = useSharedValue(0);3334 useEffect(() => {35 if (visible) {36 setIsVisible(true);37 progress.value = withSpring(1, { damping: 20 });38 } else {39 progress.value = withTiming(0, { duration: 200 }, () => runOnJS(setIsVisible)(false));40 }41 }, [visible]);4243 const animatedStyle = useAnimatedStyle(() => {44 const scale = animation === 'scale' ? interpolate(progress.value, [0, 1], [0.8, 1]) : 1;45 const translateY = animation === 'slide-up' ? interpolate(progress.value, [0, 1], [300, 0]) :46 animation === 'slide-down' ? interpolate(progress.value, [0, 1], [-300, 0]) : 0;47 return { opacity: progress.value, transform: [{ scale }, { translateY }] };48 });4950 if (!isVisible) return null;5152 return (53 <Modal visible={isVisible} transparent onRequestClose={onClose}>54 <Pressable style={styles.backdrop} onPress={onClose} />55 <View style={styles.container}>56 <Animated.View style={[styles.dialog, animatedStyle]}>57 {title && <Text style={styles.title}>{title}</Text>}58 {children || (message && <Text style={styles.message}>{message}</Text>)}59 {actions.length > 0 && (60 <View style={styles.actions}>61 {actions.map((action, i) => (62 <TouchableOpacity63 key={i}64 style={[styles.button, action.variant === 'destructive' && { backgroundColor: '#EF4444' },65 action.variant === 'secondary' && { backgroundColor: 'transparent', borderWidth: 1 },66 action.variant !== 'secondary' && action.variant !== 'destructive' && { backgroundColor: primaryColor }]}67 onPress={action.onPress}68 >69 <Text style={[styles.buttonText, action.variant === 'secondary' && { color: '#374151' }]}>70 {action.label}71 </Text>72 </TouchableOpacity>73 ))}74 </View>75 )}76 </Animated.View>77 </View>78 </Modal>79 );80};8182const styles = StyleSheet.create({83 backdrop: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.5)' },84 container: { flex: 1, justifyContent: 'center', alignItems: 'center' },85 dialog: { backgroundColor: '#FFF', borderRadius: 20, padding: 24, width: '85%', maxWidth: 340 },86 title: { fontSize: 20, fontWeight: '700', marginBottom: 8, textAlign: 'center' },87 message: { fontSize: 15, color: '#6B7280', textAlign: 'center', marginBottom: 24 },88 actions: { flexDirection: 'row', gap: 12 },89 button: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: 'center' },90 buttonText: { color: '#FFF', fontWeight: '600' },91});
Props
| Name | Type | Default | Description |
|---|---|---|---|
| visible | boolean | false | Whether the dialog is visible |
| onClose | () => void | - | Called when dialog closes |
| title | string | - | Dialog title |
| message | string | - | Dialog message |
| variant | 'default' | 'fullscreen' | 'bottom-sheet' | 'minimal' | 'default' | Visual variant |
| animation | 'fade' | 'scale' | 'slide-up' | 'slide-down' | 'scale' | Animation type |
| actions | DialogAction[] | [] | Action buttons with label, onPress, variant |
| showCloseButton | boolean | false | Show X close button |
| closeOnBackdrop | boolean | true | Close when backdrop pressed |
