Add K/M/B scale toggles for financial displays

This commit is contained in:
2026-03-01 19:23:45 -05:00
parent 209b650658
commit b8309c9969
5 changed files with 519 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'bun:test';
import { formatScaledNumber } from './format';
import { formatCurrencyByScale, formatScaledNumber } from './format';
describe('formatScaledNumber', () => {
it('keeps values below one thousand unscaled', () => {
@@ -27,3 +27,21 @@ describe('formatScaledNumber', () => {
expect(formatScaledNumber(999_950)).toBe('1M');
});
});
describe('formatCurrencyByScale', () => {
it('formats values in thousands', () => {
expect(formatCurrencyByScale(12_345, 'thousands')).toBe('$12.3K');
});
it('formats values in millions', () => {
expect(formatCurrencyByScale(12_345_678, 'millions')).toBe('$12.3M');
});
it('formats values in billions', () => {
expect(formatCurrencyByScale(12_345_678_901, 'billions')).toBe('$12.3B');
});
it('keeps sign for negative values', () => {
expect(formatCurrencyByScale(-2_500_000, 'millions')).toBe('-$2.5M');
});
});

View File

@@ -12,6 +12,8 @@ type NumberScale = {
suffix: string;
};
export type NumberScaleUnit = 'thousands' | 'millions' | 'billions';
const NUMBER_SCALES: NumberScale[] = [
{ divisor: 1, suffix: '' },
{ divisor: 1_000, suffix: 'K' },
@@ -19,11 +21,22 @@ const NUMBER_SCALES: NumberScale[] = [
{ divisor: 1_000_000_000, suffix: 'B' }
];
const NUMBER_SCALE_UNITS: Record<NumberScaleUnit, NumberScale> = {
thousands: { divisor: 1_000, suffix: 'K' },
millions: { divisor: 1_000_000, suffix: 'M' },
billions: { divisor: 1_000_000_000, suffix: 'B' }
};
type FormatScaledNumberOptions = {
minimumFractionDigits?: number;
maximumFractionDigits?: number;
};
type FormatScaledCurrencyOptions = {
minimumFractionDigits?: number;
maximumFractionDigits?: number;
};
export function formatScaledNumber(
value: string | number | null | undefined,
options: FormatScaledNumberOptions = {}
@@ -64,6 +77,28 @@ export function formatScaledNumber(
return `${formatted}${NUMBER_SCALES[scaleIndex].suffix}`;
}
export function formatCurrencyByScale(
value: string | number | null | undefined,
scale: NumberScaleUnit,
options: FormatScaledCurrencyOptions = {}
) {
const {
minimumFractionDigits = 0,
maximumFractionDigits = 1
} = options;
const { divisor, suffix } = NUMBER_SCALE_UNITS[scale];
const scaled = asNumber(value) / divisor;
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits,
maximumFractionDigits
}).format(scaled);
return `${formatted}${suffix}`;
}
export function formatCurrency(value: string | number | null | undefined) {
return new Intl.NumberFormat('en-US', {
style: 'currency',