Utility functions for consistent formatting of dates, times, numbers, file sizes, durations, and name initials across server and client environments, with UTC-pinned rendering to prevent React hydration mismatches. ## Key Components | Export | Description | |---|---| | `formatDate` | Formats a `Date` or ISO string to a localized date string; UTC-pinned by default to prevent SSR/client hydration drift | | `formatDateUTC` | UTC-anchored date formatter for RAG mappers and audit consumers; handles epoch numbers, ISO strings, and numeric string timestamps with a configurable fallback | | `formatNumber` | Formats a number with locale-aware thousands separators | | `formatPrice` | Formats a number as a currency string using `Intl.NumberFormat` (defaults to USD) | | `formatBytes` | Converts bytes to human-readable size with configurable decimal places (`Bytes` → `YB`) | | `formatBytesShort` | Compact byte formatter using single-letter `B` unit (2 decimal places, caps at `TB`) for space-constrained UIs | | `formatFileSize` | File-size formatter using 1 decimal place and `B` unit, capping at `GB`; canonical formatter for upload UIs and publication cards | | `formatLargeNumber` | Abbreviates large numbers to `K`, `M`, `B` with no decimal places | | `formatAbbreviatedNumber` | Similar abbreviation but preserves one decimal place when the value is not a whole number | | `getFirstLastInitials` | Extracts two-letter initials from the first and last word of a name (e.g., `"John Michael Doe"` → `"JD"`) | | `nameInitials` | Extracts up to two-letter initials from the first and second word; single source of truth for avatar fallbacks | | `formatDurationMMSS` | Formats seconds as `MM:SS` or `HH:MM:SS` for media players | | `formatDurationCompact` | Formats seconds as `Xh Xm` or `X min` for cards and headers | | `formatDurationFromRange` | Calculates and formats duration between two timestamps as `1h 30m` or `45m` | | `formatTimeWithTimezone` | Formats an event's wall-clock time in its own IANA timezone; falls back to UTC on invalid zone labels | ## Usage Example ```typescript import { formatDate, formatDateUTC, formatPrice, formatBytes, formatBytesShort, formatFileSize, formatLargeNumber, formatAbbreviatedNumber, nameInitials, getFirstLastInitials, formatDurationMMSS, formatDurationCompact, formatDurationFromRange, formatTimeWithTimezone, } from './format' // Dates — UTC-pinned to prevent SSR/client mismatch formatDate('2024-08-15') // → "August 15, 2024" formatDateUTC('2024-08-15', { fallback: 'N/A' }) // → "Aug 15, 2024" formatDateUTC(null) // → "N/A" // Numbers and currency formatPrice(4999.99, 'EUR') // → "€4,999.99" formatLargeNumber(1_450_000) // → "1M" formatAbbreviatedNumber(1_200) // → "1.2K" // File sizes formatBytes(1536) // → "1.50 KB" formatBytesShort(1536) // → "1.5 KB" (compact UI variant) formatFileSize(1500) // → "1.5 KB" (1 decimal, caps at GB) // Avatar initials nameInitials('John Doe') // → "JD" getFirstLastInitials('John Michael Doe') // → "JD" (first + last word) nameInitials(null, 'U') // → "U" // Durations formatDurationMMSS(3725) // → "1:02:05" formatDurationCompact(3725) // → "1h 2m" formatDurationFromRange('2024-08-15T14:00:00Z', '2024-08-15T15:30:00Z') // → "1h 30m" // Event time in its own timezone (prevents React #418 hydration mismatch) formatTimeWithTimezone('2024-08-15T20:00:00Z', 'America/New_York') // → "4:00 PM" ``` ## Notes - **Hydration safety**: `formatDate` and `formatTimeWithTimezone` pin to UTC by default so Vercel (UTC runtime) and browser clients always render identical strings, avoiding React hydration error #418. - **Avatar fallback hierarchy**: Prefer `nameInitials` over `getFirstLastInitials` for new components — it is the designated single source of truth and accepts a configurable `fallback` character. - **File size variants**: Three formatters exist for historical reasons. Use `formatFileSize` for publication cards and upload UIs, `formatBytesShort` for space-constrained progress indicators, and `formatBytes` for general-purpose display. **Source:** [`format.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/openframe-frontend-core/src/utils/format.ts) ## `titleCaseFromSlug(text, separator = '-')` The one hand-written title-caser: split on an EXPLICIT separator, capitalize each word. `formatUnderscoreText` delegates with `'_'` (byte-identical output). `formatDurationCompact` now drops the zero-minutes tail on whole hours (`3600 → "1h"`).