expo-starter/components/common/Avatar.tsx

105 lines
1.9 KiB
TypeScript

import { Image } from 'expo-image';
import {
StyleSheet,
Text,
View,
type ImageStyle,
type StyleProp,
type ViewStyle,
} from 'react-native';
import { Catamaran, FunnelSans } from '@/constants/fonts';
import { Palette } from '@/constants/palette';
import { Radius } from '@/constants/styles';
type AvatarSize = 'sm' | 'md' | 'lg';
type AvatarProps = {
uri?: string | null;
name?: string | null;
size?: AvatarSize;
style?: StyleProp<ImageStyle>;
};
const avatarSizeMap = {
sm: 40,
md: 64,
lg: 104,
} as const;
function getInitials(name?: string | null) {
if (!name?.trim()) {
return '?';
}
const parts = name
.trim()
.split(/\s+/)
.slice(0, 2);
return parts.map((part) => part[0]?.toUpperCase() ?? '').join('') || '?';
}
export function Avatar({ uri, name, size = 'md', style }: AvatarProps) {
const avatarSize = avatarSizeMap[size];
const initials = getInitials(name);
if (uri) {
return (
<Image
contentFit="cover"
source={{ uri }}
style={[
styles.base,
{
width: avatarSize,
height: avatarSize,
borderRadius: avatarSize / 2,
},
style,
]}
/>
);
}
return (
<View
style={[
styles.base,
styles.fallback,
{
width: avatarSize,
height: avatarSize,
borderRadius: avatarSize / 2,
},
style,
]}
>
<Text style={[size === 'lg' ? styles.largeText : styles.text]}>{initials}</Text>
</View>
);
}
const styles = StyleSheet.create({
base: {
overflow: 'hidden',
},
fallback: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Palette.black,
borderWidth: 1,
borderColor: Palette.gray,
},
text: {
...FunnelSans.semiBold14,
color: Palette.white,
},
largeText: {
...Catamaran.bold18,
color: Palette.white,
},
});
export default Avatar;