diff --git a/src/frontend/src/features/pip/components/PipView.tsx b/src/frontend/src/features/pip/components/PipView.tsx index da463acc..c99cb95f 100644 --- a/src/frontend/src/features/pip/components/PipView.tsx +++ b/src/frontend/src/features/pip/components/PipView.tsx @@ -1,7 +1,9 @@ +import { styled } from '@/styled-system/jsx' import { PipControlBar } from './PipControlBar' import { PipFloatingReactions } from './PipFloatingReactions' +import { PipStage } from './layout/PipStage' import { ReactionsToolbar } from '@/features/reactions/components/toolbar/ReactionsToolbar' -import { styled } from '@/styled-system/jsx' +import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar' const Container = styled('div', { base: { @@ -11,6 +13,7 @@ const Container = styled('div', { display: 'grid', gridTemplateRows: 'minmax(0, 1fr) auto auto', backgroundColor: 'primaryDark.50', + transition: 'padding .5s cubic-bezier(0.4,0,0.2,1) 5ms', // Disable LiveKit's own border-radius on tiles so our containers // (GridCell, Thumbnail, StageFrame) own the clipping exclusively. '--lk-border-radius': '4px', @@ -29,11 +32,24 @@ const Container = styled('div', { width: '100%', }, }, + variants: { + isReactionToolbarOpen: { + true: { + paddingBottom: + 'calc(var(--sizes-room-reaction-toolbar-height) + var(--sizes-room-control-bar) + 1.125rem)', + }, + false: { + paddingBottom: 'var(--sizes-room-control-bar)', + }, + }, + }, }) export const PipView = () => { + const { isOpen: isReactionToolbarOpen } = useReactionsToolbar() return ( - + + diff --git a/src/frontend/src/features/pip/components/layout/PipFocusLayout.tsx b/src/frontend/src/features/pip/components/layout/PipFocusLayout.tsx new file mode 100644 index 00000000..e09b5ef0 --- /dev/null +++ b/src/frontend/src/features/pip/components/layout/PipFocusLayout.tsx @@ -0,0 +1,81 @@ +import { memo } from 'react' +import type { TrackReferenceOrPlaceholder } from '@livekit/components-core' +import { styled } from '@/styled-system/jsx' +import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile' +import { getTrackKey } from '../../utils/pipTrackSelection' + +type PipFocusLayoutProps = { + mainTrack?: TrackReferenceOrPlaceholder + thumbnailTrack?: TrackReferenceOrPlaceholder +} + +/** + * Focus layout used when 1-2 tracks are visible in the PiP window. + * + * The main tile is letterboxed (object-fit: contain) so the camera is + * never stretched to a non-video aspect and leaves dark padding + * above/below when the window shape doesn't match the source. + * The thumbnail keeps the usual cover fill. + */ +export const PipFocusLayout = memo( + ({ mainTrack, thumbnailTrack }: PipFocusLayoutProps) => { + return ( + + {mainTrack && ( + + + + )} + {thumbnailTrack && ( + + + + )} + + ) + } +) +PipFocusLayout.displayName = 'PipFocusLayout' + +const FocusContainer = styled('div', { + base: { + position: 'relative', + width: '100%', + height: '100%', + borderRadius: '4px', + overflow: 'hidden', + backgroundColor: 'primaryDark.100', + }, +}) + +const MainSlot = styled('div', { + base: { + width: '100%', + height: '100%', + '& .lk-participant-media-video': { + objectFit: 'contain', + }, + }, +}) + +const Thumbnail = styled('div', { + base: { + position: 'absolute', + right: '1rem', + bottom: '1rem', + width: '42%', + maxWidth: '220px', + minWidth: '140px', + aspectRatio: '16 / 9', + borderRadius: '4px', + overflow: 'hidden', + boxShadow: 'md', + zIndex: 2, + }, +}) diff --git a/src/frontend/src/features/pip/components/layout/PipGridLayout.tsx b/src/frontend/src/features/pip/components/layout/PipGridLayout.tsx new file mode 100644 index 00000000..5221ef85 --- /dev/null +++ b/src/frontend/src/features/pip/components/layout/PipGridLayout.tsx @@ -0,0 +1,76 @@ +import { memo, useMemo, useRef } from 'react' +import type { TrackReferenceOrPlaceholder } from '@livekit/components-core' +import { styled } from '@/styled-system/jsx' +import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile' +import { usePipElementSize } from '../../hooks/usePipElementSize' +import { computePipGridLayout } from '../../utils/pipGrid' +import { getTrackKey } from '../../utils/pipTrackSelection' + +type PipGridLayoutProps = { + tracks: TrackReferenceOrPlaceholder[] +} + +/** + * Adaptive grid used when 3+ tracks are visible in the PiP window. + * + * All grid math (shape choice + partial-row stretching) is delegated to + * `computePipGridLayout`. This component only measures the container, + * applies the returned placements, and plays a FLIP animation when the + * tile set or grid shape changes (participant joins/leaves or shape shift). + * + * Tiles keep a stable key so resizing never remounts