Ability to click megaphone in narrow sidebar to expand sidebar

Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-01-20 14:53:08 -06:00
committed by GitHub
parent f786f40e3c
commit d0832f93d9
15 changed files with 79 additions and 52 deletions

View File

@@ -10154,6 +10154,10 @@
"messageformat": "Thanks for your feedback!",
"description": "Toast message shown when call quality survey submission succeeds"
},
"icu:Megaphone__ExpandNarrowSidebar": {
"messageformat": "Expand sidebar to interact with megaphone",
"description": "Aria label for collapsed megaphone in the narrow sidebar state. Indicates that you can click it to expand the sidebar and the megaphone."
},
"icu:WhatsNew__bugfixes": {
"messageformat": "This version contains a number of small tweaks and bug fixes to keep Signal running smoothly.",
"description": "Release notes for releases that only include bug fixes",

View File

@@ -19,12 +19,12 @@ import type {
} from '../state/ducks/calling.preload.js';
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
import type { UnreadStats } from '../util/countUnreadStats.std.js';
import type { WidthBreakpoint } from './_util.std.js';
import type { CallLinkType } from '../types/CallLink.std.js';
import type { CallStateType } from '../state/selectors/calling.std.js';
import type { StartCallData } from './ConfirmLeaveCallModal.dom.js';
import { I18n } from './I18n.dom.js';
import { AxoDropdownMenu } from '../axo/AxoDropdownMenu.dom.js';
import type { SmartPropsType as SmartToastManagerPropsType } from '../state/smart/ToastManager.preload.js';
enum CallsTabSidebarView {
CallsListView,
@@ -70,9 +70,7 @@ type CallsTabProps = Readonly<{
conversationId: string,
callHistoryGroup: CallHistoryGroup | null
) => React.JSX.Element;
renderToastManager: (_: {
containerWidthBreakpoint: WidthBreakpoint;
}) => React.JSX.Element;
renderToastManager: (_: SmartToastManagerPropsType) => React.JSX.Element;
regionCode: string | undefined;
savePreferredLeftPaneWidth: (preferredLeftPaneWidth: number) => void;
startCallLinkLobbyByRoomId: (options: { roomId: string }) => void;

View File

@@ -164,6 +164,7 @@ export function DebugLogWindow({
setDidResumeDonation={shouldNeverBeCalled}
toast={toast}
containerWidthBreakpoint={null}
expandNarrowLeftPane={shouldNeverBeCalled}
isInFullScreenCall={false}
/>
</div>
@@ -230,6 +231,7 @@ export function DebugLogWindow({
setDidResumeDonation={shouldNeverBeCalled}
toast={toast}
containerWidthBreakpoint={null}
expandNarrowLeftPane={shouldNeverBeCalled}
isInFullScreenCall={false}
/>
</div>

View File

@@ -318,6 +318,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
toast={undefined}
megaphone={undefined}
containerWidthBreakpoint={containerWidthBreakpoint}
expandNarrowLeftPane={action('expandNarrowLeftPane')}
isInFullScreenCall={false}
/>
),

View File

@@ -36,6 +36,7 @@ import * as KeyboardLayout from '../services/keyboardLayout.dom.js';
import type { LookupConversationWithoutServiceIdActionsType } from '../util/lookupConversationWithoutServiceId.preload.js';
import type { ShowConversationType } from '../state/ducks/conversations.preload.js';
import type { PropsType as UnsupportedOSDialogPropsType } from '../state/smart/UnsupportedOSDialog.preload.js';
import type { SmartPropsType as SmartToastManagerPropsType } from '../state/smart/ToastManager.preload.js';
import { ConversationList } from './ConversationList.dom.js';
import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox.dom.js';
@@ -209,9 +210,9 @@ export type PropsType = {
) => React.JSX.Element;
renderLeftPaneChatFolders: () => React.JSX.Element;
renderNotificationProfilesMenu: () => React.JSX.Element;
renderToastManager: (_: {
containerWidthBreakpoint: WidthBreakpoint;
}) => React.JSX.Element;
renderToastManager: (
_: Readonly<SmartToastManagerPropsType>
) => React.JSX.Element;
} & LookupConversationWithoutServiceIdActionsType;
export function LeftPane({

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import React, { createContext, useEffect, useState } from 'react';
import React, { createContext, useCallback, useEffect, useState } from 'react';
import classNames from 'classnames';
import { useMove } from 'react-aria';
import { NavTabsToggle } from './NavTabs.dom.js';
@@ -15,6 +15,7 @@ import {
} from '../util/leftPaneWidth.std.js';
import { WidthBreakpoint, getNavSidebarWidthBreakpoint } from './_util.std.js';
import type { UnreadStats } from '../util/countUnreadStats.std.js';
import type { SmartPropsType as SmartToastManagerPropsType } from '../state/smart/ToastManager.preload.js';
export const NavSidebarWidthBreakpointContext =
createContext<WidthBreakpoint | null>(null);
@@ -60,9 +61,7 @@ export type NavSidebarProps = Readonly<{
savePreferredLeftPaneWidth: (width: number) => void;
title: string;
otherTabsUnreadStats: UnreadStats;
renderToastManager: (_: {
containerWidthBreakpoint: WidthBreakpoint;
}) => React.JSX.Element;
renderToastManager: (_: SmartToastManagerPropsType) => React.JSX.Element;
}>;
enum DragState {
@@ -103,6 +102,13 @@ export function NavSidebar({
const widthBreakpoint = getNavSidebarWidthBreakpoint(width);
const expandNarrowLeftPane = useCallback(() => {
if (preferredWidth < MIN_FULL_WIDTH) {
setPreferredWidth(MIN_FULL_WIDTH);
savePreferredLeftPaneWidth(MIN_FULL_WIDTH);
}
}, [preferredWidth, savePreferredLeftPaneWidth]);
// `useMove` gives us keyboard and mouse dragging support.
const { moveProps } = useMove({
onMoveStart() {
@@ -229,7 +235,10 @@ export function NavSidebar({
{...moveProps}
/>
{renderToastManager({ containerWidthBreakpoint: widthBreakpoint })}
{renderToastManager({
containerWidthBreakpoint: widthBreakpoint,
expandNarrowLeftPane,
})}
</div>
</NavSidebarWidthBreakpointContext.Provider>
);

View File

@@ -28,6 +28,7 @@ export default {
body: 'Signal is powered by people like you. Show your support today!',
imagePath: '/fixtures/donate-heart.png',
isFullSize: true,
onClickNarrowMegaphone: action('onClickNarrowMegaphone'),
onInteractWithMegaphone: action('onInteractWithMegaphone'),
},
} satisfies ComponentMeta<PropsType>;

View File

@@ -12,6 +12,7 @@ import { offsetDistanceModifier } from '../util/popperUtil.std.js';
export type PropsType = Omit<RemoteActionableMegaphoneType, 'type'> & {
isFullSize: boolean;
i18n: LocalizerType;
onClickNarrowMegaphone: () => void;
};
export function RemoteMegaphone({
@@ -25,6 +26,7 @@ export function RemoteMegaphone({
secondaryCtaText,
remoteMegaphoneId,
isFullSize,
onClickNarrowMegaphone,
onInteractWithMegaphone,
}: PropsType): React.JSX.Element {
const isRTL = i18n.getLocaleDirection() === 'rtl';
@@ -37,7 +39,12 @@ export function RemoteMegaphone({
'bg-elevated-background-primary dark:bg-elevated-background-tertiary'
);
const image: React.JSX.Element = (
<div className={tw('size-[48px] shrink-0 @min-[88px]:size-[64px]')}>
<div
className={tw(
'size-[48px] shrink-0',
isFullSize ? 'size-[64px]' : 'm-auto'
)}
>
<img
alt=""
className={tw('object-cover')}
@@ -100,7 +107,6 @@ export function RemoteMegaphone({
}
// Narrow collapsed sidebar
// TODO: DESKTOP-9540
const tooltipContent: React.JSX.Element = (
<div className={tw('text-start text-label-primary')}>
<h2 className={tw('mt-1 type-body-medium font-semibold')}>{title}</h2>
@@ -112,11 +118,17 @@ export function RemoteMegaphone({
<Tooltip
content={tooltipContent}
className="RemoteMegaphoneTooltip"
wrapperClassName={wrapperClassName}
direction={isRTL ? TooltipPlacement.Left : TooltipPlacement.Right}
popperModifiers={[offsetDistanceModifier(15)]}
>
<div className={tw('m-auto')}>{image}</div>
<button
aria-label={i18n('icu:Megaphone__ExpandNarrowSidebar')}
className={wrapperClassName}
onClick={onClickNarrowMegaphone}
type="button"
>
{image}
</button>
</Tooltip>
);
}

View File

@@ -27,6 +27,7 @@ import { RemoteMegaphone } from './RemoteMegaphone.dom.js';
export type PropsType = {
changeLocation: (newLocation: Location) => unknown;
expandNarrowLeftPane: () => void;
hideToast: () => unknown;
i18n: LocalizerType;
openFileInFolder: (target: string) => unknown;
@@ -948,6 +949,7 @@ export function renderMegaphone({
i18n,
megaphone,
containerWidthBreakpoint,
expandNarrowLeftPane,
}: PropsType): React.JSX.Element | null {
if (!megaphone) {
return null;
@@ -963,6 +965,7 @@ export function renderMegaphone({
{...megaphone}
i18n={i18n}
isFullSize={containerWidthBreakpoint !== WidthBreakpoint.Narrow}
onClickNarrowMegaphone={expandNarrowLeftPane}
/>
);
}

View File

@@ -9,7 +9,6 @@ import {
getPreferredLeftPaneWidth,
} from '../selectors/items.dom.js';
import { getIntl, getRegionCode } from '../selectors/user.std.js';
import type { WidthBreakpoint } from '../../components/_util.std.js';
import { CallsTab } from '../../components/CallsTab.preload.js';
import {
getAllConversations,
@@ -24,7 +23,7 @@ import type {
} from '../../types/CallDisposition.std.js';
import type { ConversationType } from '../ducks/conversations.preload.js';
import { SmartConversationDetails } from './ConversationDetails.preload.js';
import { SmartToastManager } from './ToastManager.preload.js';
import { renderToastManagerWithoutMegaphone } from './ToastManager.preload.js';
import { useCallingActions } from '../ducks/calling.preload.js';
import {
getActiveCallState,
@@ -131,12 +130,6 @@ function renderConversationDetails(
);
}
function renderToastManager(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
return <SmartToastManager disableMegaphone {...props} />;
}
export const SmartCallsTab = memo(function SmartCallsTab() {
const i18n = useSelector(getIntl);
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
@@ -253,7 +246,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
preferredLeftPaneWidth={preferredLeftPaneWidth}
renderCallLinkDetails={renderCallLinkDetails}
renderConversationDetails={renderConversationDetails}
renderToastManager={renderToastManager}
renderToastManager={renderToastManagerWithoutMegaphone}
regionCode={regionCode}
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
startCallLinkLobbyByRoomId={startCallLinkLobbyByRoomId}

View File

@@ -20,6 +20,7 @@ import OS from '../../util/os/osMain.node.js';
import { isStagingServer } from '../../util/isStagingServer.dom.js';
import { createLogger } from '../../logging/log.std.js';
import { SmartToastManager } from './ToastManager.preload.js';
import { shouldNeverBeCalled } from '../../util/shouldNeverBeCalled.std.js';
const log = createLogger('InstallScreen');
@@ -103,6 +104,7 @@ export const SmartInstallScreen = memo(function SmartInstallScreen() {
<InstallScreen {...props} />
<SmartToastManager
disableMegaphone
expandNarrowLeftPane={shouldNeverBeCalled}
containerWidthBreakpoint={WidthBreakpoint.Narrow}
/>
</>

View File

@@ -103,7 +103,11 @@ import { SmartCrashReportDialog } from './CrashReportDialog.preload.js';
import { SmartMessageSearchResult } from './MessageSearchResult.preload.js';
import { SmartNetworkStatus } from './NetworkStatus.preload.js';
import { SmartRelinkDialog } from './RelinkDialog.dom.js';
import { SmartToastManager } from './ToastManager.preload.js';
import {
renderToastManagerWithoutMegaphone,
SmartToastManager,
} from './ToastManager.preload.js';
import type { SmartPropsType as SmartToastManagerPropsType } from './ToastManager.preload.js';
import type { PropsType as SmartUnsupportedOSDialogPropsType } from './UnsupportedOSDialog.preload.js';
import { SmartUnsupportedOSDialog } from './UnsupportedOSDialog.preload.js';
import { SmartUpdateDialog } from './UpdateDialog.preload.js';
@@ -172,18 +176,12 @@ function renderUnsupportedOSDialog(
): React.JSX.Element {
return <SmartUnsupportedOSDialog {...props} />;
}
function renderToastManagerWithMegaphone(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
function renderToastManagerWithMegaphone(
props: Readonly<SmartToastManagerPropsType>
): React.JSX.Element {
return <SmartToastManager {...props} />;
}
function renderToastManagerWithoutMegaphone(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
return <SmartToastManager disableMegaphone {...props} />;
}
function renderNotificationProfilesMenu(): React.JSX.Element {
return <SmartNotificationProfilesMenu />;
}

View File

@@ -74,7 +74,7 @@ import { getPreferredBadgeSelector } from '../selectors/badges.preload.js';
import { SmartProfileEditor } from './ProfileEditor.preload.js';
import { useNavActions } from '../ducks/nav.std.js';
import { NavTab } from '../../types/Nav.std.js';
import { SmartToastManager } from './ToastManager.preload.js';
import { renderToastManagerWithoutMegaphone } from './ToastManager.preload.js';
import { useToastActions } from '../ducks/toast.preload.js';
import { DataReader, DataWriter } from '../../sql/Client.preload.js';
import { deleteAllMyStories } from '../../util/deleteAllMyStories.preload.js';
@@ -157,12 +157,6 @@ function renderProfileEditor(options: {
return <SmartProfileEditor contentsRef={options.contentsRef} />;
}
function renderToastManager(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
return <SmartToastManager disableMegaphone {...props} />;
}
function renderDonationsPane({
contentsRef,
settingsLocation,
@@ -937,7 +931,7 @@ export function SmartPreferences(): React.JSX.Element | null {
renderNotificationProfilesCreateFlow
}
renderProfileEditor={renderProfileEditor}
renderToastManager={renderToastManager}
renderToastManager={renderToastManagerWithoutMegaphone}
renderUpdateDialog={renderUpdateDialog}
renderPreferencesChatFoldersPage={renderPreferencesChatFoldersPage}
renderPreferencesEditChatFolderPage={

View File

@@ -4,8 +4,7 @@
import React, { memo, useCallback, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { SmartStoryCreator } from './StoryCreator.preload.js';
import { SmartToastManager } from './ToastManager.preload.js';
import type { WidthBreakpoint } from '../../components/_util.std.js';
import { renderToastManagerWithoutMegaphone } from './ToastManager.preload.js';
import { StoriesTab } from '../../components/StoriesTab.dom.js';
import { getMaximumOutgoingAttachmentSizeInKb } from '../../types/AttachmentSize.std.js';
import type { ConfigKeyType } from '../../RemoteConfig.dom.js';
@@ -40,12 +39,6 @@ function renderStoryCreator(): React.JSX.Element {
return <SmartStoryCreator />;
}
function renderToastManager(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
return <SmartToastManager disableMegaphone {...props} />;
}
export const SmartStoriesTab = memo(function SmartStoriesTab() {
const storiesActions = useStoriesActions();
const {
@@ -133,7 +126,7 @@ export const SmartStoriesTab = memo(function SmartStoriesTab() {
preferredLeftPaneWidth={preferredLeftPaneWidth}
preferredWidthFromStorage={preferredWidthFromStorage}
renderStoryCreator={renderStoryCreator}
renderToastManager={renderToastManager}
renderToastManager={renderToastManagerWithoutMegaphone}
retryMessageSend={retryMessageSend}
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
showConversation={showConversation}

View File

@@ -35,19 +35,34 @@ import { useDonationsActions } from '../ducks/donations.preload.js';
import { itemStorage } from '../../textsecure/Storage.preload.js';
import { getVisibleMegaphonesForDisplay } from '../selectors/megaphones.preload.js';
import { useMegaphonesActions } from '../ducks/megaphones.preload.js';
import { shouldNeverBeCalled } from '../../util/shouldNeverBeCalled.std.js';
export type SmartPropsType = Readonly<{
disableMegaphone?: boolean;
containerWidthBreakpoint: WidthBreakpoint;
expandNarrowLeftPane: () => void;
}>;
function handleShowDebugLog() {
window.IPC.showDebugLog();
}
export function renderToastManagerWithoutMegaphone(props: {
containerWidthBreakpoint: WidthBreakpoint;
}): React.JSX.Element {
return (
<SmartToastManager
disableMegaphone
expandNarrowLeftPane={shouldNeverBeCalled}
{...props}
/>
);
}
export const SmartToastManager = memo(function SmartToastManager({
disableMegaphone = false,
containerWidthBreakpoint,
expandNarrowLeftPane,
}: SmartPropsType) {
const i18n = useSelector(getIntl);
const hasCompletedUsernameOnboarding = useSelector(
@@ -119,6 +134,7 @@ export const SmartToastManager = memo(function SmartToastManager({
setDidResumeDonation={setDidResume}
centerToast={centerToast}
containerWidthBreakpoint={containerWidthBreakpoint}
expandNarrowLeftPane={expandNarrowLeftPane}
isCompositionAreaVisible={isCompositionAreaVisible}
isInFullScreenCall={isInFullScreenCall}
/>