bigbullui logobigbullui
Componentfeedback

Upload Progress

Single file upload with circular progress ring and cancel.

Preview

Live Preview
65%

ticket.pdf

Yükleniyor…

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add upload-progress

Or install the full npm package:

npm install bigbullui

Usage

import { UploadProgress } from "@/components/ui/upload-progress";

<UploadProgress />

Source code

upload-progress.tsx

Zero 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;
}

/** Tek dosya yükleme: dairesel ilerleme + dosya adı + iptal. */
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-emerald-500" : "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 ? "Yükleme tamamlandı" : "Yükleniyor…"}</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={`${filename} yüklemesini iptal et`}
        >
          <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

PropTypeDescription
filenamestring
progressnumber0-100
onCancel() => void