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
FileUploadreports files that fail the accept/size filters through its ownonReject— pass a handler if you want to surface those.- To show upload progress, keep a
Record<file.name, percent>in state and pass it toFileUpload’sprogressprop; it renders a bar per file without any extra markup here.