// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useMemo } from 'react'; import type { ReactNode } from 'react'; import { take } from 'lodash'; import { I18n } from './I18n'; import type { LocalizerType } from '../types/Util'; import { UserText } from './UserText'; import type { GroupV2Membership } from './conversation/conversation-details/ConversationDetailsMembershipList'; type PropsType = { i18n: LocalizerType; nameClassName?: string; memberships: ReadonlyArray; invitesCount?: number; onOtherMembersClick?: () => void; }; // Define renderClickableButton outside component to avoid nested component definitions function renderClickableButton( parts: ReactNode, onOtherMembersClick?: () => void ): JSX.Element { return ( ); } function MemberList({ otherMemberNames, firstThreeMemberNames, areWeInGroup, i18n, onOtherMembersClick, }: { otherMemberNames: ReadonlyArray; firstThreeMemberNames: Array; areWeInGroup: boolean; i18n: LocalizerType; onOtherMembersClick?: () => void; }): JSX.Element { if (areWeInGroup) { if (otherMemberNames.length === 0) { return ( ); } if (otherMemberNames.length === 1) { return ( ); } if (otherMemberNames.length === 2) { return ( ); } // For 3+ members, "you" is looped in with "others", not shown separately const remainingCount = otherMemberNames.length + Number(areWeInGroup) - 3; return ( renderClickableButton(parts, onOtherMembersClick), remainingCount, }} /> ); } // When the user is not in the group if (otherMemberNames.length === 0) { return ; } if (otherMemberNames.length === 1) { return ( ); } if (otherMemberNames.length === 2) { return ( ); } if (otherMemberNames.length === 3) { return ( ); } // More than 3 members const remainingCount = otherMemberNames.length - 3; return ( renderClickableButton(parts, onOtherMembersClick), remainingCount, }} /> ); } export function GroupMembersNames({ i18n, nameClassName, memberships, invitesCount, onOtherMembersClick, }: PropsType): JSX.Element { const areWeInGroup = useMemo(() => { return memberships.some(({ member }) => member.isMe); }, [memberships]); const otherMemberNames = useMemo(() => { return memberships .filter(({ member }) => !member.isMe) .map(({ member }) => member.titleShortNoDefault); }, [memberships]); // Take the first 3 members for display, prioritizing defined names // "Unknown" is the fallback name if we never got the right profileKey // for a user, or haven't fetched their profile yet. const firstThreeMembers = useMemo(() => { return take( [...otherMemberNames].sort((a, b) => { if (a === undefined) { return 1; } if (b === undefined) { return -1; } return 0; }), 3 ).map((name, i) => ( // We cannot guarantee uniqueness of member names // eslint-disable-next-line react/no-array-index-key )); }, [otherMemberNames, nameClassName, i18n]); const memberListElement = ( ); // If there are invited members, wrap in the "(+1 invited)" format if (invitesCount && invitesCount > 0) { return ( ); } // Otherwise just return the member list return memberListElement; }