import type React from "react"; type ByteSizeProps = { bytes: number; base?: 1000 | 1024; // 1000 = SI (KB, MB, ...), 1024 = IEC (KiB, MiB, ...) maximumFractionDigits?: number; // default: 2 smartRounding?: boolean; // dynamically reduces decimals for big numbers (default: true) locale?: string | string[]; // e.g., 'en', 'de', or navigator.languages space?: boolean; // space between number and unit (default: true) className?: string; style?: React.CSSProperties; fallback?: string; // shown if bytes is not a finite number (default: '—') }; const SI_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] as const; const IEC_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] as const; type FormatBytesResult = { text: string; unit: string; unitIndex: number; numeric: number; // numeric value before formatting (with sign) }; export function formatBytes( bytes: number, options?: { base?: 1000 | 1024; maximumFractionDigits?: number; smartRounding?: boolean; locale?: string | string[]; }, ): FormatBytesResult { const { base = 1000, maximumFractionDigits = 2, smartRounding = true, locale } = options ?? {}; if (!Number.isFinite(bytes)) { return { text: "—", unit: "", unitIndex: 0, numeric: NaN, }; } const units = base === 1024 ? IEC_UNITS : SI_UNITS; const sign = Math.sign(bytes) || 1; const abs = Math.abs(bytes); let idx = 0; if (abs > 0) { idx = Math.floor(Math.log(abs) / Math.log(base)); if (!Number.isFinite(idx)) idx = 0; idx = Math.max(0, Math.min(idx, units.length - 1)); } const numeric = (abs / base ** idx) * sign; const maxFrac = (() => { if (!smartRounding) return maximumFractionDigits; const v = Math.abs(numeric); if (v >= 100) return 0; if (v >= 10) return Math.min(1, maximumFractionDigits); return maximumFractionDigits; })(); const text = new Intl.NumberFormat(locale, { minimumFractionDigits: 0, maximumFractionDigits: maxFrac, }).format(numeric); return { text, unit: units[idx], unitIndex: idx, numeric, }; } export function ByteSize(props: ByteSizeProps) { const { bytes, base = 1000, maximumFractionDigits = 2, smartRounding = true, locale, space = true, className, style, fallback = "—", } = props; const { text, unit } = formatBytes(bytes, { base, maximumFractionDigits, smartRounding, locale, }); if (text === "—") { return ( {fallback} ); } return ( {text} {space ? " " : ""} {unit} ); }