From 8413b0371dd051b436c403346c7bffb4035501ef Mon Sep 17 00:00:00 2001 From: alex-vg <22611767+alex-vg@users.noreply.github.com> Date: Fri, 13 Mar 2026 22:26:04 +0100 Subject: [PATCH] feat: show call member presence in pre-join lobby view Display participant avatars with display names in the lobby when a call is already active. Shows up to 8 avatars with an overflow indicator and tooltip for additional participants. - Add useCallParticipants hook to derive participants from memberships - Add CallParticipantRow component using Compound Tooltip and Radix VisuallyHidden for accessibility - Integrate into LobbyView above VideoPreview - Include unit tests for hook and component - Add i18n strings for participant count and overflow --- locales/en/app.json | 7 ++ src/room/CallParticipantRow.module.css | 60 +++++++++++ src/room/CallParticipantRow.test.tsx | 136 +++++++++++++++++++++++++ src/room/CallParticipantRow.tsx | 109 ++++++++++++++++++++ src/room/GroupCallView.tsx | 4 + src/room/LobbyView.tsx | 7 ++ src/room/RoomPage.tsx | 1 + src/room/useCallParticipants.test.ts | 123 ++++++++++++++++++++++ src/room/useCallParticipants.ts | 50 +++++++++ 9 files changed, 497 insertions(+) create mode 100644 src/room/CallParticipantRow.module.css create mode 100644 src/room/CallParticipantRow.test.tsx create mode 100644 src/room/CallParticipantRow.tsx create mode 100644 src/room/useCallParticipants.test.ts create mode 100644 src/room/useCallParticipants.ts diff --git a/locales/en/app.json b/locales/en/app.json index 9b1a567503..3455231f45 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -156,6 +156,13 @@ "join_as_guest": "Join as guest", "join_button": "Join call", "leave_button": "Back to recents", + "participants_in_call_one": "{{count}} participant in call: {{names}}", + "participants_in_call_other": "{{count}} participants in call: {{names}}", + "participants_in_call_overflow_one": "{{count}} participant in call: {{names}}, and {{overflowCount}} other", + "participants_in_call_overflow_other": "{{count}} participants in call: {{names}}, and {{overflowCount}} others", + "participants_overflow_count": "+{{count}}", + "participants_overflow_label_one": "{{count}} more participant", + "participants_overflow_label_other": "{{count}} more participants", "waiting_for_invite": "Request sent! Waiting for permission to join…" }, "log_in": "Log In", diff --git a/src/room/CallParticipantRow.module.css b/src/room/CallParticipantRow.module.css new file mode 100644 index 0000000000..cccc8d8a62 --- /dev/null +++ b/src/room/CallParticipantRow.module.css @@ -0,0 +1,60 @@ +/* +Copyright 2026 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +.participantRow { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; + gap: var(--cpd-space-4x); + padding: 0 var(--cpd-space-4x); + max-width: 100%; +} + +.participantItem { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--cpd-space-1x); + width: 64px; +} + +.participantName { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); +} + +.overflowItem { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: var(--cpd-space-1x); + width: 64px; + cursor: default; +} + +.overflowCircle { + width: 48px; + height: 48px; + border-radius: 50%; + background-color: var(--cpd-color-bg-subtle-secondary); + display: flex; + justify-content: center; + align-items: center; + font: var(--cpd-font-body-md-semibold); + color: var(--cpd-color-text-secondary); +} + +.overflowCount { + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); +} diff --git a/src/room/CallParticipantRow.test.tsx b/src/room/CallParticipantRow.test.tsx new file mode 100644 index 0000000000..6d1ba069ee --- /dev/null +++ b/src/room/CallParticipantRow.test.tsx @@ -0,0 +1,136 @@ +/* +Copyright 2026 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { type JSX, type ReactNode } from "react"; +import { describe, expect, test, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { TooltipProvider } from "@vector-im/compound-web"; + +import { CallParticipantRow } from "./CallParticipantRow"; +import { type CallParticipant } from "./useCallParticipants"; + +// Mock the Avatar component to avoid MXC URL resolution side effects +vi.mock("../Avatar", () => ({ + Avatar: ({ + id, + name, + size, + }: { + id: string; + name: string; + size: number; + }): JSX.Element => ( +
+ ), + Size: { SM: "sm", MD: "md", LG: "lg", XL: "xl" }, +})); + +function renderWithProviders(children: ReactNode): ReturnType