UNPKG

@spark-web/dropzone

Version:

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

348 lines (342 loc) 13.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties'); var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var react$1 = require('@emotion/react'); 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('@emotion/react/jsx-runtime'); 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, multiple = _ref.multiple; 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 tooManyFilesErrorMessage = react.useMemo(function () { return { type: 'too-many-files', 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.' }] }; }, [maxFiles]); var handleDropAccepted = function handleDropAccepted(acceptedFiles) { var totalFiles = files.length + acceptedFiles.length; if (maxFiles > 0 && totalFiles > maxFiles) { setFileError(tooManyFilesErrorMessage); return; } setFiles(function (previousFiles) { return [].concat(_toConsumableArray(previousFiles), _toConsumableArray(acceptedFiles.map(function (acceptedFile, index) { return Object.assign(acceptedFile, { id: files.length === 0 ? index + 1 : files.length + index + 1, preview: acceptedFile.type.startsWith('image') ? URL.createObjectURL(acceptedFile) : undefined }); }))); }); }; 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, // If `multiple` is not provided, default to `true` if `maxFiles` is not 1 multiple: multiple !== undefined ? 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 || onChange({ target: { value: files, name: name }, type: 'change' }); onBlur === null || onBlur === void 0 || onBlur({ target: { value: files, name: name }, type: 'blur' }); }, [files, name, onBlur, onChange]); react.useEffect(function () { var errorMessage = { errors: [] }; if (fileRejections.length < 1) { return; } if (maxFiles > 0 && fileRejections.length > maxFiles) { errorMessage.type = tooManyFilesErrorMessage.type; errorMessage.errors = tooManyFilesErrorMessage.errors; } else { fileRejections.map(function (_ref2) { var errors = _ref2.errors, name = _ref2.file.name; errors.forEach(function (error) { var _Object$values; 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((_Object$values = Object.values(accept !== null && accept !== void 0 ? accept : {})) === null || _Object$values === void 0 ? void 0 : _Object$values.flat().map(function (value) { return value.startsWith('.') ? value.substring(1) : value; }).join(', '), "."); break; } errorMessage.errors.push({ name: name, message: message }); }); }); } setFileError(errorMessage); }, [accept, fileRejections, maxFileSizeKb, maxFiles, minFileSizeKb, tooManyFilesErrorMessage]); var isInvalid = invalid || isDragReject; var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); var fileUploadError = !!(fileError !== null && fileError !== void 0 && fileError.errors.length); var isMaxFilesReached = maxFiles > 0 && files.length === maxFiles; var isDropzoneDisabled = disabled || fileUploadError || isMaxFilesReached; var dropzoneStyles = useDropzoneStyles({ disabled: isDropzoneDisabled, isInvalid: isInvalid }); return jsxRuntime.jsxs(stack.Stack, { gap: "large", children: [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]) })), 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", css: react$1.css(dropzoneStyles), disabled: isDropzoneDisabled, children: [isDragActive && jsxRuntime.jsx(box.Box // Position absolute so height never changes , { position: "absolute", top: 0, bottom: 0, display: "flex", alignItems: "center", children: jsxRuntime.jsx(text.Text, { tone: isDragReject ? 'critical' : 'neutral', children: isDragReject ? 'file type not valid' : 'drop files to upload' }) }), 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", css: react$1.css(isDragActive ? { opacity: 0, pointerEvents: 'none' } : null), children: [jsxRuntime.jsx(icon.UploadIcon, { size: "medium", tone: disabled ? 'disabled' : 'neutral' }), jsxRuntime.jsxs(text.Text, { align: "center", tone: disabled ? 'disabled' : 'neutral', children: ["click to select files ", jsxRuntime.jsx("br", {}), "or drop files here ", maxFiles > 0 ? "(max ".concat(maxFiles, ")") : ''] })] })] })), fileUploadError && 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' ? jsxRuntime.jsx(text.Text, { children: (_fileError$errors$ = fileError.errors[0]) === null || _fileError$errors$ === void 0 ? void 0 : _fileError$errors$.message }) : jsxRuntime.jsx(textList.TextList, { gap: "medium", children: fileError.errors.map(function (error) { return jsxRuntime.jsxs(text.Text, { weight: "regular", children: [error.name, " - ", error.message] }, error.name); }) }) }), files.length > 0 && jsxRuntime.jsx(stack.Stack, { as: "ul", role: "list", gap: "medium", children: files.map(function (file) { return jsxRuntime.jsx(box.Box, { as: "li", border: "field", borderRadius: "small", padding: "xsmall", children: jsxRuntime.jsxs(box.Box, { display: "flex", alignItems: "center", gap: "medium", height: "medium", paddingLeft: "small", children: [showImageThumbnails && file.preview ? jsxRuntime.jsx(box.Box, { as: "img", height: "xsmall", width: "xsmall", src: file.preview, css: react$1.css({ objectFit: 'cover' }), alt: "" }) : jsxRuntime.jsx(icon.DocumentTextIcon, { size: "xsmall" }), jsxRuntime.jsx(box.Box, { flex: 1, children: jsxRuntime.jsx(text.Text, { inline: true, children: file.path }) }), 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", css: react$1.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: [jsxRuntime.jsx(a11y.VisuallyHidden, { children: "Remove file" }), 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;