@availity/mui-file-selector 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.d.mts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +120 -59
- package/dist/index.mjs +125 -65
- package/package.json +1 -1
- package/src/lib/ErrorAlert.test.tsx +15 -1
- package/src/lib/ErrorAlert.tsx +30 -13
- package/src/lib/FileSelector.stories.tsx +109 -9
- package/src/lib/FileSelector.tsx +58 -16
- package/src/lib/FileTypesMessage.test.tsx +12 -0
- package/src/lib/FileTypesMessage.tsx +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.6.0](https://github.com/Availity/element/compare/@availity/mui-file-selector@1.5.3...@availity/mui-file-selector@1.6.0) (2025-04-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **mui-file-selector:** allow customSizeMessage and customTypesMessage ([378ccc0](https://github.com/Availity/element/commit/378ccc05fac7bc0e9086aafd4ad9aa9fdd5026de))
|
|
11
|
+
|
|
5
12
|
## [1.5.3](https://github.com/Availity/element/compare/@availity/mui-file-selector@1.5.2...@availity/mui-file-selector@1.5.3) (2025-04-14)
|
|
6
13
|
|
|
7
14
|
### Dependency Updates
|
package/dist/index.d.mts
CHANGED
|
@@ -83,6 +83,7 @@ type ErrorAlertProps = {
|
|
|
83
83
|
id: number;
|
|
84
84
|
onClose: () => void;
|
|
85
85
|
};
|
|
86
|
+
declare const formatFileTooLarge: (message: string) => string;
|
|
86
87
|
declare const ErrorAlert: ({ errors, fileName, id, onClose }: ErrorAlertProps) => react_jsx_runtime.JSX.Element | null;
|
|
87
88
|
|
|
88
89
|
type Options = {
|
|
@@ -197,6 +198,14 @@ type FileSelectorProps = {
|
|
|
197
198
|
* @default false
|
|
198
199
|
*/
|
|
199
200
|
disabled?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Overrides the standard file size message
|
|
203
|
+
*/
|
|
204
|
+
customSizeMessage?: React.ReactNode;
|
|
205
|
+
/**
|
|
206
|
+
* Overrides the standard file types message
|
|
207
|
+
*/
|
|
208
|
+
customTypesMessage?: React.ReactNode;
|
|
200
209
|
/**
|
|
201
210
|
* Whether to enable the dropzone area
|
|
202
211
|
*/
|
|
@@ -206,7 +215,7 @@ type FileSelectorProps = {
|
|
|
206
215
|
*/
|
|
207
216
|
endpoint?: string;
|
|
208
217
|
/**
|
|
209
|
-
*
|
|
218
|
+
* Component to render the File information. This should return a `ListItem`
|
|
210
219
|
*/
|
|
211
220
|
customFileRow?: React.ElementType<{
|
|
212
221
|
upload?: Upload;
|
|
@@ -272,20 +281,28 @@ type FileSelectorProps = {
|
|
|
272
281
|
*/
|
|
273
282
|
disableRemove?: boolean;
|
|
274
283
|
};
|
|
275
|
-
declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customerId, customFileRow, disabled, enableDropArea, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onDrop, onUploadRemove, queryOptions, uploadOptions, validator, disableRemove, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
|
|
284
|
+
declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customSizeMessage, customTypesMessage, customerId, customFileRow, disabled, enableDropArea, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onDrop, onUploadRemove, queryOptions, uploadOptions, validator, disableRemove, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
|
|
276
285
|
|
|
277
286
|
type FileTypesMessageProps = {
|
|
278
287
|
/**
|
|
279
288
|
* Allowed file type extensions. Each extension should be prefixed with a ".". eg: .txt, .pdf, .png
|
|
280
289
|
*/
|
|
281
290
|
allowedFileTypes?: `.${string}`[];
|
|
291
|
+
/**
|
|
292
|
+
* Overrides the standard file size message
|
|
293
|
+
*/
|
|
294
|
+
customSizeMessage?: React.ReactNode;
|
|
295
|
+
/**
|
|
296
|
+
* Overrides the standard file types message
|
|
297
|
+
*/
|
|
298
|
+
customTypesMessage?: React.ReactNode;
|
|
282
299
|
/**
|
|
283
300
|
* Maximum size per file in bytes. This will be formatted. eg: 1024 * 20 = 20 KB
|
|
284
301
|
*/
|
|
285
302
|
maxFileSize?: number;
|
|
286
303
|
variant?: 'caption' | 'body2';
|
|
287
304
|
};
|
|
288
|
-
declare const FileTypesMessage: ({ allowedFileTypes, maxFileSize, variant, }: FileTypesMessageProps) => react_jsx_runtime.JSX.Element;
|
|
305
|
+
declare const FileTypesMessage: ({ allowedFileTypes, customSizeMessage, customTypesMessage, maxFileSize, variant, }: FileTypesMessageProps) => react_jsx_runtime.JSX.Element;
|
|
289
306
|
|
|
290
307
|
type HeaderMessageProps = {
|
|
291
308
|
/**
|
|
@@ -319,4 +336,4 @@ type UploadProgressBarProps = {
|
|
|
319
336
|
};
|
|
320
337
|
declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
|
|
321
338
|
|
|
322
|
-
export { Dropzone, type DropzoneProps, ErrorAlert, type ErrorAlertProps, FileList, type FileListProps, FilePickerBtn, type FilePickerBtnProps, FileRow, type FileRowProps, FileSelector, type FileSelectorProps, FileTypesMessage, type FileTypesMessageProps, HeaderMessage, type HeaderMessageProps, type Options, UploadProgressBar, type UploadProgressBarProps, type UploadQueryOptions, useUploadCore };
|
|
339
|
+
export { Dropzone, type DropzoneProps, ErrorAlert, type ErrorAlertProps, FileList, type FileListProps, FilePickerBtn, type FilePickerBtnProps, FileRow, type FileRowProps, FileSelector, type FileSelectorProps, FileTypesMessage, type FileTypesMessageProps, HeaderMessage, type HeaderMessageProps, type Options, UploadProgressBar, type UploadProgressBarProps, type UploadQueryOptions, formatFileTooLarge, useUploadCore };
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ type ErrorAlertProps = {
|
|
|
83
83
|
id: number;
|
|
84
84
|
onClose: () => void;
|
|
85
85
|
};
|
|
86
|
+
declare const formatFileTooLarge: (message: string) => string;
|
|
86
87
|
declare const ErrorAlert: ({ errors, fileName, id, onClose }: ErrorAlertProps) => react_jsx_runtime.JSX.Element | null;
|
|
87
88
|
|
|
88
89
|
type Options = {
|
|
@@ -197,6 +198,14 @@ type FileSelectorProps = {
|
|
|
197
198
|
* @default false
|
|
198
199
|
*/
|
|
199
200
|
disabled?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Overrides the standard file size message
|
|
203
|
+
*/
|
|
204
|
+
customSizeMessage?: React.ReactNode;
|
|
205
|
+
/**
|
|
206
|
+
* Overrides the standard file types message
|
|
207
|
+
*/
|
|
208
|
+
customTypesMessage?: React.ReactNode;
|
|
200
209
|
/**
|
|
201
210
|
* Whether to enable the dropzone area
|
|
202
211
|
*/
|
|
@@ -206,7 +215,7 @@ type FileSelectorProps = {
|
|
|
206
215
|
*/
|
|
207
216
|
endpoint?: string;
|
|
208
217
|
/**
|
|
209
|
-
*
|
|
218
|
+
* Component to render the File information. This should return a `ListItem`
|
|
210
219
|
*/
|
|
211
220
|
customFileRow?: React.ElementType<{
|
|
212
221
|
upload?: Upload;
|
|
@@ -272,20 +281,28 @@ type FileSelectorProps = {
|
|
|
272
281
|
*/
|
|
273
282
|
disableRemove?: boolean;
|
|
274
283
|
};
|
|
275
|
-
declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customerId, customFileRow, disabled, enableDropArea, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onDrop, onUploadRemove, queryOptions, uploadOptions, validator, disableRemove, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
|
|
284
|
+
declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customSizeMessage, customTypesMessage, customerId, customFileRow, disabled, enableDropArea, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onDrop, onUploadRemove, queryOptions, uploadOptions, validator, disableRemove, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
|
|
276
285
|
|
|
277
286
|
type FileTypesMessageProps = {
|
|
278
287
|
/**
|
|
279
288
|
* Allowed file type extensions. Each extension should be prefixed with a ".". eg: .txt, .pdf, .png
|
|
280
289
|
*/
|
|
281
290
|
allowedFileTypes?: `.${string}`[];
|
|
291
|
+
/**
|
|
292
|
+
* Overrides the standard file size message
|
|
293
|
+
*/
|
|
294
|
+
customSizeMessage?: React.ReactNode;
|
|
295
|
+
/**
|
|
296
|
+
* Overrides the standard file types message
|
|
297
|
+
*/
|
|
298
|
+
customTypesMessage?: React.ReactNode;
|
|
282
299
|
/**
|
|
283
300
|
* Maximum size per file in bytes. This will be formatted. eg: 1024 * 20 = 20 KB
|
|
284
301
|
*/
|
|
285
302
|
maxFileSize?: number;
|
|
286
303
|
variant?: 'caption' | 'body2';
|
|
287
304
|
};
|
|
288
|
-
declare const FileTypesMessage: ({ allowedFileTypes, maxFileSize, variant, }: FileTypesMessageProps) => react_jsx_runtime.JSX.Element;
|
|
305
|
+
declare const FileTypesMessage: ({ allowedFileTypes, customSizeMessage, customTypesMessage, maxFileSize, variant, }: FileTypesMessageProps) => react_jsx_runtime.JSX.Element;
|
|
289
306
|
|
|
290
307
|
type HeaderMessageProps = {
|
|
291
308
|
/**
|
|
@@ -319,4 +336,4 @@ type UploadProgressBarProps = {
|
|
|
319
336
|
};
|
|
320
337
|
declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
|
|
321
338
|
|
|
322
|
-
export { Dropzone, type DropzoneProps, ErrorAlert, type ErrorAlertProps, FileList, type FileListProps, FilePickerBtn, type FilePickerBtnProps, FileRow, type FileRowProps, FileSelector, type FileSelectorProps, FileTypesMessage, type FileTypesMessageProps, HeaderMessage, type HeaderMessageProps, type Options, UploadProgressBar, type UploadProgressBarProps, type UploadQueryOptions, useUploadCore };
|
|
339
|
+
export { Dropzone, type DropzoneProps, ErrorAlert, type ErrorAlertProps, FileList, type FileListProps, FilePickerBtn, type FilePickerBtnProps, FileRow, type FileRowProps, FileSelector, type FileSelectorProps, FileTypesMessage, type FileTypesMessageProps, HeaderMessage, type HeaderMessageProps, type Options, UploadProgressBar, type UploadProgressBarProps, type UploadQueryOptions, formatFileTooLarge, useUploadCore };
|
package/dist/index.js
CHANGED
|
@@ -88,6 +88,7 @@ __export(index_exports, {
|
|
|
88
88
|
FileTypesMessage: () => FileTypesMessage,
|
|
89
89
|
HeaderMessage: () => HeaderMessage,
|
|
90
90
|
UploadProgressBar: () => UploadProgressBar,
|
|
91
|
+
formatFileTooLarge: () => formatFileTooLarge,
|
|
91
92
|
useUploadCore: () => useUploadCore
|
|
92
93
|
});
|
|
93
94
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -324,6 +325,46 @@ var Dropzone = ({
|
|
|
324
325
|
// src/lib/ErrorAlert.tsx
|
|
325
326
|
var import_mui_alert = require("@availity/mui-alert");
|
|
326
327
|
var import_mui_list = require("@availity/mui-list");
|
|
328
|
+
|
|
329
|
+
// src/lib/util.ts
|
|
330
|
+
var import_mui_icon2 = require("@availity/mui-icon");
|
|
331
|
+
function formatBytes(bytes, decimals = 2) {
|
|
332
|
+
if (!+bytes) return "0 Bytes";
|
|
333
|
+
const k = 1024;
|
|
334
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
335
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
336
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
337
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
338
|
+
}
|
|
339
|
+
var FILE_EXT_ICONS = {
|
|
340
|
+
png: import_mui_icon2.FileImageIcon,
|
|
341
|
+
jpg: import_mui_icon2.FileImageIcon,
|
|
342
|
+
jpeg: import_mui_icon2.FileImageIcon,
|
|
343
|
+
gif: import_mui_icon2.FileImageIcon,
|
|
344
|
+
csv: import_mui_icon2.FileCsvIcon,
|
|
345
|
+
ppt: import_mui_icon2.FilePowerpointIcon,
|
|
346
|
+
pptx: import_mui_icon2.FilePowerpointIcon,
|
|
347
|
+
xls: import_mui_icon2.FileExcelIcon,
|
|
348
|
+
xlsx: import_mui_icon2.FileExcelIcon,
|
|
349
|
+
doc: import_mui_icon2.FileWordIcon,
|
|
350
|
+
docx: import_mui_icon2.FileWordIcon,
|
|
351
|
+
txt: import_mui_icon2.FileLinesIcon,
|
|
352
|
+
text: import_mui_icon2.FileLinesIcon,
|
|
353
|
+
zip: import_mui_icon2.FileArchiveIcon,
|
|
354
|
+
"7zip": import_mui_icon2.FileArchiveIcon,
|
|
355
|
+
xml: import_mui_icon2.FileCodeIcon,
|
|
356
|
+
html: import_mui_icon2.FileCodeIcon,
|
|
357
|
+
pdf: import_mui_icon2.FilePdfIcon
|
|
358
|
+
};
|
|
359
|
+
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
360
|
+
var getFileExtIcon = (fileName) => {
|
|
361
|
+
var _a;
|
|
362
|
+
const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
363
|
+
const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : import_mui_icon2.FileIcon;
|
|
364
|
+
return icon;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/lib/ErrorAlert.tsx
|
|
327
368
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
328
369
|
var codes = {
|
|
329
370
|
"file-too-large": "File exceeds maximum size",
|
|
@@ -332,6 +373,13 @@ var codes = {
|
|
|
332
373
|
"too-many-file": "Too many files",
|
|
333
374
|
"duplicate-name": "Duplicate file selected"
|
|
334
375
|
};
|
|
376
|
+
var FILE_SIZE_REGEX = /\b(\d+)\b/;
|
|
377
|
+
var formatFileTooLarge = (message) => {
|
|
378
|
+
const fileSize = message.match(FILE_SIZE_REGEX);
|
|
379
|
+
if (!fileSize) return message;
|
|
380
|
+
const formattedSize = formatBytes(Number(fileSize[0]));
|
|
381
|
+
return message.replace(` bytes`, "").replace(FILE_SIZE_REGEX, formattedSize);
|
|
382
|
+
};
|
|
335
383
|
var ErrorAlert = ({ errors, fileName, id, onClose }) => {
|
|
336
384
|
if (errors.length === 0) return null;
|
|
337
385
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_mui_alert.Alert, { severity: "error", onClose, children: errors.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
@@ -341,23 +389,31 @@ var ErrorAlert = ({ errors, fileName, id, onClose }) => {
|
|
|
341
389
|
" error(s) found when uploading ",
|
|
342
390
|
fileName
|
|
343
391
|
] }),
|
|
344
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
392
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
393
|
+
import_mui_list.List,
|
|
394
|
+
{
|
|
395
|
+
sx: {
|
|
396
|
+
listStyleType: "disc",
|
|
397
|
+
listStylePosition: "inside",
|
|
398
|
+
marginLeft: 1,
|
|
399
|
+
".MuiListItem-root": {
|
|
400
|
+
display: "list-item"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
disablePadding: true,
|
|
404
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: errors.map((error) => {
|
|
405
|
+
const message = error.code === "file-too-large" ? formatFileTooLarge(error.message) : error.message;
|
|
406
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_mui_list.ListItem, { disableGutters: true, disablePadding: true, divider: false, children: message }, `${id}-${error.code}`);
|
|
407
|
+
}) })
|
|
350
408
|
}
|
|
351
|
-
|
|
352
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_mui_list.ListItem, { disableGutters: true, disablePadding: true, divider: false, children: error.message }, `${id}-${error.code}`);
|
|
353
|
-
}) }) })
|
|
409
|
+
)
|
|
354
410
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
355
411
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_mui_alert.AlertTitle, { children: [
|
|
356
412
|
codes[errors[0].code] || "Error",
|
|
357
413
|
": ",
|
|
358
414
|
fileName
|
|
359
415
|
] }),
|
|
360
|
-
errors[0].message
|
|
416
|
+
errors[0].code === "file-too-large" ? formatFileTooLarge(errors[0].message) : errors[0].message
|
|
361
417
|
] }) });
|
|
362
418
|
};
|
|
363
419
|
|
|
@@ -376,9 +432,9 @@ var import_mui_button3 = require("@availity/mui-button");
|
|
|
376
432
|
var import_Dialog = __toESM(require("@mui/material/Dialog"));
|
|
377
433
|
var import_styles2 = require("@mui/material/styles");
|
|
378
434
|
var import_mui_button2 = require("@availity/mui-button");
|
|
379
|
-
var
|
|
435
|
+
var import_mui_icon3 = require("@availity/mui-icon");
|
|
380
436
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
381
|
-
var CloseButton = (args) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_mui_button2.IconButton, __spreadProps(__spreadValues({ title: "Close Dialog", color: "secondary" }, args), { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
437
|
+
var CloseButton = (args) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_mui_button2.IconButton, __spreadProps(__spreadValues({ title: "Close Dialog", color: "secondary" }, args), { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_mui_icon3.CloseIcon, { fontSize: "xsmall" }) }));
|
|
382
438
|
var CloseButtonSlot = (0, import_styles2.styled)(CloseButton, {
|
|
383
439
|
name: "MuiDialog",
|
|
384
440
|
slot: "AvCloseButton",
|
|
@@ -444,7 +500,7 @@ var DialogTitle = (_a) => {
|
|
|
444
500
|
|
|
445
501
|
// src/lib/UploadProgressBar.tsx
|
|
446
502
|
var import_mui_form_utils3 = require("@availity/mui-form-utils");
|
|
447
|
-
var
|
|
503
|
+
var import_mui_icon4 = require("@availity/mui-icon");
|
|
448
504
|
var import_mui_layout3 = require("@availity/mui-layout");
|
|
449
505
|
var import_mui_list2 = require("@availity/mui-list");
|
|
450
506
|
var import_mui_progress = require("@availity/mui-progress");
|
|
@@ -587,7 +643,7 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
|
|
|
587
643
|
upload.onError.push(handleOnError);
|
|
588
644
|
return errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_mui_layout3.Box, { sx: { display: "flex", flexWrap: "wrap", columnGap: "4px" }, children: [
|
|
589
645
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_mui_list2.ListItemText, { slotProps: { primary: { color: "text.error", variant: "body2", component: "div" } }, sx: { wordWrap: "break-word" }, children: [
|
|
590
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_icon4.WarningTriangleIcon, { sx: { verticalAlign: "middle", mt: "-2px" } }),
|
|
591
647
|
" ",
|
|
592
648
|
errorMessage
|
|
593
649
|
] }),
|
|
@@ -604,7 +660,7 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
|
|
|
604
660
|
onChange: handlePasswordChange,
|
|
605
661
|
autoFocus: true,
|
|
606
662
|
InputProps: {
|
|
607
|
-
endAdornment: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_form_utils3.InputAdornment, { position: "end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_button3.IconButton, { title: "password visibility", onClick: () => setShowPassword((prev) => !prev), edge: "end", children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
663
|
+
endAdornment: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_form_utils3.InputAdornment, { position: "end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_button3.IconButton, { title: "password visibility", onClick: () => setShowPassword((prev) => !prev), edge: "end", children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_icon4.EyeIcon, { fontSize: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_icon4.EyeSlashIcon, { fontSize: "small" }) }) })
|
|
608
664
|
}
|
|
609
665
|
}
|
|
610
666
|
) }),
|
|
@@ -614,44 +670,6 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
|
|
|
614
670
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_mui_progress.LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
|
|
615
671
|
};
|
|
616
672
|
|
|
617
|
-
// src/lib/util.ts
|
|
618
|
-
var import_mui_icon4 = require("@availity/mui-icon");
|
|
619
|
-
function formatBytes(bytes, decimals = 2) {
|
|
620
|
-
if (!+bytes) return "0 Bytes";
|
|
621
|
-
const k = 1024;
|
|
622
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
623
|
-
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
624
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
625
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
626
|
-
}
|
|
627
|
-
var FILE_EXT_ICONS = {
|
|
628
|
-
png: import_mui_icon4.FileImageIcon,
|
|
629
|
-
jpg: import_mui_icon4.FileImageIcon,
|
|
630
|
-
jpeg: import_mui_icon4.FileImageIcon,
|
|
631
|
-
gif: import_mui_icon4.FileImageIcon,
|
|
632
|
-
csv: import_mui_icon4.FileCsvIcon,
|
|
633
|
-
ppt: import_mui_icon4.FilePowerpointIcon,
|
|
634
|
-
pptx: import_mui_icon4.FilePowerpointIcon,
|
|
635
|
-
xls: import_mui_icon4.FileExcelIcon,
|
|
636
|
-
xlsx: import_mui_icon4.FileExcelIcon,
|
|
637
|
-
doc: import_mui_icon4.FileWordIcon,
|
|
638
|
-
docx: import_mui_icon4.FileWordIcon,
|
|
639
|
-
txt: import_mui_icon4.FileLinesIcon,
|
|
640
|
-
text: import_mui_icon4.FileLinesIcon,
|
|
641
|
-
zip: import_mui_icon4.FileArchiveIcon,
|
|
642
|
-
"7zip": import_mui_icon4.FileArchiveIcon,
|
|
643
|
-
xml: import_mui_icon4.FileCodeIcon,
|
|
644
|
-
html: import_mui_icon4.FileCodeIcon,
|
|
645
|
-
pdf: import_mui_icon4.FilePdfIcon
|
|
646
|
-
};
|
|
647
|
-
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
648
|
-
var getFileExtIcon = (fileName) => {
|
|
649
|
-
var _a;
|
|
650
|
-
const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
651
|
-
const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : import_mui_icon4.FileIcon;
|
|
652
|
-
return icon;
|
|
653
|
-
};
|
|
654
|
-
|
|
655
673
|
// src/lib/useUploadCore.tsx
|
|
656
674
|
var import_react_query = require("@tanstack/react-query");
|
|
657
675
|
var import_upload_core = __toESM(require("@availity/upload-core"));
|
|
@@ -751,17 +769,20 @@ var import_react_hook_form3 = require("react-hook-form");
|
|
|
751
769
|
var import_react_query2 = require("@tanstack/react-query");
|
|
752
770
|
var import_mui_layout5 = require("@availity/mui-layout");
|
|
753
771
|
var import_mui_typography5 = require("@availity/mui-typography");
|
|
772
|
+
var import_mui_alert3 = require("@availity/mui-alert");
|
|
754
773
|
|
|
755
774
|
// src/lib/FileTypesMessage.tsx
|
|
756
775
|
var import_mui_typography3 = require("@availity/mui-typography");
|
|
757
776
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
758
777
|
var FileTypesMessage = ({
|
|
759
778
|
allowedFileTypes = [],
|
|
779
|
+
customSizeMessage,
|
|
780
|
+
customTypesMessage,
|
|
760
781
|
maxFileSize,
|
|
761
782
|
variant = "caption"
|
|
762
783
|
}) => {
|
|
763
|
-
const fileSizeMsg = typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null;
|
|
764
|
-
const fileTypesMsg = allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}` : "All file types allowed.";
|
|
784
|
+
const fileSizeMsg = customSizeMessage || (typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null);
|
|
785
|
+
const fileTypesMsg = customTypesMessage || (allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}` : "All file types allowed.");
|
|
765
786
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_mui_typography3.Typography, { variant, children: [
|
|
766
787
|
fileSizeMsg,
|
|
767
788
|
fileTypesMsg
|
|
@@ -790,6 +811,8 @@ var FileSelector = ({
|
|
|
790
811
|
bucketId,
|
|
791
812
|
clientId,
|
|
792
813
|
children,
|
|
814
|
+
customSizeMessage,
|
|
815
|
+
customTypesMessage,
|
|
793
816
|
customerId,
|
|
794
817
|
customFileRow,
|
|
795
818
|
disabled = false,
|
|
@@ -843,6 +866,13 @@ var FileSelector = ({
|
|
|
843
866
|
const rejections = fileRejections.filter((value) => value.id !== id);
|
|
844
867
|
setFileRejections(rejections);
|
|
845
868
|
};
|
|
869
|
+
const TOO_MANY_FILES_CODE = "too-many-files";
|
|
870
|
+
const tooManyFilesRejections = fileRejections.filter(
|
|
871
|
+
(rejection) => rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
872
|
+
);
|
|
873
|
+
const otherRejections = fileRejections.filter(
|
|
874
|
+
(rejection) => !rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
875
|
+
);
|
|
846
876
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
847
877
|
enableDropArea ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
848
878
|
label ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_mui_typography5.Typography, { marginBottom: "4px", children: label }) : null,
|
|
@@ -863,12 +893,29 @@ var FileSelector = ({
|
|
|
863
893
|
validator
|
|
864
894
|
}
|
|
865
895
|
),
|
|
866
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
897
|
+
FileTypesMessage,
|
|
898
|
+
{
|
|
899
|
+
allowedFileTypes,
|
|
900
|
+
maxFileSize: maxSize,
|
|
901
|
+
customSizeMessage,
|
|
902
|
+
customTypesMessage,
|
|
903
|
+
variant: "caption"
|
|
904
|
+
}
|
|
905
|
+
),
|
|
867
906
|
children
|
|
868
907
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_mui_layout5.Grid, { container: true, rowSpacing: 3, flexDirection: "column", children: [
|
|
869
908
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_mui_layout5.Grid, { children: [
|
|
870
909
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HeaderMessage, { maxFiles, maxSize }),
|
|
871
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
911
|
+
FileTypesMessage,
|
|
912
|
+
{
|
|
913
|
+
allowedFileTypes,
|
|
914
|
+
customSizeMessage,
|
|
915
|
+
customTypesMessage,
|
|
916
|
+
variant: "body2"
|
|
917
|
+
}
|
|
918
|
+
)
|
|
872
919
|
] }),
|
|
873
920
|
children ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_mui_layout5.Grid, { children }) : null,
|
|
874
921
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_mui_layout5.Grid, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
@@ -889,7 +936,20 @@ var FileSelector = ({
|
|
|
889
936
|
}
|
|
890
937
|
) })
|
|
891
938
|
] }),
|
|
892
|
-
|
|
939
|
+
tooManyFilesRejections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
940
|
+
import_mui_alert3.Alert,
|
|
941
|
+
{
|
|
942
|
+
severity: "error",
|
|
943
|
+
onClose: () => tooManyFilesRejections.forEach((rejection) => handleRemoveRejection(rejection.id)),
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_mui_alert3.AlertTitle, { children: "Items not allowed." }),
|
|
946
|
+
"Too many files are selected for upload, maximum ",
|
|
947
|
+
maxFiles,
|
|
948
|
+
" allowed."
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
),
|
|
952
|
+
otherRejections.length > 0 && otherRejections.map((rejection) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
893
953
|
ErrorAlert,
|
|
894
954
|
{
|
|
895
955
|
errors: rejection.errors,
|
|
@@ -898,7 +958,7 @@ var FileSelector = ({
|
|
|
898
958
|
onClose: () => handleRemoveRejection(rejection.id)
|
|
899
959
|
},
|
|
900
960
|
rejection.id
|
|
901
|
-
))
|
|
961
|
+
)),
|
|
902
962
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
903
963
|
FileList,
|
|
904
964
|
{
|
|
@@ -923,5 +983,6 @@ var FileSelector = ({
|
|
|
923
983
|
FileTypesMessage,
|
|
924
984
|
HeaderMessage,
|
|
925
985
|
UploadProgressBar,
|
|
986
|
+
formatFileTooLarge,
|
|
926
987
|
useUploadCore
|
|
927
988
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -282,6 +282,57 @@ var Dropzone = ({
|
|
|
282
282
|
// src/lib/ErrorAlert.tsx
|
|
283
283
|
import { Alert, AlertTitle } from "@availity/mui-alert";
|
|
284
284
|
import { List, ListItem } from "@availity/mui-list";
|
|
285
|
+
|
|
286
|
+
// src/lib/util.ts
|
|
287
|
+
import {
|
|
288
|
+
FileArchiveIcon,
|
|
289
|
+
FileCodeIcon,
|
|
290
|
+
FileCsvIcon,
|
|
291
|
+
FileExcelIcon,
|
|
292
|
+
FileIcon,
|
|
293
|
+
FileImageIcon,
|
|
294
|
+
FileLinesIcon,
|
|
295
|
+
FilePdfIcon,
|
|
296
|
+
FilePowerpointIcon,
|
|
297
|
+
FileWordIcon
|
|
298
|
+
} from "@availity/mui-icon";
|
|
299
|
+
function formatBytes(bytes, decimals = 2) {
|
|
300
|
+
if (!+bytes) return "0 Bytes";
|
|
301
|
+
const k = 1024;
|
|
302
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
303
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
304
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
305
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
306
|
+
}
|
|
307
|
+
var FILE_EXT_ICONS = {
|
|
308
|
+
png: FileImageIcon,
|
|
309
|
+
jpg: FileImageIcon,
|
|
310
|
+
jpeg: FileImageIcon,
|
|
311
|
+
gif: FileImageIcon,
|
|
312
|
+
csv: FileCsvIcon,
|
|
313
|
+
ppt: FilePowerpointIcon,
|
|
314
|
+
pptx: FilePowerpointIcon,
|
|
315
|
+
xls: FileExcelIcon,
|
|
316
|
+
xlsx: FileExcelIcon,
|
|
317
|
+
doc: FileWordIcon,
|
|
318
|
+
docx: FileWordIcon,
|
|
319
|
+
txt: FileLinesIcon,
|
|
320
|
+
text: FileLinesIcon,
|
|
321
|
+
zip: FileArchiveIcon,
|
|
322
|
+
"7zip": FileArchiveIcon,
|
|
323
|
+
xml: FileCodeIcon,
|
|
324
|
+
html: FileCodeIcon,
|
|
325
|
+
pdf: FilePdfIcon
|
|
326
|
+
};
|
|
327
|
+
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
328
|
+
var getFileExtIcon = (fileName) => {
|
|
329
|
+
var _a;
|
|
330
|
+
const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
331
|
+
const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : FileIcon;
|
|
332
|
+
return icon;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// src/lib/ErrorAlert.tsx
|
|
285
336
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
286
337
|
var codes = {
|
|
287
338
|
"file-too-large": "File exceeds maximum size",
|
|
@@ -290,6 +341,13 @@ var codes = {
|
|
|
290
341
|
"too-many-file": "Too many files",
|
|
291
342
|
"duplicate-name": "Duplicate file selected"
|
|
292
343
|
};
|
|
344
|
+
var FILE_SIZE_REGEX = /\b(\d+)\b/;
|
|
345
|
+
var formatFileTooLarge = (message) => {
|
|
346
|
+
const fileSize = message.match(FILE_SIZE_REGEX);
|
|
347
|
+
if (!fileSize) return message;
|
|
348
|
+
const formattedSize = formatBytes(Number(fileSize[0]));
|
|
349
|
+
return message.replace(` bytes`, "").replace(FILE_SIZE_REGEX, formattedSize);
|
|
350
|
+
};
|
|
293
351
|
var ErrorAlert = ({ errors, fileName, id, onClose }) => {
|
|
294
352
|
if (errors.length === 0) return null;
|
|
295
353
|
return /* @__PURE__ */ jsx3(Alert, { severity: "error", onClose, children: errors.length > 1 ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
@@ -299,23 +357,31 @@ var ErrorAlert = ({ errors, fileName, id, onClose }) => {
|
|
|
299
357
|
" error(s) found when uploading ",
|
|
300
358
|
fileName
|
|
301
359
|
] }),
|
|
302
|
-
/* @__PURE__ */ jsx3(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
360
|
+
/* @__PURE__ */ jsx3(
|
|
361
|
+
List,
|
|
362
|
+
{
|
|
363
|
+
sx: {
|
|
364
|
+
listStyleType: "disc",
|
|
365
|
+
listStylePosition: "inside",
|
|
366
|
+
marginLeft: 1,
|
|
367
|
+
".MuiListItem-root": {
|
|
368
|
+
display: "list-item"
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
disablePadding: true,
|
|
372
|
+
children: /* @__PURE__ */ jsx3(Fragment3, { children: errors.map((error) => {
|
|
373
|
+
const message = error.code === "file-too-large" ? formatFileTooLarge(error.message) : error.message;
|
|
374
|
+
return /* @__PURE__ */ jsx3(ListItem, { disableGutters: true, disablePadding: true, divider: false, children: message }, `${id}-${error.code}`);
|
|
375
|
+
}) })
|
|
308
376
|
}
|
|
309
|
-
|
|
310
|
-
return /* @__PURE__ */ jsx3(ListItem, { disableGutters: true, disablePadding: true, divider: false, children: error.message }, `${id}-${error.code}`);
|
|
311
|
-
}) }) })
|
|
377
|
+
)
|
|
312
378
|
] }) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
313
379
|
/* @__PURE__ */ jsxs3(AlertTitle, { children: [
|
|
314
380
|
codes[errors[0].code] || "Error",
|
|
315
381
|
": ",
|
|
316
382
|
fileName
|
|
317
383
|
] }),
|
|
318
|
-
errors[0].message
|
|
384
|
+
errors[0].code === "file-too-large" ? formatFileTooLarge(errors[0].message) : errors[0].message
|
|
319
385
|
] }) });
|
|
320
386
|
};
|
|
321
387
|
|
|
@@ -578,55 +644,6 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
|
|
|
578
644
|
] }) : /* @__PURE__ */ jsx10(LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
|
|
579
645
|
};
|
|
580
646
|
|
|
581
|
-
// src/lib/util.ts
|
|
582
|
-
import {
|
|
583
|
-
FileArchiveIcon,
|
|
584
|
-
FileCodeIcon,
|
|
585
|
-
FileCsvIcon,
|
|
586
|
-
FileExcelIcon,
|
|
587
|
-
FileIcon,
|
|
588
|
-
FileImageIcon,
|
|
589
|
-
FileLinesIcon,
|
|
590
|
-
FilePdfIcon,
|
|
591
|
-
FilePowerpointIcon,
|
|
592
|
-
FileWordIcon
|
|
593
|
-
} from "@availity/mui-icon";
|
|
594
|
-
function formatBytes(bytes, decimals = 2) {
|
|
595
|
-
if (!+bytes) return "0 Bytes";
|
|
596
|
-
const k = 1024;
|
|
597
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
598
|
-
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
599
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
600
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
601
|
-
}
|
|
602
|
-
var FILE_EXT_ICONS = {
|
|
603
|
-
png: FileImageIcon,
|
|
604
|
-
jpg: FileImageIcon,
|
|
605
|
-
jpeg: FileImageIcon,
|
|
606
|
-
gif: FileImageIcon,
|
|
607
|
-
csv: FileCsvIcon,
|
|
608
|
-
ppt: FilePowerpointIcon,
|
|
609
|
-
pptx: FilePowerpointIcon,
|
|
610
|
-
xls: FileExcelIcon,
|
|
611
|
-
xlsx: FileExcelIcon,
|
|
612
|
-
doc: FileWordIcon,
|
|
613
|
-
docx: FileWordIcon,
|
|
614
|
-
txt: FileLinesIcon,
|
|
615
|
-
text: FileLinesIcon,
|
|
616
|
-
zip: FileArchiveIcon,
|
|
617
|
-
"7zip": FileArchiveIcon,
|
|
618
|
-
xml: FileCodeIcon,
|
|
619
|
-
html: FileCodeIcon,
|
|
620
|
-
pdf: FilePdfIcon
|
|
621
|
-
};
|
|
622
|
-
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
623
|
-
var getFileExtIcon = (fileName) => {
|
|
624
|
-
var _a;
|
|
625
|
-
const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
626
|
-
const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : FileIcon;
|
|
627
|
-
return icon;
|
|
628
|
-
};
|
|
629
|
-
|
|
630
647
|
// src/lib/useUploadCore.tsx
|
|
631
648
|
import { useQuery } from "@tanstack/react-query";
|
|
632
649
|
import Upload from "@availity/upload-core";
|
|
@@ -726,17 +743,20 @@ import { useFormContext as useFormContext2 } from "react-hook-form";
|
|
|
726
743
|
import { useQueryClient } from "@tanstack/react-query";
|
|
727
744
|
import { Grid as Grid3 } from "@availity/mui-layout";
|
|
728
745
|
import { Typography as Typography5 } from "@availity/mui-typography";
|
|
746
|
+
import { Alert as Alert2, AlertTitle as AlertTitle2 } from "@availity/mui-alert";
|
|
729
747
|
|
|
730
748
|
// src/lib/FileTypesMessage.tsx
|
|
731
749
|
import { Typography as Typography3 } from "@availity/mui-typography";
|
|
732
750
|
import { jsxs as jsxs9 } from "react/jsx-runtime";
|
|
733
751
|
var FileTypesMessage = ({
|
|
734
752
|
allowedFileTypes = [],
|
|
753
|
+
customSizeMessage,
|
|
754
|
+
customTypesMessage,
|
|
735
755
|
maxFileSize,
|
|
736
756
|
variant = "caption"
|
|
737
757
|
}) => {
|
|
738
|
-
const fileSizeMsg = typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null;
|
|
739
|
-
const fileTypesMsg = allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}` : "All file types allowed.";
|
|
758
|
+
const fileSizeMsg = customSizeMessage || (typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null);
|
|
759
|
+
const fileTypesMsg = customTypesMessage || (allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}` : "All file types allowed.");
|
|
740
760
|
return /* @__PURE__ */ jsxs9(Typography3, { variant, children: [
|
|
741
761
|
fileSizeMsg,
|
|
742
762
|
fileTypesMsg
|
|
@@ -765,6 +785,8 @@ var FileSelector = ({
|
|
|
765
785
|
bucketId,
|
|
766
786
|
clientId,
|
|
767
787
|
children,
|
|
788
|
+
customSizeMessage,
|
|
789
|
+
customTypesMessage,
|
|
768
790
|
customerId,
|
|
769
791
|
customFileRow,
|
|
770
792
|
disabled = false,
|
|
@@ -818,6 +840,13 @@ var FileSelector = ({
|
|
|
818
840
|
const rejections = fileRejections.filter((value) => value.id !== id);
|
|
819
841
|
setFileRejections(rejections);
|
|
820
842
|
};
|
|
843
|
+
const TOO_MANY_FILES_CODE = "too-many-files";
|
|
844
|
+
const tooManyFilesRejections = fileRejections.filter(
|
|
845
|
+
(rejection) => rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
846
|
+
);
|
|
847
|
+
const otherRejections = fileRejections.filter(
|
|
848
|
+
(rejection) => !rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
849
|
+
);
|
|
821
850
|
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
822
851
|
enableDropArea ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
823
852
|
label ? /* @__PURE__ */ jsx12(Typography5, { marginBottom: "4px", children: label }) : null,
|
|
@@ -838,12 +867,29 @@ var FileSelector = ({
|
|
|
838
867
|
validator
|
|
839
868
|
}
|
|
840
869
|
),
|
|
841
|
-
/* @__PURE__ */ jsx12(
|
|
870
|
+
/* @__PURE__ */ jsx12(
|
|
871
|
+
FileTypesMessage,
|
|
872
|
+
{
|
|
873
|
+
allowedFileTypes,
|
|
874
|
+
maxFileSize: maxSize,
|
|
875
|
+
customSizeMessage,
|
|
876
|
+
customTypesMessage,
|
|
877
|
+
variant: "caption"
|
|
878
|
+
}
|
|
879
|
+
),
|
|
842
880
|
children
|
|
843
881
|
] }) : /* @__PURE__ */ jsxs11(Grid3, { container: true, rowSpacing: 3, flexDirection: "column", children: [
|
|
844
882
|
/* @__PURE__ */ jsxs11(Grid3, { children: [
|
|
845
883
|
/* @__PURE__ */ jsx12(HeaderMessage, { maxFiles, maxSize }),
|
|
846
|
-
/* @__PURE__ */ jsx12(
|
|
884
|
+
/* @__PURE__ */ jsx12(
|
|
885
|
+
FileTypesMessage,
|
|
886
|
+
{
|
|
887
|
+
allowedFileTypes,
|
|
888
|
+
customSizeMessage,
|
|
889
|
+
customTypesMessage,
|
|
890
|
+
variant: "body2"
|
|
891
|
+
}
|
|
892
|
+
)
|
|
847
893
|
] }),
|
|
848
894
|
children ? /* @__PURE__ */ jsx12(Grid3, { children }) : null,
|
|
849
895
|
/* @__PURE__ */ jsx12(Grid3, { children: /* @__PURE__ */ jsx12(
|
|
@@ -864,7 +910,20 @@ var FileSelector = ({
|
|
|
864
910
|
}
|
|
865
911
|
) })
|
|
866
912
|
] }),
|
|
867
|
-
|
|
913
|
+
tooManyFilesRejections.length > 0 && /* @__PURE__ */ jsxs11(
|
|
914
|
+
Alert2,
|
|
915
|
+
{
|
|
916
|
+
severity: "error",
|
|
917
|
+
onClose: () => tooManyFilesRejections.forEach((rejection) => handleRemoveRejection(rejection.id)),
|
|
918
|
+
children: [
|
|
919
|
+
/* @__PURE__ */ jsx12(AlertTitle2, { children: "Items not allowed." }),
|
|
920
|
+
"Too many files are selected for upload, maximum ",
|
|
921
|
+
maxFiles,
|
|
922
|
+
" allowed."
|
|
923
|
+
]
|
|
924
|
+
}
|
|
925
|
+
),
|
|
926
|
+
otherRejections.length > 0 && otherRejections.map((rejection) => /* @__PURE__ */ jsx12(
|
|
868
927
|
ErrorAlert,
|
|
869
928
|
{
|
|
870
929
|
errors: rejection.errors,
|
|
@@ -873,7 +932,7 @@ var FileSelector = ({
|
|
|
873
932
|
onClose: () => handleRemoveRejection(rejection.id)
|
|
874
933
|
},
|
|
875
934
|
rejection.id
|
|
876
|
-
))
|
|
935
|
+
)),
|
|
877
936
|
/* @__PURE__ */ jsx12(
|
|
878
937
|
FileList,
|
|
879
938
|
{
|
|
@@ -897,5 +956,6 @@ export {
|
|
|
897
956
|
FileTypesMessage,
|
|
898
957
|
HeaderMessage,
|
|
899
958
|
UploadProgressBar,
|
|
959
|
+
formatFileTooLarge,
|
|
900
960
|
useUploadCore
|
|
901
961
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
|
|
3
|
-
import { ErrorAlert } from './ErrorAlert';
|
|
3
|
+
import { ErrorAlert, formatFileTooLarge } from './ErrorAlert';
|
|
4
4
|
|
|
5
5
|
describe('ErrorAlert', () => {
|
|
6
6
|
test('should render error message', () => {
|
|
@@ -9,3 +9,17 @@ describe('ErrorAlert', () => {
|
|
|
9
9
|
expect(screen.getByText('Error: file')).toBeDefined();
|
|
10
10
|
});
|
|
11
11
|
});
|
|
12
|
+
|
|
13
|
+
describe('formatFileTooLarge', () => {
|
|
14
|
+
test('should format file too large error message', () => {
|
|
15
|
+
const formattedError = formatFileTooLarge('File is larger than 10485760 bytes');
|
|
16
|
+
|
|
17
|
+
expect(formattedError).toEqual('File is larger than 10 MB');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should return original error message if it does not match', () => {
|
|
21
|
+
const formattedError = formatFileTooLarge('File is larger than one hundred bytes');
|
|
22
|
+
|
|
23
|
+
expect(formattedError).toEqual('File is larger than one hundred bytes');
|
|
24
|
+
});
|
|
25
|
+
});
|
package/src/lib/ErrorAlert.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Alert, AlertTitle } from '@availity/mui-alert';
|
|
2
2
|
import { List, ListItem } from '@availity/mui-list';
|
|
3
3
|
import type { FileRejection } from 'react-dropzone';
|
|
4
|
+
import { formatBytes } from './util';
|
|
4
5
|
|
|
5
6
|
const codes: Record<string, string> = {
|
|
6
7
|
'file-too-large': 'File exceeds maximum size',
|
|
@@ -27,43 +28,59 @@ export type ErrorAlertProps = {
|
|
|
27
28
|
onClose: () => void;
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
const FILE_SIZE_REGEX = /\b(\d+)\b/;
|
|
32
|
+
|
|
33
|
+
export const formatFileTooLarge = (message: string): string => {
|
|
34
|
+
const fileSize = message.match(FILE_SIZE_REGEX);
|
|
35
|
+
|
|
36
|
+
if (!fileSize) return message;
|
|
37
|
+
|
|
38
|
+
const formattedSize = formatBytes(Number(fileSize[0]));
|
|
39
|
+
|
|
40
|
+
return message.replace(` bytes`, '').replace(FILE_SIZE_REGEX, formattedSize);
|
|
41
|
+
};
|
|
42
|
+
|
|
30
43
|
export const ErrorAlert = ({ errors, fileName, id, onClose }: ErrorAlertProps) => {
|
|
31
44
|
if (errors.length === 0) return null;
|
|
32
45
|
|
|
33
46
|
return (
|
|
34
47
|
<Alert severity="error" onClose={onClose}>
|
|
35
|
-
{errors.length > 1 ?
|
|
48
|
+
{errors.length > 1 ? (
|
|
36
49
|
<>
|
|
37
50
|
<AlertTitle>
|
|
38
51
|
There were {errors.length} error(s) found when uploading {fileName}
|
|
39
52
|
</AlertTitle>
|
|
40
|
-
<List
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
<List
|
|
54
|
+
sx={{
|
|
55
|
+
listStyleType: 'disc',
|
|
56
|
+
listStylePosition: 'inside',
|
|
57
|
+
marginLeft: 1,
|
|
58
|
+
'.MuiListItem-root': {
|
|
59
|
+
display: 'list-item',
|
|
60
|
+
},
|
|
61
|
+
}}
|
|
62
|
+
disablePadding
|
|
63
|
+
>
|
|
48
64
|
<>
|
|
49
65
|
{errors.map((error) => {
|
|
66
|
+
const message = error.code === 'file-too-large' ? formatFileTooLarge(error.message) : error.message;
|
|
50
67
|
return (
|
|
51
68
|
<ListItem disableGutters disablePadding divider={false} key={`${id}-${error.code}`}>
|
|
52
|
-
{
|
|
69
|
+
{message}
|
|
53
70
|
</ListItem>
|
|
54
71
|
);
|
|
55
72
|
})}
|
|
56
73
|
</>
|
|
57
74
|
</List>
|
|
58
75
|
</>
|
|
59
|
-
:
|
|
76
|
+
) : (
|
|
60
77
|
<>
|
|
61
78
|
<AlertTitle>
|
|
62
79
|
{codes[errors[0].code] || 'Error'}: {fileName}
|
|
63
80
|
</AlertTitle>
|
|
64
|
-
{errors[0].message}
|
|
81
|
+
{errors[0].code === 'file-too-large' ? formatFileTooLarge(errors[0].message) : errors[0].message}
|
|
65
82
|
</>
|
|
66
|
-
}
|
|
83
|
+
)}
|
|
67
84
|
</Alert>
|
|
68
85
|
);
|
|
69
86
|
};
|
|
@@ -42,7 +42,7 @@ const meta: Meta<typeof FileSelector> = {
|
|
|
42
42
|
uploadOptions: {
|
|
43
43
|
retryDelays: [],
|
|
44
44
|
},
|
|
45
|
-
maxFiles:
|
|
45
|
+
maxFiles: 2,
|
|
46
46
|
maxSize: 1 * 1024 * 1024, // 1MB
|
|
47
47
|
enableDropArea: true,
|
|
48
48
|
isCloud: true,
|
|
@@ -100,7 +100,7 @@ export const _FileSelector: StoryObj<typeof FileSelector> = {
|
|
|
100
100
|
</DismissableAlert>
|
|
101
101
|
</FileSelector>
|
|
102
102
|
{files.length > 0 && (
|
|
103
|
-
<Grid size={{xs: 12}} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
103
|
+
<Grid size={{ xs: 12 }} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
104
104
|
<Button type="submit" sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
|
105
105
|
Submit
|
|
106
106
|
</Button>
|
|
@@ -140,9 +140,9 @@ export const _ButtonOnly: StoryObj<typeof FileSelector> = {
|
|
|
140
140
|
<Paper sx={{ padding: '2rem' }}>
|
|
141
141
|
<FormProvider {...methods}>
|
|
142
142
|
<form onSubmit={methods.handleSubmit(handleOnSubmit)}>
|
|
143
|
-
<FileSelector {...props} enableDropArea={false}/>
|
|
143
|
+
<FileSelector {...props} enableDropArea={false} />
|
|
144
144
|
{files.length > 0 && (
|
|
145
|
-
<Grid size={{xs: 12}} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
145
|
+
<Grid size={{ xs: 12 }} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
146
146
|
<Button type="submit" sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
|
147
147
|
Submit
|
|
148
148
|
</Button>
|
|
@@ -182,9 +182,9 @@ export const _Encrypted: StoryObj<typeof FileSelector> = {
|
|
|
182
182
|
<Paper sx={{ padding: '2rem' }}>
|
|
183
183
|
<FormProvider {...methods}>
|
|
184
184
|
<form onSubmit={methods.handleSubmit(handleOnSubmit)}>
|
|
185
|
-
<FileSelector {...props} bucketId="enc"/>
|
|
185
|
+
<FileSelector {...props} bucketId="enc" />
|
|
186
186
|
{files.length > 0 && (
|
|
187
|
-
<Grid size={{xs: 12}} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
187
|
+
<Grid size={{ xs: 12 }} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
188
188
|
<Button type="submit" sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
|
189
189
|
Submit
|
|
190
190
|
</Button>
|
|
@@ -196,9 +196,109 @@ export const _Encrypted: StoryObj<typeof FileSelector> = {
|
|
|
196
196
|
);
|
|
197
197
|
},
|
|
198
198
|
args: {
|
|
199
|
-
bucketId: 'enc'
|
|
199
|
+
bucketId: 'enc',
|
|
200
200
|
},
|
|
201
201
|
argTypes: {
|
|
202
|
-
bucketId: { control: false }
|
|
203
|
-
}
|
|
202
|
+
bucketId: { control: false },
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const _FileSelectorCustomTypesMessage: StoryObj<typeof FileSelector> = {
|
|
207
|
+
render: (props: FileSelectorProps) => {
|
|
208
|
+
const methods = useForm({
|
|
209
|
+
defaultValues: {
|
|
210
|
+
[props.name]: [] as File[],
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const client = useQueryClient();
|
|
215
|
+
|
|
216
|
+
const files = methods.watch(props.name);
|
|
217
|
+
|
|
218
|
+
const handleOnSubmit = (values: Record<string, File[]>) => {
|
|
219
|
+
if (values[props.name].length === 0) return;
|
|
220
|
+
|
|
221
|
+
const queries = client.getQueriesData<Upload>(['upload']);
|
|
222
|
+
const uploads = [];
|
|
223
|
+
for (const [, data] of queries) {
|
|
224
|
+
if (data) uploads.push(data);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<Paper sx={{ padding: '2rem' }}>
|
|
230
|
+
<FormProvider {...methods}>
|
|
231
|
+
<form onSubmit={methods.handleSubmit(handleOnSubmit)}>
|
|
232
|
+
<FileSelector {...props}>
|
|
233
|
+
<DismissableAlert severity="warning">
|
|
234
|
+
<AlertTitle>Make an Appeal</AlertTitle>
|
|
235
|
+
This is an example alert. It is not part of the component. `children` you pass to the component will
|
|
236
|
+
show up here.
|
|
237
|
+
</DismissableAlert>
|
|
238
|
+
</FileSelector>
|
|
239
|
+
{files.length > 0 && (
|
|
240
|
+
<Grid size={{ xs: 12 }} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
241
|
+
<Button type="submit" sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
|
242
|
+
Submit
|
|
243
|
+
</Button>
|
|
244
|
+
</Grid>
|
|
245
|
+
)}
|
|
246
|
+
</form>
|
|
247
|
+
</FormProvider>
|
|
248
|
+
</Paper>
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
args: {
|
|
252
|
+
customTypesMessage: 'Only cool file types allowed',
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const _FileSelectorCustomSizeMessage: StoryObj<typeof FileSelector> = {
|
|
257
|
+
render: (props: FileSelectorProps) => {
|
|
258
|
+
const methods = useForm({
|
|
259
|
+
defaultValues: {
|
|
260
|
+
[props.name]: [] as File[],
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const client = useQueryClient();
|
|
265
|
+
|
|
266
|
+
const files = methods.watch(props.name);
|
|
267
|
+
|
|
268
|
+
const handleOnSubmit = (values: Record<string, File[]>) => {
|
|
269
|
+
if (values[props.name].length === 0) return;
|
|
270
|
+
|
|
271
|
+
const queries = client.getQueriesData<Upload>(['upload']);
|
|
272
|
+
const uploads = [];
|
|
273
|
+
for (const [, data] of queries) {
|
|
274
|
+
if (data) uploads.push(data);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<Paper sx={{ padding: '2rem' }}>
|
|
280
|
+
<FormProvider {...methods}>
|
|
281
|
+
<form onSubmit={methods.handleSubmit(handleOnSubmit)}>
|
|
282
|
+
<FileSelector {...props}>
|
|
283
|
+
<DismissableAlert severity="warning">
|
|
284
|
+
<AlertTitle>Make an Appeal</AlertTitle>
|
|
285
|
+
This is an example alert. It is not part of the component. `children` you pass to the component will
|
|
286
|
+
show up here.
|
|
287
|
+
</DismissableAlert>
|
|
288
|
+
</FileSelector>
|
|
289
|
+
{files.length > 0 && (
|
|
290
|
+
<Grid size={{ xs: 12 }} justifyContent="end" display="flex" paddingTop={2.5}>
|
|
291
|
+
<Button type="submit" sx={{ marginLeft: 'auto', marginRight: 0 }}>
|
|
292
|
+
Submit
|
|
293
|
+
</Button>
|
|
294
|
+
</Grid>
|
|
295
|
+
)}
|
|
296
|
+
</form>
|
|
297
|
+
</FormProvider>
|
|
298
|
+
</Paper>
|
|
299
|
+
);
|
|
300
|
+
},
|
|
301
|
+
args: {
|
|
302
|
+
customSizeMessage: <div>Only huge files allowed</div>,
|
|
303
|
+
},
|
|
204
304
|
};
|
package/src/lib/FileSelector.tsx
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import type { ChangeEvent,
|
|
2
|
+
import type { ChangeEvent, ReactNode } from 'react';
|
|
3
3
|
import { useFormContext } from 'react-hook-form';
|
|
4
4
|
import type { DropEvent, FileError, FileRejection } from 'react-dropzone/typings/react-dropzone';
|
|
5
5
|
import { useQueryClient } from '@tanstack/react-query';
|
|
6
6
|
import type { default as Upload } from '@availity/upload-core';
|
|
7
7
|
import { Grid } from '@availity/mui-layout';
|
|
8
8
|
import { Typography } from '@availity/mui-typography';
|
|
9
|
+
import { Alert, AlertTitle } from '@availity/mui-alert';
|
|
9
10
|
|
|
10
11
|
import { Dropzone } from './Dropzone';
|
|
11
12
|
import { ErrorAlert } from './ErrorAlert';
|
|
12
13
|
import { FileList } from './FileList';
|
|
13
|
-
import type { FileListProps } from './FileList';
|
|
14
14
|
import { FileTypesMessage } from './FileTypesMessage';
|
|
15
15
|
import { HeaderMessage } from './HeaderMessage';
|
|
16
16
|
import type { Options, UploadQueryOptions } from './useUploadCore';
|
|
@@ -55,6 +55,14 @@ export type FileSelectorProps = {
|
|
|
55
55
|
* @default false
|
|
56
56
|
*/
|
|
57
57
|
disabled?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Overrides the standard file size message
|
|
60
|
+
*/
|
|
61
|
+
customSizeMessage?: React.ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* Overrides the standard file types message
|
|
64
|
+
*/
|
|
65
|
+
customTypesMessage?: React.ReactNode;
|
|
58
66
|
/**
|
|
59
67
|
* Whether to enable the dropzone area
|
|
60
68
|
*/
|
|
@@ -64,7 +72,7 @@ export type FileSelectorProps = {
|
|
|
64
72
|
*/
|
|
65
73
|
endpoint?: string;
|
|
66
74
|
/**
|
|
67
|
-
*
|
|
75
|
+
* Component to render the File information. This should return a `ListItem`
|
|
68
76
|
*/
|
|
69
77
|
customFileRow?: React.ElementType<{
|
|
70
78
|
upload?: Upload;
|
|
@@ -136,6 +144,8 @@ export const FileSelector = ({
|
|
|
136
144
|
bucketId,
|
|
137
145
|
clientId,
|
|
138
146
|
children,
|
|
147
|
+
customSizeMessage,
|
|
148
|
+
customTypesMessage,
|
|
139
149
|
customerId,
|
|
140
150
|
customFileRow,
|
|
141
151
|
disabled = false,
|
|
@@ -206,6 +216,18 @@ export const FileSelector = ({
|
|
|
206
216
|
setFileRejections(rejections);
|
|
207
217
|
};
|
|
208
218
|
|
|
219
|
+
const TOO_MANY_FILES_CODE = 'too-many-files';
|
|
220
|
+
|
|
221
|
+
// Extract too-many-files rejections
|
|
222
|
+
const tooManyFilesRejections = fileRejections.filter((rejection) =>
|
|
223
|
+
rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Extract other rejections
|
|
227
|
+
const otherRejections = fileRejections.filter(
|
|
228
|
+
(rejection) => !rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE)
|
|
229
|
+
);
|
|
230
|
+
|
|
209
231
|
return (
|
|
210
232
|
<>
|
|
211
233
|
{enableDropArea ? (
|
|
@@ -225,14 +247,25 @@ export const FileSelector = ({
|
|
|
225
247
|
setTotalSize={setTotalSize}
|
|
226
248
|
validator={validator}
|
|
227
249
|
/>
|
|
228
|
-
<FileTypesMessage
|
|
250
|
+
<FileTypesMessage
|
|
251
|
+
allowedFileTypes={allowedFileTypes}
|
|
252
|
+
maxFileSize={maxSize}
|
|
253
|
+
customSizeMessage={customSizeMessage}
|
|
254
|
+
customTypesMessage={customTypesMessage}
|
|
255
|
+
variant="caption"
|
|
256
|
+
/>
|
|
229
257
|
{children}
|
|
230
258
|
</>
|
|
231
259
|
) : (
|
|
232
260
|
<Grid container rowSpacing={3} flexDirection="column">
|
|
233
261
|
<Grid>
|
|
234
262
|
<HeaderMessage maxFiles={maxFiles} maxSize={maxSize} />
|
|
235
|
-
<FileTypesMessage
|
|
263
|
+
<FileTypesMessage
|
|
264
|
+
allowedFileTypes={allowedFileTypes}
|
|
265
|
+
customSizeMessage={customSizeMessage}
|
|
266
|
+
customTypesMessage={customTypesMessage}
|
|
267
|
+
variant="body2"
|
|
268
|
+
/>
|
|
236
269
|
</Grid>
|
|
237
270
|
{children ? <Grid>{children}</Grid> : null}
|
|
238
271
|
<Grid>
|
|
@@ -253,17 +286,26 @@ export const FileSelector = ({
|
|
|
253
286
|
</Grid>
|
|
254
287
|
</Grid>
|
|
255
288
|
)}
|
|
256
|
-
{
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
289
|
+
{tooManyFilesRejections.length > 0 && (
|
|
290
|
+
<Alert
|
|
291
|
+
severity="error"
|
|
292
|
+
onClose={() => tooManyFilesRejections.forEach((rejection) => handleRemoveRejection(rejection.id))}
|
|
293
|
+
>
|
|
294
|
+
<AlertTitle>Items not allowed.</AlertTitle>
|
|
295
|
+
Too many files are selected for upload, maximum {maxFiles} allowed.
|
|
296
|
+
</Alert>
|
|
297
|
+
)}
|
|
298
|
+
|
|
299
|
+
{otherRejections.length > 0 &&
|
|
300
|
+
otherRejections.map((rejection) => (
|
|
301
|
+
<ErrorAlert
|
|
302
|
+
key={rejection.id}
|
|
303
|
+
errors={rejection.errors}
|
|
304
|
+
fileName={rejection.file.name}
|
|
305
|
+
id={rejection.id}
|
|
306
|
+
onClose={() => handleRemoveRejection(rejection.id)}
|
|
307
|
+
/>
|
|
308
|
+
))}
|
|
267
309
|
<FileList
|
|
268
310
|
files={files || []}
|
|
269
311
|
options={options}
|
|
@@ -14,4 +14,16 @@ describe('FileTypesMessage', () => {
|
|
|
14
14
|
|
|
15
15
|
expect(screen.getByText(/Maximum file size is/)).toBeTruthy();
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
test('custom size message', () => {
|
|
19
|
+
render(<FileTypesMessage customSizeMessage="Only small files" variant="body2" />);
|
|
20
|
+
|
|
21
|
+
expect(screen.getByText(/Only small files/)).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('custom file types message', () => {
|
|
25
|
+
render(<FileTypesMessage customTypesMessage="Only cool types allowed" variant="body2" />);
|
|
26
|
+
|
|
27
|
+
expect(screen.getByText(/Only cool types allowed/)).toBeTruthy();
|
|
28
|
+
});
|
|
17
29
|
});
|
|
@@ -7,6 +7,14 @@ export type FileTypesMessageProps = {
|
|
|
7
7
|
* Allowed file type extensions. Each extension should be prefixed with a ".". eg: .txt, .pdf, .png
|
|
8
8
|
*/
|
|
9
9
|
allowedFileTypes?: `.${string}`[];
|
|
10
|
+
/**
|
|
11
|
+
* Overrides the standard file size message
|
|
12
|
+
*/
|
|
13
|
+
customSizeMessage?: React.ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* Overrides the standard file types message
|
|
16
|
+
*/
|
|
17
|
+
customTypesMessage?: React.ReactNode;
|
|
10
18
|
/**
|
|
11
19
|
* Maximum size per file in bytes. This will be formatted. eg: 1024 * 20 = 20 KB
|
|
12
20
|
*/
|
|
@@ -16,15 +24,19 @@ export type FileTypesMessageProps = {
|
|
|
16
24
|
|
|
17
25
|
export const FileTypesMessage = ({
|
|
18
26
|
allowedFileTypes = [],
|
|
27
|
+
customSizeMessage,
|
|
28
|
+
customTypesMessage,
|
|
19
29
|
maxFileSize,
|
|
20
30
|
variant = 'caption',
|
|
21
31
|
}: FileTypesMessageProps) => {
|
|
22
|
-
const fileSizeMsg =
|
|
32
|
+
const fileSizeMsg =
|
|
33
|
+
customSizeMessage ||
|
|
34
|
+
(typeof maxFileSize === 'number' ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null);
|
|
23
35
|
const fileTypesMsg =
|
|
24
|
-
|
|
36
|
+
customTypesMessage ||
|
|
37
|
+
(allowedFileTypes.length > 0
|
|
25
38
|
? `Supported file types include: ${allowedFileTypes.join(', ')}`
|
|
26
|
-
: 'All file types allowed.';
|
|
27
|
-
|
|
39
|
+
: 'All file types allowed.');
|
|
28
40
|
return (
|
|
29
41
|
<Typography variant={variant}>
|
|
30
42
|
{fileSizeMsg}
|