Components
Registry
Components
"use client"
import { useMemo, useState } from "react"This is the timeline from the Repost portal, not a plain chart. Drag across the
plot to select a range, drag the handles to resize it, use the frame tabs to
zoom the visible window, and the chevrons to walk backwards and forwards through
history. The selection is reported through onApplyRange so your feed can filter
to it.
Installation#
pnpm dlx shadcn@latest add @repost/timeline-histogramInstalls this item, every component it depends on, and the npm packages listed below.
Usage#
useTimelineScale owns the visible window (scale + frame + jumping); the
component owns selection. Feed it histogram slots, from
useLogsHistogram (@repost/portal-react)
or any source with the same shape.
"use client"
import { useMemo, useState } from "react"
import {
TIMELINE_SCALE_OPTIONS,
TimelineHistogram,
useTimelineScale,
type TimelineRange,
type TimelineSeries,
type TimelineSlot,
} from "@/components/timeline-histogram"
const HOUR_MS = 60 * 60 * 1000
const SERIES: TimelineSeries[] = [
{ key: "2xx", label: "2xx", color: "var(--status-success)", statuses: ["2xx"] },
{ key: "3xx", label: "3xx", color: "var(--status-info)", statuses: ["3xx"] },
{ key: "4xx", label: "4xx", color: "var(--status-warning)", statuses: ["4xx"] },
{ key: "5xx", label: "5xx", color: "var(--status-error)", statuses: ["5xx"] },
]
/** Deterministic delivery volume so the preview never flickers between renders. */
function buildSlots(from: Date, to: Date, intervalSecs: number): TimelineSlot[] {
const step = intervalSecs * 1000
const slots: TimelineSlot[] = []
for (let t = from.getTime(), i = 0; t < to.getTime(); t += step, i++) {
const base = 40 + ((i * 37) % 90)
const spike = i % 17 === 0 ? 60 : 0
const counts = {
"2xx": base + spike,
"3xx": (i * 5) % 7,
"4xx": i % 11 === 3 ? 6 + (i % 5) : i % 3,
"5xx": i % 13 === 5 ? 9 + (i % 6) : 0,
}
slots.push({
timestamp: new Date(t),
total: Object.values(counts).reduce((sum, value) => sum + value, 0),
counts,
})
}
return slots
}
/** Start with a pinned range so the scrubber (glass overlay + handles) is visible. */
const initialSelection = (): TimelineRange => {
const to = new Date()
return { from: new Date(to.getTime() - 8 * HOUR_MS), to: new Date(to.getTime() - 2 * HOUR_MS) }
}
export function TimelineHistogramDemo() {
const [selection, setSelection] = useState<TimelineRange | null>(initialSelection)
const {
scale,
options,
activeOptionKey,
selectSpan,
frameOptions,
activeFrameKey,
selectFrame,
jump,
canJumpBack,
canJumpForward,
} = useTimelineScale({
selection,
liveDateWindow: "today",
planWindowDays: 90,
})
// One bucket per ~120 columns of the visible frame, like the portal.
const intervalSecs = useMemo(
() => Math.max(60, Math.round((scale.to.getTime() - scale.from.getTime()) / 120 / 1000)),
[scale.from, scale.to]
)
const slots = useMemo(
() => buildSlots(scale.from, scale.to, intervalSecs),
[scale.from, scale.to, intervalSecs]
)
return (
<TimelineHistogram
slots={slots}
intervalSecs={intervalSecs}
dateFrom={scale.from}
dateTo={scale.to}
series={SERIES}
isLoading={false}
selection={selection}
onApplyRange={setSelection}
scaleOptions={options ?? TIMELINE_SCALE_OPTIONS}
activeOptionKey={activeOptionKey}
onSelectSpan={selectSpan}
frameOptions={frameOptions}
activeFrameKey={activeFrameKey}
onSelectFrame={selectFrame}
onJump={jump}
canJumpBack={canJumpBack}
canJumpForward={canJumpForward}
/>
)
}
Props#
| Prop | Type | Description |
|---|---|---|
slots | TimelineSlot[] | Time-bucketed counts keyed by series. |
intervalSecs | number | Bucket width; drives bar geometry and tooltips. |
dateFrom / dateTo | Date | The visible window. |
series | TimelineSeries[] | Stacked series with label, color, and statuses. |
selection | TimelineRange | null | The applied range, drawn as the on-chart selection. |
onApplyRange | (range) => void | Fired when a drag or bucket click commits a range. |
scaleOptions / activeOptionKey / onSelectSpan | — | The scale rail (Last hour, Today, This week, This month). |
frameOptions / activeFrameKey / onSelectFrame | — | The zoom rail (24h, 7D, 30D, 90D). |
onJump / canJumpBack / canJumpForward | — | Walk the window backwards and forwards. |
dateRange | TimelineDateRangeControl | Optional date-range picker rendered in the header. |
isLoading | boolean | Renders the skeleton state. |