A deploy streams its build log under a bar in the header, labelled with the current phase. Waiting for the health check hangs for seconds with nothing new in the log, and the ruler zooms the whole time until the 200 lands.
registry/demos/zeno-progress-deploy-demo.tsx
"use client";
import * as React from "react";
import { AnimatePresence, motion, useInView } from "motion/react";
import { ZenoProgress } from "@/components/ui/zeno-progress";
type Line = { at: number; text: string; phase: string; tone?: "dim" | "ok" | "wait" };
// ms from the start of the run. The health check hangs on purpose: nothing
// arrives for seconds, and the bar's ruler zooms the whole time.
const script: Line[] = [
{ at: 300, text: "Cloning acme/storefront@8f3c2a1", phase: "Cloning" },
{ at: 900, text: "Restored build cache (412 MB)", phase: "Cloning", tone: "dim" },
{ at: 1500, text: "$ npm ci", phase: "Installing" },
{ at: 2300, text: "added 1284 packages in 2.7s", phase: "Installing", tone: "dim" },
{ at: 2900, text: "$ next build", phase: "Building" },
{ at: 3700, text: "✓ Compiled successfully in 2.9s", phase: "Building", tone: "ok" },
{ at: 4300, text: "✓ Generating static pages (42/42)", phase: "Building", tone: "ok" },
{ at: 4900, text: "Uploading build output (18.4 MB)", phase: "Uploading" },
{ at: 5600, text: "Starting 3 instances in iad1", phase: "Starting" },
{ at: 6200, text: "Waiting for health check…", phase: "Waiting for health check", tone: "wait" },
{ at: 13800, text: "✓ GET /api/health 200 in 41ms", phase: "Waiting for health check", tone: "ok" },
];
const DONE_AT = 14300;
const toneClass = {
dim: "text-zinc-500",
ok: "text-emerald-400",
wait: "text-amber-300",
} as const;
export function ZenoProgressDeployDemo() {
const ref = React.useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, amount: 0.4 });
const [run, setRun] = React.useState(0);
const [elapsed, setElapsed] = React.useState(0);
React.useEffect(() => {
if (!inView) return;
const start = performance.now();
const timer = window.setInterval(() => {
const t = performance.now() - start;
setElapsed(Math.min(t, DONE_AT));
if (t >= DONE_AT) window.clearInterval(timer);
}, 100);
return () => window.clearInterval(timer);
}, [inView, run]);
const lines = script.filter((line) => line.at <= elapsed);
const done = elapsed >= DONE_AT;
const phase = lines.at(-1)?.phase ?? "Queued";
const redeploy = () => {
setElapsed(0);
setRun((r) => r + 1);
};
return (
<div
ref={ref}
className="w-full max-w-xl overflow-hidden rounded-2xl border border-zinc-800 bg-zinc-950 font-mono text-[13px] text-zinc-300 shadow-sm"
>
<div className="border-b border-zinc-800/80 px-4 pt-3.5 pb-3">
<div className="mb-3 flex items-center justify-between gap-3 font-sans">
<div className="flex min-w-0 items-center gap-2">
<span className="size-2 shrink-0 rounded-full bg-emerald-400/90" aria-hidden />
<span className="truncate text-sm font-medium text-zinc-100">storefront</span>
<span className="shrink-0 rounded-full border border-zinc-800 px-2 py-0.5 text-xs text-zinc-400">
production
</span>
</div>
<button
type="button"
onClick={redeploy}
disabled={!done}
className="shrink-0 rounded-full border border-zinc-700 px-3 py-1 text-xs font-medium text-zinc-200 transition-[opacity,transform] hover:bg-zinc-900 active:scale-[0.97] disabled:opacity-40"
>
Redeploy
</button>
</div>
<ZenoProgress
key={inView ? "live" : "idle"}
done={done}
label={phase}
doneLabel="Deployed to storefront.acme.app"
interval={1500}
className="font-sans text-zinc-400"
barClassName="text-zinc-100"
/>
</div>
<div className="flex h-64 flex-col justify-end overflow-hidden px-4 py-3 leading-6">
<AnimatePresence initial={false}>
{lines.map((line) => (
<motion.div
key={`${run}-${line.at}`}
layout="position"
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", visualDuration: 0.3, bounce: 0 }}
className={`truncate ${line.tone ? toneClass[line.tone] : ""}`}
>
{line.text}
</motion.div>
))}
{done ? (
<motion.div
key={`${run}-done`}
layout="position"
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", visualDuration: 0.3, bounce: 0 }}
className="truncate text-zinc-100"
>
▲ Deployed in 14.3s
</motion.div>
) : null}
</AnimatePresence>
</div>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/zeno-progress#zeno-progress-deploy-demo