Settings Tab: Better layout for narrow windows

This commit is contained in:
Scott Nonnenberg
2025-06-07 08:16:40 +10:00
committed by GitHub
parent 9e3f397032
commit 974c29fd41
8 changed files with 438 additions and 163 deletions

View File

@@ -141,12 +141,17 @@ $NavTabs__ProfileAvatar__size: 28px;
.NavTabs__ItemUpdateBadge { .NavTabs__ItemUpdateBadge {
background: variables.$color-ultramarine; background: variables.$color-ultramarine;
border-radius: 100%; border-radius: 100%;
border: 1px solid variables.$color-white;
height: 8px; height: 8px;
width: 8px; width: 8px;
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-end: 0; inset-inline-end: 0;
@include mixins.light-theme {
border: 1px solid variables.$color-white;
}
@include mixins.dark-theme {
border: 1px solid variables.$color-gray-80;
}
} }
.NavTabs__ItemIcon { .NavTabs__ItemIcon {

View File

@@ -286,6 +286,7 @@ $secondary-text-color: light-dark(
height: 100%; height: 100%;
flex-direction: row; flex-direction: row;
overflow-y: scroll; overflow-y: scroll;
container-type: inline-size;
} }
&__settings-pane { &__settings-pane {
@@ -374,6 +375,73 @@ $secondary-text-color: light-dark(
} }
} }
&__light-icon-label {
display: flex;
}
&__flow-control {
display: block;
padding-block: 4px;
padding-inline: 24px;
}
&__one-third-flow {
vertical-align: middle;
display: inline-block;
width: 33%;
@container (max-width: 350px) {
width: 100%;
}
}
&__half-flow {
vertical-align: middle;
display: inline-block;
width: 50%;
@container (max-width: 350px) {
width: 100%;
}
}
&__two-thirds-flow {
vertical-align: middle;
display: inline-block;
width: 66%;
@container (max-width: 350px) {
width: 100%;
}
}
&__half-flow--align-right {
text-align: end;
@container (max-width: 350px) {
text-align: start;
}
}
&__one-third-flow--align-right {
text-align: end;
@container (max-width: 350px) {
text-align: start;
}
}
&__full-flow {
vertical-align: middle;
display: inline-block;
width: 100%;
}
&__flow-value,
&__flow-description {
vertical-align: middle;
color: $secondary-text-color;
}
&__device-name-description {
padding-top: 8px;
}
&__flow-button {
padding-inline-start: 5px;
@container (max-width: 350px) {
padding-inline-start: 0px;
padding-top: 8px;
}
}
&__control { &__control {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -432,9 +500,16 @@ $secondary-text-color: light-dark(
} }
} }
&__checkbox__description,
&__description { &__description {
@include mixins.font-subtitle; @include mixins.font-subtitle;
color: $secondary-text-color; // For specificity reasons, we can't use $secondary-text-color. We need the mixins.
@include mixins.light-theme {
color: variables.$color-gray-60;
}
@include mixins.dark-theme {
color: variables.$color-gray-25;
}
&--error { &--error {
color: variables.$color-accent-red !important; color: variables.$color-accent-red !important;
} }

View File

@@ -0,0 +1,56 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { NavTabsProps } from './NavTabs';
import { NavTabs } from './NavTabs';
import { NavTab } from '../state/ducks/nav';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { ThemeType } from '../types/Util';
const { i18n } = window.SignalContext;
const createProps = (
overrideProps: Partial<NavTabsProps> = {}
): NavTabsProps => ({
badge: overrideProps.badge,
hasFailedStorySends: Boolean(overrideProps.hasFailedStorySends),
hasPendingUpdate: Boolean(overrideProps.hasPendingUpdate),
i18n,
me: getDefaultConversation(),
navTabsCollapsed: Boolean(overrideProps.navTabsCollapsed),
onChangeLocation: action('onChangeLocation'),
onToggleNavTabsCollapse: action('onToggleNavTabsCollapse'),
renderCallsTab: () => <div>Calls Tab goes here</div>,
renderChatsTab: () => <div>Chats Tab goes here</div>,
renderStoriesTab: () => <div>Stories Tab goes here</div>,
renderSettingsTab: () => <div>Settings Tab goes here</div>,
selectedNavTab: overrideProps.selectedNavTab ?? NavTab.Chats,
shouldShowProfileIcon: Boolean(overrideProps.shouldShowProfileIcon),
storiesEnabled: Boolean(overrideProps.storiesEnabled),
theme: overrideProps.theme ?? ThemeType.light,
unreadCallsCount: overrideProps.unreadCallsCount ?? 0,
unreadConversationsStats: overrideProps.unreadConversationsStats ?? {
unreadCount: 0,
unreadMentionsCount: 0,
markedUnread: false,
},
unreadStoriesCount: overrideProps.unreadStoriesCount ?? 0,
});
export default {
title: 'Components/NavTabs',
} satisfies Meta<NavTabsProps>;
export function HasPendingUpdate(): JSX.Element {
return (
<NavTabs
{...createProps({
hasPendingUpdate: true,
})}
/>
);
}

View File

@@ -43,6 +43,7 @@ import { FunSkinTonesList } from './fun/FunSkinTones';
import { emojiParentKeyConstant, type EmojiSkinTone } from './fun/data/emojis'; import { emojiParentKeyConstant, type EmojiSkinTone } from './fun/data/emojis';
import { import {
SettingsControl as Control, SettingsControl as Control,
FlowingSettingsControl as FlowingControl,
SettingsRadio, SettingsRadio,
SettingsRow, SettingsRow,
} from './PreferencesUtil'; } from './PreferencesUtil';
@@ -672,27 +673,43 @@ export function Preferences({
const pageContents = ( const pageContents = (
<> <>
<SettingsRow> <SettingsRow>
<Control <FlowingControl>
left={i18n('icu:Preferences--phone-number')} <div className="Preferences__half-flow">
right={phoneNumber} {i18n('icu:Preferences--phone-number')}
rightStyle={{ </div>
maxWidth: '33%', <div
}} className={classNames(
/> 'Preferences__flow-value',
<Control 'Preferences__half-flow',
left={ 'Preferences__half-flow--align-right'
<> )}
<div>{i18n('icu:Preferences--device-name')}</div> >
<div className="Preferences__description"> {phoneNumber}
{i18n('icu:Preferences--device-name__description')} </div>
</div> </FlowingControl>
</> <FlowingControl>
} <div className="Preferences__half-flow">
right={deviceName} {i18n('icu:Preferences--device-name')}
rightStyle={{ </div>
maxWidth: '33%', <div
}} className={classNames(
/> 'Preferences__flow-value',
'Preferences__half-flow',
'Preferences__half-flow--align-right'
)}
>
{deviceName}
</div>
<div
className={classNames(
'Preferences__device-name-description',
'Preferences__description',
'Preferences__full-flow'
)}
>
{i18n('icu:Preferences--device-name__description')}
</div>
</FlowingControl>
</SettingsRow> </SettingsRow>
<SettingsRow title={i18n('icu:Preferences--system')}> <SettingsRow title={i18n('icu:Preferences--system')}>
{isAutoLaunchSupported && ( {isAutoLaunchSupported && (
@@ -1374,24 +1391,34 @@ export function Preferences({
const pageContents = ( const pageContents = (
<> <>
<SettingsRow> <SettingsRow>
<Control <FlowingControl>
left={ <div
<div className="Preferences__pnp"> className={classNames(
<h3>{i18n('icu:Preferences__pnp__row--title')}</h3> 'Preferences__pnp',
<div className="Preferences__description"> 'Preferences__two-thirds-flow'
{i18n('icu:Preferences__pnp__row--body')} )}
</div> >
<h3>{i18n('icu:Preferences__pnp__row--title')}</h3>
<div className="Preferences__description">
{i18n('icu:Preferences__pnp__row--body')}
</div> </div>
} </div>
right={ <div
className={classNames(
'Preferences__pnp',
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
onClick={() => setPage(Page.PNP)} onClick={() => setPage(Page.PNP)}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
> >
{i18n('icu:Preferences__pnp__row--button')} {i18n('icu:Preferences__pnp__row--button')}
</Button> </Button>
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
<SettingsRow> <SettingsRow>
<Control <Control
@@ -1433,18 +1460,22 @@ export function Preferences({
/> />
)} )}
<SettingsRow title={i18n('icu:disappearingMessages')}> <SettingsRow title={i18n('icu:disappearingMessages')}>
<Control <FlowingControl>
left={ <div className="Preferences__two-thirds-flow">
<> <div>
<div> {i18n('icu:settings__DisappearingMessages__timer__label')}
{i18n('icu:settings__DisappearingMessages__timer__label')} </div>
</div> <div className="Preferences__description">
<div className="Preferences__description"> {i18n('icu:settings__DisappearingMessages__footer')}
{i18n('icu:settings__DisappearingMessages__footer')} </div>
</div> </div>
</> <div
} className={classNames(
right={ 'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Select <Select
ariaLabel={i18n( ariaLabel={i18n(
'icu:settings__DisappearingMessages__timer__label' 'icu:settings__DisappearingMessages__timer__label'
@@ -1480,8 +1511,8 @@ export function Preferences({
])} ])}
value={universalExpireTimer} value={universalExpireTimer}
/> />
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
{isContentProtectionSupported && ( {isContentProtectionSupported && (
<SettingsRow title={i18n('icu:Preferences__Privacy__Application')}> <SettingsRow title={i18n('icu:Preferences__Privacy__Application')}>
@@ -1520,17 +1551,23 @@ export function Preferences({
</ConfirmationDialog> </ConfirmationDialog>
) : null} ) : null}
<SettingsRow title={i18n('icu:Stories__title')}> <SettingsRow title={i18n('icu:Stories__title')}>
<Control <FlowingControl>
left={ <div className="Preferences__two-thirds-flow">
<label htmlFor={storiesId}> <label htmlFor={storiesId}>
<div>{i18n('icu:Stories__settings-toggle--title')}</div> <div>{i18n('icu:Stories__settings-toggle--title')}</div>
<div className="Preferences__description"> <div className="Preferences__description">
{i18n('icu:Stories__settings-toggle--description')} {i18n('icu:Stories__settings-toggle--description')}
</div> </div>
</label> </label>
} </div>
right={ <div
hasStoriesDisabled ? ( className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
{hasStoriesDisabled ? (
<Button <Button
onClick={() => onHasStoriesDisabledChanged(false)} onClick={() => onHasStoriesDisabledChanged(false)}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
@@ -1545,31 +1582,40 @@ export function Preferences({
> >
{i18n('icu:Preferences__turn-stories-off')} {i18n('icu:Preferences__turn-stories-off')}
</Button> </Button>
) )}
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
<SettingsRow> <SettingsRow>
<Control <FlowingControl>
left={ <div
<> className={classNames(
<div>{i18n('icu:clearDataHeader')}</div> 'Preferences__pnp',
<div className="Preferences__description"> 'Preferences__two-thirds-flow'
{i18n('icu:clearDataExplanation')} )}
</div> >
</> <div>{i18n('icu:clearDataHeader')}</div>
} <div className="Preferences__description">
right={ {i18n('icu:clearDataExplanation')}
<div className="Preferences__right-button">
<Button
onClick={() => setConfirmDelete(true)}
variant={ButtonVariant.SecondaryDestructive}
>
{i18n('icu:clearDataButton')}
</Button>
</div> </div>
} </div>
/>
<div
className={classNames(
'Preferences__pnp',
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button
onClick={() => setConfirmDelete(true)}
variant={ButtonVariant.SecondaryDestructive}
>
{i18n('icu:clearDataButton')}
</Button>
</div>
</FlowingControl>
</SettingsRow> </SettingsRow>
{confirmDelete ? ( {confirmDelete ? (
<ConfirmationDialog <ConfirmationDialog
@@ -1681,23 +1727,28 @@ export function Preferences({
</div> </div>
</SettingsRow> </SettingsRow>
<SettingsRow> <SettingsRow>
<Control <FlowingControl>
left={ <div className="Preferences__two-thirds-flow">
<> <div className="Preferences__option-name">
<div className="Preferences__option-name"> {i18n('icu:Preferences__sent-media-quality')}
{i18n('icu:Preferences__sent-media-quality')} </div>
</div> <div
<div className={classNames(
className={classNames( 'Preferences__description',
'Preferences__description', 'Preferences__description--medium'
'Preferences__description--medium' )}
)} >
> {i18n('icu:Preferences__sent-media-quality__description')}
{i18n('icu:Preferences__sent-media-quality__description')} </div>
</div> </div>
</>
} <div
right={ className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Select <Select
onChange={onSentMediaQualityChange} onChange={onSentMediaQualityChange}
options={[ options={[
@@ -1712,8 +1763,8 @@ export function Preferences({
]} ]}
value={sentMediaQualitySetting} value={sentMediaQualitySetting}
/> />
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
</> </>
); );

View File

@@ -2,13 +2,20 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import type { import type {
BackupsSubscriptionType, BackupsSubscriptionType,
BackupStatusType, BackupStatusType,
} from '../types/backups'; } from '../types/backups';
import type { LocalizerType } from '../types/I18N'; import type { LocalizerType } from '../types/I18N';
import { formatTimestamp } from '../util/formatTimestamp'; import { formatTimestamp } from '../util/formatTimestamp';
import { SettingsControl as Control, SettingsRow } from './PreferencesUtil'; import {
SettingsControl as Control,
FlowingSettingsControl as FlowingControl,
LightIconLabel,
SettingsRow,
} from './PreferencesUtil';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import { Button, ButtonVariant } from './Button'; import { Button, ButtonVariant } from './Button';
import type { PreferencesBackupPage } from '../types/PreferencesBackupPage'; import type { PreferencesBackupPage } from '../types/PreferencesBackupPage';
@@ -97,29 +104,36 @@ export function PreferencesBackups({
{backupSubscriptionStatus ? ( {backupSubscriptionStatus ? (
<SettingsRow className="Preferences--BackupsRow"> <SettingsRow className="Preferences--BackupsRow">
<Control <FlowingControl>
icon="Preferences__BackupsIcon" <div className="Preferences__two-thirds-flow">
left={ <LightIconLabel icon="Preferences__BackupsIcon">
<label> <label>
{i18n('icu:Preferences--signal-backups')}{' '} {i18n('icu:Preferences--signal-backups')}{' '}
<div className="Preferences__description"> <div className="Preferences__description">
{renderBackupsSubscriptionSummary({ {renderBackupsSubscriptionSummary({
subscriptionStatus: backupSubscriptionStatus, subscriptionStatus: backupSubscriptionStatus,
i18n, i18n,
locale, locale,
})} })}
</div> </div>
</label> </label>
} </LightIconLabel>
right={ </div>
<div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
onClick={() => setPage(Page.BackupsDetails)} onClick={() => setPage(Page.BackupsDetails)}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
> >
{i18n('icu:Preferences__button--manage')} {i18n('icu:Preferences__button--manage')}
</Button> </Button>
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
) : ( ) : (
<SettingsRow className="Preferences--BackupsRow"> <SettingsRow className="Preferences--BackupsRow">
@@ -148,19 +162,26 @@ export function PreferencesBackups({
className="Preferences--BackupsRow" className="Preferences--BackupsRow"
title={i18n('icu:Preferences__backup-other-ways')} title={i18n('icu:Preferences__backup-other-ways')}
> >
<Control <FlowingControl>
icon="Preferences__LocalBackupsIcon" <div className="Preferences__two-thirds-flow">
left={ <LightIconLabel icon="Preferences__LocalBackupsIcon">
<label> <label>
{i18n('icu:Preferences__local-backups')}{' '} {i18n('icu:Preferences__local-backups')}{' '}
<div className="Preferences__description"> <div className="Preferences__description">
{isLocalBackupsSetup {isLocalBackupsSetup
? null ? null
: i18n('icu:Preferences--local-backups-off-description')} : i18n('icu:Preferences--local-backups-off-description')}
</div> </div>
</label> </label>
} </LightIconLabel>
right={ </div>
<div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
onClick={() => setPage(Page.LocalBackups)} onClick={() => setPage(Page.LocalBackups)}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
@@ -169,8 +190,8 @@ export function PreferencesBackups({
? i18n('icu:Preferences__button--manage') ? i18n('icu:Preferences__button--manage')
: i18n('icu:Preferences__button--set-up')} : i18n('icu:Preferences__button--set-up')}
</Button> </Button>
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
</> </>
); );

View File

@@ -2,12 +2,14 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import classNames from 'classnames';
import type { LocalizerType } from '../types/I18N'; import type { LocalizerType } from '../types/I18N';
import { toLogFormat } from '../types/errors'; import { toLogFormat } from '../types/errors';
import { formatFileSize } from '../util/formatFileSize'; import { formatFileSize } from '../util/formatFileSize';
import { SECOND } from '../util/durations'; import { SECOND } from '../util/durations';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups'; import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
import { SettingsRow, SettingsControl } from './PreferencesUtil'; import { SettingsRow, FlowingSettingsControl } from './PreferencesUtil';
import { Button, ButtonVariant } from './Button'; import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import type { MessageCountBySchemaVersionType } from '../sql/Interface'; import type { MessageCountBySchemaVersionType } from '../sql/Interface';
@@ -124,9 +126,17 @@ export function PreferencesInternal({
className="Preferences--internal--backups" className="Preferences--internal--backups"
title={i18n('icu:Preferences__button--backups')} title={i18n('icu:Preferences__button--backups')}
> >
<SettingsControl <FlowingSettingsControl>
left={i18n('icu:Preferences__internal__validate-backup--description')} <div className="Preferences__two-thirds-flow">
right={ {i18n('icu:Preferences__internal__validate-backup--description')}
</div>
<div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
onClick={validateBackup} onClick={validateBackup}
@@ -138,8 +148,8 @@ export function PreferencesInternal({
i18n('icu:Preferences__internal__validate-backup') i18n('icu:Preferences__internal__validate-backup')
)} )}
</Button> </Button>
} </div>
/> </FlowingSettingsControl>
{renderValidationResult(validationResult)} {renderValidationResult(validationResult)}
</SettingsRow> </SettingsRow>
@@ -148,11 +158,19 @@ export function PreferencesInternal({
className="Preferences--internal--backups" className="Preferences--internal--backups"
title={i18n('icu:Preferences__internal__local-backups')} title={i18n('icu:Preferences__internal__local-backups')}
> >
<SettingsControl <FlowingSettingsControl>
left={i18n( <div className="Preferences__two-thirds-flow">
'icu:Preferences__internal__export-local-backup--description' {i18n(
)} 'icu:Preferences__internal__export-local-backup--description'
right={ )}
</div>
<div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
onClick={exportLocalBackup} onClick={exportLocalBackup}
@@ -164,8 +182,8 @@ export function PreferencesInternal({
i18n('icu:Preferences__internal__export-local-backup') i18n('icu:Preferences__internal__export-local-backup')
)} )}
</Button> </Button>
} </div>
/> </FlowingSettingsControl>
{renderValidationResult(exportResult)} {renderValidationResult(exportResult)}
</SettingsRow> </SettingsRow>
@@ -174,9 +192,17 @@ export function PreferencesInternal({
className="Preferences--internal--message-schemas" className="Preferences--internal--message-schemas"
title="Message schema versions" title="Message schema versions"
> >
<SettingsControl <FlowingSettingsControl>
left="Check message schema versions" <div className="Preferences__two-thirds-flow">
right={ Check message schema versions
</div>
<div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
onClick={async () => { onClick={async () => {
@@ -189,8 +215,8 @@ export function PreferencesInternal({
> >
Fetch data Fetch data
</Button> </Button>
} </div>
/> </FlowingSettingsControl>
{messageCountBySchemaVersion ? ( {messageCountBySchemaVersion ? (
<div className="Preferences--internal--result"> <div className="Preferences--internal--result">

View File

@@ -10,8 +10,13 @@ import React, {
useRef, useRef,
} from 'react'; } from 'react';
import { noop } from 'lodash'; import { noop } from 'lodash';
import classNames from 'classnames';
import type { LocalizerType } from '../types/I18N'; import type { LocalizerType } from '../types/I18N';
import { SettingsControl as Control, SettingsRow } from './PreferencesUtil'; import {
FlowingSettingsControl as FlowingControl,
SettingsRow,
} from './PreferencesUtil';
import { Button, ButtonSize, ButtonVariant } from './Button'; import { Button, ButtonSize, ButtonVariant } from './Button';
import { SIGNAL_BACKUPS_LEARN_MORE_URL } from './PreferencesBackups'; import { SIGNAL_BACKUPS_LEARN_MORE_URL } from './PreferencesBackups';
import { I18n } from './I18n'; import { I18n } from './I18n';
@@ -87,42 +92,54 @@ export function PreferencesLocalBackups({
</div> </div>
</div> </div>
<SettingsRow className="Preferences--BackupsRow"> <SettingsRow className="Preferences--BackupsRow">
<Control <FlowingControl>
left={ <div className="Preferences__two-thirds-flow">
<label> <label>
{i18n('icu:Preferences__local-backups-folder')} {i18n('icu:Preferences__local-backups-folder')}
<div className="Preferences__description"> <div className="Preferences__description">
{localBackupFolder} {localBackupFolder}
</div> </div>
</label> </label>
} </div>
right={ <div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
onClick={pickLocalBackupFolder} onClick={pickLocalBackupFolder}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
> >
{i18n('icu:Preferences__local-backups-folder__change')} {i18n('icu:Preferences__local-backups-folder__change')}
</Button> </Button>
} </div>
/> </FlowingControl>
<Control <FlowingControl>
left={ <div className="Preferences__two-thirds-flow">
<label> <label>
{i18n('icu:Preferences__backup-key')} {i18n('icu:Preferences__backup-key')}
<div className="Preferences__description"> <div className="Preferences__description">
{i18n('icu:Preferences__backup-key-description')} {i18n('icu:Preferences__backup-key-description')}
</div> </div>
</label> </label>
} </div>
right={ <div
className={classNames(
'Preferences__flow-button',
'Preferences__one-third-flow',
'Preferences__one-third-flow--align-right'
)}
>
<Button <Button
onClick={() => setPage(Page.LocalBackupsKeyReference)} onClick={() => setPage(Page.LocalBackupsKeyReference)}
variant={ButtonVariant.Secondary} variant={ButtonVariant.Secondary}
> >
{i18n('icu:Preferences__view-key')} {i18n('icu:Preferences__view-key')}
</Button> </Button>
} </div>
/> </FlowingControl>
</SettingsRow> </SettingsRow>
<SettingsRow className="Preferences--BackupsRow"> <SettingsRow className="Preferences--BackupsRow">
<div className="Preferences__padding"> <div className="Preferences__padding">

View File

@@ -27,19 +27,42 @@ export function SettingsRow({
); );
} }
export function FlowingSettingsControl({
children,
}: {
children: ReactNode;
}): JSX.Element {
return <div className="Preferences__flow-control">{children}</div>;
}
export function LightIconLabel({
icon,
children,
}: {
icon: string;
children: ReactNode;
}): JSX.Element {
return (
<label className="Preferences__light-icon-label">
<div className={classNames('Preferences__control--icon', icon)} />
<div>{children}</div>
</label>
);
}
export function SettingsControl({ export function SettingsControl({
icon, icon,
left, left,
onClick, onClick,
right, right,
rightStyle, description,
}: { }: {
/** 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; description?: boolean;
}): JSX.Element { }): JSX.Element {
const content = ( const content = (
<> <>
@@ -52,9 +75,10 @@ export function SettingsControl({
/> />
)} )}
<div className="Preferences__control--key">{left}</div> <div className="Preferences__control--key">{left}</div>
<div className="Preferences__control--value" style={rightStyle}> <div className="Preferences__control--value">{right}</div>
{right} {description ? (
</div> <div className="Preferences__control--value">{description}</div>
) : undefined}
</> </>
); );