️(frontend) optimize participant metadata rendering on raised hand

Push state down into the participant metadata subtree so a raised
hand change no longer re-renders the whole metadata block.

Use the children-as-props pattern so only the item actually related
to the raised hand re-renders when its state changes.

This also better encapsulates the color-update logic tied to the
raised-hand state.
This commit is contained in:
lebaudantoine
2026-07-15 16:18:54 +02:00
parent be033ae6c9
commit 158ea5adf2
2 changed files with 96 additions and 46 deletions
@@ -1,7 +1,3 @@
import {
useRaisedHand,
useRaisedHandPosition,
} from '@/features/rooms/livekit/hooks/useRaisedHand'
import {
ConnectionQualityIndicator,
LockLockedIcon,
@@ -12,8 +8,8 @@ import {
import { HStack } from '@/styled-system/jsx'
import { Participant } from 'livekit-client'
import { MutedMicIndicator } from './MutedMicIndicator'
import { RiHand } from '@remixicon/react'
import { ParticipantName } from './ParticipantName'
import { RaisedHandMetadataWrapper } from './RaisedHandMetadataWrapper'
export const ParticipantMetadata = ({
participant,
@@ -24,52 +20,15 @@ export const ParticipantMetadata = ({
}) => {
const { identity, name } = useParticipantInfo({ participant })
const isEncrypted = useIsEncrypted(participant)
const { isHandRaised } = useRaisedHand({ participant })
const { positionInQueue, firstInQueue } = useRaisedHandPosition({
participant,
})
return (
<div className="lk-participant-metadata">
<HStack gap={0.25}>
{!isScreenShare && <MutedMicIndicator participant={participant} />}
<div
className="lk-participant-metadata-item"
style={{
padding: '0.1rem 0.25rem',
backgroundColor:
isHandRaised && !isScreenShare
? firstInQueue
? '#fde047'
: 'white'
: undefined,
color: isHandRaised && !isScreenShare ? 'black' : undefined,
transition: 'background 200ms ease, color 400ms ease',
}}
<RaisedHandMetadataWrapper
participant={participant}
enabled={!isScreenShare}
>
{isHandRaised && !isScreenShare && (
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.1rem',
}}
>
<span>{positionInQueue}</span>
<RiHand
color="black"
size={16}
style={{
marginRight: '0.4rem',
marginLeft: '0.1rem',
minWidth: '16px',
animationDuration: '300ms',
animationName: 'wave_hand',
animationIterationCount: '2',
}}
/>
</span>
)}
{isScreenShare && (
<ScreenShareIcon
style={{
@@ -87,7 +46,7 @@ export const ParticipantMetadata = ({
isScreenShare={isScreenShare}
/>
</div>
</div>
</RaisedHandMetadataWrapper>
</HStack>
<ConnectionQualityIndicator className="lk-participant-metadata-item" />
</div>
@@ -0,0 +1,91 @@
import React, { ReactNode, RefObject, useEffect, useRef } from 'react'
import {
useRaisedHand,
useRaisedHandPosition,
} from '@/features/rooms/livekit/hooks/useRaisedHand'
import { Participant } from 'livekit-client'
import { RiHand } from '@remixicon/react'
const PositionInQueue = React.memo(
({ positionInQueue }: { positionInQueue?: number }) => {
if (!positionInQueue) return
return (
<span
style={{ display: 'inline-flex', alignItems: 'center', gap: '0.1rem' }}
>
<span>{positionInQueue}</span>
<RiHand
color="black"
size={16}
style={{
marginRight: '0.4rem',
marginLeft: '0.1rem',
minWidth: '16px',
animationDuration: '300ms',
animationName: 'wave_hand',
animationIterationCount: '2',
}}
/>
</span>
)
}
)
PositionInQueue.displayName = 'PositionInQueue'
const RaisedHandActiveItem = ({
participant,
targetRef,
}: {
participant: Participant
targetRef: RefObject<HTMLElement | null>
}) => {
const { positionInQueue, firstInQueue } = useRaisedHandPosition({
participant,
})
useEffect(() => {
const el = targetRef.current
if (!el) return
el.style.backgroundColor = firstInQueue ? '#fde047' : 'white'
el.style.color = 'black'
return () => {
el.style.backgroundColor = ''
el.style.color = ''
}
}, [targetRef, firstInQueue])
return <PositionInQueue positionInQueue={positionInQueue} />
}
export const RaisedHandMetadataWrapper = ({
participant,
enabled = true,
children,
}: {
participant: Participant
enabled?: boolean
children: ReactNode
}) => {
const ref = useRef(null)
const { isHandRaised } = useRaisedHand({ participant })
const showHand = isHandRaised && enabled
return (
<div
ref={ref}
className="lk-participant-metadata-item"
style={{
padding: '0.1rem 0.25rem',
transition: 'background 200ms ease',
}}
>
{showHand && (
<RaisedHandActiveItem participant={participant} targetRef={ref} />
)}
{children}
</div>
)
}