Initial Commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# useRelativeTime
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`useRelativeTime(deltaMs, options?)` formats a millisecond delta as a
|
||||
localised relative-time string ("in 5 minutes", "3 hours ago", "il y a
|
||||
2 jours"). Wraps `Intl.RelativeTimeFormat` directly with a per-(locale,
|
||||
style, numeric) cache so the formatter constructor is paid once per
|
||||
process per option combination. Memoised on `(resolvedLocale, deltaMs,
|
||||
options-hash)` so a no-op re-render does not reach into the cache or
|
||||
the formatter.
|
||||
|
||||
When the `unit` option is omitted the hook auto-selects the largest
|
||||
unit whose absolute value is ≥ 1 (per the universal-core
|
||||
`formatRelativeTime` contract). Pass `unit` explicitly when a fixed
|
||||
granularity matters — e.g. always render activity timestamps in
|
||||
minutes within the first hour for consistency with a refresh interval.
|
||||
|
||||
## Features
|
||||
|
||||
- Auto-selects unit (seconds → years) when `unit` is omitted.
|
||||
- Forwards `style` (`'long' | 'short' | 'narrow'`) and `numeric` (`'always' | 'auto'`).
|
||||
- Per-(locale, style, numeric) cache of `Intl.RelativeTimeFormat` instances.
|
||||
- Memoised on `(locale, deltaMs, options-hash)` — no re-format on identical inputs.
|
||||
- Production no-Provider fallback uses `'en-GB'` so the hook always returns a real string.
|
||||
- Re-renders on `languageChanged` via the inherited `useLocale` subscription.
|
||||
- `clearUseRelativeTimeCache()` exposed for tests / hot-reload scenarios.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```tsx
|
||||
import { useRelativeTime } from "@suaveplan/react-i18n";
|
||||
|
||||
export function PostedAt(props: { readonly postedAt: Date }) {
|
||||
const delta = props.postedAt.getTime() - Date.now();
|
||||
const formatted = useRelativeTime(delta);
|
||||
return <time dateTime={props.postedAt.toISOString()}>{formatted}</time>;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
For activity feeds that update once per second, pin the unit to
|
||||
`'seconds'` for the first minute and `'minutes'` thereafter. The
|
||||
pattern below shows a row that picks the unit explicitly to avoid
|
||||
stuttering between auto-selected units near boundary thresholds.
|
||||
|
||||
```tsx
|
||||
import { useRelativeTime } from "@suaveplan/react-i18n";
|
||||
|
||||
export function LiveTimestamp(props: { readonly when: Date }) {
|
||||
const delta = props.when.getTime() - Date.now();
|
||||
const abs = Math.abs(delta);
|
||||
const unit = abs < 60_000 ? "seconds" : "minutes";
|
||||
const formatted = useRelativeTime(delta, {
|
||||
style: "short",
|
||||
numeric: "always",
|
||||
unit,
|
||||
});
|
||||
return <span data-live="true">{formatted}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `useRelativeTime(deltaMs: number, options?: UseRelativeTimeOptions): string`
|
||||
|
||||
- **Parameters**:
|
||||
- `deltaMs` — delta in milliseconds. Positive = future, negative = past, zero = now.
|
||||
- `options?` — `style`, `numeric`, `unit`. When `unit` is omitted, the hook auto-selects.
|
||||
- **Returns**: the formatted relative-time string.
|
||||
- **Throws**: `ReactI18nError(REACT_I18N.NO_PROVIDER)` outside a `<TranslationProvider>` in dev mode (inherited from `useLocale → useI18n`).
|
||||
|
||||
### `clearUseRelativeTimeCache(): void`
|
||||
|
||||
Test-only escape hatch to reset the process-level `Intl.RelativeTimeFormat` cache.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The auto-select thresholds match the universal-core `formatRelativeTime` table: `60 s → minutes`, `60 min → hours`, `24 h → days`, `7 d → weeks`, `30.4375 d → months` (Gregorian average), `365.25 d → years` (Julian average). This keeps React-side and server-side renders stable across the boundary.
|
||||
- The unit conversion rounds via `Math.round` on the way down. Intentionally — a 30.5-second delta rounds to "in 31 seconds" rather than truncating to "in 30 seconds" which would feel wrong to the user staring at a one-second-resolution clock.
|
||||
- The cache key is `(locale, style, numeric)` — `unit` does NOT enter the cache because `Intl.RelativeTimeFormat.format(value, unit)` accepts `unit` per call. One formatter handles all units for the same (locale, style, numeric) combination.
|
||||
- The default locale `'en-GB'` on the no-Provider fallback matches the broader package convention. This keeps the hook from throwing in production while still producing meaningful output.
|
||||
Reference in New Issue
Block a user