Better Upload

Upload Progress

A component to display upload progress for files being uploaded.

Preview

PDF

invoice_123.pdf

2 MB

Completed

IMG

photo.jpeg

12 MB

Installation

npx shadcn@latest add @better-upload/upload-progress

Install the following dependencies:

npm i lucide-react react-dropzone

Make sure to have shadcn/ui set up in your project, with the progress component installed.

Copy and paste the following code into your project.

components/ui/upload-progress.tsx
import { Progress } from '@/components/ui/progress';
import { cn } from '@/lib/utils';
import type { UploadHookControl } from '@better-upload/client';
import { formatBytes } from '@better-upload/client/helpers';
import { Dot, File } from 'lucide-react';

type UploadProgressProps = {
  control: UploadHookControl<true>;

  // Add any additional props you need.
};

export function UploadProgress({
  control: { progresses },
}: UploadProgressProps) {
  return (
    <div className="grid gap-2">
      {progresses.map((progress) => (
        <div
          key={progress.objectInfo.key}
          className={cn(
            'dark:bg-input/10 flex items-center gap-2 rounded-lg border bg-transparent p-3',
            {
              'bg-red-500/[0.04]! border-red-500/60':
                progress.status === 'failed',
            }
          )}
        >
          <FileIcon type={progress.type} />

          <div className="grid grow gap-1">
            <div className="flex items-center gap-0.5">
              <p className="max-w-40 truncate text-sm font-medium">
                {progress.name}
              </p>
              <Dot className="text-muted-foreground size-4" />
              <p className="text-muted-foreground text-xs">
                {formatBytes(progress.size)}
              </p>
            </div>

            <div className="flex h-4 items-center">
              {progress.progress < 1 && progress.status !== 'failed' ? (
                <Progress className="h-1.5" value={progress.progress * 100} />
              ) : progress.status === 'failed' ? (
                <p className="text-xs text-red-500">Failed</p>
              ) : (
                <p className="text-muted-foreground text-xs">Completed</p>
              )}
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

const iconCaptions = {
  'image/': 'IMG',
  'video/': 'VID',
  'audio/': 'AUD',
  'application/pdf': 'PDF',
  'application/zip': 'ZIP',
  'application/x-rar-compressed': 'RAR',
  'application/x-7z-compressed': '7Z',
  'application/x-tar': 'TAR',
  'application/json': 'JSON',
  'application/javascript': 'JS',
  'text/plain': 'TXT',
  'text/csv': 'CSV',
  'text/html': 'HTML',
  'text/css': 'CSS',
  'application/xml': 'XML',
  'application/x-sh': 'SH',
  'application/x-python-code': 'PY',
  'application/x-executable': 'EXE',
  'application/x-disk-image': 'ISO',
};

function FileIcon({ type }: { type: string }) {
  const caption = Object.entries(iconCaptions).find(([key]) =>
    type.startsWith(key)
  )?.[1];

  return (
    <div className="relative shrink-0">
      <File className="text-muted-foreground size-12" strokeWidth={1} />

      {caption && (
        <span className="bg-primary text-primary-foreground absolute bottom-2.5 left-0.5 select-none rounded px-1 py-px text-xs font-semibold">
          {caption}
        </span>
      )}
    </div>
  );
}

Update the import paths to match your project setup.

Usage

The <UploadProgress /> component should be used with the useUploadFiles hook.

'use client';

import { useUploadFiles } from '@better-upload/client';
import { UploadProgress } from '@/components/ui/upload-progress';

export function Uploader() {
  const { control } = useUploadFiles({
    route: 'images',
  });

  return <UploadProgress control={control} />;
}

This component should probably be used alongside another component, such as <UploadDropzone />, to trigger the file uploads.

'use client';

import { useUploadFiles } from '@better-upload/client';
import { UploadDropzone } from '@/components/ui/upload-dropzone';
import { UploadProgress } from '@/components/ui/upload-progress';

export function Uploader() {
  const { control } = useUploadFiles({
    route: 'images',
  });

  return (
    <div className="flex flex-col gap-4">
      <UploadDropzone control={control} accept="image/*" />
      <UploadProgress control={control} />
    </div>
  );
}

Props

Prop

Type

Default

On this page

Sponsored by
Next.js Weekly

Stay up to date on Next.js

A weekly newsletter to keep up with what's happening in the ecosystem.

Need software licensing?

Simple licensing for software, easily distribute your product.

  • - payment automation
  • - customer portal
  • - offline licensing