Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@patternfly/react-styles": "^4.19.3",
"@patternfly/react-tokens": "^4.21.3",
"focus-trap": "6.2.2",
"react-dropzone": "9.0.0",
"react-dropzone": "10.2.2",
"tippy.js": "5.1.2",
"tslib": "^2.0.0"
},
Expand Down
107 changes: 77 additions & 30 deletions packages/react-core/src/components/FileUpload/FileUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import Dropzone, { DropzoneProps, DropFileEventHandler } from 'react-dropzone';
import Dropzone, { DropzoneProps, DropEvent, DropzoneInputProps } from 'react-dropzone';
import { FileUploadField, FileUploadFieldProps } from './FileUploadField';
import { readFile, fileReaderType } from '../../helpers/fileUtils';

import { fromEvent } from 'file-selector';
export interface FileUploadProps
extends Omit<
FileUploadFieldProps,
Expand All @@ -18,15 +18,19 @@ export interface FileUploadProps
value?: string | File;
/** Value to be shown in the read-only filename field. */
filename?: string;
/** A callback for when the file contents change. */
/** *(deprecated)* A callback for when the file contents change. Please instead use onFileInputChange, onTextChange, onDataChange, onClearClick individually. */
onChange?: (
value: string | File,
filename: string,
event:
| React.MouseEvent<HTMLButtonElement, MouseEvent> // Clear button was clicked
| React.DragEvent<HTMLElement> // User dragged/dropped a file
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
| React.MouseEvent<HTMLButtonElement, MouseEvent> // User clicked Clear button
| React.ChangeEvent<HTMLElement> // User typed in the TextArea
| DragEvent
| Event
) => void;
/** Change event emitted from the hidden \<input type="file" \> field associated with the component */
onFileInputChange?: (event: React.ChangeEvent<HTMLInputElement> | DropEvent, file: File) => void;
/** Callback for clicking on the FileUploadField text area. By default, prevents a click in the text area from opening file dialog. */
onClick?: (event: React.MouseEvent) => void;
/** Additional classes added to the FileUpload container element. */
Expand Down Expand Up @@ -74,6 +78,12 @@ export interface FileUploadProps
onReadFailed?: (error: DOMException, fileHandle: File) => void;
/** Optional extra props to customize react-dropzone. */
dropzoneProps?: DropzoneProps;
/** Clear button was clicked */
onClearClick?: React.MouseEventHandler<HTMLButtonElement>;
/** Text area text changed */
onTextChange?: (text: string) => void;
/** On data changed - if type='text' or type='dataURL' and file was loaded it will call this method */
onDataChange?: (data: string) => void;
}

export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
Expand All @@ -83,28 +93,37 @@ export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
filename = '',
children = null,
onChange = () => {},
onFileInputChange = null,
onReadStarted = () => {},
onReadFinished = () => {},
onReadFailed = () => {},
onClearClick,
onClick = event => event.preventDefault(),
onTextChange,
onDataChange,
dropzoneProps = {},
...props
}: FileUploadProps) => {
const onDropAccepted: DropFileEventHandler = (acceptedFiles: File[], event: React.DragEvent<HTMLElement>) => {
const onDropAccepted = (acceptedFiles: File[], event: DropEvent) => {
if (acceptedFiles.length > 0) {
const fileHandle = acceptedFiles[0];
if (event.type === 'drop') {
onFileInputChange?.(event, fileHandle);
}
if (type === fileReaderType.text || type === fileReaderType.dataURL) {
onChange('', fileHandle.name, event); // Show the filename while reading
onReadStarted(fileHandle);
readFile(fileHandle, type as fileReaderType)
.then(data => {
onReadFinished(fileHandle);
onChange(data as string, fileHandle.name, event);
onDataChange?.(data as string);
})
.catch((error: DOMException) => {
onReadFailed(error, fileHandle);
onReadFinished(fileHandle);
onChange('', '', event); // Clear the filename field on a failure
onDataChange?.('');
});
} else {
onChange(fileHandle, fileHandle.name, event);
Expand All @@ -113,41 +132,69 @@ export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
dropzoneProps.onDropAccepted && dropzoneProps.onDropAccepted(acceptedFiles, event);
};

const onDropRejected: DropFileEventHandler = (rejectedFiles: File[], event: React.DragEvent<HTMLElement>) => {
const onDropRejected = (rejectedFiles: File[], event: DropEvent) => {
if (rejectedFiles.length > 0) {
onChange('', rejectedFiles[0].name, event);
}
dropzoneProps.onDropRejected && dropzoneProps.onDropRejected(rejectedFiles, event);
};

const fileInputRef = React.useRef<HTMLInputElement>();
const setFileValue = (filename: string) => {
fileInputRef.current.value = filename;
};

const onClearButtonClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
onChange('', '', event);
onClearClick?.(event);
setFileValue(null);
};

return (
<Dropzone multiple={false} {...dropzoneProps} onDropAccepted={onDropAccepted} onDropRejected={onDropRejected}>
{({ getRootProps, getInputProps, isDragActive, open }) => (
<FileUploadField
{...getRootProps({
...props,
refKey: 'containerRef',
onClick: event => event.preventDefault()
})}
tabIndex={null} // Omit the unwanted tabIndex from react-dropzone's getRootProps
id={id}
type={type}
filename={filename}
value={value}
onChange={onChange}
isDragActive={isDragActive}
onBrowseButtonClick={open}
onClearButtonClick={onClearButtonClick}
onTextAreaClick={onClick}
>
<input {...getInputProps()} /* hidden, necessary for react-dropzone */ />
{children}
</FileUploadField>
)}
<Dropzone
multiple={false}
{...dropzoneProps}
onDropAccepted={onDropAccepted}
onDropRejected={onDropRejected}
noClick={true}
>
{({ getRootProps, getInputProps, isDragActive, open }) => {
const oldInputProps = getInputProps();
const inputProps: DropzoneInputProps = {
...oldInputProps,
onChange: async (e: React.ChangeEvent<HTMLInputElement>) => {
oldInputProps.onChange?.(e);
const files = await fromEvent(e.nativeEvent);
if (files.length === 1) {
onFileInputChange?.(e, files[0] as File);
}
}
};

return (
<FileUploadField
{...getRootProps({
...props,
refKey: 'containerRef',
onClick: event => event.preventDefault()
})}
tabIndex={null} // Omit the unwanted tabIndex from react-dropzone's getRootProps
id={id}
type={type}
filename={filename}
value={value}
onChange={onChange}
isDragActive={isDragActive}
onBrowseButtonClick={open}
onClearButtonClick={onClearButtonClick}
onTextAreaClick={onClick}
onTextChange={onTextChange}
>
<input {...inputProps} ref={fileInputRef} /* hidden, necessary for react-dropzone */ />
{children}
</FileUploadField>
);
}}
</Dropzone>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export interface FileUploadFieldProps extends Omit<React.HTMLProps<HTMLDivElemen
isDragActive?: boolean;
/** A reference object to attach to the FileUploadField container element. */
containerRef?: React.Ref<HTMLDivElement>;
/** Text area text changed */
onTextChange?: (text: string) => void;
}

export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
Expand All @@ -87,6 +89,7 @@ export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
onBrowseButtonClick = () => {},
onClearButtonClick = () => {},
onTextAreaClick,
onTextChange,
className = '',
isDisabled = false,
isReadOnly = false,
Expand All @@ -105,10 +108,12 @@ export const FileUploadField: React.FunctionComponent<FileUploadFieldProps> = ({
allowEditingUploadedText = false,
hideDefaultPreview = false,
children = null,

...props
}: FileUploadFieldProps) => {
const onTextAreaChange = (newValue: string, event: React.ChangeEvent<HTMLTextAreaElement>) => {
onChange(newValue, filename, event);
onTextChange?.(newValue);
};
return (
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`simple fileupload 1`] = `
<t
disabled={false}
getDataTransferItems={[Function]}
maxSize={Infinity}
minSize={0}
<Dropzone
multiple={false}
noClick={true}
onDropAccepted={[Function]}
onDropRejected={[Function]}
preventDropOnDocument={true}
>
<Component />
</t>
</Dropzone>
`;
Loading