9742eaea28
Four changes to match screenshare full-screen UX for camera feeds:
1. Fullscreen button always visible
CallControls.tsx: remove `screenshare &&` gate — the ⛶ fullscreen
button now appears in camera-only calls, not just during screenshare.
2. Per-participant camera focus (CallControl.focusCameraParticipant)
Finds the target's video tile in the EC iframe DOM via:
[data-testid="videoTile"] / [data-video-fit]
closest ancestor of [aria-label="${userId}"]
Enables spotlight mode if not already active, then clicks the tile
so EC's internal focus handler runs. Falls back gracefully if the
tile is not in the DOM (camera off).
3. MemberGlance participant popup
Clicking a participant avatar in the call status bar now shows a
small menu: "Focus camera" (calls focusCameraParticipant) and
"View profile" (existing behaviour). Previously it opened the
profile immediately with no way to focus the camera.
4. PiP fullscreen button
A ⛶/⊡ icon button appears in the PiP overlay top-right area,
letting users go fullscreen directly from PiP mode without
navigating back to the call room first.
UNTESTED — requires a real multi-participant call to verify tile
clicking behaviour and fullscreen transitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import React from 'react';
|
|
import { Box, Spinner } from 'folds';
|
|
import classNames from 'classnames';
|
|
import { LiveChip } from './LiveChip';
|
|
import * as css from './styles.css';
|
|
import { CallRoomName } from './CallRoomName';
|
|
import { CallControl } from './CallControl';
|
|
import { ContainerColor } from '../../styles/ContainerColor.css';
|
|
import { useCallMembers, useCallSession } from '../../hooks/useCall';
|
|
import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize';
|
|
import { MemberGlance } from './MemberGlance';
|
|
import { StatusDivider } from './components';
|
|
import { CallEmbed } from '../../plugins/call/CallEmbed';
|
|
import { useCallJoined } from '../../hooks/useCallEmbed';
|
|
import { useCallSpeakers } from '../../hooks/useCallSpeakers';
|
|
import { MemberSpeaking } from './MemberSpeaking';
|
|
|
|
type CallStatusProps = {
|
|
callEmbed: CallEmbed;
|
|
};
|
|
export function CallStatus({ callEmbed }: CallStatusProps) {
|
|
const { room } = callEmbed;
|
|
|
|
const callSession = useCallSession(room);
|
|
const callMembers = useCallMembers(callSession);
|
|
const screenSize = useScreenSize();
|
|
const callJoined = useCallJoined(callEmbed);
|
|
const speakers = useCallSpeakers(callEmbed);
|
|
|
|
const compact = screenSize === ScreenSize.Mobile;
|
|
|
|
const memberVisible = callJoined && callMembers.length > 0;
|
|
|
|
return (
|
|
<Box
|
|
className={classNames(css.CallStatus, ContainerColor({ variant: 'Background' }))}
|
|
shrink="No"
|
|
gap="400"
|
|
alignItems={compact ? undefined : 'Center'}
|
|
direction={compact ? 'Column' : 'Row'}
|
|
>
|
|
<Box grow="Yes" alignItems="Center" gap="200">
|
|
{memberVisible ? (
|
|
<Box shrink="No">
|
|
<LiveChip count={callMembers.length} room={room} members={callMembers} />
|
|
</Box>
|
|
) : (
|
|
<Spinner variant="Secondary" size="200" />
|
|
)}
|
|
<Box grow="Yes" alignItems="Center" gap="Inherit">
|
|
{!compact && (
|
|
<>
|
|
<CallRoomName room={room} />
|
|
{speakers.size > 0 && (
|
|
<>
|
|
<StatusDivider />
|
|
<span data-spacing-node />
|
|
<MemberSpeaking room={room} speakers={speakers} />
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</Box>
|
|
{memberVisible && (
|
|
<Box shrink="No">
|
|
<MemberGlance room={room} members={callMembers} speakers={speakers} callEmbed={callEmbed} />
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
{memberVisible && !compact && <StatusDivider />}
|
|
<Box shrink="No" alignItems="Center" gap="Inherit">
|
|
{compact && (
|
|
<Box grow="Yes">
|
|
<CallRoomName room={room} />
|
|
</Box>
|
|
)}
|
|
<CallControl callJoined={callJoined} compact={compact} callEmbed={callEmbed} />
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|