Files
digital-pilates/components/ui/ConfirmDialog.tsx
2025-08-16 14:15:11 +08:00

251 lines
5.7 KiB
TypeScript

import { Ionicons } from '@expo/vector-icons';
import * as Haptics from 'expo-haptics';
import React, { useEffect, useRef } from 'react';
import {
Animated,
Dimensions,
Modal,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
const { width: screenWidth } = Dimensions.get('window');
interface ConfirmDialogProps {
visible: boolean;
onClose: () => void;
title: string;
message?: string;
confirmText?: string;
cancelText?: string;
onConfirm: () => void;
destructive?: boolean;
icon?: keyof typeof Ionicons.glyphMap;
iconColor?: string;
}
export function ConfirmDialog({
visible,
onClose,
title,
message,
confirmText = '确定',
cancelText = '取消',
onConfirm,
destructive = false,
icon,
iconColor,
}: ConfirmDialogProps) {
const scaleAnim = useRef(new Animated.Value(0.8)).current;
const opacityAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (visible) {
// 显示动画
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
useNativeDriver: true,
tension: 100,
friction: 8,
}),
Animated.timing(opacityAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]).start();
} else {
// 隐藏动画
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 150,
useNativeDriver: true,
}),
Animated.timing(opacityAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}),
]).start();
}
}, [visible]);
const handleConfirm = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
onClose();
// 延迟执行确认回调,让关闭动画先完成
setTimeout(() => {
onConfirm();
}, 100);
};
const handleCancel = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
onClose();
};
if (!visible) return null;
const defaultIconColor = destructive ? '#EF4444' : '#3B82F6';
const confirmButtonColor = destructive ? '#EF4444' : '#3B82F6';
return (
<Modal
visible={visible}
transparent
animationType="none"
onRequestClose={onClose}
statusBarTranslucent
>
<View style={styles.container}>
{/* 背景遮罩 */}
<Animated.View
style={[
styles.backdrop,
{
opacity: opacityAnim,
},
]}
>
<TouchableOpacity
style={StyleSheet.absoluteFillObject}
activeOpacity={1}
onPress={handleCancel}
/>
</Animated.View>
{/* 弹窗内容 */}
<Animated.View
style={[
styles.dialog,
{
transform: [{ scale: scaleAnim }],
opacity: opacityAnim,
},
]}
>
{/* 图标 */}
{icon && (
<View style={[styles.iconContainer, { backgroundColor: `${iconColor || defaultIconColor}15` }]}>
<Ionicons
name={icon}
size={32}
color={iconColor || defaultIconColor}
/>
</View>
)}
{/* 标题 */}
<Text style={styles.title}>{title}</Text>
{/* 消息 */}
{message && <Text style={styles.message}>{message}</Text>}
{/* 按钮组 */}
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={handleCancel}
activeOpacity={0.7}
>
<Text style={styles.cancelButtonText}>{cancelText}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.button,
styles.confirmButton,
{ backgroundColor: confirmButtonColor },
]}
onPress={handleConfirm}
activeOpacity={0.7}
>
<Text style={styles.confirmButtonText}>{confirmText}</Text>
</TouchableOpacity>
</View>
</Animated.View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 40,
},
backdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
},
dialog: {
backgroundColor: '#FFFFFF',
borderRadius: 20,
paddingTop: 32,
paddingBottom: 24,
paddingHorizontal: 24,
width: '100%',
maxWidth: screenWidth - 80,
alignItems: 'center',
shadowColor: '#000',
shadowOpacity: 0.15,
shadowRadius: 20,
shadowOffset: { width: 0, height: 10 },
elevation: 10,
},
iconContainer: {
width: 64,
height: 64,
borderRadius: 32,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 20,
},
title: {
fontSize: 18,
fontWeight: '700',
color: '#111827',
textAlign: 'center',
marginBottom: 8,
},
message: {
fontSize: 15,
color: '#6B7280',
textAlign: 'center',
lineHeight: 22,
marginBottom: 24,
},
buttonContainer: {
flexDirection: 'row',
gap: 12,
width: '100%',
},
button: {
flex: 1,
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
},
cancelButton: {
backgroundColor: '#F3F4F6',
},
confirmButton: {
backgroundColor: '#3B82F6',
},
cancelButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#374151',
},
confirmButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#FFFFFF',
},
});