Improve call screen header buttons and context menu

This commit is contained in:
trevor-signal
2026-06-09 12:39:14 -04:00
committed by GitHub
parent 6914bf38f9
commit bd9a23c5ef
8 changed files with 111 additions and 223 deletions
-19
View File
@@ -4161,25 +4161,6 @@ button.module-image__border-overlay:focus {
}
}
.module-calling-tools {
position: absolute;
top: calc(32px + var(--title-bar-drag-area-height));
inset-inline-end: 0;
display: flex;
&__button {
margin-inline-end: 12px;
}
&__button:last-child {
margin-inline-end: 24px;
}
.ContextMenu__container {
background: none;
text-wrap: nowrap;
}
}
.module-calling-pip {
backface-visibility: hidden;
background-color: variables.$color-gray-95;
@@ -1,62 +0,0 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
@use '../mixins';
@use '../variables';
@mixin CallSettingsButton-icon($path) {
@include mixins.color-svg($path, variables.$color-gray-15);
}
.CallSettingsButton__Button {
align-items: center;
background-color: variables.$color-gray-78;
border: none;
border-radius: 40px;
display: flex;
height: 36px;
justify-content: center;
outline: none;
width: 36px;
@include mixins.keyboard-mode {
&:focus {
outline-offset: 1px;
outline: 2px solid variables.$color-ultramarine;
}
}
}
.CallSettingsButton__Icon {
height: 20px;
width: 20px;
border: none;
}
.CallSettingsButton__Icon--Cancel {
@include CallSettingsButton-icon('../images/icons/v3/x/x.svg');
}
.CallSettingsButton__Icon--SidebarView {
@include CallSettingsButton-icon(
'../images/icons/v3/sidebar_view/sidebar_view.svg'
);
}
.CallSettingsButton__Icon--PaginatedView {
@include CallSettingsButton-icon('../images/icons/v3/grid/grid.svg');
}
.CallSettingsButton__Icon--Pip {
@include CallSettingsButton-icon('../images/icons/v3/pip/pip.svg');
}
.CallSettingsButton__Icon--Settings {
@include CallSettingsButton-icon('../images/icons/v3/settings/settings.svg');
}
.CallSettingsButton__Icon--SpeakerView {
@include CallSettingsButton-icon(
'../images/icons/v3/speaker_view/speaker_view.svg'
);
}
-10
View File
@@ -66,13 +66,3 @@
transform: translateY(-100%);
inset-inline-start: 0;
}
.CallingToast__viewChanged {
display: flex;
align-items: center;
gap: 8px;
&__icon {
width: 18px;
height: 18px;
}
}
-1
View File
@@ -47,7 +47,6 @@ $is-storybook: false !default;
@use 'components/CallingStatusIndicator.scss';
@use 'components/CallingButton.scss';
@use 'components/CallControls.scss';
@use 'components/CallSettingsButton.scss';
@use 'components/CallingLobby.scss';
@use 'components/CallingPendingParticipants.scss';
@use 'components/CallingPreCallInfo.scss';
+9 -5
View File
@@ -449,14 +449,16 @@ export namespace AxoDropdownMenu {
>
<AxoBaseMenu.ItemLeadingSlot>
<AxoBaseMenu.ItemCheckPlaceholder>
<DropdownMenu.ItemIndicator>
<DropdownMenu.ItemIndicator
className={tw('inline-flex items-center')}
>
<AxoBaseMenu.ItemCheck />
</DropdownMenu.ItemIndicator>
</AxoBaseMenu.ItemCheckPlaceholder>
</AxoBaseMenu.ItemLeadingSlot>
<AxoBaseMenu.ItemContentSlot>
{props.symbol && (
<span className={tw('me-2')}>
<span className={tw('me-2 inline-flex items-center')}>
<AxoBaseMenu.ItemSymbol symbol={props.symbol} />
</span>
)}
@@ -518,14 +520,16 @@ export namespace AxoDropdownMenu {
>
<AxoBaseMenu.ItemLeadingSlot>
<AxoBaseMenu.ItemCheckPlaceholder>
<DropdownMenu.ItemIndicator>
<DropdownMenu.ItemIndicator
className={tw('inline-flex items-center')}
>
<AxoBaseMenu.ItemCheck />
</DropdownMenu.ItemIndicator>
</AxoBaseMenu.ItemCheckPlaceholder>
</AxoBaseMenu.ItemLeadingSlot>
<AxoBaseMenu.ItemContentSlot>
{props.symbol && (
<span className={tw('me-2')}>
<span className={tw('me-2 inline-flex items-center')}>
<AxoBaseMenu.ItemSymbol symbol={props.symbol} />
</span>
)}
@@ -617,7 +621,7 @@ export namespace AxoDropdownMenu {
)}
<AxoBaseMenu.ItemContentSlot>
<AxoBaseMenu.ItemText>{props.children}</AxoBaseMenu.ItemText>
<span className={tw('ms-auto')}>
<span className={tw('ms-auto inline-flex items-center')}>
<AxoSymbol.Icon size={14} symbol="chevron-[end]" label={null} />
</span>
</AxoBaseMenu.ItemContentSlot>
+6 -10
View File
@@ -29,10 +29,7 @@ import type {
SetRendererCanvasType,
} from '../state/ducks/calling.preload.ts';
import { Avatar, AvatarSize } from './Avatar.dom.tsx';
import {
CallingHeader,
getCallViewIconClassname,
} from './CallingHeader.dom.tsx';
import { CallingHeader, getCallViewModeIcon } from './CallingHeader.dom.tsx';
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo.dom.tsx';
import { CallingButton, CallingButtonType } from './CallingButton.dom.tsx';
import { Button, ButtonVariant } from './Button.dom.tsx';
@@ -1348,13 +1345,12 @@ function useViewModeChangedToast({
showToast({
key: VIEW_MODE_CHANGED_TOAST_KEY,
content: (
<div className="CallingToast__viewChanged">
<span
className={classNames(
'CallingToast__viewChanged__icon',
getCallViewIconClassname(viewMode)
)}
<div>
<AxoSymbol.InlineGlyph
symbol={getCallViewModeIcon(viewMode)}
label={null}
/>
&nbsp;&nbsp;
{i18n('icu:calling__view_mode--updated')}
</div>
),
+95 -112
View File
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { JSX } from 'react';
import { useCallback } from 'react';
import classNames from 'classnames';
import type { LocalizerType } from '../types/Util.std.ts';
import { CallViewMode } from '../types/Calling.std.ts';
import { Tooltip } from './Tooltip.dom.tsx';
import { Theme } from '../util/theme.std.ts';
import { ContextMenu } from './ContextMenu.dom.tsx';
import { AxoDropdownMenu } from '../axo/AxoDropdownMenu.dom.tsx';
import { AxoIconButton } from '../axo/AxoIconButton.dom.tsx';
import { tw } from '../axo/tw.dom.tsx';
import type { AxoSymbolIconName } from '../axo/_internal/AxoSymbolDefs.generated.std.ts';
import { missingCaseError } from '../util/missingCaseError.std.ts';
export type PropsType = {
callViewMode?: CallViewMode;
@@ -31,127 +33,108 @@ export function CallingHeader({
togglePip,
toggleSettings,
}: PropsType): JSX.Element {
const handleViewModeChange = useCallback(
(value: string) => {
changeCallView?.(value as CallViewMode);
},
[changeCallView]
);
return (
<div className="module-calling-tools">
<div
className={tw(
'absolute inset-e-6 top-[calc(32px+var(--title-bar-drag-area-height))] flex gap-3'
)}
>
{isGroupCall &&
participantCount > 2 &&
callViewMode &&
changeCallView && (
<div className="module-calling-tools__button">
<ContextMenu
ariaLabel={i18n('icu:calling__change-view')}
i18n={i18n}
menuOptions={[
{
icon: 'CallSettingsButton__Icon--PaginatedView',
label: i18n('icu:calling__view_mode--paginated'),
onClick: () => changeCallView(CallViewMode.Paginated),
value: CallViewMode.Paginated,
},
{
icon: 'CallSettingsButton__Icon--SidebarView',
label: i18n('icu:calling__view_mode--overflow'),
onClick: () => changeCallView(CallViewMode.Sidebar),
value: CallViewMode.Sidebar,
},
{
icon: 'CallSettingsButton__Icon--SpeakerView',
label: i18n('icu:calling__view_mode--speaker'),
onClick: () => changeCallView(CallViewMode.Speaker),
value: CallViewMode.Speaker,
},
]}
theme={Theme.Dark}
popperOptions={{
placement: 'bottom',
strategy: 'absolute',
}}
value={
// If it's Presentation we want to still show Speaker as selected
callViewMode === CallViewMode.Presentation
? CallViewMode.Speaker
: callViewMode
}
>
<Tooltip
content={i18n('icu:calling__change-view')}
className="CallingButton__tooltip"
theme={Theme.Dark}
<AxoDropdownMenu.Root>
<AxoDropdownMenu.Trigger>
<AxoIconButton.Root
variant="floating-secondary"
size="lg"
iconWeight={300}
symbol={getCallViewModeIcon(callViewMode)}
label={i18n('icu:calling__change-view')}
tooltip={false}
/>
</AxoDropdownMenu.Trigger>
<AxoDropdownMenu.Content align="end">
<AxoDropdownMenu.RadioGroup
value={
// If it's Presentation we want to still show Speaker as selected
callViewMode === CallViewMode.Presentation
? CallViewMode.Speaker
: callViewMode
}
onValueChange={handleViewModeChange}
>
<div className="CallSettingsButton__Button">
<span
className={classNames(
'CallSettingsButton__Icon',
getCallViewIconClassname(callViewMode)
)}
/>
</div>
</Tooltip>
</ContextMenu>
</div>
<AxoDropdownMenu.RadioItem
value={CallViewMode.Paginated}
symbol="grid"
>
{i18n('icu:calling__view_mode--paginated')}
</AxoDropdownMenu.RadioItem>
<AxoDropdownMenu.RadioItem
value={CallViewMode.Sidebar}
symbol="grid-sidebar"
>
{i18n('icu:calling__view_mode--overflow')}
</AxoDropdownMenu.RadioItem>
<AxoDropdownMenu.RadioItem
value={CallViewMode.Speaker}
symbol="sidebar"
>
{i18n('icu:calling__view_mode--speaker')}
</AxoDropdownMenu.RadioItem>
</AxoDropdownMenu.RadioGroup>
</AxoDropdownMenu.Content>
</AxoDropdownMenu.Root>
)}
<div className="module-calling-tools__button">
<Tooltip
content={i18n('icu:callingDeviceSelection__settings')}
className="CallingButton__tooltip"
theme={Theme.Dark}
>
<button
aria-label={i18n('icu:callingDeviceSelection__settings')}
className="CallSettingsButton__Button"
onClick={toggleSettings}
type="button"
>
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Settings" />
</button>
</Tooltip>
</div>
<AxoIconButton.Root
variant="floating-secondary"
size="lg"
iconWeight={300}
symbol="settings"
label={i18n('icu:callingDeviceSelection__settings')}
onClick={toggleSettings}
/>
{togglePip && (
<div className="module-calling-tools__button">
<Tooltip
content={i18n('icu:calling__pip--on')}
className="CallingButton__tooltip"
theme={Theme.Dark}
>
<button
aria-label={i18n('icu:calling__pip--on')}
className="CallSettingsButton__Button"
onClick={togglePip}
type="button"
>
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Pip" />
</button>
</Tooltip>
</div>
<AxoIconButton.Root
variant="floating-secondary"
size="lg"
iconWeight={300}
symbol="pip"
label={i18n('icu:calling__pip--on')}
onClick={togglePip}
/>
)}
{onCancel && (
<div className="module-calling-tools__button">
<Tooltip
content={i18n('icu:cancel')}
theme={Theme.Dark}
className="CallingButton__tooltip"
>
<button
aria-label={i18n('icu:cancel')}
className="CallSettingsButton__Button CallSettingsButton__Button--Cancel"
onClick={onCancel}
type="button"
>
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Cancel" />
</button>
</Tooltip>
</div>
<AxoIconButton.Root
variant="floating-secondary"
size="lg"
iconWeight={300}
symbol="x"
label={i18n('icu:cancel')}
onClick={onCancel}
/>
)}
</div>
);
}
const CALL_VIEW_MODE_ICON_CLASSNAMES: Record<CallViewMode, string> = {
[CallViewMode.Sidebar]: 'CallSettingsButton__Icon--SidebarView',
[CallViewMode.Paginated]: 'CallSettingsButton__Icon--PaginatedView',
[CallViewMode.Speaker]: 'CallSettingsButton__Icon--SpeakerView',
[CallViewMode.Presentation]: 'CallSettingsButton__Icon--SpeakerView',
};
export function getCallViewIconClassname(viewMode: CallViewMode): string {
return CALL_VIEW_MODE_ICON_CLASSNAMES[viewMode];
export function getCallViewModeIcon(mode: CallViewMode): AxoSymbolIconName {
switch (mode) {
case CallViewMode.Paginated:
return 'grid';
case CallViewMode.Sidebar:
return 'grid-sidebar';
case CallViewMode.Presentation:
case CallViewMode.Speaker:
return 'sidebar';
default:
throw missingCaseError(mode);
}
}
@@ -193,10 +193,7 @@ describe('callMessages', function callMessages(this: Mocha.Suite) {
async function setInputAndOutput(win: Page, input: string, output: string) {
debug(`setInputAndOutput input: ${input} output: ${output}`);
await win
.locator('.CallSettingsButton__Button')
.and(win.getByLabel('Settings'))
.click();
await win.getByLabel('Settings').click();
await win.locator('#audio-input').selectOption(input);
await win.locator('#audio-output').selectOption(output);
await win.locator('.module-calling-device-selection__close-button').click();