Migrate all preferences buttons to axo button

This commit is contained in:
Jamie
2025-10-17 12:43:08 -07:00
committed by GitHub
parent 5d1a9d22f0
commit 0906da9806
18 changed files with 371 additions and 485 deletions

View File

@@ -819,10 +819,6 @@ $secondary-text-color: light-dark(
}
}
.Preferences--BackupsAuthButton[disabled] {
cursor: auto;
}
.Preferences--BackupsRow {
padding-block: 8px;
margin-block-start: 8px;
@@ -915,6 +911,7 @@ $secondary-text-color: light-dark(
}
.Preferences--LocalBackupsSetupScreenPane-top {
flex-grow: 0;
min-height: 154px;
}
@@ -923,25 +920,6 @@ $secondary-text-color: light-dark(
width: 100%;
}
.Preferences--LocalBackupsSetupScreenCopyButton {
@include mixins.font-body-small;
padding-inline: 15px 21px;
font-weight: 500;
vertical-align: text-top;
&::before {
content: '';
display: inline-block;
height: 16px;
width: 16px;
margin-inline-end: 6px;
@include mixins.color-svg(
'../images/icons/v3/copy/copy-compact.svg',
variables.$color-black
);
}
}
.Preferences--LocalBackupsSetupScreenPane-footer {
flex-direction: row;
flex-grow: 0;
@@ -958,30 +936,14 @@ $secondary-text-color: light-dark(
justify-content: right;
}
.Preferences--LocalBackupsSetupScreenFooterSeeKeyButton {
@include mixins.font-body-1-bold;
padding-block: 0;
padding-inline: 0;
background: none;
border: none;
outline: none;
color: variables.$color-ultramarine;
@include mixins.keyboard-mode {
&:focus {
outline: 2px solid variables.$color-ultramarine;
}
}
}
.Preferences--LocalBackupsSetupScreenFooterButton {
padding-inline: 34px;
}
.Preferences--LocalBackupsSetupScreenBody {
@include mixins.font-body-1;
margin-block: 8px;
color: $secondary-text-color;
a {
text-decoration: none;
}
}
.Preferences--LocalBackupsSetupScreenBody--folder {
@@ -1082,237 +1044,6 @@ $secondary-text-color: light-dark(
color: $secondary-text-color;
}
.Preferences--LocalBackupsConfirmKeyModalButton {
padding-inline: 32px;
}
.Preferences--LocalBackupsConfirmKeyModal .module-Modal__button-footer {
justify-content: center;
}
.Preferences__BackupsIcon {
@include mixins.light-theme {
@include mixins.color-svg(
'../images/icons/v3/signal_backups/signal_backups.svg',
variables.$color-gray-75
);
}
@include mixins.dark-theme {
@include mixins.color-svg(
'../images/icons/v3/signal_backups/signal_backups.svg',
variables.$color-gray-15
);
}
}
.Preferences__LocalBackupsIcon {
@include mixins.light-theme {
@include mixins.color-svg(
'../images/icons/v3/device/device-laptop.svg',
variables.$color-gray-75
);
}
@include mixins.dark-theme {
@include mixins.color-svg(
'../images/icons/v3/device/device-laptop.svg',
variables.$color-gray-15
);
}
}
.Preferences--LocalBackupsSetupScreen {
display: flex;
flex-direction: column;
text-align: center;
}
.Preferences--LocalBackupsSetupScreenHeader {
@include mixins.font-title-2;
margin-block: 8px;
}
.Preferences--LocalBackupsSetupScreenPane {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.Preferences--LocalBackupsSetupScreenPane-top {
flex-grow: 0;
min-height: 154px;
}
.Preferences--LocalBackupsSetupScreenPaneContent {
display: block;
width: 100%;
}
.Preferences--LocalBackupsSetupScreenCopyButton {
@include mixins.font-body-small;
padding-inline: 15px 21px;
font-weight: 500;
vertical-align: text-top;
&::before {
content: '';
display: inline-block;
height: 16px;
width: 16px;
margin-inline-end: 6px;
@include mixins.color-svg(
'../images/icons/v3/copy/copy-compact.svg',
variables.$color-black
);
}
}
.Preferences--LocalBackupsSetupScreenPane-footer {
flex-direction: row;
flex-grow: 0;
flex-shrink: 1;
}
.Preferences--LocalBackupsSetupScreenFooterSection {
display: flex;
flex-grow: 1;
}
.Preferences--LocalBackupsSetupScreenFooterSection-right {
justify-content: right;
}
.Preferences--LocalBackupsSetupScreenFooterSeeKeyButton {
@include mixins.font-body-1-bold;
padding-block: 0;
padding-inline: 0;
background: none;
border: none;
outline: none;
color: variables.$color-ultramarine;
@include mixins.keyboard-mode {
&:focus {
outline: 2px solid variables.$color-ultramarine;
}
}
}
.Preferences--LocalBackupsSetupScreenFooterButton {
padding-inline: 34px;
}
.Preferences--LocalBackupsSetupScreenBody {
@include mixins.font-body-1;
margin-block: 8px;
color: $secondary-text-color;
}
.Preferences--LocalBackupsSetupScreenBody a {
text-decoration: none;
}
.Preferences--LocalBackupsSetupScreenBody--folder {
margin-block-end: 57px;
}
.Preferences--LocalBackupsBackupKey {
width: 274px;
height: 201px;
padding-block: 28px;
padding-inline: 36px;
margin-block: 28px 20px;
background: variables.$color-gray-02;
border-radius: 12px;
border-width: 0;
outline: none;
color: variables.$color-gray-90;
font-family: variables.$monospace;
font-size: 16px;
font-weight: 400;
line-height: 36.128px;
letter-spacing: 0.624px;
overflow: hidden;
resize: none;
word-break: break-all;
text-transform: uppercase;
&::placeholder {
color: variables.$color-gray-45;
text-transform: none;
}
}
.Preferences--LocalBackupsSetupIcon {
display: inline-flex;
width: 64px;
height: 64px;
border-radius: 64px;
background: variables.$color-ultramarine-pale;
align-items: center;
justify-content: center;
&::before {
height: 38px;
width: 38px;
content: '';
}
}
.Preferences--LocalBackupsSetupIcon-folder {
margin-block-start: 60px;
margin-block-end: 12px;
&::before {
@include mixins.color-svg(
'../images/icons/v3/folder/folder.svg',
variables.$color-ultramarine-logo
);
}
}
.Preferences--LocalBackupsSetupIcon-key {
&::before {
@include mixins.color-svg(
'../images/icons/v3/key/key.svg',
variables.$color-ultramarine-logo
);
}
}
.Preferences--LocalBackupsSetupIcon-lock {
&::before {
@include mixins.color-svg(
'../images/icons/v3/lock/lock.svg',
variables.$color-ultramarine-logo
);
}
}
.Preferences--LocalBackupsConfirmKeyModal {
padding-block: 36px 20px;
padding-inline: 32px;
text-align: center;
}
.Preferences--LocalBackupsConfirmKeyModal__body {
padding: 0;
}
.Preferences--LocalBackupsConfirmKeyModalTitle {
@include mixins.font-title-medium;
margin-block: 12px;
}
.Preferences--LocalBackupsConfirmKeyModalBody {
@include mixins.font-body-1;
margin-block: 8px 32px;
color: $secondary-text-color;
}
.Preferences--LocalBackupsConfirmKeyModalButton {
padding-inline: 32px;
}
.Preferences--LocalBackupsConfirmKeyModal .module-Modal__button-footer {
justify-content: center;
}

View File

@@ -42,10 +42,6 @@
}
}
&__donate-button {
margin-block-end: 32px;
}
&__separator {
width: 100%;
height: 0.5px;
@@ -466,15 +462,6 @@
}
}
.PreferencesDonations__PrimaryButton {
@include mixins.font-body-2;
padding-block: 5px;
padding-inline: 12px;
font-weight: 400;
border: 0.5px solid variables.$color-black-alpha-16;
border-radius: 6px;
}
.PreferencesDonations__badge-list {
width: 100%;
margin-block: 4px 8px;

View File

@@ -392,12 +392,3 @@
justify-content: center;
margin-block-end: 16px;
}
.ProfileEditor__EditPhoto {
@include mixins.font-subtitle;
padding-block: 5px;
padding-inline: 10px;
border-radius: 14px;
font-weight: 600;
}

View File

@@ -1,6 +1,6 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import React, { useState } from 'react';
import type { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import {
@@ -9,6 +9,7 @@ import {
AxoButton,
} from './AxoButton.dom.js';
import { tw } from './tw.dom.js';
import { AxoSwitch } from './AxoSwitch.dom.js';
export default {
title: 'Axo/AxoButton',
@@ -89,3 +90,46 @@ export function Basic(): JSX.Element {
</div>
);
}
export function Spinner(): JSX.Element {
const sizes = _getAllAxoButtonSizes();
const variants = _getAllAxoButtonVariants();
const [loading, setLoading] = useState(true);
function handleClick() {
setLoading(true);
}
return (
<>
<div className={tw('mb-4 flex gap-2')}>
<AxoSwitch.Root checked={loading} onCheckedChange={setLoading} />
<span>Loading</span>
</div>
<div className={tw('flex flex-col gap-2')}>
{sizes.map(size => {
return (
<div key={size} className={tw('flex gap-2')}>
{variants.map(variant => {
return (
<AxoButton.Root
variant={variant}
size={size}
disabled={loading}
experimentalSpinner={
loading ? { 'aria-label': 'Loading' } : null
}
onClick={handleClick}
>
Save
</AxoButton.Root>
);
})}
</div>
);
})}
</div>
</>
);
}

View File

@@ -6,11 +6,13 @@ import type { TailwindStyles } from './tw.dom.js';
import { tw } from './tw.dom.js';
import { AxoSymbol } from './AxoSymbol.dom.js';
import { assert } from './_internal/assert.dom.js';
import type { SpinnerVariant } from '../components/SpinnerV2.dom.js';
import { SpinnerV2 } from '../components/SpinnerV2.dom.js';
const Namespace = 'AxoButton';
const baseAxoButtonStyles = tw(
'flex items-center-safe justify-center-safe gap-1 truncate rounded-full select-none',
'relative inline-flex items-center-safe justify-center-safe rounded-full select-none',
'outline-0 outline-border-focused focused:outline-[2.5px]',
'forced-colors:border'
);
@@ -126,9 +128,9 @@ const AxoButtonVariants = {
};
const AxoButtonSizes = {
large: tw('px-4 py-2 type-body-medium font-medium'),
medium: tw('px-3 py-1.5 type-body-medium font-medium'),
small: tw('px-2 py-1 type-body-small font-medium'),
large: tw('min-w-16 px-4 py-2 type-body-medium font-medium'),
medium: tw('min-w-14 px-3 py-1.5 type-body-medium font-medium'),
small: tw('min-w-12 px-2 py-1 type-body-small font-medium'),
} as const satisfies Record<string, TailwindStyles>;
type BaseButtonAttrs = Omit<
@@ -147,21 +149,82 @@ export function _getAllAxoButtonSizes(): ReadonlyArray<AxoButtonSize> {
return Object.keys(AxoButtonSizes) as Array<AxoButtonSize>;
}
const AxoButtonSpinnerVariants: Record<AxoButtonVariant, SpinnerVariant> = {
primary: 'axo-button-spinner-on-color',
secondary: 'axo-button-spinner-secondary',
affirmative: 'axo-button-spinner-on-color',
destructive: 'axo-button-spinner-on-color',
'subtle-primary': 'axo-button-spinner-primary',
'subtle-affirmative': 'axo-button-spinner-affirmative',
'subtle-destructive': 'axo-button-spinner-destructive',
'floating-primary': 'axo-button-spinner-primary',
'floating-secondary': 'axo-button-spinner-secondary',
'floating-affirmative': 'axo-button-spinner-affirmative',
'floating-destructive': 'axo-button-spinner-destructive',
'borderless-primary': 'axo-button-spinner-primary',
'borderless-secondary': 'axo-button-spinner-secondary',
'borderless-affirmative': 'axo-button-spinner-affirmative',
'borderless-destructive': 'axo-button-spinner-destructive',
};
const AxoButtonSpinnerSizes: Record<
AxoButtonSize,
{ size: number; strokeWidth: number }
> = {
large: { size: 20, strokeWidth: 2 },
medium: { size: 20, strokeWidth: 2 },
small: { size: 16, strokeWidth: 1.5 },
};
type ExperimentalButtonSpinnerProps = Readonly<{
buttonVariant: AxoButtonVariant;
buttonSize: AxoButtonSize;
'aria-label': string;
}>;
function ExperimentalButtonSpinner(
props: ExperimentalButtonSpinnerProps
): JSX.Element {
const variant = AxoButtonSpinnerVariants[props.buttonVariant];
const sizeConfig = AxoButtonSpinnerSizes[props.buttonSize];
return (
<span className={tw('absolute inset-0 flex items-center justify-center')}>
<SpinnerV2
size={sizeConfig.size}
strokeWidth={sizeConfig.strokeWidth}
variant={variant}
value="indeterminate"
ariaLabel={props['aria-label']}
/>
</span>
);
}
export namespace AxoButton {
export type Variant = AxoButtonVariant;
export type Size = AxoButtonSize;
export type RootProps = BaseButtonAttrs &
Readonly<{
variant: AxoButtonVariant;
size: AxoButtonSize;
symbol?: AxoSymbol.InlineGlyphName;
arrow?: boolean;
experimentalSpinner?: { 'aria-label': string } | null;
children: ReactNode;
}>;
export const Root: FC<RootProps> = memo(
forwardRef((props, ref: ForwardedRef<HTMLButtonElement>) => {
const { variant, size, symbol, arrow, children, ...rest } = props;
const {
variant,
size,
symbol,
arrow,
experimentalSpinner,
children,
...rest
} = props;
const variantStyles = assert(
AxoButtonVariants[variant],
`${Namespace}: Invalid variant ${variant}`
@@ -170,6 +233,7 @@ export namespace AxoButton {
AxoButtonSizes[size],
`${Namespace}: Invalid size ${size}`
);
return (
<button
ref={ref}
@@ -177,12 +241,26 @@ export namespace AxoButton {
className={tw(variantStyles, sizeStyles)}
{...rest}
>
{symbol != null && (
<AxoSymbol.InlineGlyph symbol={symbol} label={null} />
)}
{children}
{arrow && (
<AxoSymbol.InlineGlyph symbol="chevron-[end]" label={null} />
<span
className={tw(
'flex shrink grow items-center-safe justify-center-safe gap-1 truncate',
experimentalSpinner != null ? 'opacity-0' : null
)}
>
{symbol != null && (
<AxoSymbol.InlineGlyph symbol={symbol} label={null} />
)}
{children}
{arrow && (
<AxoSymbol.InlineGlyph symbol="chevron-[end]" label={null} />
)}
</span>
{experimentalSpinner != null && (
<ExperimentalButtonSpinner
buttonVariant={variant}
buttonSize={size}
aria-label={experimentalSpinner['aria-label']}
/>
)}
</button>
);

View File

@@ -2,11 +2,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import { Button, ButtonVariant } from './Button.dom.js';
import { ConfirmDiscardDialog } from './ConfirmDiscardDialog.dom.js';
import type { LocalizerType } from '../types/Util.std.js';
import { Modal } from './Modal.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
export type PropsType = {
hasChanges: boolean;
@@ -27,7 +26,9 @@ export function AvatarModalButtons({
return (
<Modal.ButtonFooter>
<Button
<AxoButton.Root
variant="secondary"
size="large"
onClick={() => {
if (hasChanges) {
setConfirmDiscardAction(() => onCancel);
@@ -35,13 +36,17 @@ export function AvatarModalButtons({
onCancel();
}
}}
variant={ButtonVariant.Secondary}
>
{i18n('icu:cancel')}
</Button>
<Button disabled={!hasChanges} onClick={onSave}>
</AxoButton.Root>
<AxoButton.Root
variant="primary"
size="large"
disabled={!hasChanges}
onClick={onSave}
>
{i18n('icu:save')}
</Button>
</AxoButton.Root>
{confirmDiscardAction && (
<ConfirmDiscardDialog
i18n={i18n}

View File

@@ -16,8 +16,6 @@ import * as LocaleMatcher from '@formatjs/intl-localematcher';
import type { MutableRefObject, ReactNode } from 'react';
import type { RowType } from '@signalapp/sqlcipher';
import type { BackupLevel } from '@signalapp/libsignal-client/zkgroup.js';
import { Button, ButtonVariant } from './Button.dom.js';
import { ChatColorPicker } from './ChatColorPicker.dom.js';
import { Checkbox } from './Checkbox.dom.js';
import { WidthBreakpoint } from './_util.std.js';
@@ -26,7 +24,6 @@ import { DisappearingTimeDialog } from './DisappearingTimeDialog.dom.js';
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability.std.js';
import { PhoneNumberSharingMode } from '../types/PhoneNumberSharingMode.std.js';
import { Select } from './Select.dom.js';
import { Spinner } from './Spinner.dom.js';
import { getCustomColorStyle } from '../util/getCustomColorStyle.dom.js';
import {
DEFAULT_DURATIONS_IN_SECONDS,
@@ -949,21 +946,23 @@ export function Preferences({
}
modalFooter={
<>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={closeLanguageDialog}
>
{i18n('icu:cancel')}
</Button>
<Button
variant={ButtonVariant.Primary}
</AxoButton.Root>
<AxoButton.Root
variant="primary"
size="large"
disabled={selectedLanguageLocale === localeOverride}
onClick={() => {
setLanguageDialog(LanguageDialog.Confirmation);
}}
>
{i18n('icu:Preferences__LanguageModal__Set')}
</Button>
</AxoButton.Root>
</>
}
>
@@ -1210,7 +1209,7 @@ export function Preferences({
}
right={
<AxoButton.Root
size="medium"
size="large"
variant="secondary"
onClick={() => {
setSettingsLocation({
@@ -1254,12 +1253,13 @@ export function Preferences({
}
right={
<div className="Preferences__right-button">
<Button
aria-label={
nowSyncing ? i18n('icu:syncing') : i18n('icu:syncNow')
}
aria-live="polite"
<AxoButton.Root
variant="secondary"
size="large"
disabled={nowSyncing}
experimentalSpinner={
nowSyncing ? { 'aria-label': i18n('icu:syncing') } : null
}
onClick={async () => {
setShowSyncFailed(false);
setNowSyncing(true);
@@ -1272,14 +1272,9 @@ export function Preferences({
setNowSyncing(false);
}
}}
variant={ButtonVariant.SecondaryAffirmative}
>
{nowSyncing ? (
<Spinner svgSize="small" />
) : (
i18n('icu:syncNow')
)}
</Button>
{i18n('icu:syncNow')}
</AxoButton.Root>
</div>
}
/>
@@ -1558,11 +1553,9 @@ export function Preferences({
</>
}
right={
<Button
testId="OnboardNotificationProfiles"
aria-label={i18n('icu:NotificationProfiles--setup')}
aria-live="polite"
variant={ButtonVariant.SecondaryAffirmative}
<AxoButton.Root
variant="secondary"
size="large"
onClick={() =>
setSettingsLocation({
page: SettingsPage.NotificationProfilesHome,
@@ -1570,7 +1563,7 @@ export function Preferences({
}
>
{i18n('icu:NotificationProfiles--setup')}
</Button>
</AxoButton.Root>
}
/>
</SettingsRow>
@@ -1610,12 +1603,13 @@ export function Preferences({
'Preferences__one-third-flow--align-right'
)}
>
<Button
<AxoButton.Root
variant="secondary"
size="large"
onClick={() => setSettingsLocation({ page: SettingsPage.PNP })}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__pnp__row--button')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
</SettingsRow>
@@ -1767,20 +1761,21 @@ export function Preferences({
)}
>
{hasStoriesDisabled ? (
<Button
<AxoButton.Root
onClick={() => onHasStoriesDisabledChanged(false)}
variant={ButtonVariant.Secondary}
variant="secondary"
size="large"
>
{i18n('icu:Preferences__turn-stories-on')}
</Button>
</AxoButton.Root>
) : (
<Button
className="Preferences__stories-off"
<AxoButton.Root
onClick={() => setConfirmStoriesOff(true)}
variant={ButtonVariant.SecondaryDestructive}
variant="subtle-destructive"
size="large"
>
{i18n('icu:Preferences__turn-stories-off')}
</Button>
</AxoButton.Root>
)}
</div>
</FlowingControl>
@@ -1807,12 +1802,13 @@ export function Preferences({
'Preferences__one-third-flow--align-right'
)}
>
<Button
<AxoButton.Root
variant="subtle-destructive"
size="large"
onClick={() => setConfirmDelete(true)}
variant={ButtonVariant.SecondaryDestructive}
>
{i18n('icu:clearDataButton')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
</SettingsRow>

View File

@@ -16,7 +16,7 @@ import {
LightIconLabel,
SettingsRow,
} from './PreferencesUtil.dom.js';
import { Button, ButtonVariant } from './Button.dom.js';
import { ButtonVariant } from './Button.dom.js';
import type { SettingsLocation } from '../types/Nav.std.js';
import { SettingsPage } from '../types/Nav.std.js';
import { I18n } from './I18n.dom.js';
@@ -27,6 +27,7 @@ import type {
PromptOSAuthResultType,
} from '../util/os/promptOSAuthMain.main.js';
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
import { BackupLevel } from '../services/backups/types.std.js';
import {
BackupsDetailsPage,
@@ -230,14 +231,15 @@ export function PreferencesBackups({
'Preferences__one-third-flow--align-right'
)}
>
<Button
<AxoButton.Root
variant="secondary"
size="large"
onClick={() =>
setSettingsLocation({ page: SettingsPage.BackupsDetails })
}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__button--manage')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
</SettingsRow>
@@ -273,8 +275,9 @@ export function PreferencesBackups({
'Preferences__one-third-flow--align-right'
)}
>
<Button
className="Preferences--BackupsAuthButton"
<AxoButton.Root
variant="secondary"
size="large"
disabled={isAuthPending}
onClick={async () => {
setAuthError(undefined);
@@ -294,12 +297,11 @@ export function PreferencesBackups({
setSettingsLocation({ page: SettingsPage.LocalBackups });
}}
variant={ButtonVariant.Secondary}
>
{isLocalBackupsSetup
? i18n('icu:Preferences__button--manage')
: i18n('icu:Preferences__button--set-up')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
</SettingsRow>

View File

@@ -13,7 +13,6 @@ import React, {
import classNames from 'classnames';
import type { LocalizerType } from '../types/Util.std.js';
import { useConfirmDiscard } from '../hooks/useConfirmDiscard.dom.js';
import { Button, ButtonVariant } from './Button.dom.js';
import {
donationStateSchema,
ONE_TIME_DONATION_CONFIG_ID,
@@ -70,6 +69,7 @@ import { DonationsOfflineTooltip } from './conversation/DonationsOfflineTooltip.
import { DonateInputAmount } from './preferences/donations/DonateInputAmount.dom.js';
import { Tooltip, TooltipPlacement } from './Tooltip.dom.js';
import { offsetDistanceModifier } from '../util/popperUtil.std.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
const SUPPORT_URL = 'https://support.signal.org/hc/requests/new?desktop';
@@ -516,14 +516,14 @@ function AmountPicker({
}
const continueButton = (
<Button
className="PreferencesDonations__PrimaryButton"
<AxoButton.Root
variant={isOnline ? 'primary' : 'secondary'}
size="large"
disabled={!isContinueEnabled}
onClick={handleContinueClicked}
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
>
{i18n('icu:DonateFlow__continue')}
</Button>
</AxoButton.Root>
);
let continueButtonWithTooltip: JSX.Element | undefined;
@@ -759,16 +759,16 @@ function CardForm({
}, [handleDonateClicked, isDonateDisabled]);
const donateButton = (
<Button
className="PreferencesDonations__PrimaryButton"
<AxoButton.Root
disabled={isDonateDisabled}
onClick={handleDonateClicked}
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
variant={isOnline ? 'primary' : 'secondary'}
size="large"
>
{i18n('icu:PreferencesDonations__donate-button-with-amount', {
formattedCurrencyAmount,
})}
</Button>
</AxoButton.Root>
);
return (

View File

@@ -55,6 +55,8 @@ import type { AvatarUpdateOptionsType } from '../types/Avatar.std.js';
import { drop } from '../util/drop.std.js';
import { DonationsOfflineTooltip } from './conversation/DonationsOfflineTooltip.dom.js';
import { getInProgressDonation } from '../util/donations.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
import { tw } from '../axo/tw.dom.js';
const { groupBy, sortBy } = lodash;
@@ -234,15 +236,16 @@ function DonationsHome({
const hasReceipts = donationReceipts.length > 0;
const donateButton = (
<Button
className="PreferencesDonations__PrimaryButton PreferencesDonations__donate-button"
disabled={!isOnline}
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
size={ButtonSize.Medium}
onClick={handleDonateButtonClicked}
>
{i18n('icu:PreferencesDonations__donate-button')}
</Button>
<span className={tw('mb-8')}>
<AxoButton.Root
variant={isOnline ? 'primary' : 'secondary'}
size="medium"
disabled={!isOnline}
onClick={handleDonateButtonClicked}
>
{i18n('icu:PreferencesDonations__donate-button')}
</AxoButton.Root>
</span>
);
return (

View File

@@ -12,8 +12,6 @@ import { formatFileSize } from '../util/formatFileSize.std.js';
import { SECOND } from '../util/durations/index.std.js';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups/index.preload.js';
import { SettingsRow, FlowingSettingsControl } from './PreferencesUtil.dom.js';
import { Button, ButtonVariant } from './Button.dom.js';
import { Spinner } from './Spinner.dom.js';
import type { MessageCountBySchemaVersionType } from '../sql/Interface.std.js';
import type { MessageAttributesType } from '../model-types.d.ts';
import type { DonationReceipt } from '../types/Donations.std.js';
@@ -21,6 +19,7 @@ import { createLogger } from '../logging/log.std.js';
import { isStagingServer } from '../util/isStagingServer.dom.js';
import { getHumanDonationAmount } from '../util/currency.dom.js';
import { AutoSizeTextArea } from './AutoSizeTextArea.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
const log = createLogger('PreferencesInternal');
@@ -240,17 +239,19 @@ export function PreferencesInternal({
'Preferences__one-third-flow--align-right'
)}
>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={validateBackup}
disabled={isValidationPending}
experimentalSpinner={
isValidationPending
? { 'aria-label': i18n('icu:loading') }
: null
}
>
{isValidationPending ? (
<Spinner size="22px" svgSize="small" />
) : (
i18n('icu:Preferences__internal__validate-backup')
)}
</Button>
{i18n('icu:Preferences__internal__validate-backup')}
</AxoButton.Root>
</div>
</FlowingSettingsControl>
@@ -274,17 +275,17 @@ export function PreferencesInternal({
'Preferences__one-third-flow--align-right'
)}
>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={exportLocalBackup}
disabled={isExportPending}
experimentalSpinner={
isExportPending ? { 'aria-label': i18n('icu:loading') } : null
}
>
{isExportPending ? (
<Spinner size="22px" svgSize="small" />
) : (
i18n('icu:Preferences__internal__export-local-backup')
)}
</Button>
{i18n('icu:Preferences__internal__export-local-backup')}
</AxoButton.Root>
</div>
</FlowingSettingsControl>
@@ -306,8 +307,9 @@ export function PreferencesInternal({
'Preferences__one-third-flow--align-right'
)}
>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={async () => {
setMessageCountBySchemaVersion(
await getMessageCountBySchemaVersion()
@@ -317,7 +319,7 @@ export function PreferencesInternal({
disabled={isExportPending}
>
Fetch data
</Button>
</AxoButton.Root>
</div>
</FlowingSettingsControl>
@@ -400,12 +402,13 @@ export function PreferencesInternal({
'Preferences__one-third-flow--align-right'
)}
>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={handleAddTestReceipt}
>
Add Test Receipt
</Button>
</AxoButton.Root>
</div>
</FlowingSettingsControl>
@@ -450,17 +453,19 @@ export function PreferencesInternal({
{receipt.id.substring(0, 8)}...
</td>
<td style={{ padding: '8px' }}>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
variant="secondary"
size="large"
onClick={() => handleGenerateReceipt(receipt)}
disabled={isGeneratingReceipt}
experimentalSpinner={
isGeneratingReceipt
? { 'aria-label': i18n('icu:loading') }
: null
}
>
{isGeneratingReceipt ? (
<Spinner size="16px" svgSize="small" />
) : (
'Download'
)}
</Button>
Download
</AxoButton.Root>
</td>
</tr>
))}
@@ -486,12 +491,13 @@ export function PreferencesInternal({
placeholder="SELECT * FROM items"
moduleClassName="Preferences__ReadonlySqlPlayground__Textarea"
/>
<Button
variant={ButtonVariant.Destructive}
<AxoButton.Root
variant="destructive"
size="large"
onClick={handleReadOnlySqlInputSubmit}
>
Run Query
</Button>
</AxoButton.Root>
{readOnlySqlResults != null && (
<AutoSizeTextArea
i18n={i18n}

View File

@@ -17,7 +17,7 @@ import {
FlowingSettingsControl as FlowingControl,
SettingsRow,
} from './PreferencesUtil.dom.js';
import { Button, ButtonSize, ButtonVariant } from './Button.dom.js';
import { ButtonVariant } from './Button.dom.js';
import {
getOSAuthErrorString,
SIGNAL_BACKUPS_LEARN_MORE_URL,
@@ -34,6 +34,7 @@ import type {
PromptOSAuthResultType,
} from '../util/os/promptOSAuthMain.main.js';
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
const { noop } = lodash;
@@ -129,12 +130,13 @@ export function PreferencesLocalBackups({
'Preferences__one-third-flow--align-right'
)}
>
<Button
<AxoButton.Root
variant="secondary"
size="large"
onClick={pickLocalBackupFolder}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__local-backups-folder__change')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
<FlowingControl>
@@ -153,9 +155,13 @@ export function PreferencesLocalBackups({
'Preferences__one-third-flow--align-right'
)}
>
<Button
className="Preferences--BackupsAuthButton"
<AxoButton.Root
variant="secondary"
size="large"
disabled={isAuthPending}
experimentalSpinner={
isAuthPending ? { 'aria-label': i18n('icu:loading') } : null
}
onClick={async () => {
setAuthError(undefined);
@@ -173,10 +179,9 @@ export function PreferencesLocalBackups({
setIsAuthPending(false);
}
}}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__view-key')}
</Button>
</AxoButton.Root>
</div>
</FlowingControl>
</SettingsRow>
@@ -226,9 +231,13 @@ function LocalBackupsSetupFolderPicker({
<div className="Preferences--LocalBackupsSetupScreenBody Preferences--LocalBackupsSetupScreenBody--folder">
{i18n('icu:Preferences--local-backups-setup-folder-description')}
</div>
<Button onClick={pickLocalBackupFolder} variant={ButtonVariant.Primary}>
<AxoButton.Root
variant="primary"
size="large"
onClick={pickLocalBackupFolder}
>
{i18n('icu:Preferences__button--choose-folder')}
</Button>
</AxoButton.Root>
</div>
</div>
);
@@ -295,23 +304,23 @@ function LocalBackupsBackupKeyViewer({
);
if (step === 'view') {
footerRight = (
<Button
className="Preferences--LocalBackupsSetupScreenFooterButton"
<AxoButton.Root
variant="primary"
size="large"
onClick={() => setStep('confirm')}
variant={ButtonVariant.Primary}
>
{i18n('icu:Preferences--local-backups-setup-next')}
</Button>
</AxoButton.Root>
);
} else {
footerRight = (
<Button
className="Preferences--LocalBackupsSetupScreenFooterButton"
<AxoButton.Root
variant="primary"
size="large"
onClick={onBackupKeyViewed}
variant={ButtonVariant.Primary}
>
{i18n('icu:Preferences--local-backups-view-backup-key-done')}
</Button>
</AxoButton.Root>
);
}
} else {
@@ -320,23 +329,23 @@ function LocalBackupsBackupKeyViewer({
'icu:Preferences--local-backups-confirm-backup-key-description'
);
footerLeft = (
<button
className="Preferences--LocalBackupsSetupScreenFooterSeeKeyButton"
<AxoButton.Root
variant="borderless-primary"
size="large"
onClick={() => setStep('view')}
type="button"
>
{i18n('icu:Preferences--local-backups-see-backup-key-again')}
</button>
</AxoButton.Root>
);
footerRight = (
<Button
className="Preferences--LocalBackupsSetupScreenFooterButton"
<AxoButton.Root
variant="primary"
size="large"
disabled={!isBackupKeyConfirmed}
onClick={() => setStep('caution')}
variant={ButtonVariant.Primary}
>
{i18n('icu:Preferences--local-backups-setup-next')}
</Button>
</AxoButton.Root>
);
}
@@ -348,14 +357,15 @@ function LocalBackupsBackupKeyViewer({
modalName="CallingAdhocCallInfo.UnknownContactInfo"
moduleClassName="Preferences--LocalBackupsConfirmKeyModal"
modalFooter={
<Button
className="Preferences--LocalBackupsConfirmKeyModalButton"
<AxoButton.Root
variant="primary"
size="large"
onClick={onBackupKeyViewed}
>
{i18n(
'icu:Preferences__local-backups-confirm-key-modal-continue'
)}
</Button>
</AxoButton.Root>
}
onClose={() => setStep('confirm')}
padded={false}
@@ -391,14 +401,14 @@ function LocalBackupsBackupKeyViewer({
</div>
{isStepViewOrReference && (
<div className="Preferences--LocalBackupsSetupScreenPaneContent">
<Button
className="Preferences--LocalBackupsSetupScreenCopyButton"
<AxoButton.Root
variant="secondary"
size="small"
symbol="copy"
onClick={onCopyBackupKey}
size={ButtonSize.Small}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__local-backups-copy-key')}
</Button>
</AxoButton.Root>
</div>
)}
</div>

View File

@@ -15,7 +15,7 @@ import type { MutableRefObject } from 'react';
import { AvatarColors } from '../types/Colors.std.js';
import { AvatarEditor } from './AvatarEditor.dom.js';
import { AvatarPreview } from './AvatarPreview.dom.js';
import { Button, ButtonVariant } from './Button.dom.js';
import { ButtonVariant } from './Button.dom.js';
import { Input } from './Input.dom.js';
import { PanelRow } from './conversation/conversation-details/PanelRow.dom.js';
import { UsernameEditState } from '../state/ducks/usernameEnums.std.js';
@@ -68,6 +68,7 @@ import type { ShowToastAction } from '../state/ducks/toast.preload.js';
import type { EmojiParentKey, EmojiVariantKey } from './fun/data/emojis.std.js';
import type { FunEmojiSelection } from './fun/panels/FunPanelEmojis.dom.js';
import { useConfirmDiscard } from '../hooks/useConfirmDiscard.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
type ProfileEditorData = {
firstName: string;
@@ -407,10 +408,12 @@ export function ProfileEditor({
value={stagedProfile.familyName}
/>
<div className="ProfileEditor__button-footer">
<Button onClick={handleBack} variant={ButtonVariant.Secondary}>
<AxoButton.Root variant="secondary" size="large" onClick={handleBack}>
{i18n('icu:cancel')}
</Button>
<Button
</AxoButton.Root>
<AxoButton.Root
variant="primary"
size="large"
disabled={shouldDisableSave}
onClick={() => {
if (!stagedProfile.firstName) {
@@ -428,7 +431,7 @@ export function ProfileEditor({
}}
>
{i18n('icu:save')}
</Button>
</AxoButton.Root>
</div>
</>
);
@@ -513,10 +516,12 @@ export function ProfileEditor({
})}
<div className="ProfileEditor__button-footer">
<Button onClick={handleBack} variant={ButtonVariant.Secondary}>
<AxoButton.Root variant="secondary" size="large" onClick={handleBack}>
{i18n('icu:cancel')}
</Button>
<Button
</AxoButton.Root>
<AxoButton.Root
variant="primary"
size="large"
disabled={shouldDisableSave}
onClick={() => {
setFullBio({
@@ -531,7 +536,7 @@ export function ProfileEditor({
}}
>
{i18n('icu:save')}
</Button>
</AxoButton.Root>
</div>
</>
);
@@ -714,15 +719,15 @@ export function ProfileEditor({
}}
/>
<div className="ProfileEditor__EditPhotoContainer">
<Button
<AxoButton.Root
onClick={() => {
setEditState(ProfileEditorPage.BetterAvatar);
}}
variant={ButtonVariant.Secondary}
className="ProfileEditor__EditPhoto"
variant="secondary"
size="small"
>
{i18n('icu:ProfileEditor--edit-photo')}
</Button>
</AxoButton.Root>
</div>
<PanelRow
className="ProfileEditor__row"

View File

@@ -43,6 +43,26 @@ const SpinnerVariants = {
bg: tw('stroke-fill-secondary'),
fg: tw('stroke-border-selected'),
},
'axo-button-spinner-secondary': {
bg: tw('stroke-none'),
fg: tw('stroke-label-primary'),
},
'axo-button-spinner-on-color': {
bg: tw('stroke-none'),
fg: tw('stroke-label-primary-on-color'),
},
'axo-button-spinner-primary': {
bg: tw('stroke-none'),
fg: tw('stroke-color-label-primary'),
},
'axo-button-spinner-affirmative': {
bg: tw('stroke-none'),
fg: tw('stroke-color-label-affirmative'),
},
'axo-button-spinner-destructive': {
bg: tw('stroke-none'),
fg: tw('stroke-color-label-destructive'),
},
} as const satisfies Record<string, SpinnerVariantStyles>;
export type SpinnerVariant = keyof typeof SpinnerVariants;
@@ -81,6 +101,7 @@ export function SpinnerV2({
return (
<svg
className={tw('fill-none')}
aria-label={ariaLabel}
width={sizeInPixels}
height={sizeInPixels}
>

View File

@@ -26,7 +26,6 @@ import {
} from '../state/ducks/usernameEnums.std.js';
import type { ReserveUsernameOptionsType } from '../state/ducks/username.preload.js';
import type { ShowToastAction } from '../state/ducks/toast.preload.js';
import { AutoSizeInput } from './AutoSizeInput.dom.js';
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
import { Input } from './Input.dom.js';
@@ -34,6 +33,7 @@ import { Spinner } from './Spinner.dom.js';
import { Modal } from './Modal.dom.js';
import { Button, ButtonVariant } from './Button.dom.js';
import { useConfirmDiscard } from '../hooks/useConfirmDiscard.dom.js';
import { AxoButton } from '../axo/AxoButton.dom.js';
const { noop } = lodash;
@@ -382,20 +382,25 @@ export function UsernameEditor({
</button>
</div>
<div className="UsernameEditor__button-footer">
<Button
<AxoButton.Root
variant="secondary"
size="large"
disabled={isConfirming}
onClick={onCancel}
variant={ButtonVariant.Secondary}
>
{i18n('icu:cancel')}
</Button>
<Button disabled={!canSave} onClick={onSave}>
{isConfirming ? (
<Spinner size="20px" svgSize="small" direction="on-avatar" />
) : (
i18n('icu:save')
)}
</Button>
</AxoButton.Root>
<AxoButton.Root
variant="primary"
size="large"
disabled={!canSave}
onClick={onSave}
experimentalSpinner={
isConfirming ? { 'aria-label': i18n('icu:loading') } : null
}
>
{i18n('icu:save')}
</AxoButton.Root>
</div>
{confirmDiscardModal}

View File

@@ -8,7 +8,6 @@ import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges
import type { LocalizerType } from '../../../types/I18N.std.js';
import type { ThemeType } from '../../../types/Util.std.js';
import { Input } from '../../Input.dom.js';
import { Button, ButtonVariant } from '../../Button.dom.js';
import { ConfirmationDialog } from '../../ConfirmationDialog.dom.js';
import type { ChatFolderSelection } from '../PreferencesSelectChatsDialog.dom.js';
import { SettingsControl, SettingsRow } from '../../PreferencesUtil.dom.js';
@@ -47,6 +46,7 @@ import {
ItemContent,
ItemTitle,
} from './PreferencesChatFolderItems.dom.js';
import { AxoButton } from '../../../axo/AxoButton.dom.js';
export type PreferencesEditChatFolderPageProps = Readonly<{
i18n: LocalizerType;
@@ -550,19 +550,21 @@ export function PreferencesEditChatFolderPage(
title={i18n('icu:Preferences__EditChatFolderPage__Title')}
actions={
<>
<Button
variant={ButtonVariant.Secondary}
<AxoButton.Root
size="large"
variant="secondary"
onClick={handleDiscardAndBack}
>
{i18n('icu:Preferences__EditChatFolderPage__CancelButton')}
</Button>
<Button
variant={ButtonVariant.Primary}
</AxoButton.Root>
<AxoButton.Root
size="large"
variant="primary"
onClick={handleSaveChangesAndBack}
disabled={!(isChanged && isValid)}
>
{i18n('icu:Preferences__EditChatFolderPage__SaveButton')}
</Button>
</AxoButton.Root>
</>
}
/>

View File

@@ -214,7 +214,7 @@ describe('pnp/username', function (this: Mocha.Suite) {
debug('saving username');
let state = await phone.expectStorageState('consistency check');
await profileEditor.locator('.module-Button >> "Save"').click();
await profileEditor.getByRole('button', { name: 'Save' }).click();
debug('checking the username is saved');
{

View File

@@ -72,7 +72,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) {
const profileName = 'NewProfile';
debug('Starting Notification Profiles onboarding');
await window.getByTestId('OnboardNotificationProfiles').click();
await window.getByRole('button', { name: 'Set up' }).click();
debug('Dismiss onboarding dialog');
await window.getByRole('button', { name: 'Continue' }).click();
@@ -207,7 +207,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) {
await window.getByRole('button', { name: 'Notifications' }).click();
debug('Open Notification Profiles list page');
await window.getByTestId('OnboardNotificationProfiles').click();
await window.getByRole('button', { name: 'Set up' }).click();
debug('Dismiss onboarding dialog');
await window.getByRole('button', { name: 'Continue' }).click();