The Basics

What it is

FileUpload allow users to upload content of their own. A file uploader is commonly found in forms but can also live as a standalone element.

File Upload has 2 variants:

  • Simple: Triggered by clicking on a button.
  • Drag and Drop: Triggered either by clicking on a tertiary button or by dragging files into a designated area.

 

FileUpload can also be used within a Forge Modal, an example of this is shown in the Demos section.

  • Modal: Uses a Drag and Drop variant but exists within a modal. This can be triggered by anything that makes sense in the context of a design, such as a button.

How it works

Simple

  • The user clicks button or uses the space bar or enter key to activate it.
  • Button triggers the opening of a dialog box that is provided by the user's OS that affords the selection of one or more files.
  • Once files are selected loading and error status indicators allow the user to follow the progress of their selection's upload.
  • Upload complete indicators optionally allow the user to remove individual uploaded files.

Drag and Drop

  • The user clicks tertiary button or uses the space bar or enter key to activate it or drags the desired files into the area around the component designated by a dotted line.
  • Using the tertiary button will trigger the opening of a dialog box that affords the selection of one or more files and is provided by the user's OS.
  • Upon file selection, files will be displayed in a 'preview' state as tags within the designated drag area.
  • The presence of tags will activate a button for confirming the selection before the files will actually upload. This intermediary step acts as a safeguard against erroneous drag selections.
  • Once files are selected loading, linear progress bars and associated error messages (as needed) will display below the designated drag area.
  • Upload complete indicators allow the user to optionally remove individual uploaded files as well as undo inadvertent upload removals.

When to use

  • To upload one or more files
  • To upload files by dragging and dropping
  • To show the process of uploading
  • To remove uploaded files from the same workflow that uploaded them

When not to use

  • For triggering workflows that are not specifically intended to upload files

What to use instead

Button

Use a button to open a new workflow on a new page. Note, this is not a true alternative as FileUpload should still be utilized at the time of file selection and upload confirmation.

Modal

Modal can also be used with FileUpload when the file upload behavior is linked to other form behavior. This is a similar pseudo-alternative as button, as it does not eliminate the need to use the FileUpload pattern.

How to use

Use FileUpload any time file uploading is needed.

Variants

Simple

  • Use the Simple variant when you need to conserve space on the screen, increasing the opportunity for visibility for form elements that may appear lower on the screen.

Drag and Drop

  • Drag and Drop should only be enabled if there is adequate space for the user to drag an item into the space.
  • Drag and Drop is a good option when the user is likely to be in a context where other windows with draggable items are likely to be present, such as File Manager (Windows) or Finder (Mac).

FileUpload can also be used within a Forge Modal, an example of this is shown in the Demos section.

  • Use within a Modal when you want to isolate your upload sequence from the main workflow that triggered your FileUpload.

 

Sizes

Each variant is available in all sizes. Choose a size based on your use case:

  • Fixed: A fixed 550 px width. Best for wide screens where a responsive width would be too large.
  • Responsive: Recommended for most use cases. A responsive width that fills its container.

Options

  • Disabled: Displays the Button as disabled when users must meet a requirement (like filling out a form) before activating the Button.
  • File Information: Displays optional metadata defined by the implementing application.

 

Style

Design details

  • Drag and Drop, including when in the form of a Modal, is centered and fills the container horizontally.
  • FileUpload should not be used in containers narrower than 360 px, as that will be too narrow for a full error statement to be shown.
  • If the container is too narrow to display all the upload information, do not show optional file information
  • If the container is too narrow to show the file name, truncate the file name to "FileNa...1.txt" and reveal the fill name on hover.

Placement and hierarchy

When used in a Form, a single Simple or Drag and Drop version of FileUpload is left-aligned with the left side of the main input fields.

Demos

File Upload With Mock Application Share

File Upload Drag And Drop Share

Drag And Drop Upload Conditions Share

File Upload In Modal Share

Coding

Developer tips

Updating state

FileUpload cannot actually upload a file to a remote server. Only application code can do this. However, there are some state transitions that FileUpload can do on its own, such as adding files to the pending list. Other state transitions cannot be made internal to FileUpload, like transitioning from in-progress to completed.

One common pattern in React components is to make a component controlled by having an onChange callback function update application state, then update a value prop in the controlled component. This could have been done for FileUpload, but then every application would need to write a reducer to manage all state transitions. Instead, FileUpload manages its state internally, but exposes its dispatch method via the ref prop. This dispatch method can be called to inform FileUpload of changes, such as upload progress changes or a successful upload. If an application wishes to render a FileUpload with previously-uploaded files, the defaultFiles prop can be used to set the initial state. If an application wants to control every state transition within FileUpload, then set-state actions can be dispatched. FileUpload's reducer, fileUploadReducer is exported, so it can be used within an application's reducer if desired.

There are three broad categories of state transitions:

  1. State transitions initiated by FileUpload, but do not require application input, such as adding files to the pending list.
  2. State transitions that are initiated by FileUpload, but the application may wish to add additional information, such as setting the percentComplete for an upload that has just started.
  3. State transitions initiated by the app. This includes updating percentComplete or marking an upload as completed.

The application is informed of any state transitions initiated by FileUpload via the onChange callback function. If the application returns a FileUploadAction object, then that action will be processed by fileUploadReducer instead of the original action. It is not recommended to dispatch actions directly from this callback, as this will lead to two dispatch events, one from the original FileUploadAction, and another for the custom action.

Any state transitions initiated by the app are dispatched via ref.current.dispatch().

Supported actions

The supported actions are as follows:

action.typeother action fieldDescription
set-statestate

Replaces the current state.

Recommended only when tight control over the behavior of FileUpload is required.

pendingaffectedFilesAdds files to the pending list.
remove-pendingaffectedFileRemoves a file from the pending list.
startaffectedFiles

Starts a file upload, removing it from the pending list.

Each file in affectedFiles can optionally have percentComplete set to a number. Otherwise, it will default to 'indeterminate'.

update-progressaffectedFiles

Updates the progress of a file in the pending list

Update percentComplete within affectedFiles to update the progress.

erroraffectedFiles

Marks files in the in-progress list as having an error.

Modify errorMessage within affectedFiles to update the error message.

retryaffectedFile

Retries uploading a file. File may either be in the error or completed lists

Like the start action, affectedFile.percentComplete can be set to a number.

completedaffectedFiles

Marks a file as uploaded

Optionally, metaData can be set on each file to display next to the file. This may include information such as date/time uploaded, upload-by, etc.

It's also recommended to set url so that a direct hyperlink can be created to the completed file. Otherwise, a blob URL will be created.

deleteaffectedFile

Marks a file as deleted, or removes an errored file from the error list.

Optionally, errorMessage can be set on each file to display next to the file.

undo-deleteaffectedFile

Undoes a file deletion, moving it back to the completed list

If your application has hard-deleted the file and needs to re-upload it, return a retry event from the onChange handler instead.

Limitations of client-side validation

FileUpload does some client-side validation of file extensions and fileUploadLimit, but it has its limitations. With behavior="simple", files bypass the "pending" stage and go directly to "start". Without an upload button, there's no way to allow the user to correct validation errors before uploading. Similarly, there's nothing preventing a user from giving an executable file a .txt extension, and having that pass file extension validation. Multiple files with the same name are supported, under the assumption that they are from different directories on the host machine, but the same file could be uploaded multiple times as well. It's a little tricker to do when using the "Browse Files" button, but it can be done. Extremely large files could also be uploaded.

Virus Scanning

Server-side validation of uploaded files is paramount to ensure vulnerable components or files are not uploaded.

Most front-end virus prevention or file validation can be bypassed with enough technical know-how. Thus, virus scanning should be done on the back end, not the front end. As front-end library, Forge cannot control the back end of AthenaOne and provide server-side validation in FileUpload.

At best, FileUpload can restrict files based on their file extension. This can be used to make it more difficult to upload executable files, but a malicious user could always rename an executable file with an approved extension, so server-side validation is still required.

Repository

Implementation links

FileUpload directory in Bitbucket

Implementation details

It is strongly recommended to familiarize yourself with the Forge source code. While this documentation is a best effort to document the intent and usage of a component, sometimes some features only become clear when looking at the source code. Also, looking at Forge's source code may help identify and fix bugs in either your application or Forge itself.

Storybook files

Forge maintains at least one storybook file per component. While the primary audience for these files is typically the Forge team, these storybook files may cover usages of the component not covered by a demo. The storybook for the latest version of forge can be found at go/forge-storybook.

Testing library

Forge strongly encourages using testing-library to write tests for your application.

"The more your tests resemble the way your software is used, the more confidence they can give you."

If you're having trouble testing a Forge component using testing-library, it would be a good idea to see how Forge tests its own components. For the most part, Forge tries to use screen.getByRole as much as it can, as that API provides the best feedback on a11y compliance. Forge discourages the use of document.querySelector and screen.getByTestId as both APIs encourage using implementation details to test your component, and discourage adding roles to your component.

With that being said, many of Forge's components were not built with accessibility in mind. These components do break the recommendations listed above.

Import statements

In Nimbus applications

athenaOne serves the Forge bundle independently from your application's bundle. Importing Forge components directly from '@athena/forge' takes advantage of this feature.

import { FileUpload } from '@athena/forge'

In standalone applications

Importing components using the exact path to the module takes advantage of webpack's tree shaking feature. Webpack will include only that module and its dependencies.

import FileUpload from '@athena/forge/FileUpload';

To use this import guidance, Typescript applications must use typescript >= 4.7.3, and should add this setting to their tsconfig.json file:

{
"compilerOptions": {
"moduleResolution": "Node16",
}
}

If this setting doesn't work for your application, use this import statement instead:

import FileUpload from '@athena/forge/dist/FileUpload';

Props