Primitives
File upload
card, dropzone, gallery, avatar, button, compact. Rejected files are listed with the reason; images get a thumbnail; progress and retry.
card · the border turns while a file is over it
Drag and drop files
or · up to 5.0 MB each
IMAGEPDFCSV
dropzone · the default
gallery · site photos, progress on the tiles, up to 7
avatar · one round image
Site logo
Square, PNG or JPG, up to 2 MB
button
PDF
compact · one row in a form
Rates sheet (.csv or .xlsx)
file-upload.tsx
"use client";
import { FileUpload } from "@waveinno/ui";
import * as React from "react";
function useFakeUpload(files: File[]) {
const [progress, setProgress] = React.useState<Record<string, number>>({});
const started = React.useRef(new Set<string>());
React.useEffect(() => {
const fresh = files.filter((f) => !started.current.has(f.name));
if (!fresh.length) return;
fresh.forEach((f) => started.current.add(f.name));
const id = window.setInterval(() => {
setProgress((p) => {
const next = { ...p };
let moving = false;
for (const f of fresh) {
const now = next[f.name] ?? 0;
if (now < 100) {
next[f.name] = Math.min(100, now + 6 + Math.random() * 12);
moving = true;
}
}
if (!moving) window.clearInterval(id);
return next;
});
}, 220);
}, [files]);
return progress;
}
export default function UploadDemo() {
const [files, setFiles] = React.useState<File[]>([]);
const [photos, setPhotos] = React.useState<File[]>([]);
const progress = useFakeUpload(files);
const photoProgress = useFakeUpload(photos);
return (
<div className="space-y-10">
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<p className="text-[12px] text-muted">card · the border turns while a file is over it</p>
<FileUpload variant="card" value={files} onValueChange={setFiles} accept="image/*,.pdf,.csv" maxSize={5 * 1024 * 1024} progress={progress} />
</div>
<div className="space-y-2">
<p className="text-[12px] text-muted">dropzone · the default</p>
<FileUpload accept="image/*,.pdf,.csv" maxSize={5 * 1024 * 1024} />
</div>
</div>
<div className="space-y-2">
<p className="text-[12px] text-muted">gallery · site photos, progress on the tiles, up to 7</p>
<FileUpload variant="gallery" value={photos} onValueChange={setPhotos} accept="image/*" maxFiles={7} progress={photoProgress} />
</div>
<div className="grid gap-6 md:grid-cols-3">
<div className="space-y-2">
<p className="text-[12px] text-muted">avatar · one round image</p>
<FileUpload variant="avatar" accept="image/*" maxSize={2 * 1024 * 1024} title="Site logo" hint="Square, PNG or JPG, up to 2 MB" />
</div>
<div className="space-y-2">
<p className="text-[12px] text-muted">button</p>
<FileUpload variant="button" multiple={false} accept=".pdf" title="Attach the contract" />
</div>
<div className="space-y-2">
<p className="text-[12px] text-muted">compact · one row in a form</p>
<FileUpload variant="compact" multiple={false} accept=".csv,.xlsx" title="Rates sheet (.csv or .xlsx)" />
</div>
</div>
</div>
);
}Import#
Everything comes from the package root. The example above uses:
your-component.tsx
import { FileUpload } from "@waveinno/ui";Props#
Read from the source of this version, so they match the code exactly.
<FileUpload>
src/components/ui/file-upload.tsx| Prop | Type | Default | Description |
|---|---|---|---|
value | File[] | — | |
defaultValue | File[] | [] | |
onValueChange | (files: File[]) => void | — | |
accept | string | — | As on <input type="file">: "image/*", ".csv,.xlsx". |
multiple | boolean | — | |
maxSize | number | — | Bytes. |
maxFiles | number | — | |
variant | "dropzone" | "card" | "compact" | "button" | "gallery" | "avatar" | "dropzone" | |
title | React.ReactNode | — | |
hint | React.ReactNode | — | |
progress | Record<string, number> | — | Upload progress by file name, 0–100. |
failed | string[] | — | Names of files whose upload failed; they get a retry button. |
onRetry | (file: File) => void | — | |
currentUrl | string | — | avatar: the current image (a URL) before a new one is picked. |
disabled | boolean | — | |
className | string | — |
Install#
If the project does not have the package yet, add it, then follow the setup for Next.js or Vite once.
$ npm install @waveinno/ui