On this page
Import
import { DropZone } from '@envsemble/envui';
Usage
A DropZone takes dropped files and opens the file picker when pressed.
labelsays what to add anddescriptionwhich files fit; together they name the zone.onFilesAcceptedreceives the files it takes.iconreplaces the upload icon.
import { useEffect, useRef, useState } from 'react';
import { Button, DropZone, Column, Graphic, Row, Text } from '@envsemble/envui';
const twoMegabytes = 2 * 1024 * 1024;
export default function LogoUpload() {
const picker = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState<File | null>(null);
const [logo, setLogo] = useState<File | null>(null);
const [preview, setPreview] = useState<string>();
const [error, setError] = useState<string>();
// A URL for the chosen image, released when it changes.
useEffect(() => {
if (!logo) return setPreview(undefined);
const url = URL.createObjectURL(logo);
setPreview(url);
return () => URL.revokeObjectURL(url);
}, [logo]);
// Uploading takes a moment; a timer stands in for it here.
useEffect(() => {
if (!uploading) return;
const timer = window.setTimeout(() => {
setLogo(uploading);
setUploading(null);
}, 1500);
return () => window.clearTimeout(timer);
}, [uploading]);
return (
// As wide as a form column.
<Column gap={3} style={{ inlineSize: 'min(100%, 24rem)' }}>
<DropZone
hidden={logo != null}
inputRef={picker}
label={uploading ? `Uploading ${uploading.name}` : 'Drop a logo here, or browse'}
description={uploading ? 'This takes a moment' : 'PNG, JPG or WebP up to 2 MB'}
accept="image/png,image/jpeg,.webp"
pending={uploading != null}
error={error}
onFilesAccepted={([file]) => {
// The zone checks the type; the size is the application's to check.
if (file.size > twoMegabytes) return setError('Choose an image up to 2 MB.');
setError(undefined);
setUploading(file);
}}
onFilesRejected={() => setError('Choose a PNG, JPG or WebP image.')}
/>
{logo && (
<Row wrap align="center" gap={4}>
<Graphic src={preview} alt="" size="lg" />
<Text render={<span />}>{logo.name}</Text>
{/* Replace opens the zone's file picker while the zone is hidden. */}
<Button variant="secondary" onClick={() => picker.current?.click()}>
Replace
</Button>
<Button variant="text" onClick={() => setLogo(null)}>
Remove
</Button>
</Row>
)}
</Column>
);
}Accept, several files and errors
accept filters dropped and chosen files, as the native attribute does, with MIME types such as image/png, groups such as image/* and extensions such as .webp; any file by default. Without multiple the zone takes the first file. Files that don't fit, and those after the first without multiple, go to onFilesRejected. Check size and contents yourself, and show the message in error, which takes the error color on the zone's outline. disabled turns the zone off; it still keeps the browser from opening dropped files.
<DropZone
multiple
accept="application/pdf,.pdf"
label="Drop documents here, or browse"
description="PDF up to 10 MB each"
error={error}
onFilesAccepted={(files) => setDocuments((current) => [...current, ...files])}
onFilesRejected={() => setError('Only PDF files can be attached.')}
/>
Pending work
While pending, a spinner replaces the icon, the zone reports aria-busy and takes no new files. Change the label to say what is happening, and announce when the upload finishes, such as in a role="status" text.
<DropZone
pending={uploading != null}
label={uploading ? `Uploading ${uploading}` : 'Drop a report here, or browse'}
description={uploading ? 'This takes a moment' : 'PDF up to 10 MB'}
accept="application/pdf,.pdf"
onFilesAccepted={upload}
/>
Sizes
size is md, the tall area with a large icon and the default, or sm, one row with a small icon, for attachments in a dense form or a row.
In a form
With name, a native form submits the last files the zone took, dropped ones too, and a reset clears them; new FormData(form).getAll('attachments') reads them, with an empty entry when there are none. files hands the zone the files the application keeps, such as after a Remove, and replaces the ones it took. form names a form the zone sits outside of, and inputRef reaches the native file input, to open the picker from a Replace button. ref reaches the zone itself: unlike a field's input, the file input holds no value a form library could register.
<form onSubmit={send}>
<TextField label="Subject" name="subject" />
<DropZone
size="sm"
multiple
name="attachments"
label="Attach files"
description="Sent with the message"
onFilesAccepted={setAttachments}
/>
<Button type="submit">Send</Button>
</form>
Composition
The zone's frame is Field's root and error. Its parts are the zone, env-drop-zone, the target people press, env-drop-zone-target, with its icon, env-drop-zone-icon, its label, env-drop-zone-label, and its description, env-drop-zone-description; then the error, env-field-error. List the chosen files beside it, such as in a List with Replace and Remove, and keep links and other controls out of the zone.
Accessibility
You provide
- A label that says what to add.
- Checks of size and contents, and the
errormessage. - An announcement when an upload finishes, and the list of chosen files.
envUI provides
- The zone as a button named by its label and described by its description and error.
aria-busywhile pending.- A native file input for the picker and for forms.
Props
DropZone
A drop target for files that also opens the file picker; the application shows what was chosen.
| Prop | Type | Description |
|---|---|---|
labelrequired | ReactNode | What to drop or choose, such as "Drop a logo here, or browse". With the description, it names the zone, as its visible text does. |
description | ReactNode | Which files fit, such as "PNG, JPG or WebP up to 2 MB"; it is read as part of the name. |
icon | ReactNode | Replaces the upload icon. |
acceptDefault any file | string | The file types the zone takes, as in the native accept attribute: MIME types such as image/png, groups such as image/*, and extensions such as .webp. |
multipleDefault false | boolean | Take several files at once; otherwise the zone takes the first file. |
onFilesAcceptedrequired | ( | Receives the dropped or chosen files that the zone takes. |
onFilesRejected | ( | Receives the files that do not match accept, and those after the first without multiple. |
disabledDefault false | boolean | The zone can't be pressed or focused and takes no files; it still keeps the browser from opening dropped files. |
pendingDefault false | boolean | The application is working on the files: a spinner replaces the icon, the zone reports that it is busy and takes no new files. |
error | ReactNode | A message linked to the zone, whose outline takes the error color. |
sizeDefault 'md' | 'sm''md' | md is the tall area with a large icon; sm is one row with a small icon, for a form or a sidebar. |
name | string | Submits files with a native form under this name: the last files the zone took, until the form resets, or files. |
files | readonly File[ | The files a named zone submits when the application keeps them, such as after a Remove action; they replace the files the zone took, also after the form resets. |
form | string | The id of a form the zone belongs to when it is outside of it. |
inputRef | Ref< | The native file input that opens the file picker, for example from a Replace button. The zone holds no value a form library could register, as a field's input does, so ref reaches the zone itself, the drop target, and this ref the picker. |
Also accepts the native props of <div>, such as ref, className and aria-* attributes.
Part names
Each part's class and state attributes are public and versioned, for an application's CSS to style.
env-drop-zone- The area that takes dropped files.
env-drop-zone-description- The sentence about the accepted files.
env-drop-zone-icon- The icon.
env-drop-zone-label- The label.
env-drop-zone-target- The target that takes the drop and opens the file picker.
env-drop-zone-text- The column with the label and the description.
data-drag-over- Present
data-invalid- Present
data-pending- Present
data-sizemd·sm
Changelog
- 1.0.0
onFilesisonFilesAcceptedandonRejectisonFilesRejected; they run for dropped and browsed files alike. The zone is named by what it shows, its label and its description, and described by its error. Its frame is Field's root and error.FileDropzoneisDropZone, with theenv-drop-zone-*classes; the page isdrop-zone.- 1.0.0-prerelease.6
Colors under the pointer and while pressed change with
--env-motion-ease-enter, rather than easing in slowly, so hover and press show from the first frame.