diff --git a/playwright/widget/pip-call-button-interaction.test.ts b/playwright/widget/pip-call-button-interaction.test.ts new file mode 100644 index 00000000..1dda652d --- /dev/null +++ b/playwright/widget/pip-call-button-interaction.test.ts @@ -0,0 +1,68 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { expect, test } from "@playwright/test"; + +import { widgetTest } from "../fixtures/widget-user.ts"; +import { HOST1, TestHelpers } from "./test-helpers.ts"; + +widgetTest("Footer interaction in PiP", async ({ addUser, browserName }) => { + test.skip( + browserName === "firefox", + "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", + ); + + test.slow(); + + const valere = await addUser("Valere", HOST1); + + const callRoom = "CallRoom"; + await TestHelpers.createRoom("CallRoom", valere.page); + + await TestHelpers.createRoom("OtherRoom", valere.page); + + await TestHelpers.switchToRoomNamed(valere.page, callRoom); + + // Start the call as Valere + await TestHelpers.startCallInCurrentRoom(valere.page, false); + await expect( + valere.page.locator('iframe[title="Element Call"]'), + ).toBeVisible(); + + await TestHelpers.joinCallFromLobby(valere.page); + // wait a bit so that the PIP has rendered + await valere.page.waitForTimeout(600); + + // Switch to the other room, the call should go to PIP + await TestHelpers.switchToRoomNamed(valere.page, "OtherRoom"); + + // We should see the PIP overlay + const iFrame = valere.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + + { + // Check for a bug where the video had the wrong fit in PIP + const hangupBtn = iFrame.getByRole("button", { name: "End call" }); + const audioBtn = iFrame.getByTestId("incall_mute"); + const videoBtn = iFrame.getByTestId("incall_videomute"); + await expect(hangupBtn).toBeVisible(); + await expect(audioBtn).toBeVisible(); + await expect(videoBtn).toBeVisible(); + await expect(audioBtn).toHaveAttribute("aria-label", /^Mute microphone$/); + await expect(videoBtn).toHaveAttribute("aria-label", /^Stop video$/); + + await videoBtn.click(); + await audioBtn.click(); + + // stop hovering on any of the buttons + await iFrame.getByTestId("videoTile").hover(); + + await expect(audioBtn).toHaveAttribute("aria-label", /^Unmute microphone$/); + await expect(videoBtn).toHaveAttribute("aria-label", /^Start video$/); + } +}); diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index 49ebec52..d57befc1 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -55,7 +55,7 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { await TestHelpers.switchToRoomNamed(valere.page, "DoubleTask"); // We should see the PIP overlay - await expect(valere.page.locator(".mx_WidgetPip_overlay")).toBeVisible(); + await expect(valere.page.getByTestId("widget-pip-container")).toBeVisible(); { // wait a bit so that the PIP has rendered the video diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 3136e2da..00d803f1 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -23,6 +23,7 @@ import styles from "./Button.module.css"; interface MicButtonProps extends ComponentPropsWithoutRef<"button"> { muted: boolean; + size?: "sm" | "lg"; } export const MicButton: FC = ({ muted, ...props }) => { @@ -47,6 +48,7 @@ export const MicButton: FC = ({ muted, ...props }) => { interface VideoButtonProps extends ComponentPropsWithoutRef<"button"> { muted: boolean; + size?: "sm" | "lg"; } export const VideoButton: FC = ({ muted, ...props }) => { @@ -71,6 +73,7 @@ export const VideoButton: FC = ({ muted, ...props }) => { interface ShareScreenButtonProps extends ComponentPropsWithoutRef<"button"> { enabled: boolean; + size: "sm" | "lg"; } export const ShareScreenButton: FC = ({ @@ -94,7 +97,11 @@ export const ShareScreenButton: FC = ({ ); }; -export const EndCallButton: FC> = ({ +interface EndCallButtonProps extends ComponentPropsWithoutRef<"button"> { + size?: "sm" | "lg"; +} + +export const EndCallButton: FC = ({ className, ...props }) => { @@ -114,9 +121,10 @@ export const EndCallButton: FC> = ({ ); }; -export const SettingsButton: FC> = ( - props, -) => { +interface SettingsButtonProps extends ComponentPropsWithoutRef<"button"> { + size?: "sm" | "lg"; +} +export const SettingsButton: FC = (props) => { const { t } = useTranslation(); return ( diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index 0c722baf..28163321 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -166,6 +166,7 @@ export function ReactionPopupMenu({ interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> { identifier: string; vm: CallViewModel; + size?: "sm" | "lg"; } export function ReactionToggleButton({ diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css index 55724932..70f7c73a 100644 --- a/src/room/InCallView.module.css +++ b/src/room/InCallView.module.css @@ -108,22 +108,9 @@ Please see LICENSE in the repository root for full details. } } -@media (max-width: 370px) { - .shareScreen { - display: none; - } - - @media (max-height: 400px) { - .footer { - display: none; - } - } -} - -@media (max-width: 320px) { - .invite, - .raiseHand { - display: none; +@media (max-height: 800px) { + .footer { + padding-block: var(--cpd-space-8x); } } @@ -133,9 +120,27 @@ Please see LICENSE in the repository root for full details. } } -@media (max-height: 800px) { - .footer { - padding-block: var(--cpd-space-8x); +@media (max-width: 370px) { + .shareScreen { + display: none; + } + + /* PIP custom css */ + @media (max-height: 400px) { + .shareScreen { + display: flex; + } + .footer { + padding-block-start: var(--cpd-space-3x); + padding-block-end: var(--cpd-space-2x); + } + } +} + +@media (max-width: 320px) { + .invite, + .raiseHand { + display: none; } } diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index d8803b22..f1a872a0 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -640,8 +640,10 @@ export const InCallView: FC = ({ const buttons: JSX.Element[] = []; + const buttonSize = layout.type === "pip" ? "sm" : "lg"; buttons.push( = ({ data-testid="incall_mute" />, = ({ if (vm.toggleScreenSharing !== null) { buttons.push( = ({ if (supportsReactions) { buttons.push( = ({ ); } if (layout.type !== "pip") - buttons.push(); + buttons.push( + , + ); buttons.push( { switch (mode) { case "pip": - return of(false); + return of(platform === "desktop" ? true : false); case "normal": case "narrow": return of(true); diff --git a/src/state/PipLayout.ts b/src/state/PipLayout.ts index 56e9aeb2..6ac1e4f0 100644 --- a/src/state/PipLayout.ts +++ b/src/state/PipLayout.ts @@ -5,6 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ +import { platform } from "../Platform.ts"; import { type PipLayout, type PipLayoutMedia } from "./layout-types.ts"; import { type TileStore } from "./TileStore"; @@ -16,7 +17,11 @@ export function pipLayout( prevTiles: TileStore, ): [PipLayout, TileStore] { const update = prevTiles.from(0); - update.registerSpotlight(media.spotlight, true); + // Dont maximise in pip on EW since we want the rounded corners and the footer + update.registerSpotlight( + media.spotlight, + platform === "desktop" ? false : true, + ); const tiles = update.build(); return [ {