data display

timeline

a list of things that happened, newest first.

  1. deployed to production

    version 1.0.4 is now live.

  2. build triggered

    started by manyports

  3. merged pull request #42

    added new components

  4. flaky test detected

    carousel.spec.ts

installation

new to blurrow? this sets up your project in one go: the package, the styles and the font. already done it? skip straight to usage. the installation page has the details.

bash
npx @blurrow/ui init
›rather own the code? copy the source instead

let the cli copy it, along with anything it needs:

bash
npx @blurrow/ui add timeline

or by hand: drop this file into components/ui and import from there.

components/ui/Timeline.tsxtsx
import React from "react";
type TimelineVariant = "default" | "success" | "warning" | "accent";
interface TimelineItem {
key: string;
title: string;
description?: string;
date?: string;
icon?: React.ReactNode;
variant?: TimelineVariant;
}
interface TimelineProps {
items: TimelineItem[];
className?: string;
}
/* Each event is a small raised node with a lit dot, like an indicator
lamp; the variant only changes the lamp's colour. Nodes are joined by
one continuous groove, and titles sit on the node's centre line. */
const lamp: Record<TimelineVariant, string> = {
default: "#262626",
success: "#10b981",
warning: "#f59e0b",
accent: "#8b5cf6",
};
const iconText: Record<TimelineVariant, string> = {
default: "text-neutral-600",
success: "text-emerald-600",
warning: "text-amber-600",
accent: "text-violet-600",
};
export function Timeline({ items, className = "" }: TimelineProps) {
return (
<div
className={["rounded-[18px] p-1.5", className].filter(Boolean).join(" ")}
style={{ background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }}
>
<ol
className="bg-white rounded-[13px] px-5 py-5 flex flex-col"
style={{ boxShadow: "var(--panel-face-shadow)" }}
>
{items.map((item, index) => {
const variant = item.variant ?? "default";
const isLast = index === items.length - 1;
return (
<li key={item.key} className={["relative flex gap-4", isLast ? "" : "pb-6"].join(" ")}>
{!isLast && (
<span
aria-hidden
className="absolute left-[11px] top-[30px] bottom-[6px] w-[2px] rounded-full"
style={{ background: "var(--well)", boxShadow: "var(--well-shadow)" }}
/>
)}
<span
aria-hidden
className={[
"relative w-6 h-6 rounded-full flex items-center justify-center shrink-0",
iconText[variant],
].join(" ")}
style={{ background: "var(--face-light)", boxShadow: "var(--tile-light-shadow)" }}
>
{item.icon ?? (
<span
className="w-2 h-2 rounded-full"
style={{
background: lamp[variant],
boxShadow: `0 0 6px ${lamp[variant]}66, inset 0 1px 1px rgba(255,255,255,0.35)`,
}}
/>
)}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-baseline justify-between gap-3">
<p className="text-[13.5px] font-medium text-neutral-900 leading-6 tracking-[-0.005em]">
{item.title}
</p>
{item.date && (
<time className="text-[12px] text-neutral-400 tabular-nums shrink-0">
{item.date}
</time>
)}
</div>
{item.description && (
<p className="text-[13px] text-neutral-500 leading-relaxed mt-0.5">
{item.description}
</p>
)}
</div>
</li>
);
})}
</ol>
</div>
);
}

usage

tsx
import { Timeline } from "@blurrow/ui";
tsx
<Timeline items={events} />

props

straight from the source, so they're always up to date.

propsts
type TimelineVariant = "default" | "success" | "warning" | "accent";
interface TimelineItem {
key: string;
title: string;
description?: string;
date?: string;
icon?: React.ReactNode;
variant?: TimelineVariant;
}
interface TimelineProps {
items: TimelineItem[];
className?: string;
}