Lifecycle Timeline
v0.1.1Vertical connected event timeline showing an elapsed T+ offset from the first event plus an absolute timestamp. Per-event icon, label, and accent resolve from a caller-supplied style map; detail bodies render via a render prop. Domain-agnostic, with empty and loading states.
Install
npx shadcn@latest add @signalos/lifecycle-timelineRequires a configured @signalos registry and a valid SIGNALOS_REGISTRY_TOKEN - get access. Registry dependencies (@signalos/tokens, @signalos/utils, @signalos/skeleton) are pulled automatically.
Preview
Example & code
lifecycle-timeline.example.tsx
// Example: a lifecycle timeline with per-kind styles and custom detail bodies.
import {
ArrowLeftRight,
CheckCircle2,
Radio,
Sparkles,
Zap,
} from "lucide-react"
import { LifecycleTimeline } from "@/components/widgets/lifecycle-timeline/LifecycleTimeline"
import type { LifecycleEventStyle } from "@/components/widgets/lifecycle-timeline/LifecycleTimeline.types"
const EVENT_STYLES: Record<string, LifecycleEventStyle> = {
"signal.fired": {
icon: Zap,
label: "Signal fired",
iconTone: "text-warning-500",
chipTone: "bg-warning-500/15 text-warning-600 dark:text-warning-400",
},
"hypothesis.formed": {
icon: Sparkles,
label: "Hypothesis formed",
iconTone: "text-info-500",
chipTone: "bg-info-500/15 text-info-500",
},
"hypothesis.routed": {
icon: Radio,
label: "Routed",
iconTone: "text-primary",
chipTone: "bg-primary/15 text-primary",
},
"slot.transferred": {
icon: ArrowLeftRight,
label: "Transferred",
iconTone: "text-warning-500",
chipTone: "bg-warning-500/15 text-warning-600 dark:text-warning-400",
},
"decision.created": {
icon: CheckCircle2,
label: "Decision",
iconTone: "text-success-500",
chipTone: "bg-success-500/15 text-success-600 dark:text-success-400",
},
}
export default function Example() {
return (
<div className="max-w-md rounded-md border border-border/60 bg-card px-3 py-2">
<LifecycleTimeline
eventStyles={EVENT_STYLES}
events={[
{
at: "2026-06-09T11:56:40Z",
kind: "signal.fired",
detail: "coverage_rate · 3.2σ",
},
{
at: "2026-06-09T13:04:51Z",
kind: "hypothesis.formed",
detail: "cagg 0.99 · suff 7.38",
},
{
at: "2026-06-09T13:05:29Z",
kind: "hypothesis.routed",
detail: "admin · ops reviewer",
},
{
at: "2026-06-16T12:48:35Z",
kind: "decision.created",
detail: "escalate",
},
]}
renderDetail={(e) =>
e.detail ? (
<span className="text-[11px] text-muted-foreground">
{String(e.detail)}
</span>
) : null
}
/>
</div>
)
}
Props
LifecycleEventStyle
| Prop | Type | Default | Description |
|---|---|---|---|
| icon* | TimelineIcon | - | Marker icon. |
| label* | string | - | Human label for the event kind. |
| iconTone | string | undefined | - | Tailwind text-* class for the icon color. |
| chipTone | string | undefined | - | Tailwind classes for the marker chip background/text. |
LifecycleEvent
| Prop | Type | Default | Description |
|---|---|---|---|
| at* | string | - | ISO timestamp used for ordering and the `T+…` offset. |
| kind* | string | - | Event kind, used to resolve style + render the detail body. |
| detail | TDetail | undefined | - | Arbitrary payload passed to `renderDetail`. |
LifecycleTimelineProps
| Prop | Type | Default | Description |
|---|---|---|---|
| events* | readonly LifecycleEvent<TDetail>[] | - | Events in chronological order (first event anchors `T+0`). |
| eventStyles | Record<string, LifecycleEventStyle> | undefined | - | Style map keyed by `event.kind`. Unknown kinds fall back gracefully. |
| fallbackStyle | LifecycleEventStyle | undefined | - | Fallback style for kinds missing from `eventStyles`. |
| renderDetail | ((event: LifecycleEvent<TDetail>) => ReactNode) | undefined | - | Renders the per-event detail body. |
| formatTimestamp | ((at: string) => ReactNode) | undefined | - | Formats the absolute timestamp shown on the right. Defaults to ISO. |
| formatOffset | ((seconds: number) => ReactNode) | undefined | - | Formats the elapsed-seconds offset into the `T+…` suffix. |
| loading | boolean | undefined | false | Renders a skeleton instead of events. |
| emptyLabel | ReactNode | - | Message shown when there are no events. |
| className | string | undefined | - | |
| data-testid | string | undefined | - |
Supporting types
export type TimelineIcon = ComponentType<{ className?: string }>npm dependencies
lucide-react@^1.7.0Changelog
lifecycle-timeline
0.1.1
- Format source with the repo prettier config (no behavior change).
0.1.0
- Initial release, genericized from the
attention-routing-01block'sRoutingFlowTimeline. Hardcoded event kinds and detail renderers are replaced by a caller-suppliedeventStylesmap and arenderDetailrender prop, so any event stream can drive the timeline. Ships empty/loading states and a matchingLifecycleTimelineSkeleton.