Skip to content

Commit 06e8586

Browse files
committed
Fix loading issue in docs, render preview for unknown file type, update docs
1 parent d68cdfb commit 06e8586

File tree

3 files changed

+141
-31
lines changed

3 files changed

+141
-31
lines changed

packages/react-core/src/components/FileUpload/FileUpload.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ export interface FileUploadProps
1111
> {
1212
/** Unique id for the TextArea, also used to generate ids for accessible labels. */
1313
id: string;
14-
/** What type of file. Determines what is is passed to `onChange` and expected by `value` (a string for 'text' and 'dataURL', or a [File object](https://developer.mozilla.org/en-US/docs/Web/API/File) otherwise). */
14+
/** What type of file. Determines what is is passed to `onChange` and expected by `value`
15+
* (a string for 'text' and 'dataURL', or a File object otherwise. */
1516
type?: 'text' | 'dataURL';
16-
/** Value of the file's contents (string if text file, [File object](https://developer.mozilla.org/en-US/docs/Web/API/File) otherwise) */
17+
/** Value of the file's contents
18+
* (string if text file, File object otherwise) */
1719
value?: string | File;
1820
/** Value to be shown in the read-only filename field. */
1921
filename?: string;
@@ -53,8 +55,11 @@ export interface FileUploadProps
5355
browseButtonText?: string;
5456
/** Text for the Clear button */
5557
clearButtonText?: string;
56-
/** Flag to hide the TextArea. */
57-
hideTextArea?: boolean; // TODO replace with showPreview!
58+
/** Flag to show a built-in preview of the file where available.
59+
* If false, You can use children to render an alternate preview. */
60+
showPreview?: boolean;
61+
/** Additional children to render after (or instead of) the file preview. */
62+
children?: React.ReactNode;
5863

5964
// Props available in FileUpload but not FileUploadField:
6065

@@ -69,13 +74,13 @@ export interface FileUploadProps
6974
}
7075

7176
// TODO handle an optional message for errors without using FieldGroup
72-
// TODO also accept children here for custom previews
7377

7478
export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
7579
id,
7680
type,
7781
value = type === fileReaderType.text || type === fileReaderType.dataURL ? '' : null,
7882
filename = '',
83+
children = null,
7984
onChange = (): any => undefined,
8085
onReadStarted = (): any => undefined,
8186
onReadFinished = (): any => undefined,
@@ -133,6 +138,7 @@ export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
133138
onClearButtonClick={onClearButtonClick}
134139
>
135140
<input {...getInputProps()} /* hidden, necessary for react-dropzone */ />
141+
{children}
136142
</FileUploadField>
137143
)}
138144
</Dropzone>

packages/react-core/src/components/FileUpload/FileUploadField.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
import * as React from 'react';
22
import styles from '@patternfly/react-styles/css/components/FileUpload/file-upload';
3+
import FileUploadIcon from '@patternfly/react-icons/dist/js/icons/file-upload-icon';
34
import { css } from '@patternfly/react-styles';
45
import { Omit } from '../../helpers';
56
import { InputGroup } from '../InputGroup';
67
import { TextInput } from '../TextInput';
78
import { Button, ButtonVariant } from '../Button';
89
import { TextArea, TextAreResizeOrientation } from '../TextArea';
910
import { Spinner, spinnerSize } from '../Spinner';
11+
import { Flex, FlexItem, FlexModifiers } from '../../layouts/Flex';
12+
import { Bullseye } from '../../layouts/Bullseye';
13+
import { Text, TextVariants } from '../Text';
1014
import { fileReaderType } from '../../helpers/fileUtils';
1115

1216
export interface FileUploadFieldProps extends Omit<React.HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
1317
/** Unique id for the TextArea, also used to generate ids for accessible labels */
1418
id: string;
15-
/** What type of file. Determines what is is expected by `value` (a string for 'text' and 'dataURL', or a [File object](https://developer.mozilla.org/en-US/docs/Web/API/File) otherwise). */
19+
/** What type of file. Determines what is is expected by `value`
20+
* (a string for 'text' and 'dataURL', or a File object otherwise). */
1621
type?: 'text' | 'dataURL';
17-
/** Value of the file's contents (string if text file, [File object](https://developer.mozilla.org/en-US/docs/Web/API/File) otherwise) */
22+
/** Value of the file's contents
23+
* (string if text file, File object otherwise) */
1824
value?: string | File;
1925
/** Value to be shown in the read-only filename field. */
2026
filename?: string;
@@ -54,14 +60,15 @@ export interface FileUploadFieldProps extends Omit<React.HTMLProps<HTMLDivElemen
5460
/** Text for the Clear button */
5561
clearButtonText?: string;
5662
/** Flag to disable the Clear button */
57-
clearButtonDisabled?: boolean;
58-
/** Flag to hide the TextArea. Use with children to add custom support for non-text files. */
59-
hideTextArea?: boolean;
63+
isClearButtonDisabled?: boolean;
64+
/** Flag to show a built-in preview of the file where available.
65+
* If false, You can use children to render an alternate preview. */
66+
showPreview?: boolean;
67+
/** Additional children to render after (or instead of) the file preview. */
68+
children?: React.ReactNode;
6069

6170
// Props available in FileUploadField but not FileUpload:
6271

63-
/** Additional children to render after (or instead of) the TextArea. */
64-
children?: React.ReactNode;
6572
/** A callback for when the Browse button is clicked. */
6673
onBrowseButtonClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
6774
/** A callback for when the Clear button is clicked. */
@@ -93,10 +100,10 @@ export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
93100
filenameAriaLabel = filename ? 'Read only filename' : filenamePlaceholder,
94101
browseButtonText = 'Browse...',
95102
clearButtonText = 'Clear',
96-
clearButtonDisabled = !filename && !value,
103+
isClearButtonDisabled = !filename && !value,
97104
containerRef = null as React.Ref<any>,
98105
children = null,
99-
hideTextArea = false,
106+
showPreview = false,
100107
...props
101108
}: FileUploadFieldProps) => {
102109
const onTextAreaChange = (newValue: string, event: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -136,15 +143,15 @@ export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
136143
</Button>
137144
<Button
138145
variant={ButtonVariant.control}
139-
isDisabled={isDisabled || clearButtonDisabled}
146+
isDisabled={isDisabled || isClearButtonDisabled}
140147
onClick={onClearButtonClick}
141148
>
142149
{clearButtonText}
143150
</Button>
144151
</InputGroup>
145152
</div>
146153
<div className={styles.fileUploadFileDetails}>
147-
{!hideTextArea /* TODO replace with showPreview */ && type === fileReaderType.text && (
154+
{showPreview && type === fileReaderType.text && (
148155
<TextArea
149156
readOnly={isReadOnly || !!filename} // A truthy filename means a real file, so no editing
150157
disabled={isDisabled}
@@ -158,7 +165,17 @@ export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
158165
onChange={onTextAreaChange}
159166
/>
160167
)}
161-
{/* TODO handle other `type`s like dataURL and undefined (File) */}
168+
{showPreview && value instanceof File && !type && (
169+
<Flex breakpointMods={[{ modifier: FlexModifiers['space-items-l'] }]}>
170+
<FlexItem breakpointMods={[{ modifier: FlexModifiers['align-self-center'] }]}>
171+
<FileUploadIcon size="lg" />
172+
</FlexItem>
173+
<FlexItem breakpointMods={[{ modifier: FlexModifiers.grow }]}>
174+
{value.type && <Text component={TextVariants.h3}>{value.type}</Text>}
175+
<Text component={TextVariants.h3}>{value.size} bytes</Text>
176+
</FlexItem>
177+
</Flex>
178+
)}
162179
{isLoading && (
163180
<div className={styles.fileUploadFileDetailsSpinner}>
164181
<Spinner size={spinnerSize.lg} aria-valuetext={spinnerAriaValueText} />

packages/react-core/src/components/FileUpload/examples/FileUpload.md

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ import { FileUpload, Form, FormGroup, FileUploadField, Checkbox } from '@pattern
1111

1212
## Examples
1313

14-
The basic `FileUpload` component can handle simple text files via browse or drag-and-drop, loading them into memory and passing their contents as a string to an `onChange` prop.
14+
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 `onChange` props. The `type` prop determines how the `FileUpload` component behaves upon accepting a file, what type of value it passes to its `onChange` prop, and what type it expects for its `value` prop.
1515

16-
```js title=Simple-text-file isBeta
16+
### Text files
17+
18+
If `type="text"` is passed, 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 `onChange` prop as a string (along with its filename). Typing/pasting text in the box will also call `onChange` with a string, and a string value is expected for the `value` prop.
19+
20+
```js title=Simple-text-file
1721
import React from 'react';
1822
import { FileUpload } from '@patternfly/react-core';
1923

@@ -27,7 +31,7 @@ class SimpleTextFileUpload extends React.Component {
2731
}
2832

2933
render() {
30-
const { value, filename } = this.state;
34+
const { value, filename, isLoading } = this.state;
3135
return (
3236
<FileUpload
3337
id="simple-text-file"
@@ -37,15 +41,17 @@ class SimpleTextFileUpload extends React.Component {
3741
onChange={this.handleFileChange}
3842
onReadStarted={this.handleFileReadStarted}
3943
onReadFinished={this.handleFileReadFinished}
44+
isLoading={isLoading}
45+
showPreview
4046
/>
4147
);
4248
}
4349
}
4450
```
4551

46-
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. This example will only accept CSV files smaller than 1 KB.
52+
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. This example will only accept CSV files smaller than 1 KB:
4753

48-
```js title=Simple-text-file-with-restrictions isBeta
54+
```js title=Simple-text-file-with-restrictions
4955
import React from 'react';
5056
import { FileUpload, Form, FormGroup } from '@patternfly/react-core';
5157

@@ -62,7 +68,7 @@ class SimpleTextFileUploadWithRestrictions extends React.Component {
6268
}
6369

6470
render() {
65-
const { value, filename, isRejected } = this.state;
71+
const { value, filename, isLoading, isRejected } = this.state;
6672
return (
6773
<Form>
6874
<FormGroup
@@ -79,12 +85,14 @@ class SimpleTextFileUploadWithRestrictions extends React.Component {
7985
onChange={this.handleFileChange}
8086
onReadStarted={this.handleFileReadStarted}
8187
onReadFinished={this.handleFileReadFinished}
88+
isLoading={isLoading}
8289
dropzoneProps={{
8390
accept: '.csv',
8491
maxSize: 1024,
8592
onDropRejected: this.handleFileRejected
8693
}}
8794
validated={isRejected ? 'error' : 'default'}
95+
showPreview
8896
/>
8997
</FormGroup>
9098
</Form>
@@ -93,9 +101,87 @@ class SimpleTextFileUploadWithRestrictions extends React.Component {
93101
}
94102
```
95103

96-
`FileUpload` is a thin wrapper around the `FileUploadField` presentational component. If you need to implement your own logic for accepting, reading or displaying files, you can instead render a `FileUploadField` directly, which does not include `react-dropzone` and requires additional props (e.g. `onBrowseButtonClick`, `onClearButtonClick`, `isDragActive`).
104+
### Other file types
105+
106+
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 to `onChange` 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 a summary of the file's type and size will be rendered instead of the `TextArea`.
107+
108+
```js title=Simple-file-of-any-format
109+
import React from 'react';
110+
import { FileUpload } from '@patternfly/react-core';
111+
112+
class SimpleFileUpload extends React.Component {
113+
constructor(props) {
114+
super(props);
115+
this.state = { value: null, filename: '', isLoading: false };
116+
this.handleFileChange = (value, filename, event) => this.setState({ value, filename });
117+
this.handleFileReadStarted = fileHandle => this.setState({ isLoading: true });
118+
this.handleFileReadFinished = fileHandle => this.setState({ isLoading: false });
119+
}
120+
121+
render() {
122+
const { value, filename, isLoading } = this.state;
123+
return (
124+
<FileUpload
125+
id="simple-file"
126+
value={value}
127+
filename={filename}
128+
onChange={this.handleFileChange}
129+
onReadStarted={this.handleFileReadStarted}
130+
onReadFinished={this.handleFileReadFinished}
131+
isLoading={isLoading}
132+
showPreview
133+
/>
134+
);
135+
}
136+
}
137+
```
138+
139+
### Customizing the file preview
140+
141+
Regardless of `type`, the preview area (TextArea or type/size summary) can be removed by using `showPreview={false}`, and a custom one can be rendered by passing `children`.
142+
143+
```js title=Custom-file-preview
144+
import React from 'react';
145+
import { FileUpload } from '@patternfly/react-core';
146+
147+
class SimpleFileUpload extends React.Component {
148+
constructor(props) {
149+
super(props);
150+
this.state = { value: null, filename: '', isLoading: false };
151+
this.handleFileChange = (value, filename, event) => this.setState({ value, filename });
152+
this.handleFileReadStarted = fileHandle => this.setState({ isLoading: true });
153+
this.handleFileReadFinished = fileHandle => this.setState({ isLoading: false });
154+
}
155+
156+
render() {
157+
const { value, filename, isLoading } = this.state;
158+
return (
159+
<FileUpload
160+
id="simple-file"
161+
value={value}
162+
filename={filename}
163+
onChange={this.handleFileChange}
164+
onReadStarted={this.handleFileReadStarted}
165+
onReadFinished={this.handleFileReadFinished}
166+
isLoading={isLoading}
167+
showPreview={false}
168+
>
169+
{value && (
170+
<h1>
171+
Custom preview here for your {value.size}-byte file named {value.name}
172+
</h1>
173+
)}
174+
</FileUpload>
175+
);
176+
}
177+
}
178+
```
179+
180+
### Bringing your own file browse logic
181+
182+
`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`).
97183

98-
```js title=Custom-file-upload isBeta
184+
```js title=Custom-file-upload
99185
import React from 'react';
100186
import { FileUploadField, Checkbox } from '@patternfly/react-core';
101187

@@ -105,10 +191,10 @@ class CustomFileUpload extends React.Component {
105191
this.state = {
106192
value: '',
107193
filename: false,
108-
clearButtonDisabled: true,
194+
isClearButtonDisabled: true,
109195
isLoading: false,
110196
isDragActive: false,
111-
hideTextArea: false,
197+
showPreview: true,
112198
children: false
113199
};
114200
this.handleTextAreaChange = value => {
@@ -117,10 +203,10 @@ class CustomFileUpload extends React.Component {
117203
}
118204

119205
render() {
120-
const { value, filename, clearButtonDisabled, isLoading, isDragActive, hideTextArea, children } = this.state;
206+
const { value, filename, isClearButtonDisabled, isLoading, isDragActive, showPreview, children } = this.state;
121207
return (
122208
<div>
123-
{['filename', 'clearButtonDisabled', 'isLoading', 'isDragActive', 'hideTextArea', 'children'].map(stateKey => (
209+
{['filename', 'isClearButtonDisabled', 'isLoading', 'isDragActive', 'showPreview', 'children'].map(stateKey => (
124210
<Checkbox
125211
key={stateKey}
126212
id={stateKey}
@@ -133,16 +219,17 @@ class CustomFileUpload extends React.Component {
133219
<br />
134220
<FileUploadField
135221
id="custom-file-upload"
222+
type="text"
136223
value={value}
137224
filename={filename ? 'example-filename.txt' : ''}
138225
onChange={this.handleTextAreaChange}
139226
filenamePlaceholder="Do something custom with this!"
140227
onBrowseButtonClick={() => alert('Browse button clicked!')}
141228
onClearButtonClick={() => alert('Clear button clicked!')}
142-
clearButtonDisabled={clearButtonDisabled}
229+
isClearButtonDisabled={isClearButtonDisabled}
143230
isLoading={isLoading}
144231
isDragActive={isDragActive}
145-
hideTextArea={hideTextArea}
232+
showPreview={showPreview}
146233
>
147234
{children && <p>(A custom preview of the uploaded file can be passed as children)</p>}
148235
</FileUploadField>

0 commit comments

Comments
 (0)