fixup! ♻️(frontend) normalize PiP component patterns

This commit is contained in:
Cyril
2026-05-07 11:08:42 +02:00
parent 47ec78973b
commit f9ca489b2f
3 changed files with 37 additions and 31 deletions
@@ -14,17 +14,21 @@ import { usePipElementSize } from '../hooks/usePipElementSize'
import { PipOptionsMenu } from './controls/PipOptionsMenu' import { PipOptionsMenu } from './controls/PipOptionsMenu'
import { PipReactionsToggle } from './PipReactionsToggle' import { PipReactionsToggle } from './PipReactionsToggle'
export const CollapsibleControls = {
HAND: 'hand',
SUBTITLES: 'subtitles',
SCREEN_SHARE: 'screenShare',
REACTIONS: 'reactions',
} as const
export type CollapsibleControl = export type CollapsibleControl =
| 'hand' (typeof CollapsibleControls)[keyof typeof CollapsibleControls]
| 'subtitles'
| 'screenShare'
| 'reactions'
const COLLAPSE_ORDER: CollapsibleControl[] = [ const COLLAPSE_ORDER: CollapsibleControl[] = [
'hand', CollapsibleControls.HAND,
'subtitles', CollapsibleControls.SUBTITLES,
'screenShare', CollapsibleControls.SCREEN_SHARE,
'reactions', CollapsibleControls.REACTIONS,
] ]
const BUTTON_SLOT = 50 const BUTTON_SLOT = 50
@@ -39,7 +43,7 @@ const getHiddenControls = (
const collapsible = showScreenShare const collapsible = showScreenShare
? COLLAPSE_ORDER ? COLLAPSE_ORDER
: COLLAPSE_ORDER.filter((c) => c !== 'screenShare') : COLLAPSE_ORDER.filter((c) => c !== CollapsibleControls.SCREEN_SHARE)
const available = containerWidth - ESSENTIAL_WIDTH const available = containerWidth - ESSENTIAL_WIDTH
const maxVisible = Math.max(0, Math.floor(available / BUTTON_SLOT)) const maxVisible = Math.max(0, Math.floor(available / BUTTON_SLOT))
@@ -84,10 +88,10 @@ export const PipControlBar = ({
<PipControlsCenter> <PipControlsCenter>
<AudioDevicesControl hideMenu /> <AudioDevicesControl hideMenu />
<VideoDeviceControl hideMenu /> <VideoDeviceControl hideMenu />
{!hidden.has('reactions') && <PipReactionsToggle />} {!hidden.has(CollapsibleControls.REACTIONS) && <PipReactionsToggle />}
{showScreenShare && !hidden.has('screenShare') && <ScreenShareToggle />} {showScreenShare && !hidden.has(CollapsibleControls.SCREEN_SHARE) && <ScreenShareToggle />}
{!hidden.has('subtitles') && <SubtitlesToggle />} {!hidden.has(CollapsibleControls.SUBTITLES) && <SubtitlesToggle />}
{!hidden.has('hand') && <HandToggle />} {!hidden.has(CollapsibleControls.HAND) && <HandToggle />}
<PipOptionsMenu overflowControls={hidden} /> <PipOptionsMenu overflowControls={hidden} />
<LeaveButton /> <LeaveButton />
<StartMediaButton /> <StartMediaButton />
@@ -1,5 +1,4 @@
import { Menu as RACMenu, MenuSection } from 'react-aria-components' import { Menu as RACMenu, MenuSection } from 'react-aria-components'
import { useTranslation } from 'react-i18next'
import { Separator } from '@/primitives/Separator' import { Separator } from '@/primitives/Separator'
import { FeedbackMenuItem } from '@/features/rooms/livekit/components/controls/Options/FeedbackMenuItem' import { FeedbackMenuItem } from '@/features/rooms/livekit/components/controls/Options/FeedbackMenuItem'
import { EffectsMenuItem } from '@/features/rooms/livekit/components/controls/Options/EffectsMenuItem' import { EffectsMenuItem } from '@/features/rooms/livekit/components/controls/Options/EffectsMenuItem'
@@ -16,7 +15,6 @@ type PipOptionsMenuItemsProps = {
export const PipOptionsMenuItems = ({ export const PipOptionsMenuItems = ({
overflowControls, overflowControls,
}: PipOptionsMenuItemsProps) => { }: PipOptionsMenuItemsProps) => {
const { t } = useTranslation('rooms')
const hasOverflow = overflowControls && overflowControls.size > 0 const hasOverflow = overflowControls && overflowControls.size > 0
return ( return (
@@ -29,7 +27,7 @@ export const PipOptionsMenuItems = ({
{hasOverflow && ( {hasOverflow && (
<> <>
<MenuSection> <MenuSection>
<PipOverflowItems overflowControls={overflowControls} t={t} /> <PipOverflowItems overflowControls={overflowControls} />
</MenuSection> </MenuSection>
<Separator /> <Separator />
</> </>
@@ -1,3 +1,4 @@
import React from 'react'
import { MenuItem } from 'react-aria-components' import { MenuItem } from 'react-aria-components'
import { import {
RiHand, RiHand,
@@ -5,57 +6,60 @@ import {
RiArrowUpLine, RiArrowUpLine,
RiEmotionLine, RiEmotionLine,
} from '@remixicon/react' } from '@remixicon/react'
import { TFunction } from 'i18next' import { useTranslation } from 'react-i18next'
import { Track } from 'livekit-client'
import { pipLayoutStore } from '@/features/pip/stores/pipLayoutStore' import { pipLayoutStore } from '@/features/pip/stores/pipLayoutStore'
import { menuRecipe } from '@/primitives/menuRecipe' import { menuRecipe } from '@/primitives/menuRecipe'
import { useRoomContext } from '@livekit/components-react' import { useRoomContext, useTrackToggle } from '@livekit/components-react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand' import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles' import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { useAreSubtitlesAvailable } from '@/features/subtitle/hooks/useAreSubtitlesAvailable' import { useAreSubtitlesAvailable } from '@/features/subtitle/hooks/useAreSubtitlesAvailable'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import type { CollapsibleControl } from '../PipControlBar' import { CollapsibleControls, type CollapsibleControl } from '../PipControlBar'
type PipOverflowItemsProps = { type PipOverflowItemsProps = {
overflowControls: Set<CollapsibleControl> overflowControls: Set<CollapsibleControl>
t: TFunction<'rooms'>
} }
export const PipOverflowItems = ({ export const PipOverflowItems = ({
overflowControls, overflowControls,
t,
}: PipOverflowItemsProps) => { }: PipOverflowItemsProps) => {
const { t } = useTranslation('rooms')
const room = useRoomContext() const room = useRoomContext()
const { isHandRaised, toggleRaisedHand } = useRaisedHand({ const { isHandRaised, toggleRaisedHand } = useRaisedHand({
participant: room.localParticipant, participant: room.localParticipant,
}) })
const { areSubtitlesOpen, toggleSubtitles } = useSubtitles() const { areSubtitlesOpen, toggleSubtitles } = useSubtitles()
const areSubtitlesAvailable = useAreSubtitlesAvailable() const areSubtitlesAvailable = useAreSubtitlesAvailable()
const pipSnap = useSnapshot(pipLayoutStore) const { buttonProps: screenShareProps, enabled: isScreenSharing } =
useTrackToggle({
source: Track.Source.ScreenShare,
captureOptions: { audio: true, selfBrowserSurface: 'include' },
})
const pipLayoutSnap = useSnapshot(pipLayoutStore)
const toggleReactions = () => { const toggleReactions = () => {
pipLayoutStore.showReactionsToolbar = !pipSnap.showReactionsToolbar pipLayoutStore.showReactionsToolbar = !pipLayoutSnap.showReactionsToolbar
} }
const itemClass = menuRecipe({ icon: true, variant: 'dark' }).item const itemClass = menuRecipe({ icon: true, variant: 'dark' }).item
return ( return (
<> <>
{overflowControls.has('reactions') && ( {overflowControls.has(CollapsibleControls.REACTIONS) && (
<MenuItem onAction={toggleReactions} className={itemClass}> <MenuItem onAction={toggleReactions} className={itemClass}>
<RiEmotionLine size={20} /> <RiEmotionLine size={20} />
{t('controls.reactions.button')} {t('controls.reactions.button')}
</MenuItem> </MenuItem>
)} )}
{overflowControls.has('screenShare') && ( {overflowControls.has(CollapsibleControls.SCREEN_SHARE) && (
<MenuItem <MenuItem
onAction={() => { onAction={() => screenShareProps.onClick?.({} as React.MouseEvent<HTMLButtonElement>)}
/* screen share requires track toggle, handled externally */
}}
className={itemClass} className={itemClass}
> >
<RiArrowUpLine size={20} /> <RiArrowUpLine size={20} />
{t('controls.screenShare.start')} {t(isScreenSharing ? 'controls.screenShare.stop' : 'controls.screenShare.start')}
</MenuItem> </MenuItem>
)} )}
{overflowControls.has('subtitles') && areSubtitlesAvailable && ( {overflowControls.has(CollapsibleControls.SUBTITLES) && areSubtitlesAvailable && (
<MenuItem onAction={toggleSubtitles} className={itemClass}> <MenuItem onAction={toggleSubtitles} className={itemClass}>
<RiClosedCaptioningLine size={20} /> <RiClosedCaptioningLine size={20} />
{areSubtitlesOpen {areSubtitlesOpen
@@ -63,7 +67,7 @@ export const PipOverflowItems = ({
: t('controls.subtitles.closed')} : t('controls.subtitles.closed')}
</MenuItem> </MenuItem>
)} )}
{overflowControls.has('hand') && ( {overflowControls.has(CollapsibleControls.HAND) && (
<MenuItem onAction={toggleRaisedHand} className={itemClass}> <MenuItem onAction={toggleRaisedHand} className={itemClass}>
<RiHand size={20} /> <RiHand size={20} />
{isHandRaised ? t('controls.hand.lower') : t('controls.hand.raise')} {isHandRaised ? t('controls.hand.lower') : t('controls.hand.raise')}