UNPKG

baseui

Version:

A React Component library implementing the Base design language

437 lines (431 loc) • 23.6 kB
"use strict"; 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: () => {} };