121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { View, Text, StyleSheet, Modal, TouchableOpacity, ScrollView, Pressable, Platform, Animated, Easing } from 'react-native';
|
|
import { Feather } from '@expo/vector-icons';
|
|
import { useAppTheme } from '../context/ThemeContext';
|
|
|
|
interface PopupProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
showCloseBtn?: boolean;
|
|
type?: 'center' | 'bottom';
|
|
}
|
|
|
|
export const Popup: React.FC<PopupProps> = ({
|
|
visible,
|
|
onClose,
|
|
title,
|
|
children,
|
|
showCloseBtn = true,
|
|
type = 'center'
|
|
}) => {
|
|
const { colors, isDark } = useAppTheme();
|
|
const [shouldRender, setShouldRender] = useState(visible);
|
|
|
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
const scale = useRef(new Animated.Value(type === 'center' ? 0.9 : 1)).current;
|
|
const translateY = useRef(new Animated.Value(type === 'bottom' ? 600 : 0)).current;
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
setShouldRender(true);
|
|
Animated.parallel([
|
|
Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true }),
|
|
type === 'center'
|
|
? Animated.spring(scale, { toValue: 1, damping: 15, stiffness: 100, useNativeDriver: true })
|
|
: Animated.spring(translateY, { toValue: 0, damping: 20, stiffness: 90, useNativeDriver: true })
|
|
]).start();
|
|
} else {
|
|
Animated.parallel([
|
|
Animated.timing(opacity, { toValue: 0, duration: 250, useNativeDriver: true }),
|
|
type === 'center'
|
|
? Animated.timing(scale, { toValue: 0.95, duration: 250, useNativeDriver: true })
|
|
: Animated.timing(translateY, { toValue: 600, duration: 250, useNativeDriver: true })
|
|
]).start(() => {
|
|
setShouldRender(false);
|
|
});
|
|
}
|
|
}, [visible, type]);
|
|
|
|
if (!shouldRender) return null;
|
|
|
|
const cardBg = isDark ? '#1A1A1A' : '#FFFFFF';
|
|
const border = isDark ? '#2A2A2A' : '#EEEEEE';
|
|
|
|
return (
|
|
<Modal visible={shouldRender} transparent statusBarTranslucent animationType="none" onRequestClose={onClose}>
|
|
<View style={[
|
|
styles.overlay,
|
|
type === 'bottom' && { justifyContent: 'flex-end', padding: 0 }
|
|
]}>
|
|
<Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: 'rgba(0,0,0,0.7)', opacity }]} />
|
|
<Pressable style={StyleSheet.absoluteFill} onPress={onClose} />
|
|
|
|
<Animated.View style={[
|
|
styles.content,
|
|
{
|
|
backgroundColor: cardBg,
|
|
borderColor: border,
|
|
borderWidth: 1,
|
|
opacity,
|
|
transform: [
|
|
{ scale: type === 'center' ? scale : 1 },
|
|
{ translateY: type === 'bottom' ? translateY : 0 }
|
|
]
|
|
},
|
|
type === 'bottom' && {
|
|
borderBottomLeftRadius: 0,
|
|
borderBottomRightRadius: 0,
|
|
borderTopLeftRadius: 36,
|
|
borderTopRightRadius: 36,
|
|
maxWidth: '100%',
|
|
maxHeight: '92%',
|
|
}
|
|
]}>
|
|
{type === 'bottom' && (
|
|
<View style={[styles.handle, { backgroundColor: isDark ? '#333' : '#E0E0E0' }]} />
|
|
)}
|
|
|
|
<View style={styles.header}>
|
|
<Text style={[styles.title, { color: colors.text }]}>{title}</Text>
|
|
{showCloseBtn && (
|
|
<TouchableOpacity onPress={onClose} style={[styles.closeBtn, { backgroundColor: isDark ? '#222' : '#F5F5F5' }]}>
|
|
<Feather name="x" size={18} color={isDark ? '#FFF' : '#1A1A1A'} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={styles.scrollContent}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
{children}
|
|
</ScrollView>
|
|
</Animated.View>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
|
|
content: { width: '100%', maxWidth: 420, maxHeight: '85%', borderRadius: 28, overflow: 'hidden', elevation: 20 },
|
|
handle: { width: 40, height: 4, borderRadius: 2, alignSelf: 'center', marginTop: 12 },
|
|
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingTop: 24, paddingBottom: 16 },
|
|
title: { fontSize: 22, fontFamily: 'Outfit_800ExtraBold', letterSpacing: -0.5 },
|
|
closeBtn: { width: 34, height: 34, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
|
|
scrollContent: { paddingHorizontal: 24, paddingBottom: Platform.OS === 'ios' ? 40 : 24 },
|
|
});
|