
Compact file-upload card with three accent-colored bar segments for uploading, processing and ready. The current segment pulses while its stage runs and holds still under reduced motion.
blocks/stepper/upload-stages/index.tsx
"use client"
import { useState } from "react"
import { FileIcon } from "@phosphor-icons/react"
import {
Stepper,
StepperItem,
StepperList,
StepperSeparator,
StepperTitle,
} from "@/components/stepper/stepper"
import { Button } from "@/components/ui/button"
// The copy: change text here, not in the markup.
const COPY = {
stages: ["Uploading", "Processing", "Ready"],
file: "launch-brief-v3.pdf",
size: "2.4 MB",
advance: "Advance stage",
reset: "Reset",
} as const
// Three stages as bars in the accent ink. The current segment breathes
// while its stage runs; reduced motion stills it. Advance is here only so
// the states can be seen; a real upload drives the value.
export default function UploadStages() {
const [stage, setStage] = useState(1)
const last = COPY.stages.length - 1
const running = stage < last
return (
<div className="mx-auto w-full max-w-sm rounded-xl border border-border bg-card p-4 text-card-foreground [--stepper-accent:oklch(0.52_0.12_158)]">
<div className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-lg bg-muted text-muted-foreground">
<FileIcon className="size-4" aria-hidden />
</span>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{COPY.file}</p>
<p className="text-xs text-muted-foreground">
{COPY.size} · {COPY.stages[stage]}
</p>
</div>
</div>
<Stepper
value={stage}
variant="bars"
rail="accent"
className="mt-4 gap-0"
>
<StepperList>
{COPY.stages.map((s, i) => (
<StepperItem key={s} value={i}>
<StepperSeparator
className={
running
? "data-[state=active]:animate-pulse motion-reduce:animate-none"
: undefined
}
/>
<StepperTitle className="sr-only">{s}</StepperTitle>
</StepperItem>
))}
</StepperList>
</Stepper>
<div className="mt-4 flex justify-end gap-2">
<Button size="xs" variant="ghost" onClick={() => setStage(0)}>
{COPY.reset}
</Button>
<Button
size="xs"
variant="outline"
disabled={!running}
onClick={() => setStage(stage + 1)}
>
{COPY.advance}
</Button>
</div>
</div>
)
}