// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; import { tw, type TailwindStyles } from '../axo/tw.js'; import { roundFractionForProgressBar } from '../util/numbers.js'; export type Props = { 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({ value = 'indeterminate', min = 0, max = 1, variant = 'normal', marginRatio, size, strokeWidth, ariaLabel, }: Props): JSX.Element { 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 ( {bgElem} ); } const fractionComplete = roundFractionForProgressBar( (value - min) / (max - min) ); return ( {bgElem} ); }