format-file-size.ts1.8 KBView on GitHub /**
* One byte formatter for the Files UI.
*
* `apps/mail` had six of these before this file — `lib/utils.ts`, `AttachedSection`,
* `agentExecutions/utils/format`, two in the mail thread views, one in the composer — each
* with its own unit ladder and rounding, so the same 104857600 bytes printed as
* "100 MB", "100.00 MB" and "104.9 MB" depending on which screen you were looking at.
*
* The rounding is Google Drive's, because that is the surface the Files list is modelled
* on: one decimal below 10 in a unit, whole numbers above it. "104.2 MB", "4 KB", "1.5 GB".
* Bytes never get a decimal — "912 B", not "912.0 B".
*/
const UNITS = ['KB', 'MB', 'GB', 'TB'] as const;
/**
* Bytes as a column value. `null`/`undefined` becomes an em dash, NOT "0 B" — the two mean
* different things and a file list has to keep them apart: a folder has no size, an empty
* document has a size of zero.
*/
export function formatFileSize(bytes: number | null | undefined): string {
if (bytes === null || bytes === undefined || !Number.isFinite(bytes)) return '—';
if (bytes < 0) return '—';
if (bytes < 1024) return `${Math.round(bytes)} B`;
let value = bytes / 1024;
let unit = 0;
while (value >= 1024 && unit < UNITS.length - 1) {
value /= 1024;
unit += 1;
}
// KB gets no decimal, MB and up get one — Drive's rule, and the right one: a tenth of a
// KB is 102 bytes, which nobody has ever needed to know, while a tenth of a MB is a
// hundred kilobytes and is the difference between two files.
if (unit === 0) return `${Math.round(value)} ${UNITS[unit]}`;
// `.0` is a decimal that carries nothing. "100 MB", not "100.0 MB".
const fixed = value.toFixed(1);
return `${fixed.endsWith('.0') ? fixed.slice(0, -2) : fixed} ${UNITS[unit]}`;
}