1 | import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';
|
2 | import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
|
3 | import _toConsumableArray from '@babel/runtime/helpers/esm/toConsumableArray';
|
4 | import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray';
|
5 | import { css } from '@emotion/css';
|
6 | import { useFocusRing, VisuallyHidden } from '@spark-web/a11y';
|
7 | import { Alert } from '@spark-web/alert';
|
8 | import { Box } from '@spark-web/box';
|
9 | import { useFieldContext } from '@spark-web/field';
|
10 | import { UploadIcon, DocumentTextIcon, XIcon } from '@spark-web/icon';
|
11 | import { Stack } from '@spark-web/stack';
|
12 | import { Text } from '@spark-web/text';
|
13 | import { TextList } from '@spark-web/text-list';
|
14 | import { useTheme } from '@spark-web/theme';
|
15 | import { mergeRefs } from '@spark-web/utils';
|
16 | import { forwardRef, useState, useMemo, useEffect } from 'react';
|
17 | import { useDropzone } from 'react-dropzone';
|
18 | import { jsxs, jsx } from 'react/jsx-runtime';
|
19 |
|
20 | var _excluded = ["role", "tabIndex"],
|
21 | _excluded2 = ["style"];
|
22 | var Dropzone = forwardRef(function (_ref, forwardedRef) {
|
23 | var _fileError$errors$;
|
24 | var accept = _ref.accept,
|
25 | _ref$maxFiles = _ref.maxFiles,
|
26 | maxFiles = _ref$maxFiles === void 0 ? 1 : _ref$maxFiles,
|
27 | maxFileSizeKb = _ref.maxFileSizeKb,
|
28 | minFileSizeKb = _ref.minFileSizeKb,
|
29 | name = _ref.name,
|
30 | onBlur = _ref.onBlur,
|
31 | onChange = _ref.onChange,
|
32 | showImageThumbnails = _ref.showImageThumbnails,
|
33 | multiple = _ref.multiple;
|
34 | var _useState = useState([]),
|
35 | _useState2 = _slicedToArray(_useState, 2),
|
36 | files = _useState2[0],
|
37 | setFiles = _useState2[1];
|
38 | var _useState3 = useState(),
|
39 | _useState4 = _slicedToArray(_useState3, 2),
|
40 | fileError = _useState4[0],
|
41 | setFileError = _useState4[1];
|
42 | var handleRemoveFile = function handleRemoveFile(id) {
|
43 | setFiles(function (previousFiles) {
|
44 | return previousFiles.filter(function (existingFile) {
|
45 | return existingFile.id !== id;
|
46 | });
|
47 | });
|
48 | };
|
49 | var tooManyFilesErrorMessage = useMemo(function () {
|
50 | return {
|
51 | type: 'too-many-files',
|
52 | errors: [{
|
53 | 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.'
|
54 | }]
|
55 | };
|
56 | }, [maxFiles]);
|
57 | var handleDropAccepted = function handleDropAccepted(acceptedFiles) {
|
58 | var totalFiles = files.length + acceptedFiles.length;
|
59 | if (maxFiles > 0 && totalFiles > maxFiles) {
|
60 | setFileError(tooManyFilesErrorMessage);
|
61 | return;
|
62 | }
|
63 | setFiles(function (previousFiles) {
|
64 | return [].concat(_toConsumableArray(previousFiles), _toConsumableArray(acceptedFiles.map(function (acceptedFile, index) {
|
65 | return Object.assign(acceptedFile, {
|
66 | id: files.length === 0 ? index + 1 : files.length + index + 1,
|
67 | preview: acceptedFile.type.startsWith('image') ? URL.createObjectURL(acceptedFile) : undefined
|
68 | });
|
69 | })));
|
70 | });
|
71 | };
|
72 | var _useFieldContext = useFieldContext(),
|
73 | _useFieldContext2 = _slicedToArray(_useFieldContext, 2),
|
74 | _useFieldContext2$ = _useFieldContext2[0],
|
75 | disabled = _useFieldContext2$.disabled,
|
76 | invalid = _useFieldContext2$.invalid,
|
77 | a11yProps = _useFieldContext2[1];
|
78 | var _useDropzone = useDropzone({
|
79 | accept: accept,
|
80 | maxFiles: maxFiles,
|
81 | maxSize: maxFileSizeKb && maxFileSizeKb * 1000,
|
82 | minSize: minFileSizeKb && minFileSizeKb * 1000,
|
83 |
|
84 | multiple: multiple !== undefined ? multiple : maxFiles !== 1,
|
85 | onDropAccepted: handleDropAccepted,
|
86 | disabled: disabled
|
87 | }),
|
88 | fileRejections = _useDropzone.fileRejections,
|
89 | getInputProps = _useDropzone.getInputProps,
|
90 | getRootProps = _useDropzone.getRootProps,
|
91 | isDragActive = _useDropzone.isDragActive,
|
92 | isDragReject = _useDropzone.isDragReject,
|
93 | dropzoneInputRef = _useDropzone.inputRef;
|
94 | var _getRootProps = getRootProps();
|
95 | _getRootProps.role;
|
96 | _getRootProps.tabIndex;
|
97 | var dropzoneProps = _objectWithoutProperties(_getRootProps, _excluded);
|
98 | var _getInputProps = getInputProps();
|
99 | _getInputProps.style;
|
100 | var dropzoneInputProps = _objectWithoutProperties(_getInputProps, _excluded2);
|
101 |
|
102 |
|
103 | useEffect(function () {
|
104 | onChange === null || onChange === void 0 || onChange({
|
105 | target: {
|
106 | value: files,
|
107 | name: name
|
108 | },
|
109 | type: 'change'
|
110 | });
|
111 | onBlur === null || onBlur === void 0 || onBlur({
|
112 | target: {
|
113 | value: files,
|
114 | name: name
|
115 | },
|
116 | type: 'blur'
|
117 | });
|
118 | }, [files, name, onBlur, onChange]);
|
119 | useEffect(function () {
|
120 | var errorMessage = {
|
121 | errors: []
|
122 | };
|
123 | if (fileRejections.length < 1) {
|
124 | return;
|
125 | }
|
126 | if (maxFiles > 0 && fileRejections.length > maxFiles) {
|
127 | errorMessage.type = tooManyFilesErrorMessage.type;
|
128 | errorMessage.errors = tooManyFilesErrorMessage.errors;
|
129 | } else {
|
130 | fileRejections.map(function (_ref2) {
|
131 | var errors = _ref2.errors,
|
132 | name = _ref2.file.name;
|
133 | errors.forEach(function (error) {
|
134 | var _Object$values;
|
135 | var message = 'unknown validation error.';
|
136 | switch (error.code) {
|
137 | case 'file-too-large':
|
138 | message = "is too large. Max supported file size is ".concat(formatFileSize(maxFileSizeKb || Infinity), ".");
|
139 | break;
|
140 | case 'file-too-small':
|
141 | message = "is too small. Min supported file size is ".concat(formatFileSize(minFileSizeKb || 0), ".");
|
142 | break;
|
143 | case 'file-invalid-type':
|
144 | 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) {
|
145 | return value.startsWith('.') ? value.substring(1) : value;
|
146 | }).join(', '), ".");
|
147 | break;
|
148 | }
|
149 | errorMessage.errors.push({
|
150 | name: name,
|
151 | message: message
|
152 | });
|
153 | });
|
154 | });
|
155 | }
|
156 | setFileError(errorMessage);
|
157 | }, [accept, fileRejections, maxFileSizeKb, maxFiles, minFileSizeKb, tooManyFilesErrorMessage]);
|
158 | var isInvalid = invalid || isDragReject;
|
159 | var theme = useTheme();
|
160 | var focusRingStyles = useFocusRing();
|
161 | var fileUploadError = !!(fileError !== null && fileError !== void 0 && fileError.errors.length);
|
162 | var isMaxFilesReached = maxFiles > 0 && files.length === maxFiles;
|
163 | var isDropzoneDisabled = disabled || fileUploadError || isMaxFilesReached;
|
164 | var dropzoneStyles = useDropzoneStyles({
|
165 | disabled: isDropzoneDisabled,
|
166 | isInvalid: isInvalid
|
167 | });
|
168 | return jsxs(Stack, {
|
169 | gap: "large",
|
170 | children: [jsx(VisuallyHidden, _objectSpread(_objectSpread(_objectSpread({
|
171 | as: "input",
|
172 | disabled: disabled,
|
173 | name: name,
|
174 | onBlur: onBlur,
|
175 | onChange: onChange
|
176 | }, a11yProps), dropzoneInputProps), {}, {
|
177 |
|
178 |
|
179 | ref: mergeRefs([forwardedRef, dropzoneInputRef])
|
180 | })), jsxs(Stack, _objectSpread(_objectSpread({}, dropzoneProps), {}, {
|
181 | as: "button",
|
182 | align: "center",
|
183 | background: function () {
|
184 | if (disabled) return 'inputDisabled';
|
185 | if (isInvalid) return 'criticalLight';
|
186 | return 'surfaceMuted';
|
187 | }(),
|
188 | border: function () {
|
189 | if (disabled) return 'fieldDisabled';
|
190 | if (isInvalid) return 'critical';
|
191 | return 'field';
|
192 | }(),
|
193 | borderRadius: "medium",
|
194 | borderWidth: "large",
|
195 | gap: "large",
|
196 | padding: "large",
|
197 | position: "relative",
|
198 | className: css(dropzoneStyles),
|
199 | disabled: isDropzoneDisabled,
|
200 | children: [isDragActive && jsx(Box
|
201 |
|
202 | , {
|
203 | position: "absolute",
|
204 | top: 0,
|
205 | bottom: 0,
|
206 | display: "flex",
|
207 | alignItems: "center",
|
208 | children: jsx(Text, {
|
209 | tone: isDragReject ? 'critical' : 'neutral',
|
210 | children: isDragReject ? 'file type not valid' : 'drop files to upload'
|
211 | })
|
212 | }), jsxs(Stack
|
213 |
|
214 | , {
|
215 | "aria-hidden": isDragActive,
|
216 | align: "center",
|
217 | gap: "large",
|
218 | position: "relative",
|
219 | className: css(isDragActive ? {
|
220 | opacity: 0,
|
221 | pointerEvents: 'none'
|
222 | } : null),
|
223 | children: [jsx(UploadIcon, {
|
224 | size: "medium",
|
225 | tone: disabled ? 'disabled' : 'neutral'
|
226 | }), jsxs(Text, {
|
227 | align: "center",
|
228 | tone: disabled ? 'disabled' : 'neutral',
|
229 | children: ["click to select files ", jsx("br", {}), "or drop files here ", maxFiles > 0 ? "(max ".concat(maxFiles, ")") : '']
|
230 | })]
|
231 | })]
|
232 | })), fileUploadError && jsx(Alert, {
|
233 | tone: "critical",
|
234 | heading: fileError.type === 'too-many-files' ? 'Maximum number of files reached' : 'These files couldn’t be added:',
|
235 | closeLabel: "Dismiss alert",
|
236 | onClose: function onClose() {
|
237 | return setFileError(undefined);
|
238 | },
|
239 | children: fileError.type === 'too-many-files' ? jsx(Text, {
|
240 | children: (_fileError$errors$ = fileError.errors[0]) === null || _fileError$errors$ === void 0 ? void 0 : _fileError$errors$.message
|
241 | }) : jsx(TextList, {
|
242 | gap: "medium",
|
243 | children: fileError.errors.map(function (error) {
|
244 | return jsxs(Text, {
|
245 | weight: "regular",
|
246 | children: [error.name, " - ", error.message]
|
247 | }, error.name);
|
248 | })
|
249 | })
|
250 | }), files.length > 0 && jsx(Stack, {
|
251 | as: "ul",
|
252 | role: "list",
|
253 | gap: "medium",
|
254 | children: files.map(function (file) {
|
255 | return jsx(Box, {
|
256 | as: "li",
|
257 | border: "field",
|
258 | borderRadius: "small",
|
259 | padding: "xsmall",
|
260 | children: jsxs(Box, {
|
261 | display: "flex",
|
262 | alignItems: "center",
|
263 | gap: "medium",
|
264 | height: "medium",
|
265 | paddingLeft: "small",
|
266 | children: [showImageThumbnails && file.preview ? jsx(Box, {
|
267 | as: "img",
|
268 | height: "xsmall",
|
269 | width: "xsmall",
|
270 | src: file.preview,
|
271 | className: css({
|
272 | objectFit: 'cover'
|
273 | }),
|
274 | alt: ""
|
275 | }) : jsx(DocumentTextIcon, {
|
276 | size: "xsmall"
|
277 | }), jsx(Box, {
|
278 | flex: 1,
|
279 | children: jsx(Text, {
|
280 | inline: true,
|
281 | children: file.path
|
282 | })
|
283 | }), jsxs(Box, {
|
284 | as: "button",
|
285 | type: "button",
|
286 | onClick: function onClick() {
|
287 | return handleRemoveFile(file.id);
|
288 | },
|
289 | cursor: "pointer",
|
290 | display: "flex",
|
291 | alignItems: "center",
|
292 | justifyContent: "center",
|
293 | borderRadius: "small",
|
294 | height: "medium",
|
295 | width: "medium",
|
296 | className: css({
|
297 | transitionProperty: 'all',
|
298 | transitionTimingFunction: theme.animation.standard.easing,
|
299 | transitionDuration: "".concat(theme.animation.standard.duration, "ms"),
|
300 | ':hover': {
|
301 | backgroundColor: theme.color.background.surfaceMuted
|
302 | },
|
303 | ':focus': focusRingStyles
|
304 | }),
|
305 | children: [jsx(VisuallyHidden, {
|
306 | children: "Remove file"
|
307 | }), jsx(XIcon, {
|
308 | size: "xxsmall"
|
309 | })]
|
310 | })]
|
311 | })
|
312 | }, file.id);
|
313 | })
|
314 | })]
|
315 | });
|
316 | });
|
317 | Dropzone.displayName = 'Dropzone';
|
318 | function useDropzoneStyles(_ref3) {
|
319 | var disabled = _ref3.disabled,
|
320 | isInvalid = _ref3.isInvalid;
|
321 | var theme = useTheme();
|
322 | var focusRingStyles = useFocusRing();
|
323 | return {
|
324 | borderStyle: 'dashed',
|
325 | cursor: disabled ? 'default' : 'pointer',
|
326 | transitionProperty: 'all',
|
327 | transitionTimingFunction: theme.animation.standard.easing,
|
328 | transitionDuration: "".concat(theme.animation.standard.duration, "ms"),
|
329 | ':hover': {
|
330 | backgroundColor: disabled || isInvalid ? undefined : theme.color.background.infoLight,
|
331 | borderColor: disabled || isInvalid ? undefined : theme.border.color.fieldHover
|
332 | },
|
333 | ':focus': focusRingStyles
|
334 | };
|
335 | }
|
336 | function formatFileSize(numKb) {
|
337 | if (numKb < 1000) {
|
338 | return "".concat(Math.round(numKb).toFixed(), "kB");
|
339 | }
|
340 | return "".concat(Math.round(numKb / 1000).toFixed(), "MB");
|
341 | }
|
342 |
|
343 | export { Dropzone };
|