import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { IconAdd, IconArrowRight, IconRemove, useUIKit } from '@tencentcloud/uikit-base-component-react'; import cs from 'classnames'; import { GroupMemberRole } from 'tuikit-atomicx-react/chat'; import type { GroupMember } from 'tuikit-atomicx-react/chat'; import { useUIContext, useGroupStore } from 'tuikit-atomicx-react/chat'; import { View } from 'tuikit-atomicx-react/chat'; import { GroupPermission, hasGroupPermission } from '../../../../utils/chatGroupPermission'; import { Avatar } from 'tuikit-atomicx-react/chat'; import classes from './GroupMembers.module.scss'; interface GroupMembersProps { showAddButton: boolean; showRemoveButton: boolean; title?: string; members?: GroupMember[] | undefined; memberCount?: number; maxRows?: number; hiddenMemberCount?: boolean; expandable?: boolean; onAddButtonClick?: () => void; onRemoveButtonClick?: () => void; onMemberClick?: (member: GroupMember) => void; onViewAll?: () => void; onReachEnd?: () => void; loading?: boolean; hasMore?: boolean; className?: string; } const ITEM_MIN_WIDTH = 50; const GRID_GAP = 6; const GRID_PADDING_X = 40; // 20px padding on each side const getRolePriority = (role?: GroupMemberRole): number => { switch (role) { case GroupMemberRole.Owner: return 100; case GroupMemberRole.Admin: return 80; case GroupMemberRole.Member: return 60; default: return 0; } }; const GroupMembers = forwardRef((props, ref) => { const { t } = useUIKit(); const { title = t('ChatSetting.group_members'), members: propMembers, memberCount: propMemberCount, hiddenMemberCount = false, maxRows = 2, showAddButton = true, showRemoveButton = true, expandable = true, onAddButtonClick, onRemoveButtonClick, onMemberClick, onViewAll, onReachEnd, loading = false, hasMore = true, className, } = props; const [isExpanded, setIsExpanded] = useState(false); const [itemsPerRow, setItemsPerRow] = useState(5); const scrollContainerRef = useRef(null); const { activeConversationID } = useUIContext(); const { joinedGroupList } = useGroupStore(); const currentGroupID = activeConversationID?.startsWith('GROUP') ? activeConversationID.slice(5) : undefined; const groupInfo = useMemo(() => joinedGroupList.find(g => g.groupID === currentGroupID), [joinedGroupList, currentGroupID]); // ResizeObserver: dynamically compute items per row based on grid width useEffect(() => { const el = scrollContainerRef.current; if (!el) return undefined; const updateItemsPerRow = () => { const gridWidth = el.clientWidth - GRID_PADDING_X; const count = Math.floor((gridWidth + GRID_GAP) / (ITEM_MIN_WIDTH + GRID_GAP)); setItemsPerRow(Math.max(count, 1)); }; updateItemsPerRow(); const observer = new ResizeObserver(updateItemsPerRow); observer.observe(el); return () => { observer.disconnect(); }; }, []); const maxDisplayCount = itemsPerRow * maxRows; const members = propMembers; const memberCount = propMemberCount || members?.length; const sortedMembers = useMemo(() => { if (!members || members.length === 0) return []; return [...members].sort((a, b) => getRolePriority(b.role) - getRolePriority(a.role)); }, [members]); const actionButtonOrder = useMemo(() => { const actions: Array<'add' | 'remove'> = []; if (showAddButton) actions.push('add'); if (showRemoveButton) actions.push('remove'); return actions; }, [showAddButton, showRemoveButton]); const visibleActionButtons = useMemo(() => { if (isExpanded) return [] as Array<'add' | 'remove'>; return actionButtonOrder.slice(0, maxDisplayCount); }, [actionButtonOrder, isExpanded, maxDisplayCount]); const displayMiniMembers = useMemo(() => { if (isExpanded) return sortedMembers; const availableMemberSlots = Math.max(0, maxDisplayCount - visibleActionButtons.length); return sortedMembers.slice(0, availableMemberSlots); }, [isExpanded, maxDisplayCount, sortedMembers, visibleActionButtons.length]); const skeletonItems = useMemo(() => { if (members !== undefined) return []; const availableMemberSlots = Math.max(0, maxDisplayCount - visibleActionButtons.length); const skeletonCount = Math.min(propMemberCount || availableMemberSlots, availableMemberSlots); return Array.from({ length: skeletonCount }, (_, index) => index); }, [maxDisplayCount, members, propMemberCount, visibleActionButtons.length]); const canShowAddButton = visibleActionButtons.includes('add'); const canShowRemoveButton = visibleActionButtons.includes('remove'); const shouldShowExpand = useMemo(() => { if (!expandable) return false; const availableSlots = maxDisplayCount - actionButtonOrder.length; return sortedMembers.length > availableSlots; }, [expandable, maxDisplayCount, actionButtonOrder.length, sortedMembers.length]); const handleScroll = useCallback(() => { if (!isExpanded || !onReachEnd || !hasMore || loading) return; const container = scrollContainerRef.current; if (!container) return; const { scrollTop, scrollHeight, clientHeight } = container; if (scrollHeight - scrollTop - clientHeight < 100) { onReachEnd(); } }, [hasMore, isExpanded, loading, onReachEnd]); useEffect(() => { if (!isExpanded) return undefined; const container = scrollContainerRef.current; if (!container) return undefined; container.addEventListener('scroll', handleScroll); return () => { container.removeEventListener('scroll', handleScroll); }; }, [handleScroll, isExpanded]); if (!hasGroupPermission(GroupPermission.VIEW_MEMBER_LIST, groupInfo?.selfRole, groupInfo?.groupType)) { return null; } return ( {title} {!hiddenMemberCount && ( ({memberCount}) )} {shouldShowExpand && ( { setIsExpanded(!isExpanded); if (!isExpanded) onViewAll?.(); }} /> )} {skeletonItems.map(index => ( ))} {members !== undefined && displayMiniMembers.map(member => ( onMemberClick?.(member)} > {member.nickname || member.userID} ))} {!isExpanded && canShowAddButton && ( )} {!isExpanded && canShowRemoveButton && ( )} {isExpanded && loading && ( {t('ChatSetting.loading')} )} {isExpanded && !hasMore && !loading && sortedMembers.length > 0 && ( {t('ChatSetting.all_members_shown')} )} {isExpanded && ( setIsExpanded(false)}> {t('ChatSetting.collapse')} )} ); }); GroupMembers.displayName = 'GroupMembers'; export { GroupMembers };