Saurabh

BeUI File Upload

Component 289

About

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds BeUI File Upload to your project and fits it to your theme.

Code

How BeUI File Upload is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { RotateCcw } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import {
  FileUpload,
  type FileUploadItem,
  type FileUploadVariant,
} from "@/components/ui/be-ui-file-upload";

const initialItems: FileUploadItem[] = [
  {
    id: "brand-assets",
    name: "brand-assets.zip",
    size: 18_400_000,
    type: "application/zip",
    progress: 100,
    status: "success",
  },
  {
    id: "release-video",
    name: "release-cut.mov",
    size: 84_200_000,
    type: "video/quicktime",
    progress: 58,
    status: "uploading",
  },
  {
    id: "contracts",
    name: "vendor-contract.pdf",
    size: 2_800_000,
    type: "application/pdf",
    progress: 32,
    status: "error",
    error: "Connection lost",
  },
];

const variants: { id: FileUploadVariant; label: string }[] = [
  { id: "centered", label: "Centered" },
  { id: "default", label: "Row" },
];

export default function FileUploadPreview() {
  const [items, setItems] = useState(initialItems);
  const [variant, setVariant] = useState<FileUploadVariant>("centered");
  const timersRef = useRef<Map<string, ReturnType<typeof setInterval>>>(
    new Map(),
  );

  const stopUpload = useCallback((id: string) => {
    const timer = timersRef.current.get(id);
    if (!timer) return;
    clearInterval(timer);
    timersRef.current.delete(id);
  }, []);

  const startUpload = useCallback(
    (id: string) => {
      stopUpload(id);

      const timer = setInterval(() => {
        setItems((current) => {
          const target = current.find((item) => item.id === id);
          if (target?.status !== "uploading") {
            stopUpload(id);
            return current;
          }

          const nextProgress = Math.min(
            100,
            (target.progress ?? 0) + 7 + Math.random() * 12,
          );

          if (nextProgress >= 100) {
            stopUpload(id);
          }

          return current.map((item) =>
            item.id === id
              ? {
                  ...item,
                  progress: nextProgress,
                  status: nextProgress >= 100 ? "success" : "uploading",
                }
              : item,
          );
        });
      }, 520);

      timersRef.current.set(id, timer);
    },
    [stopUpload],
  );

  useEffect(() => {
    startUpload("release-video");

    return () => {
      for (const timer of timersRef.current.values()) {
        clearInterval(timer);
      }
      timersRef.current.clear();
    };
  }, [startUpload]);

  return (
    <div className="flex min-h-[30rem] w-full items-center justify-center">
      <div className="w-full max-w-md rounded-[2rem] border border-border bg-background p-3">
        <div className="mb-3 flex flex-wrap items-center justify-between gap-2 px-1">
          <div>
            <p className="text-sm font-semibold text-foreground">
              Upload package
            </p>
            <p className="text-xs text-muted-foreground">
              {items.filter((item) => item.status === "success").length} of{" "}
              {items.length} files ready
            </p>
          </div>

          <div className="flex items-center gap-1.5">
            <div className="flex rounded-full border border-border bg-muted p-1">
              {variants.map((entry) => {
                const selected = entry.id === variant;

                return (
                  <button
                    key={entry.id}
                    type="button"
                    onClick={() => setVariant(entry.id)}
                    data-selected={selected}
                    className="h-7 rounded-full px-3 text-xs font-medium text-muted-foreground transition-[background-color,color,transform] duration-150 hover:text-foreground active:scale-95 data-[selected=true]:bg-background data-[selected=true]:text-foreground"
                  >
                    {entry.label}
                  </button>
                );
              })}
            </div>

            <button
              type="button"
              onClick={() => {
                for (const item of items) {
                  stopUpload(item.id);
                }
                setItems(initialItems);
                startUpload("release-video");
              }}
              className="grid h-9 w-9 place-items-center rounded-full border border-border text-muted-foreground transition-colors hover:text-foreground active:scale-95"
              aria-label="Reset upload queue"
            >
              <RotateCcw className="h-3.5 w-3.5" />
            </button>
          </div>
        </div>

        <FileUpload
          value={items}
          variant={variant}
          onValueChange={setItems}
          onFilesAdded={(added) => {
            for (const item of added) {
              startUpload(item.id);
            }
          }}
          onRetry={(item) => startUpload(item.id)}
          onRemove={(item) => stopUpload(item.id)}
          maxFiles={5}
          title={variant === "centered" ? "Drop files to upload" : "Drop release files"}
          description="PDF, images, video or zipped assets"
        />
      </div>
    </div>
  );
}

Dependencies

More by Saurabh

View all
  • BeUI Tilt Card

    BeUI Tilt Card

    366
  • BeUI Expandable Tabs

    BeUI Expandable Tabs

    342
  • BeUI Bouncy Accordion

    BeUI Bouncy Accordion

    200
  • BeUI Create Menu

    BeUI Create Menu

    199
  • BeUI Expanable Action Bar

    BeUI Expanable Action Bar

    182
  • Loader

    Loader

    173
  • BeUI OTP Input

    BeUI OTP Input

    166
  • BeUI 404 / Not Found

    BeUI 404 / Not Found

    165