You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/uix/components/relative-time/+page.svelte

399 lines
16 KiB

<script lang="ts">
import {
RelativeTime,
type RelativeTimeUnit,
type RelativeTimeStyle,
type RelativeTimeNumeric
} from '$uix/eidos/components/relative-time';
import { compileMorfo } from '$uix/morfo';
import { relativeTimeMorfo } from '@/uix/morfo/components/relative-time';
import { getActiveUix } from '$active-uix';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state<Tab>('live');
const uix = getActiveUix();
const compiled = compileMorfo(relativeTimeMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
// ── Live state ────────────────────────────────────────────────────────
// Demo owns its own `locale` state so the toolbar's global locale
// doesn't drag this demo around. The "Locale override" pattern row
// keeps its own hard-coded list for side-by-side comparison.
type DemoLocale = 'es-ES' | 'en-US' | 'en-GB' | 'de-DE' | 'fr-FR' | 'ja-JP' | 'ar-EG';
const DEMO_LOCALES: readonly DemoLocale[] = [
'es-ES',
'en-US',
'en-GB',
'de-DE',
'fr-FR',
'ja-JP',
'ar-EG'
];
let offsetSec = $state(-3600); // 1h ago
const value = $derived(new Date(Date.now() + offsetSec * 1000));
let demoLocale = $state<DemoLocale>('es-ES');
let unit = $state<RelativeTimeUnit>('auto');
let style = $state<RelativeTimeStyle>('long');
let numeric = $state<RelativeTimeNumeric>('auto');
let live = $state(true);
// Pre-computed offsets for the pattern grid.
const offsets = [
{ label: '5 sec ago', sec: -5 },
{ label: '45 sec ago', sec: -45 },
{ label: '5 min ago', sec: -5 * 60 },
{ label: '2 hours ago', sec: -2 * 60 * 60 },
{ label: '1 day ago', sec: -86_400 },
{ label: '3 weeks ago', sec: -3 * 7 * 86_400 },
{ label: '6 months ago', sec: -6 * 30 * 86_400 },
{ label: '2 years ago', sec: -2 * 365 * 86_400 },
{ label: 'now', sec: 0 },
{ label: 'in 30 sec', sec: 30 },
{ label: 'in 10 min', sec: 10 * 60 },
{ label: 'in 5 hours', sec: 5 * 60 * 60 },
{ label: 'in 3 days', sec: 3 * 86_400 },
{ label: 'in 4 weeks', sec: 4 * 7 * 86_400 },
{ label: 'in 8 months', sec: 8 * 30 * 86_400 },
{ label: 'in 5 years', sec: 5 * 365 * 86_400 }
];
function timeAt(deltaSec: number): Date {
return new Date(Date.now() + deltaSec * 1000);
}
</script>
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Utilities · RelativeTime</div>
<h1 data-uix-page-title>RelativeTime</h1>
<p data-uix-page-lede>
Service component rendering <code>Intl.RelativeTimeFormat</code> output with an
auto-refresh tick. "hace 2 horas" / "in 3 days" — automatically picks the most natural
unit (year → month → day → hour → minute → second) and ticks at a sensible cadence
(1s for seconds, 30s for minutes, 5min for hours, 1h for days). Family with
<a href="/uix/components/trans">Trans</a>,
<a href="/uix/components/format-number">FormatNumber</a>,
<a href="/uix/components/format-date">FormatDate</a>.
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill>
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>events</span>0
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>units</span>7
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>auto-tick</span>{live ? 'on' : 'off'}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>locale</span>
<code>{demoLocale}</code>
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos
</span>
</div>
</header>
<!-- Live preview -->
<div data-uix-stage>
<div data-uix-stage-area>
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--uix-space-6); inline-size: 100%;">
<div style="font-size: var(--uix-font-size-2xl); font-weight: 600;">
<RelativeTime
{value}
{unit}
{numeric}
{style}
locale={demoLocale}
updateInterval={live ? 'auto' : 0}
/>
</div>
</div>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>offset</span>
<code>{offsetSec}s</code>
<span style="color: var(--uix-text-faint)">·</span>
<span data-uix-stage-trace-key>unit</span>
<span>{unit}</span>
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>locale</span>
<code>{demoLocale}</code> ·
<span data-uix-stage-trace-key>style</span> {style} ·
<span data-uix-stage-trace-key>numeric</span> {numeric}
</span>
</div>
</div>
<div data-uix-tabs role="tablist">
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>
API <span data-uix-tab-count>9</span>
</button>
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
<span data-uix-layer-badge="morfo">morfo</span>
<span data-uix-tab-count>{partsList.length}p · 0e</span>
</button>
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
<span data-uix-layer-badge="sema">sema</span>
<span data-uix-tab-count>0</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
{#if tab === 'live'}
<section data-uix-section>
<h2 data-uix-section-title>Controls</h2>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>offset (s)</span>
<input
type="range"
bind:value={offsetSec}
min="-31536000"
max="31536000"
step="1"
style="inline-size: 18rem;"
/>
<span data-uix-control-hint>{offsetSec}s</span>
</label>
<label data-uix-control>
<span data-uix-control-label>locale</span>
<span data-uix-chips role="radiogroup">
{#each DEMO_LOCALES as l}
<button
data-uix-chip
data-active={demoLocale === l}
onclick={() => (demoLocale = l)}>{l}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>unit</span>
<span data-uix-chips role="radiogroup">
{#each ['auto', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as u}
<button
data-uix-chip
data-active={unit === u}
onclick={() => (unit = u as RelativeTimeUnit)}>{u}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>style</span>
<span data-uix-chips role="radiogroup">
{#each ['long', 'short', 'narrow'] as s}
<button
data-uix-chip
data-active={style === s}
onclick={() => (style = s as RelativeTimeStyle)}>{s}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>numeric</span>
<span data-uix-chips role="radiogroup">
{#each ['auto', 'always'] as n}
<button
data-uix-chip
data-active={numeric === n}
onclick={() => (numeric = n as RelativeTimeNumeric)}>{n}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>live tick</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={live} />
<span data-uix-switch-label>{live ? 'on' : 'off'}</span>
</span>
</label>
</div>
<div data-uix-subsection-head>Patterns · common shapes</div>
<div data-uix-pattern-stack>
<div data-uix-pattern>
<h3 data-uix-pattern-title>Auto-unit · the engine picks the natural granularity</h3>
<div data-uix-pattern-row>
<table data-uix-table>
<thead><tr><th>Offset</th><th>long</th><th>short</th><th>narrow</th></tr></thead>
<tbody>
{#each offsets as o (o.label)}
<tr>
<td><code>{o.label}</code></td>
<td><strong><RelativeTime value={timeAt(o.sec)} updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(o.sec)} style="short" updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(o.sec)} style="narrow" updateInterval={0} /></strong></td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
<div data-uix-pattern>
<h3 data-uix-pattern-title>Numeric mode · "auto" collapses 0/-1/+1 into words</h3>
<div data-uix-pattern-row>
<table data-uix-table>
<thead><tr><th>Offset</th><th>numeric="auto"</th><th>numeric="always"</th></tr></thead>
<tbody>
<tr>
<td><code>0 sec</code></td>
<td><strong><RelativeTime value={timeAt(0)} updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(0)} numeric="always" updateInterval={0} /></strong></td>
</tr>
<tr>
<td><code>-86400 sec (1 day ago)</code></td>
<td><strong><RelativeTime value={timeAt(-86400)} updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(-86400)} numeric="always" updateInterval={0} /></strong></td>
</tr>
<tr>
<td><code>+86400 sec (1 day from now)</code></td>
<td><strong><RelativeTime value={timeAt(86400)} updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(86400)} numeric="always" updateInterval={0} /></strong></td>
</tr>
</tbody>
</table>
</div>
</div>
<div data-uix-pattern>
<h3 data-uix-pattern-title>Unit override · force a specific granularity</h3>
<div data-uix-pattern-row>
<table data-uix-table>
<thead><tr><th>Offset</th><th>unit="hour"</th><th>unit="day"</th><th>unit="week"</th></tr></thead>
<tbody>
<tr>
<td><code>3 days ago</code></td>
<td><strong><RelativeTime value={timeAt(-3 * 86400)} unit="hour" updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(-3 * 86400)} unit="day" updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(-3 * 86400)} unit="week" updateInterval={0} /></strong></td>
</tr>
<tr>
<td><code>in 6 weeks</code></td>
<td><strong><RelativeTime value={timeAt(6 * 7 * 86400)} unit="hour" updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(6 * 7 * 86400)} unit="day" updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(6 * 7 * 86400)} unit="week" updateInterval={0} /></strong></td>
</tr>
</tbody>
</table>
</div>
</div>
<div data-uix-pattern>
<h3 data-uix-pattern-title>Locale override · same offset, different language</h3>
<div data-uix-pattern-row>
<table data-uix-table>
<thead><tr><th>Locale</th><th>1 hour ago</th><th>in 3 days</th></tr></thead>
<tbody>
{#each ['es-ES', 'en-US', 'de-DE', 'fr-FR', 'ja-JP'] as l}
<tr>
<td><code>{l}</code></td>
<td><strong><RelativeTime value={timeAt(-3600)} locale={l} updateInterval={0} /></strong></td>
<td><strong><RelativeTime value={timeAt(3 * 86400)} locale={l} updateInterval={0} /></strong></td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</section>
{/if}
{#if tab === 'api'}
<section data-uix-section>
<h2 data-uix-section-title>API reference</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">value</td><td class="type">Date | number | string | null</td><td class="default empty">—</td><td>Target time relative to <code>now</code>.</td></tr>
<tr><td class="name">now</td><td class="type">Date | number | string</td><td class="default empty">— Date.now()</td><td>Reference time. Override for "3 days before deadline" style pivots.</td></tr>
<tr><td class="name">unit</td><td class="type">'auto' | 'year' | ... | 'second'</td><td class="default">'auto'</td><td>Granularity. Auto picks the natural unit by magnitude.</td></tr>
<tr><td class="name">numeric</td><td class="type">'auto' | 'always'</td><td class="default">'auto'</td><td>'auto' collapses 0/-1/+1 into "now" / "yesterday" / "tomorrow".</td></tr>
<tr><td class="name">style</td><td class="type">'long' | 'short' | 'narrow'</td><td class="default">'long'</td><td>Length flavour.</td></tr>
<tr><td class="name">updateInterval</td><td class="type">number | 'auto'</td><td class="default">'auto'</td><td>Auto-refresh interval ms. <code>0</code> disables. 'auto' picks by unit: 1s/30s/5min/1h.</td></tr>
<tr><td class="name">locale</td><td class="type">SupportedLocale</td><td class="default empty">— uix.format.dates locale</td><td>Optional locale override.</td></tr>
<tr><td class="name">fallback</td><td class="type">string</td><td class="default">''</td><td>Render when value is null/invalid.</td></tr>
<tr><td class="name">as</td><td class="type">keyof HTMLElementTagNameMap</td><td class="default">'time'</td><td>HTML tag. Default <code>'time'</code> + <code>datetime</code> attr.</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'morfo'}
<section data-uix-section>
<h2 data-uix-section-title>
<span data-uix-layer-badge="morfo">morfo</span> · declarative contract
</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Field</th><th>Value</th></tr></thead>
<tbody>
<tr><td class="name">name</td><td>{relativeTimeMorfo.name}</td></tr>
<tr><td class="name">kebab</td><td><code>{relativeTimeMorfo.kebab}</code></td></tr>
<tr><td class="name">scope</td><td>{relativeTimeMorfo.scope.join(', ')}</td></tr>
<tr><td class="name">parts</td><td>{partsList.length}</td></tr>
<tr><td class="name">events</td><td>0</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'sema'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="sema">sema</span> · events</h2>
<p data-uix-section-desc>RelativeTime declares no semantic events.</p>
</section>
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>
<p data-uix-section-desc>
RelativeTime ships <strong>no CSS recipe</strong>. The element is a plain
<code>&lt;time&gt;</code> with <code>datetime</code> + <code>data-relative-time</code>
+ <code>data-unit</code> markers for tooling.
</p>
</section>
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Concern</th><th>Contract</th></tr></thead>
<tbody>
<tr><td class="name">&lt;time&gt; element</td><td><code>datetime</code> attr exposes the canonical ISO 8601 timestamp so assistive tech can announce both the relative and the absolute time.</td></tr>
<tr><td class="name">Live updates</td><td>The auto-tick updates the rendered text without alerting screen readers (no <code>aria-live</code>). For announcement-style updates wrap in <a href="/uix/components/announce">&lt;Announce&gt;</a>.</td></tr>
<tr><td class="name">Tick cost</td><td>For lists with many RelativeTime instances, set <code>updateInterval={'{0}'}</code> on the row level and re-mount on a parent timer to avoid N concurrent intervals.</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>
<style>
[data-uix-pattern-stack] { display: flex; flex-direction: column; gap: var(--uix-space-5); }
[data-uix-pattern-row] { padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); }
h3[data-uix-pattern-title] { margin: 0 0 var(--uix-space-3); font-family: var(--uix-font-ui); font-size: var(--uix-font-size-sm); font-weight: 600; color: var(--uix-text-faint); text-transform: uppercase; letter-spacing: 0.04em; }
</style>

Powered by TurnKey Linux.