mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-28 19:27:50 +00:00
⚡️(frontend) extract participant count into a dedicated component
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.
This commit is contained in:
committed by
aleb_the_flash
parent
d1dde71bea
commit
c3c1e918b0
@@ -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 (
|
||||||
|
<>
|
||||||
|
<span id={describedById} className={srOnly}>
|
||||||
|
{t('count', { count })}
|
||||||
|
</span>
|
||||||
|
<div className={badgeStyles} aria-hidden>
|
||||||
|
{count < 100 ? count : <RiInfinityLine size={10} />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ParticipantsCount.displayName = 'ParticipantsCount'
|
||||||
+20
-60
@@ -1,36 +1,22 @@
|
|||||||
|
import { useCallback, useId } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiGroupLine, RiInfinityLine } from '@remixicon/react'
|
import { RiGroupLine } from '@remixicon/react'
|
||||||
import { ToggleButton } from '@/primitives'
|
import { ToggleButton, type ToggleButtonProps } from '@/primitives/ToggleButton'
|
||||||
import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip'
|
import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { useRemoteParticipants } from '@livekit/components-react'
|
|
||||||
import { ToggleButtonProps } from '@/primitives/ToggleButton'
|
|
||||||
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
|
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
|
||||||
import { RoomEvent } from 'livekit-client'
|
|
||||||
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
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 = ({
|
export const ParticipantsToggle = ({
|
||||||
onPress,
|
onPress,
|
||||||
...props
|
...props
|
||||||
}: ToggleButtonProps) => {
|
}: ToggleButtonProps) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.participants' })
|
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 { isParticipantsOpen, toggleParticipants } = useSidePanel()
|
||||||
|
const countId = useId()
|
||||||
|
|
||||||
const tooltipLabel = isParticipantsOpen ? 'open' : 'closed'
|
const tooltipLabel = isParticipantsOpen ? 'open' : 'closed'
|
||||||
|
|
||||||
@@ -39,58 +25,32 @@ export const ParticipantsToggle = ({
|
|||||||
handler: toggleParticipants,
|
handler: toggleParticipants,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handlePress = useCallback(
|
||||||
|
(e: Parameters<NonNullable<ToggleButtonProps['onPress']>>[0]) => {
|
||||||
|
toggleParticipants()
|
||||||
|
onPress?.(e)
|
||||||
|
},
|
||||||
|
[toggleParticipants, onPress]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={containerStyles}>
|
||||||
className={css({
|
|
||||||
position: 'relative',
|
|
||||||
display: 'inline-block',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<VisualOnlyTooltip tooltip={t(tooltipLabel)}>
|
<VisualOnlyTooltip tooltip={t(tooltipLabel)}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
square
|
square
|
||||||
variant="primaryTextDark"
|
variant="primaryTextDark"
|
||||||
aria-label={`${t(tooltipLabel)}. ${t('count', {
|
aria-label={t(tooltipLabel)}
|
||||||
count: announcedCount,
|
aria-describedby={countId}
|
||||||
})}.`}
|
|
||||||
isSelected={isParticipantsOpen}
|
isSelected={isParticipantsOpen}
|
||||||
aria-expanded={isParticipantsOpen}
|
aria-expanded={isParticipantsOpen}
|
||||||
onPress={(e) => {
|
onPress={handlePress}
|
||||||
toggleParticipants()
|
|
||||||
onPress?.(e)
|
|
||||||
}}
|
|
||||||
data-attr={`controls-participants-${tooltipLabel}`}
|
data-attr={`controls-participants-${tooltipLabel}`}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<RiGroupLine />
|
<RiGroupLine />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</VisualOnlyTooltip>
|
</VisualOnlyTooltip>
|
||||||
<div
|
<ParticipantsCount describedById={countId} />
|
||||||
className={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',
|
|
||||||
})}
|
|
||||||
aria-hidden={true}
|
|
||||||
>
|
|
||||||
{numParticipants < 100 ? (
|
|
||||||
numParticipants || 1
|
|
||||||
) : (
|
|
||||||
<RiInfinityLine size={10} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user