diff --git a/src/components/FilePicker/index.jsx b/src/components/FilePicker/index.jsx
index 151f9bb2d..4c0b44ad7 100644
--- a/src/components/FilePicker/index.jsx
+++ b/src/components/FilePicker/index.jsx
@@ -1,129 +1,169 @@
import classNames from 'classnames';
+import _ from 'lodash';
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useState, useRef } from 'react';
import Button from 'react-bootstrap/lib/Button';
import './styles.scss';
-const baseClass = 'filepicker-component';
+const baseClass = 'aui--filepicker-component';
-class FilePickerComponent extends React.PureComponent {
- static propTypes = {
- /**
- * determines if the filePicker is disabled
- */
- disabled: PropTypes.bool,
- /**
- * data-test-selector of the filePicker
- */
- dts: PropTypes.string,
- /**
- * determines what file types the user can pick from the file input dialog box
- */
- filter: PropTypes.string,
- /**
- * determines if the filePicker is highlighted or not
- */
- isHighlighted: PropTypes.bool,
- /**
- * the label to be displayed
- */
- label: PropTypes.string,
- /**
- * function called when onRemove event is fired
- */
- onRemove: PropTypes.func,
- /**
- * function called when onSelect event is fired
- */
- onSelect: PropTypes.func.isRequired,
- /**
- * determines the placeholder when no date is selected
- */
- placeholder: PropTypes.string,
- };
-
- static defaultProps = {
- isHighlighted: false,
- label: 'Select',
- placeholder: 'No file selected',
- disabled: false,
- };
-
- constructor(props) {
- super(props);
-
- this.fileInput = React.createRef();
- }
+const FilePickerComponent = ({
+ disabled,
+ dts,
+ filter,
+ isHighlighted,
+ label,
+ onRemove,
+ onSelect,
+ onClick,
+ onChange,
+ value,
+ placeholder,
+}) => {
+ const [isFileSelected, setIsFileSelected] = useState(false);
+ const [fileName, setFileName] = useState('');
+ const fileInput = useRef();
- state = {
- isFileSelected: false,
- fileName: '',
- };
+ const selectFile = event => {
+ if (!isFileSelected) {
+ const file = event.target.files[0];
+ setIsFileSelected(true);
+ if (_.isFunction(onChange)) {
+ onChange(file.name);
+ }
+ setFileName(file.name);
- onChange = changeEvent => {
- if (!this.state.isFileSelected) {
- this.setState({ isFileSelected: true, fileName: changeEvent.target.files[0].name });
- this.props.onSelect(changeEvent.target.files[0]);
+ onSelect(file);
}
};
- onUploadBtnClick = () => {
- this.fileInput.current.click();
+ const onUploadBtnClick = () => {
+ fileInput.current.click();
};
- removeFile = () => {
- if (this.state.isFileSelected) {
- this.fileInput.current.value = null;
- this.setState({ isFileSelected: false, fileName: '' });
- if (this.props.onRemove) {
- this.props.onRemove();
+ const removeFile = () => {
+ if (isFileSelected || !_.isEmpty(value)) {
+ fileInput.current.value = null;
+ setIsFileSelected(false);
+ if (_.isFunction(onChange)) {
+ onChange('');
+ }
+ setFileName('');
+
+ if (_.isFunction(onRemove)) {
+ onRemove();
}
}
};
- render() {
- const mainClass = classNames({ [`${baseClass}-highlight`]: this.props.isHighlighted }, baseClass, 'input-group');
- const { isFileSelected, fileName } = this.state;
+ const clickFile = () => {
+ if (_.isFunction(onClick)) {
+ onClick();
+ }
+ };
+
+ if (value && !onChange)
+ console.warn(
+ 'Failed prop type: You have provided a `value` prop to FilePicker Component without an `onChange` handler. This will render a read-only field.'
+ );
- return (
-
-
-
+ );
+};
+
+FilePickerComponent.propTypes = {
+ /**
+ * determines if the filePicker is disabled
+ */
+ disabled: PropTypes.bool,
+ /**
+ * data-test-selector of the filePicker
+ */
+ dts: PropTypes.string,
+ /**
+ * determines what file types the user can pick from the file input dialog box
+ */
+ filter: PropTypes.string,
+ /**
+ * determines if the filePicker is highlighted or not
+ */
+ isHighlighted: PropTypes.bool,
+ /**
+ * label on button
+ */
+ label: PropTypes.string,
+ /**
+ * function called when onRemove event is fired
+ */
+ onRemove: PropTypes.func,
+ /**
+ * function called when onSelect event is fired
+ */
+ onSelect: PropTypes.func.isRequired,
+ /**
+ * function called when user click the file name
+ */
+ onClick: PropTypes.func,
+ /**
+ * function called when the file name changes
+ */
+ onChange: PropTypes.func,
+ /**
+ * file name on input
+ */
+ value: PropTypes.string,
+ /**
+ * determines the placeholder when no date is selected
+ */
+ placeholder: PropTypes.string,
+};
+
+FilePickerComponent.defaultProps = {
+ isHighlighted: false,
+ label: 'Select',
+ placeholder: 'No file selected',
+ disabled: false,
+};
export default FilePickerComponent;
diff --git a/src/components/FilePicker/index.spec.jsx b/src/components/FilePicker/index.spec.jsx
index 27eb49652..8667a2632 100644
--- a/src/components/FilePicker/index.spec.jsx
+++ b/src/components/FilePicker/index.spec.jsx
@@ -7,8 +7,9 @@ afterEach(cleanup);
describe('
', () => {
it('should render with defaults', () => {
- const { getByTestId } = render(
);
- expect(getByTestId('file-picker-wrapper')).toHaveClass('filepicker-component input-group');
+ const { getByTestId } = render(
);
+ expect(getByTestId('file-picker-wrapper')).toHaveClass('aui--filepicker-component input-group');
+ expect(getByTestId('file-picker-wrapper')).toHaveAttribute('data-test-selector', 'test-file-picker-input');
expect(getByTestId('file-picker-form-control')).toHaveClass('form-control');
expect(getByTestId('file-picker-form-control')).toHaveAttribute('placeholder', 'No file selected');
@@ -20,9 +21,7 @@ describe('
', () => {
it('should show remove button and call `onSelect` when file selected', () => {
const onSelect = jest.fn();
- const { getByTestId, queryAllByTestId } = render(
-
- );
+ const { getByTestId, queryAllByTestId } = render(
);
expect(getByTestId('file-picker-form-control')).toHaveAttribute('title', '');
expect(getByTestId('file-picker-form-control')).toHaveAttribute('placeholder', 'No file selected');
@@ -30,11 +29,6 @@ describe('
', () => {
expect(queryAllByTestId('file-picker-remove-button')).toHaveLength(0);
expect(getByTestId('file-picker-input-button')).toBeEnabled();
- expect(getByTestId('file-picker-input-button-input')).toHaveAttribute(
- 'data-test-selector',
- 'test-file-picker-input'
- );
-
fireEvent.change(getByTestId('file-picker-input-button-input'), { target: { files: [{ name: 'selected file' }] } });
expect(onSelect).toHaveBeenCalledTimes(1);
expect(onSelect).toHaveBeenCalledWith({ name: 'selected file' });
@@ -95,4 +89,67 @@ describe('
', () => {
expect(getByTestId('file-picker-form-control')).toHaveAttribute('title', '');
expect(onRemove).toHaveBeenCalledTimes(1);
});
+
+ it('should show value as file name, show remove button and disable input button if value is provided', () => {
+ const { getByTestId, queryAllByTestId } = render(
+
+ );
+ expect(getByTestId('file-picker-form-control')).toHaveAttribute('value', 'custom_file_name');
+ expect(getByTestId('file-picker-form-control')).toHaveAttribute('title', 'custom_file_name');
+ expect(queryAllByTestId('file-picker-remove-button')).toHaveLength(1);
+ expect(getByTestId('file-picker-input-button')).toBeDisabled();
+ });
+
+ it('should remove file name, hide remove button and enable input button if value is set to empty string', () => {
+ const { getByTestId, queryAllByTestId, rerender } = render(
+
+ );
+ expect(getByTestId('file-picker-form-control')).toHaveAttribute('value', 'custom_file_name');
+
+ rerender(
);
+ expect(getByTestId('file-picker-form-control')).toHaveAttribute('value', '');
+ expect(queryAllByTestId('file-picker-remove-button')).toHaveLength(0);
+ expect(getByTestId('file-picker-input-button')).toBeEnabled();
+ });
+
+ it('should show warning if value is provided but onChange is not provided', () => {
+ console.warn = jest.fn();
+
+ render(
);
+
+ expect(console.warn).toHaveBeenCalledWith(
+ 'Failed prop type: You have provided a `value` prop to FilePicker Component without an `onChange` handler. This will render a read-only field.'
+ );
+ });
+
+ it('should call `onChange` with the file name when a file is selected', () => {
+ const onChange = jest.fn();
+ const { getByTestId } = render(
+
+ );
+ fireEvent.change(getByTestId('file-picker-input-button-input'), {
+ target: { files: [{ name: 'selected_file_name' }] },
+ });
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith('selected_file_name');
+ });
+
+ it('should call `onChange` with the empty string when a file is removed', () => {
+ const onChange = jest.fn();
+ const { getByTestId } = render(
+
+ );
+ fireEvent.click(getByTestId('file-picker-remove-button'));
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith('');
+ });
+
+ it('should call `onClick` when file name is clicked', () => {
+ const onClick = jest.fn();
+ const { getByTestId } = render(
+
+ );
+ fireEvent.click(getByTestId('file-picker-form-control'));
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/src/components/FilePicker/styles.scss b/src/components/FilePicker/styles.scss
index 5ec8d77b4..f4906f333 100644
--- a/src/components/FilePicker/styles.scss
+++ b/src/components/FilePicker/styles.scss
@@ -1,6 +1,6 @@
@import '~styles/color';
-.filepicker-component {
+.aui--filepicker-component {
&-highlight {
border: 1px solid $color-border-error;
border-radius: 2px;
@@ -15,15 +15,23 @@
margin-right: 0;
}
+ .select-file.btn {
+ box-shadow: none;
+ padding-bottom: 4px;
+ z-index: 3;
+ }
+
.file-input {
display: none;
}
-}
-.has-error {
- .filepicker-component {
- .form-control {
- border: 0; // Error style is applied to .filepicker-component-highlight
+ .form-control {
+ border: 0;
+ cursor: not-allowed;
+
+ &.clickable {
+ cursor: pointer;
}
}
}
+
diff --git a/src/components/RichTextEditor/index.jsx b/src/components/RichTextEditor/index.jsx
index 68885d442..20c4542b0 100644
--- a/src/components/RichTextEditor/index.jsx
+++ b/src/components/RichTextEditor/index.jsx
@@ -8,7 +8,7 @@ import InlineStyleButtons from './InlineStyleButtons';
import BlockStyleButtons from './BlockStyleButtons';
import './styles.scss';
-const RichTextEditor = ({ className, value, initialValue, onChange, placeholder }) => {
+const RichTextEditor = ({ className, value, initialValue, onChange, placeholder, dts }) => {
const editor = React.createRef(null);
const focusEditor = () => editor.current.focus();
@@ -40,7 +40,7 @@ const RichTextEditor = ({ className, value, initialValue, onChange, placeholder
};
return (
-
+
draft-js editor state */
value: PropTypes.instanceOf(EditorState),
onChange: PropTypes.func,
+ /**
+ * data-test-selector of the rich text editor
+ */
+ dts: PropTypes.string,
};
RichTextEditor.defaultProps = {
diff --git a/www/containers/props.json b/www/containers/props.json
index f9b9e7d10..c8d8008fc 100644
--- a/www/containers/props.json
+++ b/www/containers/props.json
@@ -1332,34 +1332,7 @@
{
"description": "",
"displayName": "FilePickerComponent",
- "methods": [
- {
- "name": "onChange",
- "docblock": null,
- "modifiers": [],
- "params": [
- {
- "name": "changeEvent",
- "type": null
- }
- ],
- "returns": null
- },
- {
- "name": "onUploadBtnClick",
- "docblock": null,
- "modifiers": [],
- "params": [],
- "returns": null
- },
- {
- "name": "removeFile",
- "docblock": null,
- "modifiers": [],
- "params": [],
- "returns": null
- }
- ],
+ "methods": [],
"props": {
"disabled": {
"type": {
@@ -1402,7 +1375,7 @@
"name": "string"
},
"required": false,
- "description": "the label to be displayed",
+ "description": "label on button",
"defaultValue": {
"value": "'Select'",
"computed": false
@@ -1422,6 +1395,27 @@
"required": true,
"description": "function called when onSelect event is fired"
},
+ "onClick": {
+ "type": {
+ "name": "func"
+ },
+ "required": false,
+ "description": "function called when user click the file name"
+ },
+ "onChange": {
+ "type": {
+ "name": "func"
+ },
+ "required": false,
+ "description": "function called when the file name changes"
+ },
+ "value": {
+ "type": {
+ "name": "string"
+ },
+ "required": false,
+ "description": "file name on input"
+ },
"placeholder": {
"type": {
"name": "string"
@@ -3480,6 +3474,13 @@
},
"required": false,
"description": ""
+ },
+ "dts": {
+ "type": {
+ "name": "string"
+ },
+ "required": false,
+ "description": "data-test-selector of the rich text editor"
}
}
}
diff --git a/www/examples/FilePicker.mdx b/www/examples/FilePicker.mdx
index 117eca991..22d5381fd 100644
--- a/www/examples/FilePicker.mdx
+++ b/www/examples/FilePicker.mdx
@@ -4,12 +4,20 @@ import DesignNotes from '../containers/DesignNotes.jsx';
## File Picker
```jsx live=true
-class FilePickerExample extends React.PureComponent {
- render() {
- const onSelect = _.noop;
- return ;
- }
-}
+const FilePickerExample = () => {
+ const onSelect = _.noop;
+ const [fileName, setFileName] = React.useState('');
+
+ return (
+ <>
+ Uncontrolled:
+
+
+ Controlled:
+ setFileName(fileName)} />
+ >
+ );
+};
render(