139 lines
3.6 KiB
TypeScript
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',
|
|
},
|
|
}); |