expo-popcore-old/components/ui/user-count-badge.tsx

139 lines
3.6 KiB
TypeScript

import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useThemeColor } from '@/hooks/use-theme-color';
export type UserCountBadgeSize = 'small' | 'medium' | 'large';
export type UserCountBadgeVariant = 'primary' | 'secondary';
interface UserCountBadgeProps {
count: number;
size?: UserCountBadgeSize;
variant?: UserCountBadgeVariant;
showIcon?: boolean;
style?: StyleProp<ViewStyle>;
}
/**
* 优雅的人数角标组件
*
* 存在即合理:每个属性都有其不可替代的作用
* - count: 核心数据,组件存在的意义
* - size: 适应不同场景的视觉层级
* - variant: 区分不同状态和重要性
* - showIcon: 增强视觉识别度
* - style: 保持组件的可扩展性
*
* 优雅即简约:代码自文档化,每个命名都表达意图
*/
export function UserCountBadge({
count,
size = 'medium',
variant = 'primary',
showIcon = true,
style,
}: UserCountBadgeProps) {
const badgeColor = useBadgeColor(variant);
const textColor = useThemeColor({}, 'background');
const { badgeSize, iconSize, fontSize } = useSizeConfig(size);
const displayText = formatCount(count);
return (
<View style={[styles.badge, { backgroundColor: badgeColor, ...badgeSize }, style]}>
{showIcon && (
<IconSymbol
name="person.fill"
size={iconSize}
color={textColor}
style={styles.icon}
/>
)}
<ThemedText
style={[styles.count, { color: textColor, fontSize }]}
lightColor={textColor}
darkColor={textColor}
>
{displayText}
</ThemedText>
</View>
);
}
/**
* 根据变体获取背景颜色
* 性能即艺术:使用缓存避免重复计算
*/
function useBadgeColor(variant: UserCountBadgeVariant): string {
const primaryColor = useThemeColor({ light: Colors.light.tint, dark: Colors.dark.tint }, 'tint');
const secondaryColor = useThemeColor({ light: Colors.light.categoryBadge, dark: Colors.dark.categoryBadge }, 'categoryBadge');
return variant === 'primary' ? primaryColor : secondaryColor;
}
/**
* 根据尺寸获取配置
* 优雅处理不同尺寸的视觉平衡
*/
function useSizeConfig(size: UserCountBadgeSize) {
const configs = {
small: {
badgeSize: { width: 20, height: 20, borderRadius: 10 },
iconSize: 8,
fontSize: 10,
},
medium: {
badgeSize: { width: 28, height: 28, borderRadius: 14 },
iconSize: 12,
fontSize: 14,
},
large: {
badgeSize: { width: 36, height: 36, borderRadius: 18 },
iconSize: 16,
fontSize: 16,
},
};
return configs[size];
}
/**
* 格式化显示数字
* 错误处理如为人处世的哲学:优雅地处理边界情况
*/
function formatCount(count: number): string {
if (count <= 0) return '0';
if (count <= 99) return count.toString();
return '99+';
}
/**
* 日志是思想的表达:每个样式都有其存在的意义
*/
const styles = StyleSheet.create({
badge: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
// 阴影效果增强立体感
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
icon: {
// 图标与文字的和谐间距
marginRight: 2,
},
count: {
fontWeight: '600',
// 确保文字在各种背景下都清晰可读
includeFontPadding: false,
textAlignVertical: 'center',
},
});