Files
worklenz/worklenz-frontend/src/components/AvatarGroup.tsx
chamiakJ 05729285af feat(components): introduce new UI components and enhance Vite configuration
- Added AssigneeSelector, Avatar, AvatarGroup, Button, Checkbox, CustomColordLabel, CustomNumberLabel, LabelsSelector, Progress, Tag, and Tooltip components for improved UI functionality.
- Updated Vite configuration to change the development server port to 5173 and removed unnecessary interop settings for module compatibility.
- Enhanced task management components to utilize new task structure and improve performance.
2025-06-22 14:16:39 +05:30

111 lines
3.1 KiB
TypeScript

import React, { useCallback, useMemo } from 'react';
import { Avatar, Tooltip } from './index';
interface Member {
id?: string;
team_member_id?: string;
name?: string;
names?: string[];
avatar_url?: string;
color_code?: string;
end?: boolean;
}
interface AvatarGroupProps {
members: Member[];
maxCount?: number;
size?: number | 'small' | 'default' | 'large';
isDarkMode?: boolean;
className?: string;
onClick?: (e: React.MouseEvent) => void;
}
const AvatarGroup: React.FC<AvatarGroupProps> = ({
members,
maxCount,
size = 28,
isDarkMode = false,
className = '',
onClick
}) => {
const stopPropagation = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
onClick?.(e);
}, [onClick]);
const renderAvatar = useCallback((member: Member, index: number) => {
const memberName = member.end && member.names ? member.names.join(', ') : member.name || '';
const displayName = member.end && member.names ? member.name : member.name?.charAt(0).toUpperCase();
return (
<Tooltip
key={member.team_member_id || member.id || index}
title={memberName}
isDarkMode={isDarkMode}
>
<Avatar
name={member.name || ''}
src={member.avatar_url}
size={size}
isDarkMode={isDarkMode}
backgroundColor={member.color_code}
onClick={stopPropagation}
className="border-2 border-white"
style={isDarkMode ? { borderColor: '#374151' } : {}}
/>
</Tooltip>
);
}, [stopPropagation, size, isDarkMode]);
const visibleMembers = useMemo(() => {
return maxCount ? members.slice(0, maxCount) : members;
}, [members, maxCount]);
const remainingCount = useMemo(() => {
return maxCount ? Math.max(0, members.length - maxCount) : 0;
}, [members.length, maxCount]);
const avatarElements = useMemo(() => {
return visibleMembers.map((member, index) => renderAvatar(member, index));
}, [visibleMembers, renderAvatar]);
const getSizeStyle = () => {
if (typeof size === 'number') {
return { width: size, height: size, fontSize: `${size * 0.4}px` };
}
const sizeMap = {
small: { width: 24, height: 24, fontSize: '10px' },
default: { width: 32, height: 32, fontSize: '14px' },
large: { width: 48, height: 48, fontSize: '18px' }
};
return sizeMap[size];
};
return (
<div onClick={stopPropagation} className={`flex -space-x-1 ${className}`}>
{avatarElements}
{remainingCount > 0 && (
<Tooltip
title={`${remainingCount} more`}
isDarkMode={isDarkMode}
>
<div
className={`rounded-full flex items-center justify-center text-white font-medium shadow-sm border-2 cursor-pointer ${
isDarkMode
? 'bg-gray-600 border-gray-700'
: 'bg-gray-400 border-white'
}`}
style={getSizeStyle()}
onClick={stopPropagation}
>
+{remainingCount}
</div>
</Tooltip>
)}
</div>
);
};
export default AvatarGroup;