logoChisa UI

Command Palette

Search for a command to run...

Componentschevron_rightAnimatedDialog

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)} />
<AnimatedDialog
visible={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';
4
5type DialogVariant = 'default' | 'fullscreen' | 'bottom-sheet' | 'minimal';
6type DialogAnimation = 'fade' | 'scale' | 'slide-up' | 'slide-down';
7
8interface DialogAction {
9 label: string;
10 onPress: () => void;
11 variant?: 'primary' | 'secondary' | 'destructive';
12}
13
14interface 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}
25
26export 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);
33
34 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]);
42
43 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 });
49
50 if (!isVisible) return null;
51
52 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 <TouchableOpacity
63 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};
81
82const 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

NameTypeDefaultDescription
visiblebooleanfalseWhether the dialog is visible
onClose() => void-Called when dialog closes
titlestring-Dialog title
messagestring-Dialog message
variant'default' | 'fullscreen' | 'bottom-sheet' | 'minimal''default'Visual variant
animation'fade' | 'scale' | 'slide-up' | 'slide-down''scale'Animation type
actionsDialogAction[][]Action buttons with label, onPress, variant
showCloseButtonbooleanfalseShow X close button
closeOnBackdropbooleantrueClose when backdrop pressed