Skip to content

File drop zone

FileUpload handles the dropzone, the native picker, and accept/size filtering; this block wires it to local state and renders each picked file as an AttachmentTile with a remove button. It stops at selection — hand the files to your own upload pipeline via onUpload and feed progress back through FileUpload’s progress map if you want per-file bars.

'use client';
import { useState } from 'react';
import { AttachmentTile, FileUpload, Stack } from '@helix-ui/core';
export interface FileDropZoneProps {
/** MIME / extension filter, e.g. "image/*" or ".pdf,.docx". */
accept?: string;
/** Max file size in MB. Larger files are rejected. */
maxSizeMb?: number;
multiple?: boolean;
onUpload?: (files: File[]) => void;
}
export function FileDropZone({
accept,
maxSizeMb = 10,
multiple = true,
onUpload,
}: FileDropZoneProps) {
const [files, setFiles] = useState<File[]>([]);
return (
<Stack gap={3}>
<FileUpload
label="Attachments"
accept={accept}
maxSize={maxSizeMb * 1024 * 1024}
multiple={multiple}
onFiles={(incoming) => {
setFiles((prev) => [...prev, ...incoming]);
onUpload?.(incoming);
}}
/>
{files.length > 0 ? (
<Stack gap={2}>
{files.map((file, i) => (
<AttachmentTile
key={`${file.name}-${i}`}
name={file.name}
size={formatBytes(file.size)}
onRemove={() => setFiles((prev) => prev.filter((_, idx) => idx !== i))}
/>
))}
</Stack>
) : null}
</Stack>
);
}
function formatBytes(bytes: number) {
if (bytes < 1024) return `${bytes} B`;
const kb = bytes / 1024;
if (kb < 1024) return `${Math.round(kb)} KB`;
return `${(kb / 1024).toFixed(1)} MB`;
}

Usage

<FileDropZone accept=".pdf,.docx" maxSizeMb={20} onUpload={(files) => files.forEach(startUpload)} />

Notes

  • FileUpload reports files that fail the accept/size filters through its own onReject — pass a handler if you want to surface those.
  • To show upload progress, keep a Record<file.name, percent> in state and pass it to FileUpload’s progress prop; it renders a bar per file without any extra markup here.