Merge branch 'toger5/move-settings-out-of-bottom-bar' into

toger5/bottom-bar-storybook
This commit is contained in:
Timo K
2026-04-14 18:09:59 +02:00
7 changed files with 57 additions and 29 deletions
+4 -7
View File
@@ -16,7 +16,7 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import { Button, Heading, Tooltip } from "@vector-im/compound-web"; import { Heading, IconButton, Tooltip } from "@vector-im/compound-web";
import { CollapseIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { CollapseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger } from "matrix-js-sdk/lib/logger";
@@ -71,12 +71,9 @@ export const AppBar: FC<Props> = ({ children }) => {
> >
<LeftNav> <LeftNav>
<Tooltip label={t("common.back")}> <Tooltip label={t("common.back")}>
<Button <IconButton onClick={onBackClick}>
kind={"tertiary"} <CollapseIcon />
iconOnly </IconButton>
Icon={CollapseIcon}
onClick={onBackClick}
/>
</Tooltip> </Tooltip>
</LeftNav> </LeftNav>
{title && ( {title && (
+42 -13
View File
@@ -8,7 +8,11 @@ Please see LICENSE in the repository root for full details.
import { type ComponentPropsWithoutRef, type FC } from "react"; import { type ComponentPropsWithoutRef, type FC } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button as CpdButton, Tooltip } from "@vector-im/compound-web"; import {
Button as CpdButton,
IconButton,
Tooltip,
} from "@vector-im/compound-web";
import { import {
MicOnSolidIcon, MicOnSolidIcon,
MicOffSolidIcon, MicOffSolidIcon,
@@ -136,7 +140,6 @@ interface LoudspeakerButtonProps extends ComponentPropsWithoutRef<"button"> {
*/ */
isEarpieceTarget: boolean; isEarpieceTarget: boolean;
} }
export const LoudspeakerButton: FC<LoudspeakerButtonProps> = (props) => { export const LoudspeakerButton: FC<LoudspeakerButtonProps> = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const label = props.isEarpieceTarget const label = props.isEarpieceTarget
@@ -158,35 +161,61 @@ export const LoudspeakerButton: FC<LoudspeakerButtonProps> = (props) => {
); );
}; };
function classNamesForScrrenWidth(
className?: string,
forScreenWidth?: "wide" | "narrow",
): string {
return classNames(className, {
[callFooterStyles.settingsOnlyShowWide]: forScreenWidth === "wide",
[callFooterStyles.settingsOnlyShowNarrow]: forScreenWidth === "narrow",
});
}
interface SettingsIconButtonProps extends ComponentPropsWithoutRef<"button"> {
/** If this buttons should be setup to be used in the app bar */
showForScreenWidth?: "wide" | "narrow";
kind?: "secondary" | "primary";
}
export const SettingsIconButton: FC<SettingsIconButtonProps> = ({
showForScreenWidth,
className,
...props
}) => {
const { t } = useTranslation();
const Icon =
platform === "android" ? OverflowVerticalIcon : OverflowHorizontalIcon;
return (
<Tooltip label={t("common.settings")}>
<IconButton
className={classNamesForScrrenWidth(className, showForScreenWidth)}
{...props}
>
<Icon />
</IconButton>
</Tooltip>
);
};
interface SettingsButtonProps extends ComponentPropsWithoutRef<"button"> { interface SettingsButtonProps extends ComponentPropsWithoutRef<"button"> {
size?: "sm" | "lg"; size?: "sm" | "lg";
/** If the button should be styled so it fits into the footer center buttons group */
forButtonsBar?: boolean;
/** If this buttons should be setup to be used in the app bar */ /** If this buttons should be setup to be used in the app bar */
showForScreenWidth?: "wide" | "narrow"; showForScreenWidth?: "wide" | "narrow";
} }
export const SettingsButton: FC<SettingsButtonProps> = ({ export const SettingsButton: FC<SettingsButtonProps> = ({
showForScreenWidth, showForScreenWidth,
forButtonsBar,
className, className,
...props ...props
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Tooltip label={t("common.settings")}> <Tooltip label={t("common.settings")}>
<CpdButton <CpdButton
className={classNames(className, { className={classNamesForScrrenWidth(className, showForScreenWidth)}
[callFooterStyles.settingsOnlyShowWide]:
showForScreenWidth === "wide",
[callFooterStyles.settingsOnlyShowNarrow]:
showForScreenWidth === "narrow",
})}
iconOnly iconOnly
Icon={ Icon={
platform === "android" ? OverflowVerticalIcon : OverflowHorizontalIcon platform === "android" ? OverflowVerticalIcon : OverflowHorizontalIcon
} }
kind={forButtonsBar ? "secondary" : "tertiary"} kind={"secondary"}
{...props} {...props}
/> />
</Tooltip> </Tooltip>
+1
View File
@@ -54,6 +54,7 @@ Please see LICENSE in the repository root for full details.
align-items: center; align-items: center;
gap: var(--cpd-space-4x); gap: var(--cpd-space-4x);
flex-direction: row; flex-direction: row;
flex-wrap: nowrap;
} }
.logo { .logo {
+5 -4
View File
@@ -19,6 +19,7 @@ import {
SettingsButton, SettingsButton,
ReactionToggleButton, ReactionToggleButton,
LoudspeakerButton, LoudspeakerButton,
SettingsIconButton,
} from "../button"; } from "../button";
import styles from "./CallFooter.module.css"; import styles from "./CallFooter.module.css";
import { LayoutToggle } from "../room/LayoutToggle"; import { LayoutToggle } from "../room/LayoutToggle";
@@ -110,10 +111,9 @@ export const CallFooter: FC<FooterProps> = ({
const showLogo = !hideLogo && !asPip; const showLogo = !hideLogo && !asPip;
if (showSettingsButton) { if (showSettingsButton) {
// add the settings button to the center group of buttons, so it will be visible on small screens. // add the settings button to the center group of buttons, so it will be visible on small screens.
// On larger screens, it will be hidden and the one without `forButtonsBar` in the `settingsLogoContainer` will be visible. // On larger screens, it will be hidden SettingsIconButton the one with `showForScreenWidth = "wide"` in the `settingsLogoContainer` will be visible.
buttons.push( buttons.push(
<SettingsButton <SettingsButton
forButtonsBar
key="settings" key="settings"
showForScreenWidth="narrow" showForScreenWidth="narrow"
onClick={openSettings} onClick={openSettings}
@@ -186,7 +186,7 @@ export const CallFooter: FC<FooterProps> = ({
if (audioOutputButton) buttons.push(audioOutputButton); if (audioOutputButton) buttons.push(audioOutputButton);
useAppBarSecondaryButton( useAppBarSecondaryButton(
<SettingsButton key="settings" onClick={openSettings} />, <SettingsIconButton key="settings" onClick={openSettings} />,
); );
if (hangup) if (hangup)
@@ -225,8 +225,9 @@ export const CallFooter: FC<FooterProps> = ({
> >
<div className={styles.settingsLogoContainer}> <div className={styles.settingsLogoContainer}>
{showSettingsButton && ( {showSettingsButton && (
<SettingsButton <SettingsIconButton
key="settings" key="settings"
kind="secondary"
showForScreenWidth="wide" showForScreenWidth="wide"
onClick={openSettings} onClick={openSettings}
/> />
+3 -2
View File
@@ -125,7 +125,6 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
toggleScreensharing: () => {}, toggleScreensharing: () => {},
...args.callViewModelOptions, ...args.callViewModelOptions,
}, },
args.mediaDevices,
); );
rtcSession.joined = true; rtcSession.joined = true;
@@ -264,7 +263,9 @@ describe("InCallView", () => {
}, },
}); });
const { getByRole } = createInCallView({ mediaDevices }); const { getByRole } = createInCallView({
callViewModelOptions: { mediaDeviceOverride: mediaDevices },
});
// The button should be visible. When current output is "speaker", // The button should be visible. When current output is "speaker",
// the switcher targets "earpiece", so the tooltip label is "Handset". // the switcher targets "earpiece", so the tooltip label is "Handset".
const audioOutputBtn = getByRole("switch", { name: "Handset" }); const audioOutputBtn = getByRole("switch", { name: "Handset" });
+1
View File
@@ -175,6 +175,7 @@ export interface CallViewModelOptions {
matrixRTCMode$?: Behavior<MatrixRTCMode>; matrixRTCMode$?: Behavior<MatrixRTCMode>;
/** Optional behavior overriding for the screensharing, for testing */ /** Optional behavior overriding for the screensharing, for testing */
toggleScreensharing?: () => void; toggleScreensharing?: () => void;
mediaDeviceOverride?: MediaDevices;
} }
// Do not play any sounds if the participant count has exceeded this // Do not play any sounds if the participant count has exceeded this
+1 -3
View File
@@ -34,7 +34,6 @@ import {
MockRTCSession, MockRTCSession,
testScope, testScope,
} from "./test"; } from "./test";
import { type MediaDevices } from "../state/MediaDevices";
import { aliceRtcMember, localRtcMember } from "./test-fixtures"; import { aliceRtcMember, localRtcMember } from "./test-fixtures";
import { type RaisedHandInfo, type ReactionInfo } from "../reactions"; import { type RaisedHandInfo, type ReactionInfo } from "../reactions";
import { constant } from "../state/Behavior"; import { constant } from "../state/Behavior";
@@ -133,7 +132,6 @@ export function getBasicCallViewModelEnvironment(
members: RoomMember[], members: RoomMember[],
initialRtcMemberships: CallMembership[] = [localRtcMember, aliceRtcMember], initialRtcMemberships: CallMembership[] = [localRtcMember, aliceRtcMember],
callViewModelOptions: Partial<CallViewModelOptions> = {}, callViewModelOptions: Partial<CallViewModelOptions> = {},
mediaDevicesOverride?: MediaDevices,
): { ): {
vm: CallViewModel; vm: CallViewModel;
rtcMemberships$: BehaviorSubject<CallMembership[]>; rtcMemberships$: BehaviorSubject<CallMembership[]>;
@@ -154,7 +152,7 @@ export function getBasicCallViewModelEnvironment(
testScope(), testScope(),
rtcSession.asMockedSession(), rtcSession.asMockedSession(),
matrixRoom, matrixRoom,
mediaDevicesOverride ?? mockMediaDevices({}), callViewModelOptions.mediaDeviceOverride ?? mockMediaDevices({}),
mockMuteStates(), mockMuteStates(),
{ {
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT }, encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },