import { scaleTime } from 'd3-scale'; import { fmtDate } from '@/lib/format'; import type { ChangeEvent } from '@/lib/types'; import { laneColor, laneOf, occurredAt, TIMELINE_LANES } from './lanes'; /** * Mobile counterpart of `TimelineLanes`: one lane, dots coloured by category and sized by importance (server-safe SVG, * no interaction — the list below is the interactive surface on small screens). Legend = the lanes present. */ export function MobileLaneStrip({ events, className }: { events: ChangeEvent[]; className?: string }) { const w = 360; const h = 58; const pad = { l: 8, r: 8, t: 8, b: 18 }; const ts = events.map((e) => new Date(occurredAt(e)).getTime()).filter((t) => Number.isFinite(t)); if (ts.length < 2) return null; const lo = Math.min(...ts); const hi = Math.max(...ts); const x = scaleTime() .domain([new Date(lo), new Date(hi === lo ? lo + 86400000 : hi)]) .range([pad.l, w - pad.r]); const ticks = x.ticks(4); const present = new Set(events.map(laneOf)); return (
Events over time {ticks.map((t, i) => ( {t.toLocaleDateString('en-GB', { month: 'short', year: '2-digit', timeZone: 'UTC' })} ))} {events.map((e) => { const t = new Date(occurredAt(e)).getTime(); if (!Number.isFinite(t)) return null; const jitter = ((e.id.charCodeAt(e.id.length - 1) + e.id.charCodeAt(e.id.length - 2)) % 7) - 3; return = 3 ? 0.95 : 0.6} />; })}
{TIMELINE_LANES.filter((l) => present.has(l.key)).map((l) => ( {l.label} ))} {fmtDate(new Date(lo).toISOString())} → {fmtDate(new Date(hi).toISOString())}
); }