diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 5f2f9bbeba..8efa2ec7cd 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -2792,13 +2792,6 @@ button.ConversationDetails__action-button {
.module-image__progress-circle-wrapper {
@include mixins.position-absolute-center;
-
- .ProgressCircle .ProgressCircle__background {
- stroke: variables.$color-white-alpha-20;
- }
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-white;
- }
}
.module-image__spinner-container {
@@ -2954,7 +2947,7 @@ button.module-image__border-overlay:focus {
width: 24px;
@include mixins.color-svg(
'../images/icons/v3/play/play-fill.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
.module-image__stop-icon {
@@ -2965,7 +2958,7 @@ button.module-image__border-overlay:focus {
width: 24px;
@include mixins.color-svg(
'../images/icons/v3/stop/stop-fill.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
.module-image__download-icon {
@@ -2975,7 +2968,7 @@ button.module-image__border-overlay:focus {
width: 24px;
@include mixins.color-svg(
'../images/icons/v3/arrow/arrow-down.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
.module-image__undownloadable-icon {
@@ -2985,7 +2978,7 @@ button.module-image__border-overlay:focus {
width: 24px;
@include mixins.color-svg(
'../images/icons/v3/photo/photo-slash-compact.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
diff --git a/stylesheets/components/AttachmentDetailPill.scss b/stylesheets/components/AttachmentDetailPill.scss
index 94f00d54ef..f5f9d92be3 100644
--- a/stylesheets/components/AttachmentDetailPill.scss
+++ b/stylesheets/components/AttachmentDetailPill.scss
@@ -25,7 +25,7 @@
z-index: variables.$z-index-above-base;
@include mixins.font-caption;
- color: variables.$color-white;
+ color: var(--color-label-primary-on-color);
transition: width 400ms ease-out;
}
@@ -34,21 +34,6 @@
position: relative;
margin: 4px;
margin-inline-end: -4px;
-
- .ProgressCircle .ProgressCircle__background {
- stroke: variables.$color-white-alpha-20;
- }
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-white;
- }
-
- .module-spinner__circle {
- background-color: variables.$color-white-alpha-20;
- }
-
- .module-spinner__arc {
- background-color: variables.$color-white;
- }
}
.AttachmentDetailPill__text-wrapper {
@@ -72,7 +57,7 @@
width: 12px;
@include mixins.color-svg(
'../images/icons/v3/stop/stop-fill.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
.AttachmentDetailPill__download-icon {
@@ -82,6 +67,6 @@
width: 16px;
@include mixins.color-svg(
'../images/icons/v3/arrow/arrow-down.svg',
- variables.$color-white
+ var(--color-label-primary-on-color)
);
}
diff --git a/stylesheets/components/AttachmentStatusIcon.scss b/stylesheets/components/AttachmentStatusIcon.scss
index a39384aa7f..74ad1b2938 100644
--- a/stylesheets/components/AttachmentStatusIcon.scss
+++ b/stylesheets/components/AttachmentStatusIcon.scss
@@ -111,54 +111,22 @@
.AttachmentStatusIcon__circle-icon--x {
@include mixins.color-svg-themed(
'../images/icons/v3/x/x-bold.svg',
- variables.$color-white,
- variables.$color-white-alpha-90
+ var(--color-label-primary-on-color),
+ var(--color-label-primary-on-color)
);
}
.AttachmentStatusIcon__circle-icon--arrow-down {
@include mixins.color-svg-themed(
'../images/icons/v3/arrow/arrow-down-bold.svg',
- variables.$color-white,
- variables.$color-white-alpha-90
+ var(--color-label-primary-on-color),
+ var(--color-label-primary-on-color)
);
}
.AttachmentStatusIcon__circle-icon--incoming {
@include mixins.light-theme {
- background-color: variables.$color-gray-90;
+ background-color: var(--color-label-primary);
}
@include mixins.dark-theme {
- background-color: variables.$color-white-alpha-90;
- }
-}
-
-.AttachmentStatusIcon__progress-container {
- .ProgressCircle .ProgressCircle__background {
- @include mixins.light-theme {
- stroke: none;
- fill: none;
- }
- @include mixins.dark-theme {
- stroke: none;
- fill: none;
- }
- }
-
- .ProgressCircle .ProgressCircle__fill {
- @include mixins.light-theme {
- stroke: variables.$color-white;
- }
- @include mixins.dark-theme {
- stroke: variables.$color-white-alpha-90;
- }
- }
-}
-.AttachmentStatusIcon__progress-container--incoming {
- .ProgressCircle .ProgressCircle__fill {
- @include mixins.light-theme {
- stroke: variables.$color-gray-90;
- }
- @include mixins.dark-theme {
- stroke: variables.$color-white-alpha-90;
- }
+ background-color: var(--color-label-primary);
}
}
diff --git a/stylesheets/components/CallingLobby.scss b/stylesheets/components/CallingLobby.scss
index f5323ee597..843bc3846d 100644
--- a/stylesheets/components/CallingLobby.scss
+++ b/stylesheets/components/CallingLobby.scss
@@ -76,9 +76,8 @@
width: auto;
}
-.CallingLobby__CallLinkJoinRequestPendingSpinner {
- margin-inline-end: 8px;
- color: variables.$color-gray-15;
+.CallingLobby__CallLinkJoinRequestPendingText {
+ margin-inline-start: 8px;
}
.CallingLobby__Footer {
diff --git a/stylesheets/components/DonationProgressModal.scss b/stylesheets/components/DonationProgressModal.scss
index 3209c181e4..48a3aca36f 100644
--- a/stylesheets/components/DonationProgressModal.scss
+++ b/stylesheets/components/DonationProgressModal.scss
@@ -21,10 +21,7 @@
@include mixins.font-body-2;
}
-.DonationProgressModal .SpinnerV2 {
+.DonationProgressModal__SpinnerV2 {
+ display: inline-block;
margin-inline: auto;
}
-
-.DonationProgressModal .SpinnerV2__Path {
- color: variables.$color-ultramarine;
-}
diff --git a/stylesheets/components/PlaybackButton.scss b/stylesheets/components/PlaybackButton.scss
index 302c6c4ed0..727549f217 100644
--- a/stylesheets/components/PlaybackButton.scss
+++ b/stylesheets/components/PlaybackButton.scss
@@ -67,29 +67,11 @@
&--computing {
cursor: auto;
}
- &__SpinnerV2-container {
+ &__Spinner-container {
@include mixins.position-absolute-center;
}
- .ProgressCircle {
- @include mixins.position-absolute-center;
- .ProgressCircle__background {
- stroke: none;
- }
- }
- @include mixins.dark-theme {
- .ProgressCircle .ProgressCircle__background {
- stroke: none;
- }
- }
@include mixins.light-theme {
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-white;
- }
- .SpinnerV2 .SpinnerV2__Path {
- stroke: variables.$color-white;
- }
-
- @include all-audio-icons(variables.$color-gray-90);
+ @include all-audio-icons(var(--color-label-primary));
&--context-incoming {
&.PlaybackButton--variant-message {
@@ -101,24 +83,11 @@
background: variables.$color-white-alpha-40;
}
}
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-gray-90;
- }
- .SpinnerV2 .SpinnerV2__Path {
- stroke: variables.$color-gray-90;
- }
}
}
@include mixins.dark-theme {
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-white-alpha-90;
- }
- .SpinnerV2 .SpinnerV2__Path {
- stroke: variables.$color-white-alpha-90;
- }
-
- @include all-audio-icons(variables.$color-white-alpha-90);
+ @include all-audio-icons(var(--color-label-primary));
&--context-incoming {
&.PlaybackButton--variant-message {
@@ -130,12 +99,6 @@
background: variables.$color-white-alpha-40;
}
}
- .ProgressCircle .ProgressCircle__fill {
- stroke: variables.$color-white-alpha-90;
- }
- .SpinnerV2 .SpinnerV2__Path {
- stroke: variables.$color-white-alpha-90;
- }
}
}
@@ -149,12 +112,12 @@
background: variables.$color-white-alpha-40;
}
}
- @include all-audio-icons(variables.$color-white);
+ @include all-audio-icons(var(--color-label-primary-on-color));
}
@include mixins.dark-theme {
&--context-outgoing {
- @include all-audio-icons(variables.$color-white-alpha-90);
+ @include all-audio-icons(var(--color-label-primary-on-color));
}
}
}
diff --git a/stylesheets/components/ProgressCircle.scss b/stylesheets/components/ProgressCircle.scss
deleted file mode 100644
index 6e115dbab0..0000000000
--- a/stylesheets/components/ProgressCircle.scss
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2024 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-@use '../mixins';
-@use '../variables';
-
-.ProgressCircle {
- fill: none;
- transform: rotate(-90deg);
-
- .ProgressCircle__fill,
- .ProgressCircle__background {
- fill: none;
- }
-
- .ProgressCircle__background {
- stroke: variables.$color-gray-20;
- @include mixins.dark-theme() {
- stroke: variables.$color-gray-60;
- }
- }
-
- .ProgressCircle__fill {
- stroke: variables.$color-ultramarine;
- stroke-linecap: round;
- transition: stroke-dashoffset 500ms ease-out;
- }
-}
diff --git a/stylesheets/components/SpinnerV2.scss b/stylesheets/components/SpinnerV2.scss
deleted file mode 100644
index c547007f84..0000000000
--- a/stylesheets/components/SpinnerV2.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2024 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-@use '../variables';
-
-.SpinnerV2 {
- animation: SpinnerV2-rotate 2s linear infinite;
-}
-
-.SpinnerV2__Path {
- stroke: currentColor;
- stroke-linecap: round;
- animation: SpinnerV2-dash 1.5s ease-in-out infinite;
-}
-
-@keyframes SpinnerV2-rotate {
- 100% {
- transform: rotate(360deg);
- }
-}
-
-@keyframes SpinnerV2-dash {
- 0% {
- stroke-dasharray: 2%, 300%;
- stroke-dashoffset: 0;
- }
- 50% {
- stroke-dasharray: 180%, 300%;
- stroke-dashoffset: -70%;
- }
- 100% {
- stroke-dasharray: 180%, 300%;
- stroke-dashoffset: -248%;
- }
-}
diff --git a/stylesheets/components/fun/FunGif.scss b/stylesheets/components/fun/FunGif.scss
index 2f9cd0e475..1a24d273ac 100644
--- a/stylesheets/components/fun/FunGif.scss
+++ b/stylesheets/components/fun/FunGif.scss
@@ -39,10 +39,6 @@
border-radius: 8px;
}
-.FunGifPreview__Spinner {
- color: light-dark(variables.$color-gray-25, variables.$color-gray-45);
-}
-
.FunGifPreview__ErrorIcon {
width: 36px;
height: 36px;
diff --git a/stylesheets/components/fun/FunResults.scss b/stylesheets/components/fun/FunResults.scss
index 890e358742..e4cf032c0f 100644
--- a/stylesheets/components/fun/FunResults.scss
+++ b/stylesheets/components/fun/FunResults.scss
@@ -49,7 +49,3 @@
}
}
}
-
-.FunResults__Spinner {
- color: light-dark(variables.$color-gray-25, variables.$color-gray-45);
-}
diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss
index 450776647c..91812b7c5a 100644
--- a/stylesheets/manifest.scss
+++ b/stylesheets/manifest.scss
@@ -155,7 +155,6 @@
@use 'components/ProfileMovedModal.scss';
@use 'components/ProfileNameWarningModal.scss';
@use 'components/ProgressBar.scss';
-@use 'components/ProgressCircle.scss';
@use 'components/Quote.scss';
@use 'components/ReactionPickerPicker.scss';
@use 'components/RecordingComposer.scss';
@@ -174,7 +173,6 @@
@use 'components/SignalConnectionsModal.scss';
@use 'components/SignalConversationMuteToggle.scss';
@use 'components/Slider.scss';
-@use 'components/SpinnerV2.scss';
@use 'components/StagedLinkPreview.scss';
@use 'components/StickerManager.scss';
@use 'components/Stories.scss';
diff --git a/stylesheets/tailwind-config.css b/stylesheets/tailwind-config.css
index cb683accca..f2f8435bff 100644
--- a/stylesheets/tailwind-config.css
+++ b/stylesheets/tailwind-config.css
@@ -365,6 +365,8 @@
@theme {
--animate-*: initial; /* reset defaults */
--animate-fade-out: animate-fade-out 120ms var(--ease-out-cubic);
+ --animate-spinner-v2-rotate: animate-spinner-v2-rotate 2s linear infinite;
+ --animate-spinner-v2-dash: animate-spinner-v2-dash 1.5s ease-in-out infinite;
}
@layer base {
@@ -373,4 +375,28 @@
opacity: 0;
}
}
+
+ @keyframes animate-spinner-v2-rotate {
+ 0% {
+ transform: rotate(-180deg);
+ }
+ 100% {
+ transform: rotate(180deg);
+ }
+ }
+
+ @keyframes animate-spinner-v2-dash {
+ 0% {
+ stroke-dasharray: 2%, 300%;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 180%, 300%;
+ stroke-dashoffset: -70%;
+ }
+ 100% {
+ stroke-dasharray: 180%, 300%;
+ stroke-dashoffset: -248%;
+ }
+ }
}
diff --git a/ts/components/BackupMediaDownloadProgress.tsx b/ts/components/BackupMediaDownloadProgress.tsx
index b806a1ea29..1b83d4bf1c 100644
--- a/ts/components/BackupMediaDownloadProgress.tsx
+++ b/ts/components/BackupMediaDownloadProgress.tsx
@@ -5,8 +5,7 @@ import React, { useState } from 'react';
import type { LocalizerType } from '../types/Util';
import { formatFileSize } from '../util/formatFileSize';
-import { roundFractionForProgressBar } from '../util/numbers';
-import { ProgressCircle } from './ProgressCircle';
+import { SpinnerV2 } from './SpinnerV2';
import { ContextMenu } from './ContextMenu';
import { BackupMediaDownloadCancelConfirmationDialog } from './BackupMediaDownloadCancelConfirmationDialog';
import { LeftPaneDialog } from './LeftPaneDialog';
@@ -50,14 +49,10 @@ export function BackupMediaDownloadProgress({
setIsShowingCancelConfirmation(true);
}
- const fractionComplete = roundFractionForProgressBar(
- downloadedBytes / totalBytes
- );
-
let content: JSX.Element | undefined;
let icon: JSX.Element | undefined;
- const isCompleted = fractionComplete === 1;
+ const isCompleted = downloadedBytes === totalBytes;
const actionButton =
isCompleted || isIdle ? (
@@ -141,8 +136,14 @@ export function BackupMediaDownloadProgress({
);
icon = (
@@ -181,8 +182,14 @@ export function BackupMediaDownloadProgress({
);
icon = (
@@ -204,8 +211,14 @@ export function BackupMediaDownloadProgress({
);
icon = (
diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx
index 6c7c6b3c4c..9e14fec298 100644
--- a/ts/components/CallingLobby.tsx
+++ b/ts/components/CallingLobby.tsx
@@ -303,12 +303,10 @@ export function CallingLobby({
{callMode === CallMode.Adhoc ? (
isAdhocJoinRequestPending ? (
-
- {i18n('icu:CallingLobby__CallLinkNotice--join-request-pending')}
+
+
+ {i18n('icu:CallingLobby__CallLinkNotice--join-request-pending')}
+
) : (
diff --git a/ts/components/DonationProgressModal.tsx b/ts/components/DonationProgressModal.tsx
index d7b17315c4..29747b455b 100644
--- a/ts/components/DonationProgressModal.tsx
+++ b/ts/components/DonationProgressModal.tsx
@@ -37,7 +37,9 @@ export function DonationProgressModal(props: PropsType): JSX.Element {
noEscapeClose
noMouseClose
>
-
+
+
+
{i18n('icu:Donations__Processing')}
diff --git a/ts/components/PlaybackButton.stories.tsx b/ts/components/PlaybackButton.stories.tsx
index a7e072ee29..f75685ab2a 100644
--- a/ts/components/PlaybackButton.stories.tsx
+++ b/ts/components/PlaybackButton.stories.tsx
@@ -9,6 +9,7 @@ import type { ButtonProps } from './PlaybackButton';
import { PlaybackButton } from './PlaybackButton';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { ThemeType } from '../types/Util';
+import { AUDIO_MP3 } from '../types/MIME';
export default {
title: 'components/PlaybackButton',
@@ -50,6 +51,16 @@ export function Default(): JSX.Element {
key={`${variant}_${context}_${mod}`}
variant={variant}
label="playback"
+ attachment={
+ mod === 'downloading'
+ ? undefined
+ : {
+ contentType: AUDIO_MP3,
+ size: 3000,
+ totalDownloaded: 1000,
+ isPermanentlyUndownloadable: false,
+ }
+ }
onClick={action('click')}
context={context}
mod={mod}
diff --git a/ts/components/PlaybackButton.tsx b/ts/components/PlaybackButton.tsx
index d217fd1890..db82b17f82 100644
--- a/ts/components/PlaybackButton.tsx
+++ b/ts/components/PlaybackButton.tsx
@@ -5,7 +5,7 @@ import { animated, useSpring } from '@react-spring/web';
import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useReducedMotion } from '../hooks/useReducedMotion';
-import { ProgressCircle } from './ProgressCircle';
+import type { AttachmentForUIType } from '../types/Attachment';
import { SpinnerV2 } from './SpinnerV2';
const SPRING_CONFIG = {
@@ -19,7 +19,7 @@ export type ButtonProps = {
context?: 'incoming' | 'outgoing';
variant: 'message' | 'mini' | 'draft';
mod: 'play' | 'pause' | 'not-downloaded' | 'downloading' | 'computing';
- downloadFraction?: number;
+ attachment?: AttachmentForUIType;
label: string;
visible?: boolean;
onClick: () => void;
@@ -32,7 +32,7 @@ export const PlaybackButton = React.forwardRef
(
function ButtonInner(props, ref) {
const {
context,
- downloadFraction,
+ attachment,
label,
mod,
onClick,
@@ -84,25 +84,24 @@ export const PlaybackButton = React.forwardRef(
let content: JSX.Element | null = null;
const strokeWidth = variant === 'message' ? 2 : 1;
- if (mod === 'downloading' && downloadFraction) {
+ if (mod === 'computing' || mod === 'downloading') {
content = (
-
- );
- } else if (
- mod === 'computing' ||
- (mod === 'downloading' && !downloadFraction)
- ) {
- content = (
-
+
);
diff --git a/ts/components/ProgressCircle.stories.tsx b/ts/components/ProgressCircle.stories.tsx
deleted file mode 100644
index 2868bb8362..0000000000
--- a/ts/components/ProgressCircle.stories.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2024 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-import * as React from 'react';
-
-import { ProgressCircle } from './ProgressCircle';
-import type { ComponentMeta } from '../storybook/types';
-
-type Props = React.ComponentProps
;
-export default {
- title: 'Components/ProgressCircle',
- component: ProgressCircle,
- args: {
- fractionComplete: 0,
- width: undefined,
- strokeWidth: undefined,
- ariaLabel: undefined,
- },
-} satisfies ComponentMeta;
-
-export function Zero(args: Props): JSX.Element {
- return ;
-}
-
-export function Thirty(args: Props): JSX.Element {
- return ;
-}
-
-export function Done(args: Props): JSX.Element {
- return ;
-}
-export function Increasing(args: Props): JSX.Element {
- const fractionComplete = useIncreasingFractionComplete();
- return ;
-}
-
-function useIncreasingFractionComplete() {
- const [fractionComplete, setFractionComplete] = React.useState(0);
- React.useEffect(() => {
- if (fractionComplete >= 1) {
- return;
- }
- const timeout = setTimeout(() => {
- setFractionComplete(cur => Math.min(1, cur + 0.1));
- }, 300);
- return () => clearTimeout(timeout);
- }, [fractionComplete]);
- return fractionComplete;
-}
diff --git a/ts/components/ProgressCircle.tsx b/ts/components/ProgressCircle.tsx
deleted file mode 100644
index b227c4d720..0000000000
--- a/ts/components/ProgressCircle.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2024 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-import React from 'react';
-
-export function ProgressCircle({
- fractionComplete,
- width = 24,
- strokeWidth = 3,
- ariaLabel,
-}: {
- fractionComplete: number;
- width?: number;
- strokeWidth?: number;
- ariaLabel?: string;
-}): JSX.Element {
- const radius = width / 2 - strokeWidth / 2;
- const circumference = radius * 2 * Math.PI;
- const widthInPixels = `${width}px`;
-
- return (
-
- );
-}
diff --git a/ts/components/SpinnerV2.stories.tsx b/ts/components/SpinnerV2.stories.tsx
index d94a8bb4f5..60615c6de5 100644
--- a/ts/components/SpinnerV2.stories.tsx
+++ b/ts/components/SpinnerV2.stories.tsx
@@ -1,9 +1,10 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import * as React from 'react';
+import React, { useEffect, useState } from 'react';
import { SpinnerV2 } from './SpinnerV2';
+import { tw } from '../axo/tw';
import type { ComponentMeta } from '../storybook/types';
import type { Props } from './SpinnerV2';
@@ -12,42 +13,85 @@ export default {
title: 'Components/SpinnerV2',
component: SpinnerV2,
argTypes: {
+ variant: {
+ options: ['normal', 'no-background', 'no-background-incoming', 'brand'],
+ control: { type: 'select' },
+ },
size: { control: { type: 'number' } },
+ value: { control: { type: 'range', min: 0, max: 1, step: 0.1 } },
strokeWidth: { control: { type: 'number' } },
marginRatio: { control: { type: 'number' } },
},
- args: { size: 36, strokeWidth: 2, className: undefined, marginRatio: 0.8 },
+ args: {
+ size: 36,
+ strokeWidth: 2,
+ marginRatio: 0.8,
+ min: 0,
+ max: 1,
+ value: undefined,
+ variant: 'normal',
+ ariaLabel: 'label',
+ },
} satisfies ComponentMeta;
export function Default(args: Props): JSX.Element {
- return ;
-}
-
-export function Thin(args: Props): JSX.Element {
- return ;
-}
-
-export function Thick(args: Props): JSX.Element {
- return ;
-}
-
-export function NoMargin(args: Props): JSX.Element {
- return ;
-}
-
-export function BigMargin(args: Props): JSX.Element {
- return ;
-}
-
-export function Styled(args: Props): JSX.Element {
return (
-
-
-
+
+
+
+ );
+}
+
+export function Thin(args: Props): JSX.Element {
+ return (
+
+
+
+ );
+}
+
+export function Thick(args: Props): JSX.Element {
+ return (
+
+
+
+ );
+}
+
+export function NoMargin(args: Props): JSX.Element {
+ return (
+
+
+
+ );
+}
+
+export function BigMargin(args: Props): JSX.Element {
+ return (
+
+
+
+ );
+}
+
+export function SpinnerToProgress(args: Props): JSX.Element {
+ const [value, setValue] = useState
();
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setValue(v => {
+ if (v == null) {
+ return 0.3;
+ }
+ return undefined;
+ });
+ }, 2000);
+ return () => {
+ clearInterval(timer);
+ };
+ });
+ return (
+
+
);
}
diff --git a/ts/components/SpinnerV2.tsx b/ts/components/SpinnerV2.tsx
index fd6c3b9008..031923bfb8 100644
--- a/ts/components/SpinnerV2.tsx
+++ b/ts/components/SpinnerV2.tsx
@@ -2,39 +2,135 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
-import classNames from 'classnames';
+
+import { tw, type TailwindStyles } from '../axo/tw';
+import { roundFractionForProgressBar } from '../util/numbers';
export type Props = {
- className?: string;
+ value?: number | 'indeterminate'; // default: 'indeterminate'
+ min?: number; // default: 0
+ max?: number; // default: 1
+ variant?: SpinnerVariant;
+ ariaLabel?: string;
marginRatio?: number;
size: number;
strokeWidth: number;
};
+type SpinnerVariantStyles = Readonly<{
+ fg: TailwindStyles;
+ bg: TailwindStyles;
+}>;
+
+const SpinnerVariants = {
+ normal: {
+ bg: tw('stroke-label-disabled-on-color'),
+ fg: tw('stroke-label-primary-on-color'),
+ },
+ 'no-background': {
+ bg: tw('stroke-none'),
+ fg: tw('stroke-label-primary-on-color'),
+ },
+ 'no-background-incoming': {
+ bg: tw('stroke-none'),
+ fg: tw('stroke-label-primary'),
+ },
+ brand: {
+ bg: tw('stroke-fill-secondary'),
+ fg: tw('stroke-border-selected'),
+ },
+} as const satisfies Record;
+
+export type SpinnerVariant = keyof typeof SpinnerVariants;
+
export function SpinnerV2({
- className,
+ value = 'indeterminate',
+ min = 0,
+ max = 1,
+ variant = 'normal',
marginRatio,
size,
strokeWidth,
+ ariaLabel,
}: Props): JSX.Element {
- const radius = Math.min(size - strokeWidth / 2, size * (marginRatio ?? 0.8));
+ const sizeInPixels = `${size}px`;
+
+ const radius = Math.min(
+ size / 2 - strokeWidth / 2,
+ (size / 2) * (marginRatio ?? 0.8)
+ );
+ const circumference = radius * 2 * Math.PI;
+
+ const { bg, fg } = SpinnerVariants[variant];
+
+ const bgElem = (
+
+ );
+
+ if (value === 'indeterminate') {
+ return (
+
+ );
+ }
+ const fractionComplete = roundFractionForProgressBar(
+ (value - min) / (max - min)
+ );
+
return (
);
}
diff --git a/ts/components/conversation/AttachmentDetailPill.tsx b/ts/components/conversation/AttachmentDetailPill.tsx
index 7645ca8c8c..3e56f6c468 100644
--- a/ts/components/conversation/AttachmentDetailPill.tsx
+++ b/ts/components/conversation/AttachmentDetailPill.tsx
@@ -5,13 +5,11 @@ import React from 'react';
import classNames from 'classnames';
import { formatFileSize } from '../../util/formatFileSize';
-import { ProgressCircle } from '../ProgressCircle';
+import { SpinnerV2 } from '../SpinnerV2';
import type { AttachmentForUIType } from '../../types/Attachment';
import type { LocalizerType } from '../../types/I18N';
-import { Spinner } from '../Spinner';
import { isKeyboardActivation } from '../../hooks/useKeyboardShortcuts';
-import { roundFractionForProgressBar } from '../../util/numbers';
export type PropsType = {
attachments: ReadonlyArray;
@@ -130,20 +128,21 @@ export function AttachmentDetailPill({
{formatFileSize(totalSize)}
);
- } else if (totalDownloadedSize > 0) {
- const downloadFraction = roundFractionForProgressBar(
- totalDownloadedSize / totalSize
- );
+ } else {
+ const isDownloading = totalDownloadedSize > 0;
ariaLabel = i18n('icu:cancelDownload');
onClick = cancelDownloadClick;
onKeyDown = cancelDownloadKeyDown;
control = (
@@ -156,21 +155,6 @@ export function AttachmentDetailPill({
{formatFileSize(totalSize)}
);
- } else {
- ariaLabel = i18n('icu:cancelDownload');
- onClick = cancelDownloadClick;
- onKeyDown = cancelDownloadKeyDown;
- control = (
-
- );
- text = (
-
- {formatFileSize(totalSize)}
-
- );
}
return (
diff --git a/ts/components/conversation/AttachmentStatusIcon.tsx b/ts/components/conversation/AttachmentStatusIcon.tsx
index 6e6c2ef94c..3bb60c40ad 100644
--- a/ts/components/conversation/AttachmentStatusIcon.tsx
+++ b/ts/components/conversation/AttachmentStatusIcon.tsx
@@ -4,11 +4,10 @@
import React, { useRef, useState } from 'react';
import classNames from 'classnames';
-import { ProgressCircle } from '../ProgressCircle';
+import { SpinnerV2 } from '../SpinnerV2';
import { usePrevious } from '../../hooks/usePrevious';
import type { AttachmentForUIType } from '../../types/Attachment';
-import { roundFractionForProgressBar } from '../../util/numbers';
const TRANSITION_DELAY = 200;
@@ -110,15 +109,12 @@ export function AttachmentStatusIcon({
(state === IconState.Downloaded && isWaiting))
) {
const { size, totalDownloaded } = attachment;
- let downloadFraction =
- size && totalDownloaded
- ? roundFractionForProgressBar(totalDownloaded / size)
- : undefined;
+ let spinnerValue = (size && totalDownloaded) || undefined;
if (state === IconState.Downloading && isWaiting) {
- downloadFraction = undefined;
+ spinnerValue = undefined;
}
if (state === IconState.Downloaded && isWaiting) {
- downloadFraction = 1;
+ spinnerValue = size;
}
return (
@@ -131,22 +127,24 @@ export function AttachmentStatusIcon({
: undefined
)}
>
- {downloadFraction ? (
-
- ) : undefined}
+
+
+
-
-
-
- );
- }
-
- if (!attachment.pending) {
- return undefined;
- }
+ const spinnerValue =
+ (attachment.pending &&
+ !isIncremental(attachment) &&
+ attachment.size &&
+ attachment.totalDownloaded) ||
+ undefined;
return (