/**
* 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;