docs/documentation/components/file-uploader.mdx
The File Uploader component is used when users need the ability to upload a file or multiple files.
Provides the ability to upload one file at a time. This should allow the user to see the name and size of the file they’ve uploaded, as well as remove and/or replace it.
function FileUploaderSingleUploadDemo() {
const [files, setFiles] = React.useState([])
const [fileRejections, setFileRejections] = React.useState([])
const handleChange = React.useCallback((files) => setFiles([files[0]]), [])
const handleRejected = React.useCallback((fileRejections) => setFileRejections([fileRejections[0]]), [])
const handleRemove = React.useCallback(() => {
setFiles([])
setFileRejections([])
}, [])
return (
<Pane maxWidth={654}>
<FileUploader
label="Upload File"
description="You can upload 1 file. File can be up to 50 MB."
maxSizeInBytes={50 * 1024 ** 2}
maxFiles={1}
onChange={handleChange}
onRejected={handleRejected}
renderFile={(file) => {
const { name, size, type } = file
const fileRejection = fileRejections.find((fileRejection) => fileRejection.file === file)
const { message } = fileRejection || {}
return (
<FileCard
key={name}
isInvalid={fileRejection != null}
name={name}
onRemove={handleRemove}
sizeInBytes={size}
type={type}
validationMessage={message}
/>
)
}}
values={files}
/>
</Pane>
)
}
Provides the ability to upload multiple files at once. This should allow the user to see the names and sizes of all of the files they’ve uploaded, remove a file that they’ve uploaded, or add more files.
function FileUploaderMultipleUploadDemo() {
const acceptedMimeTypes = [MimeType.jpeg, MimeType.pdf]
const maxFiles = 5
const maxSizeInBytes = 50 * 1024 ** 2 // 50 MB
const [files, setFiles] = React.useState([])
const [fileRejections, setFileRejections] = React.useState([])
const values = React.useMemo(() => [...files, ...fileRejections.map((fileRejection) => fileRejection.file)], [
files,
fileRejections,
])
const handleRemove = React.useCallback(
(file) => {
const updatedFiles = files.filter((existingFile) => existingFile !== file)
const updatedFileRejections = fileRejections.filter((fileRejection) => fileRejection.file !== file)
// Call rebaseFiles to ensure accepted + rejected files are in sync (some might have previously been
// rejected for being over the file count limit, but might be under the limit now!)
const { accepted, rejected } = rebaseFiles(
[...updatedFiles, ...updatedFileRejections.map((fileRejection) => fileRejection.file)],
{ acceptedMimeTypes, maxFiles, maxSizeInBytes }
)
setFiles(accepted)
setFileRejections(rejected)
},
[acceptedMimeTypes, files, fileRejections, maxFiles, maxSizeInBytes]
)
const fileCountOverLimit = files.length + fileRejections.length - maxFiles
const fileCountError = `You can upload up to 5 files. Please remove ${fileCountOverLimit} ${
fileCountOverLimit === 1 ? 'file' : 'files'
}.`
return (
<Pane maxWidth={654}>
<FileUploader
acceptedMimeTypes={acceptedMimeTypes}
label="Upload Files"
description="You can upload up to 5 files. Files can be up to 50MB. You can upload .jpg and .pdf file formats."
disabled={files.length + fileRejections.length >= maxFiles}
maxSizeInBytes={maxSizeInBytes}
maxFiles={maxFiles}
onAccepted={setFiles}
onRejected={setFileRejections}
renderFile={(file, index) => {
const { name, size, type } = file
const renderFileCountError = index === 0 && fileCountOverLimit > 0
// We're displaying an <Alert /> component to aggregate files rejected for being over the maxFiles limit,
// so don't show those errors individually on each <FileCard />
const fileRejection = fileRejections.find(
(fileRejection) => fileRejection.file === file && fileRejection.reason !== FileRejectionReason.OverFileLimit
)
const { message } = fileRejection || {}
return (
<React.Fragment key={`${file.name}-${index}`}>
{renderFileCountError && <Alert intent="danger" marginBottom={majorScale(2)} title={fileCountError} />}
<FileCard
isInvalid={fileRejection != null}
name={name}
onRemove={() => handleRemove(file)}
sizeInBytes={size}
type={type}
validationMessage={message}
/>
</React.Fragment>
)
}}
values={values}
/>
</Pane>
)
}
The file uploader component is composed of: (1) Label, (2) Description, (3) Dropzone, (4) File Preview.
<Pane height={366} marginBottom={64} width={654}> </Pane>By default, the entire dropzone is active to drop files into. Click anywhere in the dropzone to trigger the “Browse” functionality.
<Pane height={236} width={654}> </Pane>When a user hovers over the dropzone with files, it becomes active to indicate it’s ready to receive the files.
<Pane height={236} width={654}> </Pane>When uploading multiple files, a file card is shown beneath the dropzone with a spinning icon to indicate upload in progress.
<Pane height={384} width={654}> </Pane>When a file is successfully uploaded, a thumbnail image or icon is shown alongside the file name and file size. Use the trash icon to remove the file.
<Pane height={384} width={654}> </Pane>When uploading a single file, a file card is shown without the dropzone with a spinning icon to indicate upload in progress.
<Pane height={118} width={654}> </Pane>When a file is successfully uploaded, a thumbnail image or icon is shown alongside the file name and file size. Use the trash icon to remove the file. The dropzone is hidden in this use case until the file is removed.
<Pane height={118} width={654}> </Pane>Refer to these guidelines when using the File Uploader component.
The card displaying the file that didn’t upload should have an error message beneath it that clearly communicates why it wasn’t uploaded. These error types may include invalid file type or file over the file size limit. We recommend the following phrasing:
<Table marginBottom={32} width={654}> <TableHead> <TableTextHeaderCell>Scenario</TableTextHeaderCell> <TableTextHeaderCell>Recommended Phrasing</TableTextHeaderCell> </TableHead> <TableRow> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Invalid file type </TableTextCell> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> This file is not an accepted format. You can upload [x], [x] and [x] file formats. </TableTextCell> </TableRow> <TableRow> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Exceeds file size limit </TableTextCell> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> This file is too big. You can upload files up to [file limit]. </TableTextCell> </TableRow> <TableRow> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Network errors (internet connection dropped while uploading, request timeout, service is down) </TableTextCell> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Something went wrong with the network. Check your internet connection, then try again. </TableTextCell> </TableRow> <TableRow> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Too many uploads in a certain amount of time </TableTextCell> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> We couldn’t upload so many files so quickly. Try uploading files more slowly, or try again later. </TableTextCell> </TableRow> <TableRow> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Generic (encountered an internal error) </TableTextCell> <TableTextCell textProps={{ whiteSpace: 'break-spaces', overflow: 'auto', textOverflow: 'unset' }}> Something went wrong. Try uploading your files again. </TableTextCell> </TableRow> </Table>An example of file size limit and file type errors:
<Pane height={484} marginBottom={32} width={654}> </Pane>File uploader should be accessible via keyboard support. Tabbing over the dropzone should activate the ‘Browse’ link, and users should be able to hit ‘Enter’ in order to open the file browser.