EdgeStore

Multi-file

A component for uploading multiple files with a progress list, retry and a running total.

Click to upload or drag and drop

up to 1 MB each · 10 max

Each file shows its type, size and progress. Failed uploads show the error with a retry button, and running uploads can be canceled.

Installation

Use the shadcn CLI to add the component to your project.

npx shadcn@latest add https://edgestore.dev/r/file-uploader.json

Setup for manual installation

First you will need to follow the manual install setup guide.

Copy this component

components/upload/multi-file.tsx
'use client';

import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import * as React from 'react';
import { type Accept } from 'react-dropzone';
import { Dropzone } from './dropzone';
import { FileList } from './file-list';
import { formatFileSize } from './upload-utils';
import { useUploader } from './uploader-provider';

/**
 * Props for the FileUploader component.
 */
export type FileUploaderProps = React.ComponentProps<'div'> & {
  /**
   * Maximum number of files allowed in total.
   */
  maxFiles?: number;

  /**
   * Maximum file size in bytes.
   */
  maxSize?: number;

  /**
   * Accepted file types.
   *
   * @example
   * ```tsx
   * accept={{ 'application/pdf': ['.pdf'] }}
   * ```
   */
  accept?: Accept;

  /**
   * Human-readable list of accepted types, shown in the hint and messages.
   *
   * @example "PDF or TXT"
   */
  typesLabel?: string;

  /**
   * Whether the uploader is disabled.
   */
  disabled?: boolean;

  /**
   * Additional className for the dropzone.
   */
  dropzoneClassName?: string;

  /**
   * Additional className for the file list.
   */
  fileListClassName?: string;
};

/**
 * A dropzone with a list of files, their progress and a summary.
 *
 * @example
 * ```tsx
 * <FileUploader
 *   maxFiles={5}
 *   maxSize={1024 * 1024 * 10} // 10MB
 *   accept={{ 'application/pdf': ['.pdf'] }}
 *   typesLabel="PDF"
 * />
 * ```
 */
export function FileUploader({
  maxFiles,
  maxSize,
  accept,
  typesLabel,
  disabled,
  className,
  dropzoneClassName,
  fileListClassName,
  ...props
}: FileUploaderProps) {
  const { fileStates, resetFiles } = useUploader();
  const completed = fileStates.filter((fs) => fs.status === 'COMPLETE').length;
  const totalSize = fileStates.reduce((sum, fs) => sum + fs.file.size, 0);

  return (
    <div className={cn('flex w-full flex-col gap-3', className)} {...props}>
      <Dropzone
        dropzoneOptions={{ maxFiles, maxSize, accept, typesLabel }}
        disabled={disabled}
        className={dropzoneClassName}
      />
      <FileList className={fileListClassName} disabled={disabled} />
      {fileStates.length > 0 && (
        <div className="flex items-center justify-between gap-3 text-xs text-muted-foreground">
          <span className="tabular-nums">
            {completed} of {fileStates.length} uploaded ·{' '}
            {formatFileSize(totalSize)}
          </span>
          <Button
            type="button"
            variant="ghost"
            size="sm"
            onClick={resetFiles}
            disabled={disabled}
          >
            Clear all
          </Button>
        </div>
      )}
    </div>
  );
}

Usage

Install or copy the component from Installation before using this example.

'use client';

import { FileUploader } from '@/components/upload/multi-file';
import {
  UploaderProvider,
  type UploadFn,
} from '@/components/upload/uploader-provider';
import { useEdgeStore } from '@/lib/edgestore';
import * as React from 'react';

export function MultiFileDropzoneUsage() {
  const { edgestore } = useEdgeStore();

  const uploadFn: UploadFn = React.useCallback(
    async ({ file, onProgressChange, signal }) => {
      const res = await edgestore.publicFiles.upload({
        file,
        signal,
        onProgressChange,
      });
      // you can run some server action or api here
      // to add the necessary data to your database
      console.log(res);
      return res;
    },
    [edgestore],
  );

  return (
    <UploaderProvider uploadFn={uploadFn} autoUpload>
      <FileUploader
        maxFiles={5}
        maxSize={1024 * 1024 * 1} // 1 MB
        accept={{
          'application/pdf': ['.pdf'],
          'text/plain': ['.txt'],
        }}
        typesLabel="PDF or TXT"
      />
    </UploaderProvider>
  );
}

Props

PropTypeDefaultDescription
maxFilesnumberMaximum number of files in total.
maxSizenumberMaximum file size in bytes.
acceptAcceptall typesAccepted file types, in react-dropzone's format.
typesLabelstringHuman-readable list of accepted types, shown in the hint and in error messages.
disabledbooleanfalseDisables the component.
dropzoneClassNamestringAdditional className for the dropzone.
fileListClassNamestringAdditional className for the file list.

Building your own layout

FileUploader is a Dropzone and a FileList, so you can arrange the two differently.

On this page