Componentfeedback1.16 KB gzipZero dependencies
Upload Progress
Single file upload with circular progress ring and cancel.
Preview
Live Preview
65%
ticket.pdf
Uploading…
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add upload-progressOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { UploadProgress } from "@/components/ui/upload-progress";
<UploadProgress />Usage guidance
Use Upload Progress when a ticket stub surface needs Single file upload with circular progress ring and cancel. Keep props minimal and prefer composition with sibling stubs.
Avoid Upload Progress for long form flows or dense data grids where a dedicated layout block fits better.
Source code
upload-progress.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 UploadProgressProps extends React.HTMLAttributes<HTMLDivElement> {
filename: string;
/** 0-100 */
progress: number;
onCancel?: () => void;
}
/** Single file upload progress: circular gauge + filename + cancel. */
export function UploadProgress({ filename, progress, onCancel, className, ...props }: UploadProgressProps) {
const R = 16;
const C = 2 * Math.PI * R;
const done = progress >= 100;
return (
<div className={cn("flex w-full max-w-sm items-center gap-3 rounded-md border border-border bg-card px-3 py-2.5", className)} {...props}>
<style>{`@keyframes upFade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }`}</style>
<span className="relative size-10 shrink-0" role="progressbar" aria-valuenow={progress} aria-valuemin={0} aria-valuemax={100} aria-label={filename}>
<svg viewBox="0 0 40 40" className="size-full -rotate-90">
<circle cx="20" cy="20" r={R} fill="none" stroke="var(--border)" strokeWidth="3" />
<circle
cx="20" cy="20" r={R} fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
className={done ? "text-success" : "text-accent"}
strokeDasharray={C}
strokeDashoffset={C - (C * Math.min(100, progress)) / 100}
style={{ transition: "stroke-dashoffset 0.4s ease-out" }}
/>
</svg>
<span className="absolute inset-0 flex items-center justify-center font-mono text-[9px] font-bold tabular-nums">
{done ? "✓" : `${progress}%`}
</span>
</span>
<div className="min-w-0 flex-1 animate-[upFade_0.3s_ease-out_both] motion-reduce:animate-none">
<p className="truncate text-sm font-medium">{filename}</p>
<p className="text-xs text-muted-foreground">{done ? "Upload complete" : "Uploading…"}</p>
</div>
{!done && (
<button
onClick={onCancel}
className="shrink-0 rounded-sm p-1 text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none"
aria-label={`Cancel ${filename} upload`}
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg>
</button>
)}
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| filename | string | |
| progress | number | 0-100 |
| onCancel | () => void |