From c3c1e918b054b7a5fa5f8ee91fe1fc945ceb8dc3 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Fri, 17 Jul 2026 18:21:01 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F(frontend)=20extract=20partic?= =?UTF-8?q?ipant=20count=20into=20a=20dedicated=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the participant count out of the participant badge into a dedicated, optimized component. This isolates the subscription to the participant count so it no longer triggers a re-render of the whole toggle when the count changes. --- .../components/ParticipantsCount.tsx | 52 ++++++++++++ .../controls/ParticipantsToggle.tsx | 80 +++++-------------- 2 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 src/frontend/src/features/participants/components/ParticipantsCount.tsx diff --git a/src/frontend/src/features/participants/components/ParticipantsCount.tsx b/src/frontend/src/features/participants/components/ParticipantsCount.tsx new file mode 100644 index 00000000..fff36dcc --- /dev/null +++ b/src/frontend/src/features/participants/components/ParticipantsCount.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useRemoteParticipants } from '@livekit/components-react' +import { srOnly } from '@/styles/a11y' +import { css } from '@/styled-system/css' +import { RiInfinityLine } from '@remixicon/react' + +const badgeStyles = css({ + position: 'absolute', + top: '-.25rem', + right: '-.25rem', + width: '1.25rem', + height: '1.25rem', + backgroundColor: 'gray', + borderRadius: '50%', + color: 'white', + fontSize: '0.75rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + textAlign: 'center', + zIndex: 1, + userSelect: 'none', +}) + +/** + * Isolated so participant join/leave events only re-render the badge, + * not the button, tooltip or shortcut registration. + */ +export const ParticipantsCount = React.memo( + ({ describedById }: { describedById: string }) => { + const { t } = useTranslation('rooms', { + keyPrefix: 'controls.participants', + }) + const remoteParticipants = useRemoteParticipants({ + updateOnlyOn: [], + }) + const count = (remoteParticipants?.length ?? 0) + 1 + + return ( + <> + + {t('count', { count })} + +
+ {count < 100 ? count : } +
+ + ) + } +) +ParticipantsCount.displayName = 'ParticipantsCount' diff --git a/src/frontend/src/features/rooms/livekit/components/controls/ParticipantsToggle.tsx b/src/frontend/src/features/rooms/livekit/components/controls/ParticipantsToggle.tsx index 5f809198..8e874009 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/ParticipantsToggle.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/ParticipantsToggle.tsx @@ -1,36 +1,22 @@ +import { useCallback, useId } from 'react' import { useTranslation } from 'react-i18next' -import { RiGroupLine, RiInfinityLine } from '@remixicon/react' -import { ToggleButton } from '@/primitives' +import { RiGroupLine } from '@remixicon/react' +import { ToggleButton, type ToggleButtonProps } from '@/primitives/ToggleButton' import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip' import { css } from '@/styled-system/css' -import { useRemoteParticipants } from '@livekit/components-react' -import { ToggleButtonProps } from '@/primitives/ToggleButton' import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut' -import { RoomEvent } from 'livekit-client' import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel' +import { ParticipantsCount } from '@/features/participants/components/ParticipantsCount' + +const containerStyles = css({ position: 'relative', display: 'inline-block' }) export const ParticipantsToggle = ({ onPress, ...props }: ToggleButtonProps) => { const { t } = useTranslation('rooms', { keyPrefix: 'controls.participants' }) - - /** - * Context could not be used due to inconsistent refresh behavior. - * The 'numParticipant' property on the room only updates when the room's metadata changes, - * resulting in a delay compared to the participant list's actual refresh rate. - */ - const remoteParticipants = useRemoteParticipants({ - updateOnlyOn: [ - RoomEvent.ParticipantConnected, - RoomEvent.ParticipantDisconnected, - ], - }) - const numParticipants = remoteParticipants?.length + 1 // for the local participant - const announcedCount = - numParticipants && numParticipants > 0 ? numParticipants : 1 - const { isParticipantsOpen, toggleParticipants } = useSidePanel() + const countId = useId() const tooltipLabel = isParticipantsOpen ? 'open' : 'closed' @@ -39,58 +25,32 @@ export const ParticipantsToggle = ({ handler: toggleParticipants, }) + const handlePress = useCallback( + (e: Parameters>[0]) => { + toggleParticipants() + onPress?.(e) + }, + [toggleParticipants, onPress] + ) + return ( -
+
{ - toggleParticipants() - onPress?.(e) - }} + onPress={handlePress} data-attr={`controls-participants-${tooltipLabel}`} {...props} > -
- {numParticipants < 100 ? ( - numParticipants || 1 - ) : ( - - )} -
+
) }