UNPKG

@patternfly/react-core

Version:

This library provides a set of common React components for use with the PatternFly reference implementation.

334 lines (292 loc) • 13.3 kB
--- id: File upload cssPrefix: pf-c-file-upload propComponents: ['FileUpload', 'FileUploadField'] section: components --- import FileUploadIcon from '@patternfly/react-icons/dist/esm/icons/file-upload-icon'; ## Examples The basic `FileUpload` component can accept a file via browse or drag-and-drop, and behaves like a standard form field with its `value` and `onFileInputChange` event that is similar to `<input onChange="...">` prop. The `type` prop determines how the `FileUpload` component behaves upon accepting a file, what type of value it passes to its `onDataChange` event. ### Text files If `type="text"` is passed (and `hideDefaultPreview` is not), a `TextArea` preview will be rendered underneath the filename bar. When a file is selected, its contents will be read into memory and passed to the `onDataChange` event as a string. Every filename change is passed to `onFileInputChange` same as it would do with the `<input>` element. Pressing _Clear_ button triggers `onClearClick` event. ### Simple text file ```js import React from 'react'; import { FileUpload } from '@patternfly/react-core'; class SimpleTextFileUpload extends React.Component { constructor(props) { super(props); this.state = { value: '', filename: '', isLoading: false }; this.handleFileInputChange = (event, file) => this.setState({ filename: file.name }); this.handleTextOrDataChange = value => this.setState({ value }); this.handleClear = event => this.setState({ filename: '', value: '' }); this.handleFileReadStarted = fileHandle => this.setState({ isLoading: true }); this.handleFileReadFinished = fileHandle => this.setState({ isLoading: false }); } render() { const { value, filename, isLoading } = this.state; return ( <FileUpload id="simple-text-file" type="text" value={value} filename={filename} filenamePlaceholder="Drag and drop a file or upload one" onFileInputChange={this.handleFileInputChange} onDataChange={this.handleTextOrDataChange} onTextChange={this.handleTextOrDataChange} onReadStarted={this.handleFileReadStarted} onReadFinished={this.handleFileReadFinished} onClearClick={this.handleClear} isLoading={isLoading} browseButtonText="Upload" /> ); } } ``` A user can always type instead of selecting a file, but by default, once a user selects a text file from their disk they are not allowed to edit it (to prevent unintended changes to a format-sensitive file). This behavior can be changed with the `allowEditingUploadedText` prop. Typing/pasting text in the box will call `onTextChange` with a string, and a string value is expected for the `value` prop. : ### Text file with edits allowed ```js import React from 'react'; import { FileUpload } from '@patternfly/react-core'; class TextFileWithEditsAllowed extends React.Component { constructor(props) { super(props); this.state = { value: '', filename: '', isLoading: false }; this.handleFileInputChange = (event, file) => this.setState({ filename: file.name }); this.handleTextOrDataChange = value => this.setState({ value }); this.handleClear = event => this.setState({ filename: '', value: '' }); this.handleFileReadStarted = fileHandle => this.setState({ isLoading: true }); this.handleFileReadFinished = fileHandle => this.setState({ isLoading: false }); } render() { const { value, filename, isLoading } = this.state; return ( <FileUpload id="text-file-with-edits-allowed" type="text" value={value} filename={filename} filenamePlaceholder="Drag and drop a file or upload one" onFileInputChange={this.handleFileInputChange} onDataChange={this.handleTextOrDataChange} onClearClick={this.handleClear} onTextChange={this.handleTextOrDataChange} onReadStarted={this.handleFileReadStarted} onReadFinished={this.handleFileReadFinished} isLoading={isLoading} allowEditingUploadedText browseButtonText="Upload" /> ); } } ``` ### Restricting file size and type Any [props accepted by `react-dropzone`'s `Dropzone` component](https://react-dropzone.js.org/#!/Dropzone) can be passed as a `dropzoneProps` object in order to customize the behavior of the Dropzone, such as restricting the size and type of files allowed. The following example will only accept CSV files smaller than 1 KB. Note that file type determination is not reliable across platforms (see the note on react-dropzone's docs about the `accept` prop), so be sure to test the behavior of your file upload restriction on all browsers and operating systems targeted by your application. #### IMPORTANT: A note about security Restricting file sizes and types in this way is for user convenience only, and it cannot prevent a malicious user from submitting anything to your server. As with any user input, your application should also validate, sanitize and/or reject restricted files on the server side. ### Text file with restrictions ```js import React from 'react'; import { FileUpload, Form, FormGroup } from '@patternfly/react-core'; class TextFileUploadWithRestrictions extends React.Component { constructor(props) { super(props); this.state = { value: '', filename: '', isLoading: false, isRejected: false }; this.handleFileInputChange = (event, file) => this.setState({ filename: file.name }); this.handleTextOrDataChange = value => this.setState({ value }); this.handleClear = event => this.setState({ filename: '', value: '', isRejected: false }); this.handleFileRejected = (rejectedFiles, event) => this.setState({ isRejected: true }); this.handleFileReadStarted = fileHandle => this.setState({ isLoading: true }); this.handleFileReadFinished = fileHandle => this.setState({ isLoading: false }); } render() { const { value, filename, isLoading, isRejected } = this.state; return ( <Form> <FormGroup fieldId="text-file-with-restrictions" helperText="Upload a CSV file" helperTextInvalid="Must be a CSV file no larger than 1 KB" validated={isRejected ? 'error' : 'default'} > <FileUpload id="text-file-with-restrictions" type="text" value={value} filename={filename} filenamePlaceholder="Drag and drop a file or upload one" onFileInputChange={this.handleFileInputChange} onDataChange={this.handleTextOrDataChange} onTextChange={this.handleTextOrDataChange} onClearClick={this.handleClear} onReadStarted={this.handleFileReadStarted} onReadFinished={this.handleFileReadFinished} isLoading={isLoading} dropzoneProps={{ accept: '.csv', maxSize: 1024, onDropRejected: this.handleFileRejected }} validated={isRejected ? 'error' : 'default'} browseButtonText="Upload" /> </FormGroup> </Form> ); } } ``` ### Other file types If no `type` prop is specified, the component will not read files directly. When a file is selected, a [`File` object](https://developer.mozilla.org/en-US/docs/Web/API/File) will be passed as a second argument to `onFileInputChange` and your application will be responsible for reading from it (e.g. by using the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) or attaching it to a [FormData object](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects)). A `File` object will also be expected for the `value` prop instead of a string, and no preview of the file contents will be shown by default. The `onReadStarted` and `onReadFinished` callbacks will also not be called since the component is not reading the file. ### Simple file of any format ```js import React from 'react'; import { FileUpload } from '@patternfly/react-core'; class SimpleFileUpload extends React.Component { constructor(props) { super(props); this.state = { value: null, filename: '' }; this.handleFileInputChange = (event, file) => { this.setState({ filename: file.name }); }; this.handleClear = event => this.setState({ filename: '', value: '' }); } render() { const { value, filename } = this.state; return ( <FileUpload id="simple-file" value={value} filename={filename} filenamePlaceholder="Drag and drop a file or upload one" browseButtonText="Upload" onFileInputChange={this.handleFileInputChange} onClearClick={this.handleClear} /> ); } } ``` ### Customizing the file preview Regardless of `type`, the preview area (the TextArea, or any future implementations of default previews for other types) can be removed by passing `hideDefaultPreview`, and a custom one can be rendered by passing `children`. ### Custom file preview ```js import React from 'react'; import { FileUpload } from '@patternfly/react-core'; import FileUploadIcon from '@patternfly/react-icons/dist/esm/icons/file-upload-icon'; class CustomPreviewFileUpload extends React.Component { constructor(props) { super(props); this.state = { value: null, filename: '' }; this.handleFileInputChange = (event, file) => { this.setState({ value: file, filename: file.name }); }; this.handleClear = event => this.setState({ filename: '', value: '' }); } render() { const { value, filename, isLoading } = this.state; return ( <FileUpload id="customized-preview-file" value={value} filename={filename} filenamePlaceholder="Drag and drop a file or upload one" onFileInputChange={this.handleFileInputChange} onClearClick={this.handleClear} hideDefaultPreview browseButtonText="Upload" > {value && ( <div className="pf-u-m-md"> <FileUploadIcon size="lg" /> Custom preview here for your {value.size}-byte file named {value.name} </div> )} </FileUpload> ); } } ``` ### Bringing your own file browse logic `FileUpload` is a thin wrapper around the `FileUploadField` presentational component. If you need to implement your own logic for accepting files, you can instead render a `FileUploadField` directly, which does not include `react-dropzone` and requires additional props (e.g. `onBrowseButtonClick`, `onClearButtonClick`, `isDragActive`). Note that the `isLoading` prop is styled to position the spinner dead center above the entire component, so it should not be used with `hideDefaultPreview` unless a custom empty-state preview is provided via `children`. The below example prevents `isLoading` and `hideDefaultPreview` from being used at the same time. You can always provide your own spinner as part of the `children`! ### Custom file upload ```js import React from 'react'; import { FileUploadField, Checkbox } from '@patternfly/react-core'; class CustomFileUpload extends React.Component { constructor(props) { super(props); this.state = { value: '', filename: false, isClearButtonDisabled: true, isLoading: false, isDragActive: false, hideDefaultPreview: false, children: false }; this.handleTextAreaChange = value => { this.setState({ value }); }; } render() { const { value, filename, isClearButtonDisabled, isLoading, isDragActive, hideDefaultPreview, children } = this.state; return ( <div> {['filename', 'isClearButtonDisabled', 'isDragActive', 'isLoading', 'hideDefaultPreview', 'children'].map( stateKey => ( <Checkbox key={stateKey} id={stateKey} label={stateKey} aria-label={stateKey} isChecked={this.state[stateKey]} onChange={checked => this.setState({ [stateKey]: checked, // See notes above this example ...(stateKey === 'isLoading' && checked && { hideDefaultPreview: false }), ...(stateKey === 'hideDefaultPreview' && checked && { isLoading: false }) }) } /> ) )} <br /> <FileUploadField id="custom-file-upload" type="text" value={value} filename={filename ? 'example-filename.txt' : ''} onTextChange={this.handleTextAreaChange} filenamePlaceholder="Do something custom with this!" onBrowseButtonClick={() => alert('Browse button clicked!')} onClearButtonClick={() => alert('Clear button clicked!')} isClearButtonDisabled={isClearButtonDisabled} isLoading={isLoading} isDragActive={isDragActive} hideDefaultPreview={hideDefaultPreview} browseButtonText="Upload" > {children && ( <div className="pf-u-m-md">(A custom preview of the uploaded file can be passed as children)</div> )} </FileUploadField> </div> ); } } ```