expo-popcore-app/components/ErrorState.tsx

119 lines
3.4 KiB
TypeScript

import React from 'react'
import { View, Pressable, ViewProps, StyleSheet } from 'react-native'
import { LinearGradient } from 'expo-linear-gradient'
import Svg, { Path, Circle, Defs, Rect, LinearGradient as SvgLinearGradient, Stop } from 'react-native-svg'
import Text from './ui/Text'
interface ErrorStateProps extends ViewProps {
message?: string
onRetry?: () => void
/** 是否显示为空状态图标(默认)或错误图标 */
variant?: 'empty' | 'error'
}
/** 空状态图标 - 文件夹 */
function EmptyIcon() {
return (
<Svg width={80} height={80} viewBox="0 0 80 80" fill="none">
<Defs>
<SvgLinearGradient id="folderGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#9966FF" stopOpacity={0.3} />
<Stop offset="100%" stopColor="#FF6699" stopOpacity={0.3} />
</SvgLinearGradient>
</Defs>
<Rect x="10" y="20" width="60" height="45" rx="6" fill="url(#folderGrad)" stroke="#4A4A4A" strokeWidth="2" />
<Path d="M10 26C10 22.6863 12.6863 20 16 20H30L36 14H64C67.3137 14 70 16.6863 70 20V26H10Z" fill="#2A2A2A" stroke="#4A4A4A" strokeWidth="2" />
<Circle cx="40" cy="45" r="12" stroke="#6B6B6B" strokeWidth="2" strokeDasharray="4 4" />
<Path d="M36 45H44M40 41V49" stroke="#6B6B6B" strokeWidth="2" strokeLinecap="round" />
</Svg>
)
}
/** 错误状态图标 */
function ErrorIcon() {
return (
<Svg width={80} height={80} viewBox="0 0 80 80" fill="none">
<Circle cx="40" cy="40" r="28" stroke="#FF6B6B" strokeWidth="2" strokeOpacity={0.5} />
<Circle cx="40" cy="40" r="20" fill="#2A2A2A" stroke="#FF6B6B" strokeWidth="2" />
<Path d="M40 30V44" stroke="#FF6B6B" strokeWidth="3" strokeLinecap="round" />
<Circle cx="40" cy="52" r="2" fill="#FF6B6B" />
</Svg>
)
}
export default function ErrorState({
message = 'An error occurred',
onRetry,
testID,
variant = 'empty',
...props
}: ErrorStateProps) {
return (
<View testID={testID} style={styles.container} {...props}>
{/* 图标 */}
<View style={styles.iconContainer}>
{variant === 'error' ? <ErrorIcon /> : <EmptyIcon />}
</View>
{/* 消息文本 */}
<Text style={styles.message}>{message}</Text>
{/* 重试按钮 - 使用渐变边框风格 */}
{onRetry && (
<Pressable onPress={onRetry} style={styles.retryButton}>
<LinearGradient
colors={['#9966FF', '#FF6699', '#FF9966']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.gradientBorder}
>
<View style={styles.buttonInner}>
<Text style={styles.buttonText}></Text>
</View>
</LinearGradient>
</Pressable>
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 24,
paddingVertical: 48,
minHeight: 280,
},
iconContainer: {
marginBottom: 20,
},
message: {
fontSize: 15,
color: '#8E8E93',
textAlign: 'center',
lineHeight: 22,
},
retryButton: {
marginTop: 24,
borderRadius: 22,
overflow: 'hidden',
},
gradientBorder: {
padding: 1.5,
borderRadius: 22,
},
buttonInner: {
backgroundColor: '#1C1E22',
paddingHorizontal: 28,
paddingVertical: 12,
borderRadius: 20,
},
buttonText: {
color: '#F5F5F5',
fontSize: 14,
fontWeight: '500',
},
})