baseui
Version:
A React Component library implementing the Base design language
437 lines (431 loc) • 23.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = FileUploader;
var React = _interopRequireWildcard(require("react"));
var _fileUploaderBasic = require("../file-uploader-basic");
var _button = require("../button");
var _progressBar = require("../progress-bar");
var _overrides = require("../helpers/overrides");
var _styledComponents = require("./styled-components");
var _constants = require("./constants");
var _utils = require("./utils");
var _circleCheckFilled = _interopRequireDefault(require("../icon/circle-check-filled"));
var _circleExclamationPointFilled = _interopRequireDefault(require("../icon/circle-exclamation-point-filled"));
var _paperclipFilled = _interopRequireDefault(require("../icon/paperclip-filled"));
var _trashCanFilled = _interopRequireDefault(require("../icon/trash-can-filled"));
var _upload = _interopRequireDefault(require("../icon/upload"));
var _styles = require("../styles");
var _locale = require("../locale");
var _reactUid = require("react-uid");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
function FileUploader(props) {
if (props['onDrop']) {
console.error('onDrop is not a prop for FileUploader.');
}
if (props['onDropAccepted']) {
console.error('onDropAccepted is not a prop for FileUploader.');
}
if (props['onDropRejected']) {
console.error('onDropRejected is not a prop for FileUploader.');
}
if (props['progressAmount']) {
console.error('progressAmount is not a prop for FileUploader.');
}
// Isolate props that are not meant to be passed to FileUploaderBasic
const {
fileRows,
hint,
itemPreview,
label,
maxFiles,
overrides = {},
processFileOnDrop,
progressAmountStartValue,
setFileRows,
...fileUploaderBasicProps
} = props;
// Isolate styles that are not meant to be passed to FileUploaderBasic
const {
// Overrides for FileUploader
CircleCheckFilledIcon: OverridesCircleCheckFilledIcon,
CircleExclamationPointFilledIcon: OverridesCircleExclamationPointFilledIcon,
DeleteButtonComponent: OverridesDeleteButtonComponent,
FileRow: OverridesFileRow,
FileRowColumn: OverridesFileRowColumn,
FileRowContent: OverridesFileRowContent,
FileRowFileName: OverridesFileRowFileName,
FileRowText: OverridesFileRowText,
FileRowUploadMessage: OverridesFileRowUploadMessage,
FileRowUploadText: OverridesFileRowUploadText,
FileRows: OverridesFileRows,
Hint: OverridesHint,
ImagePreviewThumbnail: OverridesImagePreviewThumbnail,
ItemPreviewContainer: OverridesItemPreviewContainer,
Label: OverridesLabel,
PaperclipFilledIcon: OverridesPaperclipFilledIcon,
ParentRoot: OverridesParentRoot,
TrashCanFilledIcon: OverridesTrashCanFilledIcon,
// Overrides for FileUploaderBasic that are modified in this file
ButtonComponent,
ContentMessage,
FileDragAndDrop,
...fileUploaderBasicOverrides
} = overrides;
const [css, theme] = (0, _styles.useStyletron)();
// Prepare icon overrides
const [CircleCheckFilledIcon, circleCheckFilledIconProps] = (0, _overrides.getOverrides)(overrides.CircleCheckFilledIcon, _circleCheckFilled.default);
const [CircleExclamationPointFilledIcon, circleExclamationPointFilledIconProps] = (0, _overrides.getOverrides)(OverridesCircleExclamationPointFilledIcon, _circleExclamationPointFilled.default);
const [PaperclipFilledIcon, paperclipFilledIconProps] = (0, _overrides.getOverrides)(OverridesPaperclipFilledIcon, _paperclipFilled.default);
const [TrashCanFilledIcon, trashCanFilledIconProps] = (0, _overrides.getOverrides)(OverridesTrashCanFilledIcon, _trashCanFilled.default);
// Prepare baseui component overrides
const [DeleteButtonComponent, deleteButtonProps] = (0, _overrides.getOverrides)(OverridesDeleteButtonComponent, _button.Button);
const [ProgressBarComponent, progressBarProps] = (0, _overrides.getOverrides)(overrides.ProgressBar, _progressBar.ProgressBar);
// Prepare styled component overrides
const [FileRow, fileRowProps] = (0, _overrides.getOverrides)(OverridesFileRow, _styledComponents.StyledFileRow);
const [FileRowColumn, fileRowColumnProps] = (0, _overrides.getOverrides)(OverridesFileRowColumn, _styledComponents.StyledFileRowColumn);
const [FileRowContent, fileRowContentProps] = (0, _overrides.getOverrides)(OverridesFileRowContent, _styledComponents.StyledFileRowContent);
const [FileRowFileName, fileRowFileNameProps] = (0, _overrides.getOverrides)(OverridesFileRowFileName, _styledComponents.StyledFileRowFileName);
const [FileRowText, fileRowTextProps] = (0, _overrides.getOverrides)(OverridesFileRowText, _styledComponents.StyledFileRowText);
const [FileRowUploadMessage, fileRowUploadMessageProps] = (0, _overrides.getOverrides)(OverridesFileRowUploadMessage, _styledComponents.StyledFileRowUploadMessage);
const [FileRowUploadText, fileRowUploadTextProps] = (0, _overrides.getOverrides)(OverridesFileRowUploadText, _styledComponents.StyledFileRowUploadText);
const [FileRows, fileRowsProps] = (0, _overrides.getOverrides)(OverridesFileRows, _styledComponents.StyledFileRows);
const [Hint, hintProps] = (0, _overrides.getOverrides)(OverridesHint, _styledComponents.StyledHint);
const [ImagePreviewThumbnail, imagePreviewThumbnailProps] = (0, _overrides.getOverrides)(OverridesImagePreviewThumbnail, _styledComponents.StyledImagePreviewThumbnail);
const [ItemPreviewContainer, itemPreviewContainerProps] = (0, _overrides.getOverrides)(OverridesItemPreviewContainer, _styledComponents.StyledItemPreviewContainer);
const [Label, labelProps] = (0, _overrides.getOverrides)(OverridesLabel, _styledComponents.StyledLabel);
const [ParentRoot, parentRootProps] = (0, _overrides.getOverrides)(OverridesParentRoot, _styledComponents.StyledParentRoot);
const onDrop = React.useCallback((acceptedFiles, rejectedFiles) => {
const newFiles = acceptedFiles.concat(rejectedFiles);
let newFileRows = [...props.fileRows];
newFiles.forEach(file => {
newFileRows.push({
errorMessage: null,
file,
id: (0, _reactUid.uid)(file),
imagePreviewThumbnail: '',
progressAmount: progressAmountStartValue ?? _constants.PROGRESS_AMOUNT_LOADING,
status: _constants.FILE_STATUS.added
});
props.setFileRows([...newFileRows]);
});
newFileRows.forEach((fileRow, index) => {
if (fileRow.status === _constants.FILE_STATUS.added) {
let reader = new FileReader();
reader.onerror = () => {
newFileRows[index].errorMessage = 'cannot read file';
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
props.setFileRows([...newFileRows]);
};
reader.onload = event => {
if (newFileRows[index].file.type.startsWith('image/')) {
newFileRows[index].imagePreviewThumbnail = event.target?.result;
props.setFileRows([...newFileRows]);
}
if (props.maxFiles !== undefined && Number.isInteger(props.maxFiles) && index >= props.maxFiles) {
// If too many files
newFileRows[index].errorMessage = `cannot process more than ${props.maxFiles} file(s)`;
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
props.setFileRows([...newFileRows]);
} else if (props.minSize !== undefined && Number.isInteger(props.minSize) && props.minSize > fileRow.file.size) {
// If file size is too small
newFileRows[index].errorMessage = `file size must be greater than ${(0, _utils.formatBytes)(props.minSize)}`;
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
props.setFileRows([...newFileRows]);
} else if (props.maxSize !== undefined && Number.isInteger(props.maxSize) && props.maxSize < fileRow.file.size) {
// If file size is too big
newFileRows[index].errorMessage = `file size must be less than ${(0, _utils.formatBytes)(props.maxSize)}`;
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
props.setFileRows([...newFileRows]);
} else if (index >= newFileRows.length - rejectedFiles.length) {
// If file was rejected by dropzone (e.g. wrong file type)
newFileRows[index].errorMessage = fileRow.file.type ? `file type of ${fileRow.file.type} is not accepted` : 'file type is not accepted';
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
props.setFileRows([...newFileRows]);
} else if (props.processFileOnDrop) {
// If caller passed in file process function
props.processFileOnDrop(fileRow.file, fileRow.id, newFileRows).then(({
errorMessage,
fileInfo
}) => {
if (fileInfo) {
newFileRows[index].fileInfo = fileInfo;
}
if (errorMessage) {
newFileRows[index].errorMessage = errorMessage;
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
} else {
newFileRows[index].status = _constants.FILE_STATUS.processed;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
}
}).catch(error => {
console.error('error with processFileOnDrop', error);
newFileRows[index].errorMessage = 'unknown processing error';
newFileRows[index].status = _constants.FILE_STATUS.error;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload failed: ${newFileRows[index].errorMessage}`);
}).finally(() => {
props.setFileRows([...newFileRows]);
});
} else {
// If no errors and no file process function
newFileRows[index].status = _constants.FILE_STATUS.processed;
newFileRows[index].progressAmount = _constants.PROGRESS_AMOUNT_LOADING_COMPLETE;
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.ADDITION, `${newFileRows[index].file.name} added, upload successful`);
props.setFileRows([...newFileRows]);
}
};
reader.readAsDataURL(fileRow.file);
}
});
}, [props]);
const removeFileRow = event => {
event.preventDefault();
const indexOfFileRowToRemove = Number(event?.currentTarget?.getAttribute('index'));
(0, _utils.handleAriaLiveUpdates)(_constants.ARIA_LIVE_ELEMENT_ID.REMOVAL, `${props.fileRows[indexOfFileRowToRemove].file.name} removed`);
props.setFileRows([...props.fileRows.toSpliced(indexOfFileRowToRemove, 1)]);
const label = document.querySelector('[data-baseweb="file-uploader-label"]');
if (label) {
label.focus();
}
};
const noFilesAreLoading = React.useMemo(() => !props.fileRows.find(fileRow => fileRow.status === _constants.FILE_STATUS.added), [props.fileRows]);
return /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(ParentRoot, _extends({
"data-baseweb": "file-uploader-parent-root"
}, parentRootProps), /*#__PURE__*/React.createElement("span", {
"aria-live": "assertive",
"aria-relevant": "additions",
className: css({
top: 0,
left: '-4px',
width: '1px',
height: '1px',
position: 'absolute',
overflow: 'hidden'
}),
id: _constants.ARIA_LIVE_ELEMENT_ID.ADDITION
}), /*#__PURE__*/React.createElement("span", {
"aria-live": "polite",
"aria-relevant": "additions",
className: css({
top: 0,
left: '-2px',
width: '1px',
height: '1px',
position: 'absolute',
overflow: 'hidden'
}),
id: _constants.ARIA_LIVE_ELEMENT_ID.REMOVAL
}), props.label && /*#__PURE__*/React.createElement(Label, _extends({
"data-baseweb": "file-uploader-label",
tabIndex: -1
}, labelProps, {
$disabled: !!props.disabled
}), props.label), /*#__PURE__*/React.createElement(_fileUploaderBasic.FileUploaderBasic, _extends({
buttonIcon: () => /*#__PURE__*/React.createElement(_upload.default, {
"aria-hidden": 'true'
}),
buttonText: locale.fileuploader.buttonText,
contentMessage: locale.fileuploader.contentMessage,
overrides: {
ButtonComponent: {
props: {
'aria-describedby': 'file-uploader-hint',
shape: _button.SHAPE.default,
size: _button.SIZE.default,
...props.overrides?.ButtonComponent?.props,
style: {
marginTop: 0,
...(0, _utils.destructureStyleOverride)(
// @ts-expect-error
props.overrides?.ButtonComponent?.props?.style, theme)
},
overrides: {
// @ts-expect-error
...props.overrides?.ButtonComponent?.props?.overrides,
BaseButton: {
// @ts-expect-error
...props.overrides?.ButtonComponent?.props?.overrides?.BaseButton,
style: {
backgroundColor: theme.colors.backgroundPrimary,
height: theme.sizing.scale950,
display: 'flex',
flexDirection: 'row',
gap: '8px',
...theme.typography.LabelSmall,
...(0, _utils.destructureStyleOverride)(
// @ts-expect-error
props.overrides?.ButtonComponent?.props?.overrides?.BaseButton?.style, theme)
}
}
}
}
},
ContentMessage: {
style: {
...theme.typography.ParagraphSmall,
color: theme.colors.contentTertiary,
...(0, _utils.destructureStyleOverride)(props.overrides?.ContentMessage?.style, theme)
}
},
FileDragAndDrop: {
style: fileDragAndDropProps => ({
backgroundColor: fileDragAndDropProps.$theme.colors.fileUploaderBackgroundColor,
borderColor: fileDragAndDropProps.$isDragActive ? fileDragAndDropProps.$theme.colors.borderSelected : fileDragAndDropProps.$theme.colors.fileUploaderBorderColorDefault,
borderStyle: 'solid',
borderWidth: '3px',
flexDirection: 'row',
flexWrap: 'wrap',
gap: theme.sizing.scale300,
paddingBottom: theme.sizing.scale600,
paddingLeft: theme.sizing.scale600,
paddingRight: theme.sizing.scale600,
paddingTop: theme.sizing.scale600,
...(0, _utils.destructureStyleOverride)(props.overrides?.FileDragAndDrop?.style, theme)
})
},
Root: {
style: {
marginBottom: theme.sizing.scale300,
...(0, _utils.destructureStyleOverride)(props.overrides?.Root?.style, theme)
}
},
...fileUploaderBasicOverrides
},
swapButtonAndMessage: true
}, fileUploaderBasicProps, {
// Disable uploads while files are loading, even if application passes disabled as false
disabled: !!props.disabled ? props.disabled : !!props.fileRows.find(fileRow => fileRow.status === _constants.FILE_STATUS.added)
// Implement or use no-op callbacks to prevent consumers from passing them in
,
onDrop: onDrop,
onDropAccepted: _ => {},
onDropRejected: _ => {},
progressAmount: undefined
})), props.fileRows.length > 0 && /*#__PURE__*/React.createElement(FileRows, _extends({
"data-baseweb": "file-uploader-file-rows"
}, fileRowsProps), props.fileRows.map((fileRow, index) => /*#__PURE__*/React.createElement(FileRow, _extends({
id: `file-uploader-file-row-${index}`,
"data-baseweb": "file-uploader-file-row",
key: fileRow.id
}, fileRowProps), props.itemPreview && /*#__PURE__*/React.createElement(ItemPreviewContainer, _extends({
"aria-hidden": 'true',
"data-baseweb": "file-uploader-item-preview-container"
}, itemPreviewContainerProps), fileRow.imagePreviewThumbnail ? /*#__PURE__*/React.createElement(ImagePreviewThumbnail, _extends({
alt: fileRow.file.name,
"data-baseweb": "file-uploader-image-preview-thumbnail",
src: fileRow.imagePreviewThumbnail
}, imagePreviewThumbnailProps)) : /*#__PURE__*/React.createElement(PaperclipFilledIcon, _extends({
"data-baseweb": "file-uploader-paperclip-filled-icon",
color: theme.colors.contentSecondary
}, paperclipFilledIconProps))), /*#__PURE__*/React.createElement(FileRowColumn, _extends({
"data-baseweb": "file-uploader-file-row-column"
}, fileRowColumnProps), /*#__PURE__*/React.createElement(FileRowContent, _extends({
"data-baseweb": "file-uploader-file-row-content"
}, fileRowContentProps), /*#__PURE__*/React.createElement(FileRowText, _extends({
"data-baseweb": "file-uploader-file-row-text"
}, fileRowTextProps), /*#__PURE__*/React.createElement(FileRowFileName, _extends({
"data-baseweb": "file-uploader-file-row-file-name"
}, fileRowFileNameProps), fileRow.file.name), /*#__PURE__*/React.createElement(FileRowUploadMessage, _extends({
"data-baseweb": "file-uploader-file-row-upload-message",
$color: (0, _constants.FILE_STATUS_TO_COLOR_MAP)(theme)[fileRow.status]
}, fileRowUploadMessageProps), fileRow.status === _constants.FILE_STATUS.error && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CircleExclamationPointFilledIcon, _extends({
"aria-hidden": 'true',
color: (0, _constants.FILE_STATUS_TO_COLOR_MAP)(theme)[fileRow.status],
"data-baseweb": "file-uploader-circle-exclamation-point-filled-icon",
title: fileRow.status
}, circleExclamationPointFilledIconProps)), /*#__PURE__*/React.createElement(FileRowUploadText, _extends({
"aria-errormessage": fileRow.errorMessage,
"aria-invalid": true,
"data-baseweb": "file-uploader-file-row-upload-message-text",
id: `file-uploader-file-row-upload-message-text-${index}`
}, fileRowUploadTextProps), locale.fileuploader.error, fileRow.errorMessage)), fileRow.status === _constants.FILE_STATUS.processed && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CircleCheckFilledIcon, _extends({
"aria-hidden": 'true',
color: (0, _constants.FILE_STATUS_TO_COLOR_MAP)(theme)[fileRow.status],
"data-baseweb": "file-uploader-circle-check-filled-icon",
title: fileRow.status
}, circleCheckFilledIconProps)), /*#__PURE__*/React.createElement(FileRowUploadText, _extends({
"data-baseweb": "file-uploader-file-row-upload-message-text"
}, fileRowUploadTextProps), locale.fileuploader.processed)), fileRow.status === _constants.FILE_STATUS.added && /*#__PURE__*/React.createElement(FileRowUploadText, _extends({
$color: theme.colors.contentTertiary,
"data-baseweb": "file-uploader-file-row-upload-message-text"
}, fileRowUploadTextProps), locale.fileuploader.added))), noFilesAreLoading && /*#__PURE__*/React.createElement(DeleteButtonComponent, _extends({
"aria-label": `Remove ${fileRow.file.name}`,
"data-baseweb": "file-uploader-delete-button-component",
index: index,
onClick: removeFileRow,
kind: _button.KIND.tertiary,
shape: _button.SHAPE.circle,
size: _button.SIZE.compact
}, deleteButtonProps), /*#__PURE__*/React.createElement(TrashCanFilledIcon, _extends({
"aria-hidden": 'true',
"data-baseweb": "file-uploader-trash-can-filled-icon",
overrides: {
Svg: {
style: {
verticalAlign: 'middle'
}
}
},
size: theme.sizing.scale600,
title: 'remove'
}, trashCanFilledIconProps)))), /*#__PURE__*/React.createElement(ProgressBarComponent, _extends({
"aria-hidden": 'true',
"data-baseweb": "file-uploader-progress-bar",
overrides: {
Bar: {
style: {
marginTop: theme.sizing.scale0,
marginBottom: theme.sizing.scale0,
marginLeft: 0,
marginRight: 0
}
},
BarContainer: {
style: {
marginTop: 0,
marginBottom: 0,
marginLeft: 0,
marginRight: 0
}
},
BarProgress: {
// @ts-ignore
style: ({
$theme
}) => ({
backgroundColor: (0, _constants.FILE_STATUS_TO_COLOR_MAP)($theme)[fileRow.status]
})
}
},
size: _progressBar.SIZE.small,
value: fileRow.progressAmount ?? _constants.PROGRESS_AMOUNT_LOADING_COMPLETE
}, progressBarProps)))))), props.hint && /*#__PURE__*/React.createElement(Hint, _extends({
"data-baseweb": "file-uploader-hint",
id: "file-uploader-hint",
$fileCount: props.fileRows.length
}, hintProps), props.hint)));
}
FileUploader.defaultProps = {
fileRows: [],
setFileRows: () => {}
};