Settings Tab: Allow resizing the left pane

This commit is contained in:
Scott Nonnenberg 2025-06-04 06:49:07 +10:00 committed by GitHub
parent eb670fb89a
commit 14c4b4ac1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 188 additions and 196 deletions

View file

@ -33,49 +33,25 @@ $secondary-text-color: light-dark(
background: variables.$color-gray-95; background: variables.$color-gray-95;
} }
&__header {
display: flex;
flex-direction: row;
}
&__header__toggle {
flex-grow: 0;
min-width: 80px;
width: 80px;
margin-inline-end: -24px;
}
&__header__text {
flex-grow: 1;
margin-inline-start: 24px;
@include mixins.font-title-medium;
line-height: 20px;
margin-top: 12px;
margin-bottom: 20px;
}
&__dialog-container { &__dialog-container {
margin-bottom: 8px; margin-bottom: 8px;
} }
&__page-selector { &__page-selector {
padding-top: var(--title-bar-drag-area-height); padding-top: 2px;
width: 279px; max-height: 100%;
flex-grow: 0;
flex-shrink: 0;
@include mixins.light-theme { @include mixins.light-theme {
background: variables.$color-gray-04; background: variables.$color-gray-04;
border-inline-end: 0.5px solid variables.$color-black-alpha-16;
} }
@include mixins.dark-theme { @include mixins.dark-theme {
background: variables.$color-gray-80; background: variables.$color-gray-80;
border-inline-end: 0.5px solid variables.$color-white-alpha-16;
} }
} }
&__scroll-area { &__scroll-area {
overflow-y: scroll; overflow-y: scroll;
max-height: calc(100% - 52px); max-height: 100%;
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@include mixins.light-theme { @include mixins.light-theme {

View file

@ -243,6 +243,7 @@ export default {
}, },
page: Page.Profile, page: Page.Profile,
preferredSystemLocales: ['en'], preferredSystemLocales: ['en'],
preferredWidthFromStorage: 300,
resolvedLocale: 'en', resolvedLocale: 'en',
selectedCamera: selectedCamera:
'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c', 'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c',
@ -328,6 +329,7 @@ export default {
), ),
resetAllChatColors: action('resetAllChatColors'), resetAllChatColors: action('resetAllChatColors'),
resetDefaultChatColor: action('resetDefaultChatColor'), resetDefaultChatColor: action('resetDefaultChatColor'),
savePreferredLeftPaneWidth: action('savePreferredLeftPaneWidth'),
setGlobalDefaultConversationColor: action( setGlobalDefaultConversationColor: action(
'setGlobalDefaultConversationColor' 'setGlobalDefaultConversationColor'
), ),

View file

@ -49,8 +49,8 @@ import {
import { PreferencesBackups } from './PreferencesBackups'; import { PreferencesBackups } from './PreferencesBackups';
import { PreferencesInternal } from './PreferencesInternal'; import { PreferencesInternal } from './PreferencesInternal';
import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider'; import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider';
import { NavTabsToggle } from './NavTabs';
import { Avatar, AvatarSize } from './Avatar'; import { Avatar, AvatarSize } from './Avatar';
import { NavSidebar } from './NavSidebar';
import type { MediaDeviceSettings } from '../types/Calling'; import type { MediaDeviceSettings } from '../types/Calling';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups'; import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
@ -151,6 +151,7 @@ export type PropsDataType = {
me: ConversationType; me: ConversationType;
badge: BadgeType | undefined; badge: BadgeType | undefined;
theme: ThemeType; theme: ThemeType;
preferredWidthFromStorage: number;
// Limited support features // Limited support features
isAutoDownloadUpdatesSupported: boolean; isAutoDownloadUpdatesSupported: boolean;
@ -200,6 +201,7 @@ type PropsFunctionType = {
removeCustomColorOnConversations: (colorId: string) => unknown; removeCustomColorOnConversations: (colorId: string) => unknown;
resetAllChatColors: () => unknown; resetAllChatColors: () => unknown;
resetDefaultChatColor: () => unknown; resetDefaultChatColor: () => unknown;
savePreferredLeftPaneWidth: (_: number) => void;
setGlobalDefaultConversationColor: ( setGlobalDefaultConversationColor: (
color: ConversationColorType, color: ConversationColorType,
customColorData?: { customColorData?: {
@ -411,6 +413,7 @@ export function Preferences({
page, page,
phoneNumber = '', phoneNumber = '',
preferredSystemLocales, preferredSystemLocales,
preferredWidthFromStorage,
refreshCloudBackupStatus, refreshCloudBackupStatus,
refreshBackupSubscriptionStatus, refreshBackupSubscriptionStatus,
removeCustomColor, removeCustomColor,
@ -421,6 +424,7 @@ export function Preferences({
resetAllChatColors, resetAllChatColors,
resetDefaultChatColor, resetDefaultChatColor,
resolvedLocale, resolvedLocale,
savePreferredLeftPaneWidth,
selectedCamera, selectedCamera,
selectedMicrophone, selectedMicrophone,
selectedSpeaker, selectedSpeaker,
@ -651,6 +655,9 @@ export function Preferences({
<Control <Control
left={i18n('icu:Preferences--phone-number')} left={i18n('icu:Preferences--phone-number')}
right={phoneNumber} right={phoneNumber}
rightStyle={{
maxWidth: '33%',
}}
/> />
<Control <Control
left={ left={
@ -662,6 +669,9 @@ export function Preferences({
</> </>
} }
right={deviceName} right={deviceName}
rightStyle={{
maxWidth: '33%',
}}
/> />
</SettingsRow> </SettingsRow>
<SettingsRow title={i18n('icu:Preferences--system')}> <SettingsRow title={i18n('icu:Preferences--system')}>
@ -1908,187 +1918,181 @@ export function Preferences({
<FunEmojiLocalizationProvider i18n={i18n}> <FunEmojiLocalizationProvider i18n={i18n}>
<div className="module-title-bar-drag-area" /> <div className="module-title-bar-drag-area" />
<div className="Preferences"> <div className="Preferences">
<div className="Preferences__page-selector"> <NavSidebar
<div className="Preferences__header"> title={i18n('icu:Preferences--header')}
{navTabsCollapsed ? ( i18n={i18n}
<div className="Preferences__header__toggle"> otherTabsUnreadStats={otherTabsUnreadStats}
<NavTabsToggle hasFailedStorySends={hasFailedStorySends}
i18n={i18n} hasPendingUpdate={false}
onToggleNavTabsCollapse={onToggleNavTabsCollapse} navTabsCollapsed={navTabsCollapsed}
navTabsCollapsed onToggleNavTabsCollapse={onToggleNavTabsCollapse}
hasFailedStorySends={hasFailedStorySends} preferredLeftPaneWidth={preferredWidthFromStorage}
otherTabsUnreadStats={otherTabsUnreadStats} requiresFullWidth
hasPendingUpdate={false} savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
/> renderToastManager={renderToastManager}
</div> >
) : undefined} <div className="Preferences__page-selector">
<h1 className="Preferences__header__text"> {maybeUpdateDialog ? (
{i18n('icu:Preferences--header')} <div className="Preferences__dialog-container">
</h1> <div className="module-left-pane__dialogs">
</div> {maybeUpdateDialog}
{maybeUpdateDialog ? (
<div className="Preferences__dialog-container">
<div className="module-left-pane__dialogs">
{maybeUpdateDialog}
</div>
</div>
) : null}
<div className="Preferences__scroll-area">
<button
type="button"
className={classNames({
'Preferences__profile-chip': true,
'Preferences__profile-chip--selected': page === Page.Profile,
})}
onClick={() => setPage(Page.Profile)}
>
<div className="Preferences__profile-chip__avatar">
<Avatar
avatarUrl={me.avatarUrl}
badge={badge}
className="module-main-header__avatar"
color={me.color}
conversationType="direct"
i18n={i18n}
phoneNumber={me.phoneNumber}
profileName={me.profileName}
theme={theme}
title={me.title}
// `sharedGroupNames` makes no sense for yourself, but
// `<Avatar>` needs it to determine blurring.
sharedGroupNames={[]}
size={AvatarSize.FORTY_EIGHT}
/>
</div>
<div className="Preferences__profile-chip__text-container">
<div className="Preferences__profile-chip__name">
{me.title}
</div> </div>
<div className="Preferences__profile-chip__number"> </div>
{me.phoneNumber} ) : null}
<div className="Preferences__scroll-area">
<button
type="button"
className={classNames({
'Preferences__profile-chip': true,
'Preferences__profile-chip--selected': page === Page.Profile,
})}
onClick={() => setPage(Page.Profile)}
>
<div className="Preferences__profile-chip__avatar">
<Avatar
avatarUrl={me.avatarUrl}
badge={badge}
className="module-main-header__avatar"
color={me.color}
conversationType="direct"
i18n={i18n}
phoneNumber={me.phoneNumber}
profileName={me.profileName}
theme={theme}
title={me.title}
// `sharedGroupNames` makes no sense for yourself, but
// `<Avatar>` needs it to determine blurring.
sharedGroupNames={[]}
size={AvatarSize.FORTY_EIGHT}
/>
</div> </div>
{me.username && ( <div className="Preferences__profile-chip__text-container">
<div className="Preferences__profile-chip__username"> <div className="Preferences__profile-chip__name">
{me.username} {me.title}
</div> </div>
)} <div className="Preferences__profile-chip__number">
</div> {me.phoneNumber}
<div className="Preferences__profile-chip__qr-icon-container"> </div>
<div className="Preferences__profile-chip__qr-icon" /> {me.username && (
</div> <div className="Preferences__profile-chip__username">
</button> {me.username}
<button </div>
type="button" )}
className={classNames({ </div>
Preferences__button: true, <div className="Preferences__profile-chip__qr-icon-container">
'Preferences__button--general': true, <div className="Preferences__profile-chip__qr-icon" />
'Preferences__button--selected': page === Page.General, </div>
})} </button>
onClick={() => setPage(Page.General)}
>
{i18n('icu:Preferences__button--general')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--appearance': true,
'Preferences__button--selected':
page === Page.Appearance || page === Page.ChatColor,
})}
onClick={() => setPage(Page.Appearance)}
>
{i18n('icu:Preferences__button--appearance')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--chats': true,
'Preferences__button--selected': page === Page.Chats,
})}
onClick={() => setPage(Page.Chats)}
>
{i18n('icu:Preferences__button--chats')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--calls': true,
'Preferences__button--selected': page === Page.Calls,
})}
onClick={() => setPage(Page.Calls)}
>
{i18n('icu:Preferences__button--calls')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--notifications': true,
'Preferences__button--selected': page === Page.Notifications,
})}
onClick={() => setPage(Page.Notifications)}
>
{i18n('icu:Preferences__button--notifications')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--privacy': true,
'Preferences__button--selected':
page === Page.Privacy || page === Page.PNP,
})}
onClick={() => setPage(Page.Privacy)}
>
{i18n('icu:Preferences__button--privacy')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--data-usage': true,
'Preferences__button--selected': page === Page.DataUsage,
})}
onClick={() => setPage(Page.DataUsage)}
>
{i18n('icu:Preferences__button--data-usage')}
</button>
{shouldShowBackupsPage ? (
<button <button
type="button" type="button"
className={classNames({ className={classNames({
Preferences__button: true, Preferences__button: true,
'Preferences__button--backups': true, 'Preferences__button--general': true,
'Preferences__button--selected': page === Page.Backups, 'Preferences__button--selected': page === Page.General,
})} })}
onClick={() => setPage(Page.Backups)} onClick={() => setPage(Page.General)}
> >
{i18n('icu:Preferences__button--backups')} {i18n('icu:Preferences__button--general')}
</button> </button>
) : null}
{isInternalUser ? (
<button <button
type="button" type="button"
className={classNames({ className={classNames({
Preferences__button: true, Preferences__button: true,
'Preferences__button--internal': true, 'Preferences__button--appearance': true,
'Preferences__button--selected': page === Page.Internal, 'Preferences__button--selected':
page === Page.Appearance || page === Page.ChatColor,
})} })}
onClick={() => setPage(Page.Internal)} onClick={() => setPage(Page.Appearance)}
> >
{i18n('icu:Preferences__button--internal')} {i18n('icu:Preferences__button--appearance')}
</button> </button>
) : null} <button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--chats': true,
'Preferences__button--selected': page === Page.Chats,
})}
onClick={() => setPage(Page.Chats)}
>
{i18n('icu:Preferences__button--chats')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--calls': true,
'Preferences__button--selected': page === Page.Calls,
})}
onClick={() => setPage(Page.Calls)}
>
{i18n('icu:Preferences__button--calls')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--notifications': true,
'Preferences__button--selected': page === Page.Notifications,
})}
onClick={() => setPage(Page.Notifications)}
>
{i18n('icu:Preferences__button--notifications')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--privacy': true,
'Preferences__button--selected':
page === Page.Privacy || page === Page.PNP,
})}
onClick={() => setPage(Page.Privacy)}
>
{i18n('icu:Preferences__button--privacy')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--data-usage': true,
'Preferences__button--selected': page === Page.DataUsage,
})}
onClick={() => setPage(Page.DataUsage)}
>
{i18n('icu:Preferences__button--data-usage')}
</button>
{shouldShowBackupsPage ? (
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--backups': true,
'Preferences__button--selected': page === Page.Backups,
})}
onClick={() => setPage(Page.Backups)}
>
{i18n('icu:Preferences__button--backups')}
</button>
) : null}
{isInternalUser ? (
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--internal': true,
'Preferences__button--selected': page === Page.Internal,
})}
onClick={() => setPage(Page.Internal)}
>
{i18n('icu:Preferences__button--internal')}
</button>
) : null}
</div>
</div> </div>
</div> </NavSidebar>
{content} {content}
</div> </div>
{renderToastManager({
containerWidthBreakpoint: WidthBreakpoint.Wide,
})}
</FunEmojiLocalizationProvider> </FunEmojiLocalizationProvider>
); );
} }

View file

@ -32,12 +32,14 @@ export function SettingsControl({
left, left,
onClick, onClick,
right, right,
rightStyle,
}: { }: {
/** A className or `true` to leave room for icon */ /** A className or `true` to leave room for icon */
icon?: string | true; icon?: string | true;
left: ReactNode; left: ReactNode;
onClick?: () => unknown; onClick?: () => unknown;
right: ReactNode; right: ReactNode;
rightStyle?: React.CSSProperties;
}): JSX.Element { }): JSX.Element {
const content = ( const content = (
<> <>
@ -50,7 +52,9 @@ export function SettingsControl({
/> />
)} )}
<div className="Preferences__control--key">{left}</div> <div className="Preferences__control--key">{left}</div>
<div className="Preferences__control--value">{right}</div> <div className="Preferences__control--value" style={rightStyle}>
{right}
</div>
</> </>
); );

View file

@ -17,6 +17,7 @@ import {
getCustomColors, getCustomColors,
getItems, getItems,
getNavTabsCollapsed, getNavTabsCollapsed,
getPreferredLeftPaneWidth,
} from '../selectors/items'; } from '../selectors/items';
import { DEFAULT_AUTO_DOWNLOAD_ATTACHMENT } from '../../textsecure/Storage'; import { DEFAULT_AUTO_DOWNLOAD_ATTACHMENT } from '../../textsecure/Storage';
import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors'; import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors';
@ -127,6 +128,7 @@ export function SmartPreferences(): JSX.Element | null {
putItem, putItem,
removeCustomColor, removeCustomColor,
resetDefaultChatColor, resetDefaultChatColor,
savePreferredLeftPaneWidth,
setEmojiSkinToneDefault: onEmojiSkinToneDefaultChange, setEmojiSkinToneDefault: onEmojiSkinToneDefaultChange,
setGlobalDefaultConversationColor, setGlobalDefaultConversationColor,
toggleNavTabsCollapse, toggleNavTabsCollapse,
@ -144,17 +146,19 @@ export function SmartPreferences(): JSX.Element | null {
const getConversationsWithCustomColor = useSelector( const getConversationsWithCustomColor = useSelector(
getConversationsWithCustomColorSelector getConversationsWithCustomColorSelector
); );
const items = useSelector(getItems);
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const items = useSelector(getItems);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const hasPendingUpdate = useSelector(getHasPendingUpdate); const hasPendingUpdate = useSelector(getHasPendingUpdate);
const isUpdateDownloaded = useSelector(getIsUpdateDownloaded); const isUpdateDownloaded = useSelector(getIsUpdateDownloaded);
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const me = useSelector(getMe); const me = useSelector(getMe);
const badge = useSelector(getPreferredBadgeSelector)(me.badges); const navTabsCollapsed = useSelector(getNavTabsCollapsed);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
const theme = useSelector(getTheme); const theme = useSelector(getTheme);
const badge = useSelector(getPreferredBadgeSelector)(me.badges);
// The weird ones // The weird ones
const makeSyncRequest = async () => { const makeSyncRequest = async () => {
@ -763,6 +767,7 @@ export function SmartPreferences(): JSX.Element | null {
otherTabsUnreadStats={otherTabsUnreadStats} otherTabsUnreadStats={otherTabsUnreadStats}
page={page} page={page}
preferredSystemLocales={preferredSystemLocales} preferredSystemLocales={preferredSystemLocales}
preferredWidthFromStorage={preferredWidthFromStorage}
refreshCloudBackupStatus={refreshCloudBackupStatus} refreshCloudBackupStatus={refreshCloudBackupStatus}
refreshBackupSubscriptionStatus={refreshBackupSubscriptionStatus} refreshBackupSubscriptionStatus={refreshBackupSubscriptionStatus}
removeCustomColorOnConversations={removeCustomColorOnConversations} removeCustomColorOnConversations={removeCustomColorOnConversations}
@ -779,6 +784,7 @@ export function SmartPreferences(): JSX.Element | null {
sentMediaQualitySetting={sentMediaQualitySetting} sentMediaQualitySetting={sentMediaQualitySetting}
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor} setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
setPage={setPage} setPage={setPage}
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
showToast={showToast} showToast={showToast}
theme={theme} theme={theme}
themeSetting={themeSetting} themeSetting={themeSetting}