UNPKG

@spark-web/dropzone

Version:

--- title: Drop Zone isExperimentalPackage: true ---

378 lines (351 loc) 13.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties'); var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray'); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var css = require('@emotion/css'); var a11y = require('@spark-web/a11y'); var alert = require('@spark-web/alert'); var box = require('@spark-web/box'); var field = require('@spark-web/field'); var icon = require('@spark-web/icon'); var stack = require('@spark-web/stack'); var text = require('@spark-web/text'); var textList = require('@spark-web/text-list'); var theme = require('@spark-web/theme'); var utils = require('@spark-web/utils'); var react = require('react'); var reactDropzone = require('react-dropzone'); var jsxRuntime = require('react/jsx-runtime'); // Add more MIME types as we need them: var mimeTypeToFileExtension = { 'audio/*': 'audio files', 'audio/mpeg': '.mpeg', 'audio/wav': '.wav', 'image/*': 'image files', 'image/gif': '.gif', 'image/heic': '.heic', 'image/jpeg': '.jpg, .jpeg', 'image/png': '.png', 'image/svg+xml': '.svg+xml', 'image/tiff': '.tiff', 'image/webp': '.webp', 'text/*': 'text files', 'text/csv': '.csv', 'text/plain': '.plain', 'text/rtf': '.rtf', 'video/*': 'video files', 'video/mp4': '.mp4', 'video/mpeg': '.mpeg', 'application/msword': '.msword', 'application/pdf': '.pdf', 'application/rtf': '.rtf', 'application/vnd.ms-excel': '.xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx', 'application/zip': '.zip' }; var _excluded = ["role", "tabIndex"], _excluded2 = ["style"]; var Dropzone = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var _fileError$errors$; var accept = _ref.accept, _ref$maxFiles = _ref.maxFiles, maxFiles = _ref$maxFiles === void 0 ? 1 : _ref$maxFiles, maxFileSizeKb = _ref.maxFileSizeKb, minFileSizeKb = _ref.minFileSizeKb, name = _ref.name, onBlur = _ref.onBlur, onChange = _ref.onChange, showImageThumbnails = _ref.showImageThumbnails; var _useState = react.useState([]), _useState2 = _slicedToArray(_useState, 2), files = _useState2[0], setFiles = _useState2[1]; var _useState3 = react.useState(), _useState4 = _slicedToArray(_useState3, 2), fileError = _useState4[0], setFileError = _useState4[1]; var handleRemoveFile = function handleRemoveFile(id) { setFiles(function (previousFiles) { return previousFiles.filter(function (existingFile) { return existingFile.id !== id; }); }); }; var handleDropAccepted = function handleDropAccepted(acceptedFiles) { var acceptedFilesWithPreview = acceptedFiles.map(function (acceptedFile, index) { return _objectSpread(_objectSpread({}, acceptedFile), {}, { id: files.length === 0 ? index + 1 : files.length + index + 1, preview: acceptedFile.type.startsWith('image') ? URL.createObjectURL(acceptedFile) : undefined }); }); setFiles(function (prevFiles) { return [].concat(_toConsumableArray(prevFiles), _toConsumableArray(acceptedFilesWithPreview)); }); }; var _useFieldContext = field.useFieldContext(), _useFieldContext2 = _slicedToArray(_useFieldContext, 2), _useFieldContext2$ = _useFieldContext2[0], disabled = _useFieldContext2$.disabled, invalid = _useFieldContext2$.invalid, a11yProps = _useFieldContext2[1]; var _useDropzone = reactDropzone.useDropzone({ accept: accept, maxFiles: maxFiles, maxSize: maxFileSizeKb && maxFileSizeKb * 1000, minSize: minFileSizeKb && minFileSizeKb * 1000, multiple: maxFiles > 1, onDropAccepted: handleDropAccepted, disabled: disabled }), fileRejections = _useDropzone.fileRejections, getInputProps = _useDropzone.getInputProps, getRootProps = _useDropzone.getRootProps, isDragActive = _useDropzone.isDragActive, isDragReject = _useDropzone.isDragReject, dropzoneInputRef = _useDropzone.inputRef; var _getRootProps = getRootProps(); _getRootProps.role; _getRootProps.tabIndex; var dropzoneProps = _objectWithoutProperties(_getRootProps, _excluded); var _getInputProps = getInputProps(); _getInputProps.style; var dropzoneInputProps = _objectWithoutProperties(_getInputProps, _excluded2); // HACK: Runs the `onChange` and `onBlur` functions and swaps in our local state whenever `files` is updated. react.useEffect(function () { onChange === null || onChange === void 0 ? void 0 : onChange({ target: { value: files, name: name }, type: 'change' }); onBlur === null || onBlur === void 0 ? void 0 : onBlur({ target: { value: files, name: name }, type: 'blur' }); }, [files, name, onBlur, onChange]); react.useEffect(function () { if (!fileRejections.length) { setFileError(undefined); return; } var errorMessage = { errors: [] }; if (fileRejections.length > maxFiles) { errorMessage.type = 'too-many-files'; errorMessage.errors = [{ message: 'We can’t upload anymore files as there’s too many files. ' + "The maximum number of files is ".concat(maxFiles, ". ") + 'Please remove a file before trying again.' }]; } else { fileRejections.map(function (_ref2) { var errors = _ref2.errors, name = _ref2.file.name; errors.forEach(function (error) { var message = 'unknown validation error.'; switch (error.code) { case 'file-too-large': message = "is too large. Max supported file size is ".concat(formatFileSize(maxFileSizeKb || Infinity), "."); break; case 'file-too-small': message = "is too small. Min supported file size is ".concat(formatFileSize(minFileSizeKb || 0), "."); break; case 'file-invalid-type': message = "is not a supported file type. Supported file types are ".concat(Array.isArray(accept) ? accept.map(function (f) { return mimeTypeToFileExtension[f]; }).join(', ') : mimeTypeToFileExtension[accept], "."); break; } errorMessage.errors.push({ name: name, message: message }); }); }); } setFileError(errorMessage); }, [accept, fileRejections, maxFileSizeKb, maxFiles, minFileSizeKb]); var isInvalid = invalid || isDragReject; var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); var dropzoneStyles = useDropzoneStyles({ disabled: disabled, isInvalid: isInvalid }); return /*#__PURE__*/jsxRuntime.jsxs(stack.Stack, { gap: "large", children: [/*#__PURE__*/jsxRuntime.jsx(a11y.VisuallyHidden, _objectSpread(_objectSpread(_objectSpread({ as: "input", disabled: disabled, name: name, onBlur: onBlur, onChange: onChange }, a11yProps), dropzoneInputProps), {}, { // We need to forward the ref to the input so that libraries like `react-hook-form` will work. // but `react-dropzone` also needs a ref, so we need to merge them. ref: utils.mergeRefs([forwardedRef, dropzoneInputRef]) })), /*#__PURE__*/jsxRuntime.jsxs(stack.Stack, _objectSpread(_objectSpread({}, dropzoneProps), {}, { as: "button", align: "center", background: function () { if (disabled) return 'inputDisabled'; if (isInvalid) return 'criticalLight'; return 'surfaceMuted'; }(), border: function () { if (disabled) return 'fieldDisabled'; if (isInvalid) return 'critical'; return 'field'; }(), borderRadius: "medium", borderWidth: "large", gap: "large", padding: "large", position: "relative", className: css.css(dropzoneStyles), children: [isDragActive && /*#__PURE__*/jsxRuntime.jsx(box.Box // Position absolute so height never changes , { position: "absolute", top: 0, bottom: 0, display: "flex", alignItems: "center", children: /*#__PURE__*/jsxRuntime.jsx(text.Text, { tone: isDragReject ? 'critical' : 'neutral', children: isDragReject ? 'file type not valid' : 'drop files to upload' }) }), /*#__PURE__*/jsxRuntime.jsxs(stack.Stack // Hide from screen-readers and visually, but keep it in the document flow so height doesn't change , { "aria-hidden": isDragActive, align: "center", gap: "large", position: "relative", className: css.css(isDragActive ? { opacity: 0, pointerEvents: 'none' } : null), children: [/*#__PURE__*/jsxRuntime.jsx(icon.UploadIcon, { size: "medium", tone: disabled ? 'disabled' : 'neutral' }), /*#__PURE__*/jsxRuntime.jsxs(text.Text, { align: "center", tone: disabled ? 'disabled' : 'neutral', children: ["click to select files ", /*#__PURE__*/jsxRuntime.jsx("br", {}), "or drop files here"] })] })] })), fileError && /*#__PURE__*/jsxRuntime.jsx(alert.Alert, { tone: "critical", heading: fileError.type === 'too-many-files' ? 'Maximum number of files reached' : 'These files couldn’t be added:', closeLabel: "Dismiss alert", onClose: function onClose() { return setFileError(undefined); }, children: fileError.type === 'too-many-files' ? /*#__PURE__*/jsxRuntime.jsx(text.Text, { children: (_fileError$errors$ = fileError.errors[0]) === null || _fileError$errors$ === void 0 ? void 0 : _fileError$errors$.message }) : /*#__PURE__*/jsxRuntime.jsx(textList.TextList, { gap: "medium", children: fileError.errors.map(function (error) { return /*#__PURE__*/jsxRuntime.jsxs(text.Text, { weight: "regular", children: [error.name, " - ", error.message] }, error.name); }) }) }), files.length > 0 && /*#__PURE__*/jsxRuntime.jsx(stack.Stack, { as: "ul", role: "list", gap: "medium", children: files.map(function (file) { return /*#__PURE__*/jsxRuntime.jsx(box.Box, { as: "li", border: "field", borderRadius: "small", padding: "xsmall", children: /*#__PURE__*/jsxRuntime.jsxs(box.Box, { display: "flex", alignItems: "center", gap: "medium", height: "medium", paddingLeft: "small", children: [showImageThumbnails && file.preview ? /*#__PURE__*/jsxRuntime.jsx(box.Box, { as: "img", height: "xsmall", width: "xsmall", src: file.preview, className: css.css({ objectFit: 'cover' }), alt: "" }) : /*#__PURE__*/jsxRuntime.jsx(icon.DocumentTextIcon, { size: "xsmall" }), /*#__PURE__*/jsxRuntime.jsx(box.Box, { flex: 1, children: /*#__PURE__*/jsxRuntime.jsx(text.Text, { inline: true, children: file.path }) }), /*#__PURE__*/jsxRuntime.jsxs(box.Box, { as: "button", type: "button", onClick: function onClick() { return handleRemoveFile(file.id); }, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "small", height: "medium", width: "medium", className: css.css({ transitionProperty: 'all', transitionTimingFunction: theme$1.animation.standard.easing, transitionDuration: "".concat(theme$1.animation.standard.duration, "ms"), ':hover': { backgroundColor: theme$1.color.background.surfaceMuted }, ':focus': focusRingStyles }), children: [/*#__PURE__*/jsxRuntime.jsx(a11y.VisuallyHidden, { children: "Remove file" }), /*#__PURE__*/jsxRuntime.jsx(icon.XIcon, { size: "xxsmall" })] })] }) }, file.id); }) })] }); }); Dropzone.displayName = 'Dropzone'; function useDropzoneStyles(_ref3) { var disabled = _ref3.disabled, isInvalid = _ref3.isInvalid; var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); return { borderStyle: 'dashed', cursor: disabled ? 'default' : 'pointer', transitionProperty: 'all', transitionTimingFunction: theme$1.animation.standard.easing, transitionDuration: "".concat(theme$1.animation.standard.duration, "ms"), ':hover': { backgroundColor: disabled || isInvalid ? undefined : theme$1.color.background.infoLight, borderColor: disabled || isInvalid ? undefined : theme$1.border.color.fieldHover }, ':focus': focusRingStyles }; } function formatFileSize(numKb) { if (numKb < 1000) { return "".concat(Math.round(numKb).toFixed(), "kB"); } return "".concat(Math.round(numKb / 1000).toFixed(), "MB"); } exports.Dropzone = Dropzone;