Skip to main content
/
/
/
/
Multiple file upload

Multiple file upload

Handling events

You can use the onInput event on <FileInputArea> to handle selected files.

Use the multiple prop to allow selecting multiple files at the same time.

<FileInputArea multiple onInput={(e) => { const files = e.currentTarget.files; if (!files || !files.length) return; // add files to selected list... }} />
<FileInputArea multiple onInput={(e) => { const files = e.currentTarget.files; if (!files || !files.length) return; // add files to selected list... }} />

Area content

<FileInputArea multiple aria-label="Upload file" onInput={...}> <Icon icon={faArrowUpFromBracket} className="bfc-base-2" style={{ marginBottom: 8 }} /> <div> Drag & drop file(s) or{" "} <span className="bf-neutral-link-text">click to upload</span> </div> <div className="bfc-base-2">PDF only</div> </FileInputArea>
<FileInputArea multiple aria-label="Upload file" onInput={...}> <Icon icon={faArrowUpFromBracket} className="bfc-base-2" style={{ marginBottom: 8 }} /> <div> Drag & drop file(s) or{" "} <span className="bf-neutral-link-text">click to upload</span> </div> <div className="bfc-base-2">PDF only</div> </FileInputArea>
Drag & drop file(s) or click to upload
PDF only

List of files

Selected files can be displayed below the input field, with a loading spinner while uploading.

We've added a disabled<Button> here so it will take up the same space as the remove button later.

<Button small disabled noPadding> <Icon.Spinner /> </Button>{" "} uploading_file.jpg
<Button small disabled noPadding> <Icon.Spinner /> </Button>{" "} uploading_file.jpg

When the upload completes, swap to a faXMark icon button to remove. Adding a tooltip for clarity is also a good idea:

<Tooltip content="Remove"> <Button onClick={/* remove file */} variant="flat" small pill noPadding> <Icon icon={faXmark} /> </Button> </Tooltip>{" "} uploaded_file.png
<Tooltip content="Remove"> <Button onClick={/* remove file */} variant="flat" small pill noPadding> <Icon icon={faXmark} /> </Button> </Tooltip>{" "} uploaded_file.png

If the upload failed, you can include a red faCircleExclamation icon and a faRefresh icon button to retry:

<Tooltip content="Remove"> <Button onClick={/* remove file */} variant="flat" small pill noPadding> <Icon icon={faXmark} /> </Button> </Tooltip>{" "} <Tooltip content="Could not upload file"> <Icon icon={faCircleExclamation} className="bfc-alert" marginRight /> </Tooltip> upload_failed.png{" "} <Tooltip content="Retry"> <Button small onClick={/* re-upload */} variant="flat" noPadding pill> <Icon icon={faRefresh} /> </Button> </Tooltip>
<Tooltip content="Remove"> <Button onClick={/* remove file */} variant="flat" small pill noPadding> <Icon icon={faXmark} /> </Button> </Tooltip>{" "} <Tooltip content="Could not upload file"> <Icon icon={faCircleExclamation} className="bfc-alert" marginRight /> </Tooltip> upload_failed.png{" "} <Tooltip content="Retry"> <Button small onClick={/* re-upload */} variant="flat" noPadding pill> <Icon icon={faRefresh} /> </Button> </Tooltip>
uploading_file.jpg
uploaded_file.png
upload_failed.png

Interactive demo

This simulation lets you select some files, fake uploading for ~1s with a 10% chance to fail.

Drag & drop file(s) or click to upload
PDF only

Sandbox

import { useEffect, useState } from "react";
import { faExclamation } from "@fortawesome/free-solid-svg-icons/faExclamation";
import { faRefresh } from "@fortawesome/free-solid-svg-icons/faRefresh";
import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
import { faArrowUpFromBracket } from "@fortawesome/free-solid-svg-icons/faArrowUpFromBracket";

import Button from "@intility/bifrost-react/Button";
import FileInputArea from "@intility/bifrost-react/FileInputArea";
import Grid from "@intility/bifrost-react/Grid";
import Icon from "@intility/bifrost-react/Icon";
import Tooltip from "@intility/bifrost-react/Tooltip";

function SelectedFile({
  file,
  removeFile,
}: {
  file: File;
  removeFile: (file: File) => void;
}) {
  const [uploading, setUploading] = useState(false);
  const [failed, setFailed] = useState(false);

  // fake a http upload, with a 10% chance to fake fail
  const initiateUpload = () => {
    setFailed(false);
    setUploading(true);
    setTimeout(
      () => {
        setUploading(false);
        if (Math.random() > 0.9) {
          setFailed(true);
        }
      },
      Math.random() * 500 + 500,
    );
  };

  useEffect(initiateUpload, []);

  return (
    <div>
      <Tooltip content="Remove" disabled={uploading}>
        <Button
          onClick={() => removeFile(file)}
          variant="flat"
          small
          aria-label="Remove file"
          noPadding
          pill
          disabled={uploading}
        >
          {uploading ? <Icon.Spinner /> : <Icon icon={faXmark} />}
        </Button>
      </Tooltip>{" "}
      {failed && (
        <>
          <Tooltip content="Could not upload file">
            <Icon icon={faExclamation} className="bfc-alert" marginRight />
          </Tooltip>
        </>
      )}
      {file.name}{" "}
      {failed && (
        <Tooltip content="Retry">
          <Button small onClick={initiateUpload} variant="flat" noPadding pill>
            <Icon icon={faRefresh} />
          </Button>
        </Tooltip>
      )}
    </div>
  );
}

export default function MultipleFileUpload() {
  const [selectedFiles, setSelectedFiles] = useState<File[]>([]);

  const inputEventHandler = (e: React.FormEvent<HTMLInputElement>) => {
    const files = e.currentTarget.files;
    if (!files || !files.length) return;

    setSelectedFiles((x) => [
      ...x,
      ...Array.from(files).filter((y) => !x.some((f) => f.name === y.name)),
    ]);
  };

  return (
    <Grid>
      <FileInputArea multiple onInput={inputEventHandler}>
        <Icon
          icon={faArrowUpFromBracket}
          className="bfc-base-2"
          style={{ marginBottom: 8 }}
        />
        <div>
          Drag & drop file(s) or{" "}
          <span className="bf-neutral-link-text">click to upload</span>
        </div>
        <div className="bfc-base-2">PDF only</div>
      </FileInputArea>
      {selectedFiles.length > 0 && (
        <Grid gap={4}>
          {selectedFiles.map((file) => (
            <SelectedFile
              key={file.name}
              file={file}
              removeFile={(fileToRemove) =>
                setSelectedFiles(
                  selectedFiles.filter((x) => x !== fileToRemove),
                )
              }
            />
          ))}
        </Grid>
      )}
    </Grid>
  );
}