/** * FieldError - Inline field validation error display * * Features: * - Animated appearance * - Icon with error message * - Compact inline design * - Accessible labels */ import React, { useEffect, useRef } from 'react'; import { View, Text, StyleSheet, Animated, AccessibilityInfo, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; interface FieldErrorProps { message: string; visible?: boolean; animated?: boolean; accessibilityLabel?: string; } export function FieldError({ message, visible = true, animated = true, accessibilityLabel, }: FieldErrorProps) { const opacityAnim = useRef(new Animated.Value(0)).current; const translateYAnim = useRef(new Animated.Value(-10)).current; useEffect(() => { if (visible) { if (animated) { Animated.parallel([ Animated.timing(opacityAnim, { toValue: 1, duration: 200, useNativeDriver: true, }), Animated.spring(translateYAnim, { toValue: 0, tension: 100, friction: 10, useNativeDriver: true, }), ]).start(); } else { opacityAnim.setValue(1); translateYAnim.setValue(0); } // Announce error to screen readers AccessibilityInfo.announceForAccessibility( accessibilityLabel || `Error: ${message}` ); } else { Animated.timing(opacityAnim, { toValue: 0, duration: 150, useNativeDriver: true, }).start(); } }, [visible, message, animated, opacityAnim, translateYAnim, accessibilityLabel]); if (!visible) { return null; } return ( {message} ); } /** * FieldErrorSummary - Summary of multiple field errors */ interface FieldErrorSummaryProps { errors: { field: string; message: string }[]; visible?: boolean; } export function FieldErrorSummary({ errors, visible = true }: FieldErrorSummaryProps) { if (!visible || errors.length === 0) { return null; } return ( Please fix the following {errors.length === 1 ? 'error' : 'errors'}: {errors.map((error, index) => ( {error.message} ))} ); } const styles = StyleSheet.create({ container: { flexDirection: 'row', alignItems: 'center', marginTop: 4, paddingHorizontal: 2, }, icon: { marginRight: 4, }, message: { fontSize: 12, color: '#DC2626', flex: 1, lineHeight: 16, }, // Summary styles summaryContainer: { backgroundColor: '#FEE2E2', borderRadius: 8, padding: 12, borderWidth: 1, borderColor: '#FECACA', marginVertical: 8, }, summaryHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, summaryTitle: { fontSize: 14, fontWeight: '600', color: '#991B1B', marginLeft: 8, }, summaryList: { paddingLeft: 26, }, summaryItem: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 4, }, summaryBullet: { fontSize: 12, color: '#991B1B', marginRight: 8, lineHeight: 16, }, summaryMessage: { fontSize: 13, color: '#991B1B', flex: 1, lineHeight: 16, }, }); export default FieldError;