EdgeStore

Upload Button

A button that opens the file dialog and also accepts files dropped onto it.

It takes the same props as the shadcn/ui Button. Several buttons can feed the same UploaderProvider, so you can pair them with a FileList or build your own list with useUploader.

Installation

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

npx shadcn@latest add https://edgestore.dev/r/upload-button.json

Setup for manual installation

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

Copy this component

components/upload/upload-button.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 { dropzoneState, useUploadDropzone } from './dropzone';

/**
 * Props for the UploadButton component.
 */
export type UploadButtonProps = Omit<
  React.ComponentProps<typeof Button>,
  'onClick' | 'type' | 'className'
> & {
  className?: string;

  /**
   * Accepted file types.
   */
  accept?: Accept;

  /**
   * Human-readable list of accepted types, used in messages.
   */
  typesLabel?: string;

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

  /**
   * Maximum number of files allowed in total.
   */
  maxFiles?: number;

  /**
   * Whether more than one file can be picked at once.
   * @default true
   */
  multiple?: boolean;

  /**
   * Called with messages about files that were not added
   * (wrong type, too large, over the limit).
   */
  onRejected?: (messages: string[]) => void;
};

/**
 * A button that opens the file dialog and also accepts files dropped onto it.
 * Several buttons can feed the same `UploaderProvider`.
 *
 * @example
 * ```tsx
 * <UploadButton maxSize={1024 * 1024 * 10} onRejected={setErrors}>
 *   <UploadIcon /> Upload files
 * </UploadButton>
 * ```
 */
export function UploadButton({
  accept,
  typesLabel,
  maxSize,
  maxFiles,
  multiple = true,
  onRejected,
  disabled,
  className,
  children,
  ...props
}: UploadButtonProps) {
  const { getRootProps, getInputProps, isDragActive, isDragReject, isFull } =
    useUploadDropzone({
      accept,
      typesLabel,
      maxSize,
      maxFiles,
      multiple,
      disabled,
      onRejected,
      // A <button> already opens the dialog on Enter and Space.
      noKeyboard: true,
    });

  // react-dropzone keeps its hidden input in the layout, so it is wrapped with the
  // button to keep it out of the parent's gaps.
  return (
    <span className="inline-flex">
      <Button
        {...props}
        {...getRootProps({ role: 'button' })}
        {...dropzoneState({ isDragActive, isDragReject })}
        type="button"
        disabled={disabled || isFull}
        className={cn(
          'data-[dragging]:ring-[3px] data-[dragging]:ring-ring/50 data-[rejected]:ring-destructive/40',
          className,
        )}
      >
        {children}
      </Button>
      <input {...getInputProps()} />
    </span>
  );
}

Usage

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

'use client';

import { UploadErrors } from '@/components/upload/dropzone';
import { FileList } from '@/components/upload/multi-file';
import { UploadButton } from '@/components/upload/upload-button';
import {
  UploaderProvider,
  type UploadFn,
} from '@/components/upload/uploader-provider';
import { useEdgeStore } from '@/lib/edgestore';
import { UploadIcon } from 'lucide-react';
import * as React from 'react';

export function UploadButtonUsage() {
  const [errors, setErrors] = React.useState<string[]>([]);
  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>
      <UploadButton
        maxSize={1024 * 1024 * 1} // 1 MB
        onRejected={setErrors}
      >
        <UploadIcon /> Upload files
      </UploadButton>
      <UploadErrors errors={errors} onDismiss={() => setErrors([])} />
      <FileList />
    </UploaderProvider>
  );
}

Props

PropTypeDefaultDescription
maxFilesnumberMaximum number of files in total.
multiplebooleantrueWhether more than one file can be picked at once.
onRejected(messages: string[]) => voidCalled with messages about files that were not added.
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.
variant, size, ...Any other Button prop.

On this page