// frontend/src/components/Avatar.tsx import type { ImgHTMLAttributes } from 'react' export type AvatarSize = 6 | 8 | 9 | 10 | 12 | 14 | 16 export type AvatarShape = 'circle' | 'rounded' export type AvatarStatus = 'gray' | 'red' | 'green' export type AvatarStatusPosition = 'top' | 'bottom' export type AvatarProps = Omit, 'size'> & { src?: string name?: string initials?: string size?: AvatarSize shape?: AvatarShape status?: AvatarStatus statusPosition?: AvatarStatusPosition /** * Online-Status der Person. true = online (grĂ¼n), false = offline (grau). * Wird ignoriert, wenn `status` explizit gesetzt ist. */ online?: boolean showPlaceholderIcon?: boolean wrapperClassName?: string } function classNames(...classes: Array) { return classes.filter(Boolean).join(' ') } const sizeClassNames: Record = { 6: 'size-6', 8: 'size-8', 9: 'size-9', 10: 'size-10', 12: 'size-12', 14: 'size-14', 16: 'size-16', } const initialsSizeClassNames: Record = { 6: 'text-xs', 8: 'text-sm', 9: 'text-sm', 10: 'text-base', 12: 'text-lg', 14: 'text-xl', 16: 'text-2xl', } const statusSizeClassNames: Record = { 6: 'size-1.5', 8: 'size-2', 9: 'size-2', 10: 'size-2.5', 12: 'size-3', 14: 'size-3.5', 16: 'size-4', } const statusColorClassNames: Record = { gray: 'bg-gray-300 dark:bg-gray-500', red: 'bg-red-400 dark:bg-red-500', green: 'bg-green-400 dark:bg-green-500', } function getInitials(name?: string, initials?: string) { if (initials) { return initials.slice(0, 2).toUpperCase() } if (!name) { return '' } return name .split(/\s+/) .filter(Boolean) .slice(0, 2) .map((part) => part.charAt(0)) .join('') .toUpperCase() } function PlaceholderIcon({ className }: { className?: string }) { return ( ) } export default function Avatar({ src, alt, name, initials, size = 10, shape = 'circle', status, statusPosition = 'bottom', online, showPlaceholderIcon = true, wrapperClassName, className, ...props }: AvatarProps) { const roundedClassName = shape === 'circle' ? 'rounded-full' : 'rounded-md' const avatarInitials = getInitials(name, initials) // `status` hat Vorrang; ansonsten leitet sich der Punkt aus `online` ab. const effectiveStatus: AvatarStatus | undefined = status ?? (online === undefined ? undefined : online ? 'green' : 'gray') const statusLabel = online === undefined ? undefined : online ? 'Online' : 'Offline' const avatar = src ? ( {alt ) : avatarInitials ? ( {avatarInitials} ) : ( {showPlaceholderIcon && ( )} ) if (!effectiveStatus) { return avatar } return ( {avatar} ) }