mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
116 lines
2.6 KiB
TypeScript
116 lines
2.6 KiB
TypeScript
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 (
|
|
<span className={className} style={style}>
|
|
{fallback}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span className={className} style={style}>
|
|
{text}
|
|
{space ? " " : ""}
|
|
{unit}
|
|
</span>
|
|
);
|
|
}
|