🚸(frontend) refactor participant tile while being hovered

Inspired by GMeet. Make central actions available on a participant
tile when a user hover it.

This new interactive zone will be extended with more actions and
controls.
This commit is contained in:
lebaudantoine
2025-01-31 10:44:15 +01:00
committed by aleb_the_flash
parent 0b74cf96f2
commit 17f8ec6319
5 changed files with 122 additions and 5 deletions

View File

@@ -1,7 +1,6 @@
import {
AudioTrack,
ConnectionQualityIndicator,
FocusToggle,
LockLockedIcon,
ParticipantName,
ParticipantTileProps,
@@ -23,11 +22,12 @@ import {
TrackReferenceOrPlaceholder,
} from '@livekit/components-core'
import { Track } from 'livekit-client'
import { ParticipantPlaceholder } from '@/features/rooms/livekit/components/ParticipantPlaceholder'
import { RiHand } from '@remixicon/react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { useRaisedHand } from '../hooks/useRaisedHand'
import { HStack } from '@/styled-system/jsx'
import { MutedMicIndicator } from '@/features/rooms/livekit/components/MutedMicIndicator'
import { MutedMicIndicator } from './MutedMicIndicator'
import { ParticipantPlaceholder } from './ParticipantPlaceholder'
import { ParticipantTileFocus } from './ParticipantTileFocus'
export function TrackRefContextIfNeeded(
props: React.PropsWithChildren<{
@@ -173,7 +173,9 @@ export const ParticipantTile: (
)}
</>
)}
{!disableMetadata && <FocusToggle trackRef={trackReference} />}
{!disableMetadata && (
<ParticipantTileFocus trackRef={trackReference} />
)}
</ParticipantContextIfNeeded>
</TrackRefContextIfNeeded>
</div>

View File

@@ -0,0 +1,97 @@
import { css } from '@/styled-system/css'
import { HStack } from '@/styled-system/jsx'
import { Button } from '@/primitives'
import { RiPushpin2Line, RiUnpinLine } from '@remixicon/react'
import { useFocusToggle } from '@livekit/components-react'
import { useTranslation } from 'react-i18next'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useEffect, useState } from 'react'
const FocusButton = ({
trackRef,
}: {
trackRef: TrackReferenceOrPlaceholder
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { mergedProps, inFocus } = useFocusToggle({
trackRef,
props: {},
})
return (
<Button
size="sm"
variant="primaryTextDark"
square
tooltip={inFocus ? t('pin.disable') : t('pin.enable')}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onPress={(event) => mergedProps?.onClick?.(event as any)}
>
{inFocus ? <RiUnpinLine /> : <RiPushpin2Line />}
</Button>
)
}
export const ParticipantTileFocus = ({
trackRef,
}: {
trackRef: TrackReferenceOrPlaceholder
}) => {
const [hovered, setHovered] = useState(false)
const [opacity, setOpacity] = useState(0)
useEffect(() => {
if (hovered) {
// Wait for next frame to ensure element is mounted
requestAnimationFrame(() => {
setOpacity(0.6)
})
} else {
setOpacity(0)
}
}, [hovered])
return (
<div
className={css({
position: 'absolute',
left: '0',
top: '0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
})}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{hovered && (
<div
className={css({
backgroundColor: 'primaryDark.50',
transition: 'opacity 200ms linear',
zIndex: 1,
borderRadius: '0.25rem',
display: 'flex',
_hover: {
opacity: '0.95 !important',
},
})}
style={{ opacity }}
>
<HStack
gap={0.5}
className={css({
padding: '0.5rem',
_hover: {
opacity: '1 !important',
},
})}
>
<FocusButton trackRef={trackRef} />
</HStack>
</div>
)}
</div>
)
}

View File

@@ -189,5 +189,11 @@
},
"recording": {
"label": ""
},
"participantTileFocus": {
"pin": {
"enable": "",
"disable": ""
}
}
}

View File

@@ -188,5 +188,11 @@
},
"recording": {
"label": "Recording"
},
"participantTileFocus": {
"pin": {
"enable": "Pin",
"disable": "Unpin"
}
}
}

View File

@@ -188,5 +188,11 @@
},
"recording": {
"label": "Enregistrement"
},
"participantTileFocus": {
"pin": {
"enable": "Épingler",
"disable": "Annuler l'épinglage"
}
}
}