"use client"; import { PauseIcon, PlayIcon, RotateCcwIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export interface MilestoneStep { id: string; title: string; description?: string; } export interface MilestoneRailProps { steps: MilestoneStep[]; title?: string; initialDone?: number; autoPlay?: boolean; stepMs?: number; onChange?: (done: number) => void; className?: string; } const ROW = 60; const NODE = 22; const spring = { type: "spring", stiffness: 380, damping: 30 } as const; const iconButton = "transition-[scale,background-color] duration-150 ease-out after:absolute after:-inset-1 active:scale-[0.97]"; function Percent({ value }: { value: number }) { const reduce = useReducedMotion(); const digits = String(value).split(""); return ( {digits.map((d, i) => ( {d} ))} % ); } export function MilestoneRail({ steps, title = "Getting started", initialDone = 0, autoPlay = false, stepMs = 1400, onChange, className, }: MilestoneRailProps) { const reduce = useReducedMotion(); const [done, setDone] = useState(initialDone); const [playing, setPlaying] = useState(autoPlay); const total = steps.length; const complete = done >= total; const pct = Math.round((Math.min(done, total) / total) * 100); const onChangeRef = useRef(onChange); onChangeRef.current = onChange; useEffect(() => { onChangeRef.current?.(done); }, [done]); useEffect(() => { if (!playing) return; const t = setTimeout(() => setDone((d) => (d >= total ? 0 : d + 1)), done >= total ? stepMs * 1.8 : stepMs); return () => clearTimeout(t); }, [playing, done, total, stepMs]); const activeIdx = Math.min(done, total - 1); const fillTo = activeIdx * ROW; return (

{title}

{complete && ( All done )}
{pct}%
1 ? fillTo / ((total - 1) * ROW) : 0 }} className={cn( "absolute left-1/2 w-0.5 -translate-x-1/2 origin-top rounded-full transition-colors duration-500", complete ? "bg-success" : "bg-primary", )} initial={false} style={{ top: ROW / 2, height: (total - 1) * ROW }} transition={reduce ? { duration: 0 } : { type: "spring", stiffness: 170, damping: 26 }} />
    {steps.map((s, i) => { const isDone = i < done; const isActive = i === done; return (
  1. ); })}
); }