FileUpload
A file field that lists what was chosen and never uploads it.
FileUpload is a file field: a drop zone, the platform's own picker, and a list of what was
chosen. It receives and returns File objects and does nothing else with them, it never reads a
file, uploads it or stores it, so where the bytes go is entirely your decision. The one rule the
field applies itself is the size limit. Like every member of the field family
it draws a label, a description and a message, and reports its own status.
Usage
import { FileUpload } from "@asheeui/web";
const [files, setFiles] = useState<File[]>([]);
<FileUpload
label="Attachments"
files={files}
onFilesSelected={(chosen) => setFiles((current) => [...current...chosen])}
onRemove={(file) => setFiles((current) => current.filter((next) => next !== file))}
/>
onFilesSelected reports what the reader chose and files tells the field what to list. That is
the whole contract: no value to keep in sync, no request, and no list the field maintains
itself. A File is the browser's own object, so it can go straight into FormData, and it is
also why the field cannot list a file it was not handed.
The size limit
maxSize is a byte count. A file larger than it is left out before onFilesSelected runs, so
the consumer never has to filter what it was given. The field reports the refusal itself, in its
message, with the count and the limit in the reader's terms.
Drops and picks take the same path, so dragging a file past the limit is refused exactly as
choosing one is. There is one message channel and the component does not stack two: a message
you pass outranks the rejection, because a field never shows two things at once. accept and
multiple are the picker's settings rather than validation, accept narrows what the picker
offers, and a file dragged in is not re-checked against it.
The list belongs to you
The field lists the array you pass and removes the file you tell it to. Without onRemove there
is no remove control on the chips, which is the right shape for a field whose files are managed
by something else.
The field keeps no copy of the list: the array, the order, the duplicates and the decision to clear it all belong to the page. That is what lets a form library own the same list, and what keeps a second upload widget from writing over the first.
Props
Three values are registered and can be restated once for the whole application under
components.fileupload: buttonLabel (Choose files), hint (Drag files here) and size
(md). status is default, isDisabled and required are off, and maxSize is unset, which
means no limit rather than a default one. The zone is drawn from the file field's own classes
rather than from the field axes, so it has no radius or variant to configure.
required states the requirement twice from one prop: the marker beside the label, and the
required attribute on the underlying <input type="file">, so a native form refuses an empty
submission and the two cannot disagree.
Accessibility
- The picker is a real
<input type="file">: it is visually hidden but still in the tab order, so the keyboard path is the platform's own and nothing about choosing a file is drag-only.isDisableddisables that input and ignores drops, and dims the zone so the state is visible. - The zone is a
<label>for that input, so a pointer anywhere in the zone opens the picker, and the zone shows keyboard focus throughfocus-within, the hidden input's focus is drawn on the visible control. - Each listed file is a chip whose close control is labelled
Remove <file name>, so a screen reader announces which file a press removes instead of announcing a row of identical buttons. - A refusal is reported in the field's message with an
errorstatus, which is a live region: the reader is told which files were left out and why, in the same place a validation message appears.