"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import {
AttachmentsTray,
type TrayFile,
} from "@/components/ui/attachments-tray";
const settings = {
failOverMb: 25,
showSamples: true,
uploadMs: 1800,
};
const SAMPLES: TrayFile[] = [
{
id: "sample-1",
name: "quote-north-office.pdf",
progress: 1,
size: 482_000,
status: "ready",
},
{
id: "sample-2",
name: "travel-budget.xlsx",
progress: 0.35,
size: 1_900_000,
status: "uploading",
},
];
const NONE: TrayFile[] = [];
const STEP_MS = 100;
const MB = 1024 * 1024;
/** One tick of a simulated upload: `step` of the way, "ready" at the end. */
function advance(file: TrayFile, step: number): TrayFile {
if (file.status !== "uploading") {
return file;
}
const progress = Math.min(1, (file.progress ?? 0) + step);
return progress < 1
? { ...file, progress }
: { ...file, progress: 1, status: "ready" };
}
type Settings = typeof settings;
/**
* The tray with simulated uploads: one timer moves every upload on while any is running, at the
* speed `uploadMs` sets now (a change applies to the running ones too); it stops on unmount.
*/
function SimulatedTray({ failOverMb, showSamples, uploadMs }: Settings) {
const [files, setFiles] = useState(showSamples ? SAMPLES : NONE);
const speed = useRef(uploadMs);
useEffect(() => {
speed.current = uploadMs;
}, [uploadMs]);
const isUploading = files.some((file) => file.status === "uploading");
useEffect(() => {
if (!isUploading) {
return;
}
const timer = setInterval(() => {
const step = STEP_MS / Math.max(STEP_MS, speed.current);
setFiles((current) => current.map((file) => advance(file, step)));
}, STEP_MS);
return () => clearInterval(timer);
}, [isUploading]);
const onAdd = useCallback(
(added: File[]) => {
const stamp = Date.now();
const rows = added.map((file, index): TrayFile => {
const id = `${stamp}-${index}-${file.name}`;
return file.size > failOverMb * MB
? {
error: `Larger than ${failOverMb} MB`,
id,
name: file.name,
size: file.size,
status: "error",
}
: {
id,
name: file.name,
progress: 0,
size: file.size,
status: "uploading",
};
});
setFiles((current) => [...current, ...rows]);
},
[failOverMb]
);
const onRemove = useCallback(
(id: string) =>
setFiles((current) => current.filter((file) => file.id !== id)),
[]
);
return <AttachmentsTray files={files} onAdd={onAdd} onRemove={onRemove} />;
}
/** Uploads are simulated: progress climbs over `uploadMs`; files over `failOverMb` fail. */
export default function Demo(props: Partial<Settings>) {
const s = { ...settings, ...props };
return (
<div className="flex min-h-[520px] w-full items-start justify-center bg-background p-6">
{/* Switching the samples starts the demo over. */}
<SimulatedTray key={String(s.showSamples)} {...s} />
</div>
);
}