Stepper

Upload Stages

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.

Free, MIT licenseOpen in Browse
"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>
  )
}

Installation

pnpm dlx shadcn add @01orbital/upload-stages
npx shadcn add @01orbital/upload-stages
yarn shadcn add @01orbital/upload-stages
bunx --bun shadcn add @01orbital/upload-stages

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/stepper/upload-stages/index.tsx, as source you own and edit.

Usage

import UploadStages from "@/components/blocks/stepper/upload-stages"

export default function Example() {
  return <UploadStages />
}

Dependencies

npm: @phosphor-icons/react. The CLI installs them for you.

Registry pieces it installs with it: