♻️(frontend) simplify videoconference layout and clarify component roles

Remove unnecessary wrapper divs to reduce layout complexity.

Explicitly name components to better reflect their
responsibilities, including RoomContentArea which handles the
video track viewport.
This commit is contained in:
lebaudantoine
2026-03-25 17:49:53 +01:00
parent 45e0665cf0
commit 416411b843
3 changed files with 103 additions and 81 deletions
@@ -1,24 +0,0 @@
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
export const LayoutWrapper = styled(
'div',
cva({
base: {
position: 'relative',
display: 'flex',
width: '100%',
transition: 'height .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
areSubtitlesOpen: {
true: {
height: 'calc(100% - 12rem)',
},
false: {
height: '100%',
},
},
},
})
)
@@ -0,0 +1,72 @@
// RoomContentArea.tsx
import React from 'react'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import {Subtitles} from "@/features/subtitle/component/Subtitles";
import {MainNotificationToast} from "@/features/notifications/MainNotificationToast";
const RoomViewport = styled(
'div',
cva({
base: {
position: 'absolute',
maxHeight: '100%',
transition: 'inset .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
isSidePanelOpen: {
true: {
inset: `var(--lk-grid-gap) calc(358px + 3rem) calc(80px + var(--lk-grid-gap)) 16px`,
},
false: {
inset: `var(--lk-grid-gap) var(--lk-grid-gap) calc(80px + var(--lk-grid-gap))`,
},
},
},
})
)
const TrackAreaContainer = styled(
'div',
cva({
base: {
position: 'relative',
display: 'flex',
width: '100%',
transition: 'height .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
areSubtitlesOpen: {
true: {
height: 'calc(100% - 12rem)',
},
false: {
height: '100%',
},
},
},
})
)
interface RoomContentAreaProps {
children: React.ReactNode
}
export function RoomContentArea({ children }: RoomContentAreaProps) {
const { isSidePanelOpen } = useSidePanel()
const { areSubtitlesOpen } = useSubtitles()
return (
<RoomViewport isSidePanelOpen={isSidePanelOpen}>
<TrackAreaContainer areSubtitlesOpen={areSubtitlesOpen}>
{children}
</TrackAreaContainer>
<Subtitles />
<MainNotificationToast />
</RoomViewport>
)
}
@@ -20,11 +20,9 @@ import {
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ControlBar } from './ControlBar/ControlBar' import { ControlBar } from './ControlBar/ControlBar'
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
import { FocusLayout } from '../components/FocusLayout' import { FocusLayout } from '../components/FocusLayout'
import { ParticipantTile } from '../components/ParticipantTile' import { ParticipantTile } from '../components/ParticipantTile'
import { SidePanel } from '../components/SidePanel' import { SidePanel } from '../components/SidePanel'
import { useSidePanel } from '../hooks/useSidePanel'
import { RecordingProvider } from '@/features/recording' import { RecordingProvider } from '@/features/recording'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal' import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver' import { useConnectionObserver } from '../hooks/useConnectionObserver'
@@ -35,15 +33,15 @@ import { useSettingsDialog } from '@/features/settings'
import { SettingsDialogExtendedKey } from '@/features/settings/type' import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription' import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider' import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { Subtitles } from '@/features/subtitle/component/Subtitles'
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal' import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
import { getParticipantName } from '@/features/rooms/utils/getParticipantName' import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce' import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
import { ReactionPortals } from '@/features/reactions/components/ReactionPortals' import { ReactionPortals } from '@/features/reactions/components/ReactionPortals'
import { CarouselLayout } from '@/features/layout/components/CarouselLayout' import { CarouselLayout } from '@/features/layout/components/CarouselLayout'
import { GridLayout } from '@/features/layout/components/GridLayout' import { GridLayout } from '@/features/layout/components/GridLayout'
import { LayoutWrapper } from '@/features/layout/components/LayoutWrapper' import {
RoomContentArea,
} from '@/features/layout/components/RoomContentArea'
/** /**
* @public * @public
@@ -231,9 +229,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
]) ])
/* eslint-enable react-hooks/exhaustive-deps */ /* eslint-enable react-hooks/exhaustive-deps */
const { isSidePanelOpen } = useSidePanel()
const { areSubtitlesOpen } = useSubtitles()
const [isShareErrorVisible, setIsShareErrorVisible] = useState(false) const [isShareErrorVisible, setIsShareErrorVisible] = useState(false)
return ( return (
@@ -254,57 +249,36 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
onClose={() => setIsShareErrorVisible(false)} onClose={() => setIsShareErrorVisible(false)}
/> />
<IsIdleDisconnectModal /> <IsIdleDisconnectModal />
<div <RoomContentArea
// todo - extract these magic values into constant
style={{
position: 'absolute',
inset: isSidePanelOpen
? `var(--lk-grid-gap) calc(358px + 3rem) calc(80px + var(--lk-grid-gap)) 16px`
: `var(--lk-grid-gap) var(--lk-grid-gap) calc(80px + var(--lk-grid-gap))`,
transition: 'inset .5s cubic-bezier(0.4,0,0.2,1) 5ms',
maxHeight: '100%',
}}
> >
<LayoutWrapper areSubtitlesOpen={areSubtitlesOpen}> {!focusTrack ? (
<div <div
style={{ className="lk-grid-layout-wrapper"
display: 'flex', style={{ height: 'auto' }}
position: 'relative', >
width: '100%', <GridLayout tracks={tracks} style={{ padding: 0 }}>
}} <ParticipantTile />
> </GridLayout>
{!focusTrack ? ( </div>
<div ) : (
className="lk-grid-layout-wrapper" <div
style={{ height: 'auto' }} className="lk-focus-layout-wrapper"
> style={{ height: 'auto' }}
<GridLayout tracks={tracks} style={{ padding: 0 }}> >
<FocusLayoutContainer style={{ padding: 0 }}>
<CarouselLayout
tracks={carouselTracks}
style={{
minWidth: '200px',
}}
>
<ParticipantTile /> <ParticipantTile />
</GridLayout> </CarouselLayout>
</div> {focusTrack && <FocusLayout trackRef={focusTrack} />}
) : ( </FocusLayoutContainer>
<div </div>
className="lk-focus-layout-wrapper" )}
style={{ height: 'auto' }} </RoomContentArea>
>
<FocusLayoutContainer style={{ padding: 0 }}>
<CarouselLayout
tracks={carouselTracks}
style={{
minWidth: '200px',
}}
>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
</div>
</LayoutWrapper>
<Subtitles />
<MainNotificationToast />
</div>
<ControlBar <ControlBar
onDeviceError={(e) => { onDeviceError={(e) => {
console.error(e) console.error(e)