Componentcharts1.1 KB gzipZero dependencies
Milestone Chart
Horizontal milestone timeline progress line with reached checkpoints.
Preview
Live Preview
Doors Open18:00
Soundcheck19:00
Main Stage20:30
Curtain Call23:00
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add milestone-chartOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { MilestoneChart } from "@/components/ui/milestone-chart";
<MilestoneChart milestones={[{ id: "1", label: "Doors Open", date: "18:00", reached: true }]} />Usage guidance
Use Milestone Chart when a ticket stub surface needs Horizontal milestone timeline progress line with reached checkpoints. Keep props minimal and prefer composition with sibling stubs.
Avoid Milestone Chart for long form flows or dense data grids where a dedicated layout block fits better.
Source code
milestone-chart.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface Milestone {
id: string;
label: string;
date: string;
reached?: boolean;
}
export interface MilestoneChartProps extends React.HTMLAttributes<HTMLDivElement> {
milestones: Milestone[];
}
/** Milestone progression chart: timeline checkpoints on track. */
export function MilestoneChart({ milestones, className, ...props }: MilestoneChartProps) {
const reachedCount = milestones.filter((m) => m.reached).length;
const pct = Math.round((reachedCount / Math.max(1, milestones.length)) * 100);
return (
<div className={cn("w-full max-w-md px-2 py-4", className)} {...props}>
<style>{`
@keyframes mcLine { from { transform: scaleX(0); } }
@keyframes mcPop { 0% { transform: scale(0); } 70% { transform: scale(1.25); } 100% { transform: scale(1); } }
`}</style>
<div className="relative">
{/* Base track */}
<div className="absolute left-0 right-0 top-2.5 h-0.5 rounded-full bg-border/60" aria-hidden="true" />
{/* Progress fill */}
<div
className="absolute left-0 top-2.5 h-0.5 origin-left rounded-full bg-accent"
style={{ width: `${pct}%`, animation: "mcLine 0.7s cubic-bezier(0.16,1,0.3,1) both" }}
aria-hidden="true"
/>
{/* Noktalar */}
<div className="relative flex justify-between">
{milestones.map((m, idx) => (
<div key={m.id} className="flex flex-col items-center gap-1.5" style={{ width: `${100 / milestones.length}%` }}>
<span
className={cn(
"size-5 rounded-full border-2",
m.reached ? "border-accent bg-accent text-accent-foreground" : "border-border bg-card"
)}
style={{ animation: m.reached ? `mcPop 0.35s ease-out both` : undefined, animationDelay: `${idx * 120}ms` }}
aria-hidden="true"
>
{m.reached && (
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" className="mx-auto mt-0.5"><path d="M20 6L9 17l-5-5" /></svg>
)}
</span>
<span className="text-center">
<span className={cn("block text-[11px] leading-tight", m.reached ? "font-medium text-foreground" : "text-muted-foreground")}>{m.label}</span>
<span className="block font-mono text-[9px] text-muted-foreground">{m.date}</span>
</span>
</div>
))}
</div>
</div>
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| milestones | Milestone[] | List of milestones with id, label, date and reached flag |