/ components / Alert.tsx
Alert.tsx
1 import React from 'react'; 2 import { 3 View, 4 Text, 5 TouchableOpacity, 6 StyleSheet, 7 Modal, 8 useColorScheme, 9 TouchableWithoutFeedback, 10 Animated, 11 } from 'react-native'; 12 import { Colors, ColorScheme } from '@/constants/Colors'; 13 import { useTheme } from '@/constants/ThemeContext'; 14 import { Ionicons } from '@expo/vector-icons'; 15 import { CustomAlertProps } from '@/types'; 16 17 const Alert: React.FC<CustomAlertProps> = ({ 18 visible, 19 title, 20 onClose, 21 type, 22 options, 23 message, 24 }) => { 25 const { theme } = useTheme(); 26 const systemColorScheme = useColorScheme() as ColorScheme; 27 const colorScheme = 28 theme === 'system' ? systemColorScheme : (theme as ColorScheme); 29 const colors = Colors[colorScheme]; 30 31 const styles = getStyles(colors); 32 const scaleValue = React.useRef(new Animated.Value(0)).current; 33 34 React.useEffect(() => { 35 if (visible) { 36 Animated.spring(scaleValue, { 37 toValue: 1, 38 useNativeDriver: true, 39 }).start(); 40 } else { 41 Animated.spring(scaleValue, { 42 toValue: 0, 43 useNativeDriver: true, 44 }).start(); 45 } 46 }, [visible]); 47 48 const renderContent = () => { 49 if (type === 'bookmarks') { 50 return ( 51 <View testID="alert-options" style={styles.optionsContainer}> 52 {options?.map((option, index) => ( 53 <TouchableOpacity 54 key={index} 55 style={styles.button} 56 onPress={() => { 57 option.onPress(); 58 onClose(); 59 }} 60 > 61 {option.icon && ( 62 <View style={styles.iconContainer}> 63 <Ionicons 64 name={option.icon} 65 size={24} 66 color={colors.primary} 67 /> 68 </View> 69 )} 70 <Text style={styles.textStyle}>{option.text}</Text> 71 </TouchableOpacity> 72 ))} 73 </View> 74 ); 75 } else if (type === 'confirm') { 76 return ( 77 <> 78 <Text style={styles.message}>{message}</Text> 79 <View style={styles.confirmButtonsContainer}> 80 {options?.map((option, index) => ( 81 <TouchableOpacity 82 key={index} 83 style={[ 84 styles.confirmButton, 85 index === 0 86 ? styles.cancelButton 87 : styles.confirmActionButton, 88 ]} 89 onPress={() => { 90 option.onPress(); 91 onClose(); 92 }} 93 > 94 <Text 95 style={[ 96 styles.confirmButtonText, 97 index === 0 98 ? styles.cancelButtonText 99 : styles.confirmActionButtonText, 100 ]} 101 > 102 {option.text} 103 </Text> 104 </TouchableOpacity> 105 ))} 106 </View> 107 </> 108 ); 109 } 110 return null; 111 }; 112 113 return ( 114 <Modal 115 transparent={true} 116 visible={visible} 117 onRequestClose={onClose} 118 animationType="fade" 119 > 120 <TouchableWithoutFeedback onPress={onClose}> 121 <View style={styles.centeredView}> 122 <TouchableWithoutFeedback> 123 <Animated.View 124 style={[styles.modalView, { transform: [{ scale: scaleValue }] }]} 125 > 126 <TouchableOpacity 127 testID="close-button" 128 style={styles.closeButton} 129 onPress={onClose} 130 > 131 <Ionicons name="close" size={24} color={colors.text} /> 132 </TouchableOpacity> 133 <Text testID="alert-title" style={styles.modalText}> 134 {title} 135 </Text> 136 {renderContent()} 137 </Animated.View> 138 </TouchableWithoutFeedback> 139 </View> 140 </TouchableWithoutFeedback> 141 </Modal> 142 ); 143 }; 144 145 const getStyles = (colors: typeof Colors.light) => 146 StyleSheet.create({ 147 centeredView: { 148 flex: 1, 149 justifyContent: 'center', 150 alignItems: 'center', 151 backgroundColor: 'rgba(0, 0, 0, 0.5)', 152 }, 153 modalView: { 154 margin: 20, 155 backgroundColor: colors.card, 156 borderRadius: 24, 157 padding: 24, 158 alignItems: 'stretch', 159 shadowColor: '#000', 160 shadowOffset: { 161 width: 0, 162 height: 4, 163 }, 164 shadowOpacity: 0.25, 165 shadowRadius: 8, 166 elevation: 5, 167 minWidth: 300, 168 maxWidth: '85%', 169 }, 170 optionsContainer: { 171 marginTop: 16, 172 }, 173 button: { 174 flexDirection: 'row', 175 alignItems: 'center', 176 borderRadius: 12, 177 padding: 14, 178 marginTop: 12, 179 backgroundColor: colors.card, 180 borderWidth: 1, 181 borderColor: colors.border, 182 }, 183 iconContainer: { 184 width: 40, 185 height: 40, 186 borderRadius: 20, 187 backgroundColor: `${colors.primary}15`, 188 justifyContent: 'center', 189 alignItems: 'center', 190 marginRight: 16, 191 }, 192 textStyle: { 193 color: colors.text, 194 fontWeight: '600', 195 fontSize: 16, 196 flex: 1, 197 }, 198 modalText: { 199 marginBottom: 16, 200 textAlign: 'center', 201 fontWeight: 'bold', 202 fontSize: 24, 203 color: colors.primary, 204 }, 205 closeButton: { 206 position: 'absolute', 207 right: 16, 208 top: 16, 209 zIndex: 1, 210 width: 32, 211 height: 32, 212 borderRadius: 16, 213 backgroundColor: `${colors.text}10`, 214 justifyContent: 'center', 215 alignItems: 'center', 216 }, 217 message: { 218 fontSize: 16, 219 color: colors.text, 220 textAlign: 'center', 221 marginBottom: 20, 222 }, 223 confirmButtonsContainer: { 224 flexDirection: 'row', 225 justifyContent: 'space-around', 226 gap: 12, 227 }, 228 confirmButton: { 229 flex: 1, 230 paddingVertical: 12, 231 paddingHorizontal: 20, 232 borderRadius: 8, 233 alignItems: 'center', 234 }, 235 confirmButtonText: { 236 fontSize: 16, 237 fontWeight: 'bold', 238 }, 239 cancelButton: { 240 backgroundColor: colors.primary, 241 }, 242 cancelButtonText: { 243 color: colors.card, 244 }, 245 confirmActionButton: { 246 backgroundColor: colors.background, 247 borderWidth: 1, 248 borderColor: colors.border, 249 }, 250 confirmActionButtonText: { 251 color: colors.text, 252 }, 253 }); 254 255 export default Alert;