Lifecycle Timeline

v0.1.1

Vertical 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.

View as Markdown

Install

npx shadcn@latest add @signalos/lifecycle-timeline

Requires 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

PropTypeDefaultDescription
icon*TimelineIcon-Marker icon.
label*string-Human label for the event kind.
iconTonestring | undefined-Tailwind text-* class for the icon color.
chipTonestring | undefined-Tailwind classes for the marker chip background/text.

LifecycleEvent

PropTypeDefaultDescription
at*string-ISO timestamp used for ordering and the `T+…` offset.
kind*string-Event kind, used to resolve style + render the detail body.
detailTDetail | undefined-Arbitrary payload passed to `renderDetail`.

LifecycleTimelineProps

PropTypeDefaultDescription
events*readonly LifecycleEvent<TDetail>[]-Events in chronological order (first event anchors `T+0`).
eventStylesRecord<string, LifecycleEventStyle> | undefined-Style map keyed by `event.kind`. Unknown kinds fall back gracefully.
fallbackStyleLifecycleEventStyle | 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.
loadingboolean | undefinedfalseRenders a skeleton instead of events.
emptyLabelReactNode-Message shown when there are no events.
classNamestring | undefined-
data-testidstring | undefined-

Supporting types

export type TimelineIcon = ComponentType<{ className?: string }>

npm dependencies

lucide-react@^1.7.0

Changelog

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-01 block's RoutingFlowTimeline. Hardcoded event kinds and detail renderers are replaced by a caller-supplied eventStyles map and a renderDetail render prop, so any event stream can drive the timeline. Ships empty/loading states and a matching LifecycleTimelineSkeleton.