@availity/mui-file-selector 0.2.0 → 0.2.2

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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.2.2](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.2.1...@availity/mui-file-selector@0.2.2) (2025-01-02)
6
+
7
+ ## [0.2.1](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.2.0...@availity/mui-file-selector@0.2.1) (2024-12-23)
8
+
9
+ ### Dependency Updates
10
+
11
+ * `mui-form-utils` updated to version `0.2.0`
5
12
  ## [0.2.0](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.1.3...@availity/mui-file-selector@0.2.0) (2024-12-16)
6
13
 
7
14
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,110 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import Upload from '@availity/upload-core';
2
+ import { ReactNode, ChangeEvent } from 'react';
3
+ import Upload, { UploadOptions } from '@availity/upload-core';
4
+
5
+ type FileSelectorProps = {
6
+ /**
7
+ * Name attribute for the form field. Used by react-hook-form for form state management
8
+ * and must be unique within the form context
9
+ */
10
+ name: string;
11
+ /**
12
+ * The ID of the bucket where files will be uploaded
13
+ */
14
+ bucketId: string;
15
+ /**
16
+ * The customer ID associated with the upload
17
+ */
18
+ customerId: string;
19
+ /**
20
+ * Regular expression pattern of allowed characters in file names
21
+ * @example "a-zA-Z0-9-_."
22
+ */
23
+ allowedFileNameCharacters?: string;
24
+ /**
25
+ * List of allowed file extensions. Each extension must start with a dot
26
+ * @example ['.pdf', '.doc', '.docx']
27
+ * @default []
28
+ */
29
+ allowedFileTypes?: `.${string}`[];
30
+ /**
31
+ * Optional content to render below the file upload area
32
+ */
33
+ children?: ReactNode;
34
+ /**
35
+ * Client identifier used for upload authentication
36
+ */
37
+ clientId: string;
38
+ /**
39
+ * Whether the file selector is disabled
40
+ * @default false
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Custom endpoint URL for file uploads. If not provided, default endpoint will be used
45
+ */
46
+ endpoint?: string;
47
+ /**
48
+ * Whether to use the cloud upload endpoint
49
+ * When true, uses '/cloud/web/appl/vault/upload/v1/resumable'
50
+ */
51
+ isCloud?: boolean;
52
+ /**
53
+ * Label text or element displayed above the upload area
54
+ * @default 'Upload file'
55
+ */
56
+ label?: ReactNode;
57
+ /**
58
+ * Maximum number of files that can be uploaded simultaneously
59
+ */
60
+ maxFiles?: number;
61
+ /**
62
+ * Maximum file size allowed per file in bytes
63
+ * Use Kibi or Mibibytes. eg: 1kb = 1024 bytes; 1mb = 1024kb
64
+ */
65
+ maxSize: number;
66
+ /**
67
+ * Whether multiple file selection is allowed
68
+ * @default true
69
+ */
70
+ multiple?: boolean;
71
+ /**
72
+ * Callback fired when files are selected
73
+ * @param event - The change event containing the selected file(s)
74
+ */
75
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
76
+ /**
77
+ * Callback fired when the form is submitted
78
+ * @param uploads - Array of Upload instances for the submitted files
79
+ * @param values - Object containing the form values, with files indexed by the name prop
80
+ */
81
+ onSubmit?: (uploads: Upload[], values: Record<string, File[]>) => void;
82
+ /**
83
+ * Callback fired when a file is successfully uploaded
84
+ */
85
+ onSuccess?: UploadOptions['onSuccess'];
86
+ /**
87
+ * Callback fired when an error occurs during upload
88
+ */
89
+ onError?: UploadOptions['onError'];
90
+ /**
91
+ * Array of functions to execute before file upload begins.
92
+ * Each function should return a boolean indicating whether to proceed with the upload.
93
+ * @default []
94
+ */
95
+ onFilePreUpload?: (() => boolean)[];
96
+ /**
97
+ * Callback fired when a file is removed from the upload list
98
+ * @param files - Array of remaining files
99
+ * @param removedUploadId - ID of the removed upload
100
+ */
101
+ onUploadRemove?: (files: File[], removedUploadId: string) => void;
102
+ /**
103
+ * Array of delays (in milliseconds) between upload retry attempts
104
+ */
105
+ retryDelays?: UploadOptions['retryDelays'];
106
+ };
107
+ declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customerId, disabled, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onSubmit, onSuccess, onError, onFilePreUpload, onUploadRemove, retryDelays, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
3
108
 
4
109
  type UploadProgressBarProps = {
5
110
  /**
@@ -21,4 +126,4 @@ type UploadProgressBarProps = {
21
126
  };
22
127
  declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
23
128
 
24
- export { UploadProgressBar, type UploadProgressBarProps };
129
+ export { FileSelector, type FileSelectorProps, UploadProgressBar, type UploadProgressBarProps };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,110 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import Upload from '@availity/upload-core';
2
+ import { ReactNode, ChangeEvent } from 'react';
3
+ import Upload, { UploadOptions } from '@availity/upload-core';
4
+
5
+ type FileSelectorProps = {
6
+ /**
7
+ * Name attribute for the form field. Used by react-hook-form for form state management
8
+ * and must be unique within the form context
9
+ */
10
+ name: string;
11
+ /**
12
+ * The ID of the bucket where files will be uploaded
13
+ */
14
+ bucketId: string;
15
+ /**
16
+ * The customer ID associated with the upload
17
+ */
18
+ customerId: string;
19
+ /**
20
+ * Regular expression pattern of allowed characters in file names
21
+ * @example "a-zA-Z0-9-_."
22
+ */
23
+ allowedFileNameCharacters?: string;
24
+ /**
25
+ * List of allowed file extensions. Each extension must start with a dot
26
+ * @example ['.pdf', '.doc', '.docx']
27
+ * @default []
28
+ */
29
+ allowedFileTypes?: `.${string}`[];
30
+ /**
31
+ * Optional content to render below the file upload area
32
+ */
33
+ children?: ReactNode;
34
+ /**
35
+ * Client identifier used for upload authentication
36
+ */
37
+ clientId: string;
38
+ /**
39
+ * Whether the file selector is disabled
40
+ * @default false
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Custom endpoint URL for file uploads. If not provided, default endpoint will be used
45
+ */
46
+ endpoint?: string;
47
+ /**
48
+ * Whether to use the cloud upload endpoint
49
+ * When true, uses '/cloud/web/appl/vault/upload/v1/resumable'
50
+ */
51
+ isCloud?: boolean;
52
+ /**
53
+ * Label text or element displayed above the upload area
54
+ * @default 'Upload file'
55
+ */
56
+ label?: ReactNode;
57
+ /**
58
+ * Maximum number of files that can be uploaded simultaneously
59
+ */
60
+ maxFiles?: number;
61
+ /**
62
+ * Maximum file size allowed per file in bytes
63
+ * Use Kibi or Mibibytes. eg: 1kb = 1024 bytes; 1mb = 1024kb
64
+ */
65
+ maxSize: number;
66
+ /**
67
+ * Whether multiple file selection is allowed
68
+ * @default true
69
+ */
70
+ multiple?: boolean;
71
+ /**
72
+ * Callback fired when files are selected
73
+ * @param event - The change event containing the selected file(s)
74
+ */
75
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
76
+ /**
77
+ * Callback fired when the form is submitted
78
+ * @param uploads - Array of Upload instances for the submitted files
79
+ * @param values - Object containing the form values, with files indexed by the name prop
80
+ */
81
+ onSubmit?: (uploads: Upload[], values: Record<string, File[]>) => void;
82
+ /**
83
+ * Callback fired when a file is successfully uploaded
84
+ */
85
+ onSuccess?: UploadOptions['onSuccess'];
86
+ /**
87
+ * Callback fired when an error occurs during upload
88
+ */
89
+ onError?: UploadOptions['onError'];
90
+ /**
91
+ * Array of functions to execute before file upload begins.
92
+ * Each function should return a boolean indicating whether to proceed with the upload.
93
+ * @default []
94
+ */
95
+ onFilePreUpload?: (() => boolean)[];
96
+ /**
97
+ * Callback fired when a file is removed from the upload list
98
+ * @param files - Array of remaining files
99
+ * @param removedUploadId - ID of the removed upload
100
+ */
101
+ onUploadRemove?: (files: File[], removedUploadId: string) => void;
102
+ /**
103
+ * Array of delays (in milliseconds) between upload retry attempts
104
+ */
105
+ retryDelays?: UploadOptions['retryDelays'];
106
+ };
107
+ declare const FileSelector: ({ name, allowedFileNameCharacters, allowedFileTypes, bucketId, clientId, children, customerId, disabled, endpoint, isCloud, label, maxFiles, maxSize, multiple, onChange, onSubmit, onSuccess, onError, onFilePreUpload, onUploadRemove, retryDelays, }: FileSelectorProps) => react_jsx_runtime.JSX.Element;
3
108
 
4
109
  type UploadProgressBarProps = {
5
110
  /**
@@ -21,4 +126,4 @@ type UploadProgressBarProps = {
21
126
  };
22
127
  declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
23
128
 
24
- export { UploadProgressBar, type UploadProgressBarProps };
129
+ export { FileSelector, type FileSelectorProps, UploadProgressBar, type UploadProgressBarProps };
package/dist/index.js CHANGED
@@ -1,8 +1,39 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
3
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
5
10
  var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __objRest = (source, exclude) => {
26
+ var target = {};
27
+ for (var prop in source)
28
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
+ target[prop] = source[prop];
30
+ if (source != null && __getOwnPropSymbols)
31
+ for (var prop of __getOwnPropSymbols(source)) {
32
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
+ target[prop] = source[prop];
34
+ }
35
+ return target;
36
+ };
6
37
  var __export = (target, all) => {
7
38
  for (var name in all)
8
39
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,24 +46,243 @@ var __copyProps = (to, from, except, desc) => {
15
46
  }
16
47
  return to;
17
48
  };
49
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
+ // If the importer is in node compatibility mode or this is not an ESM
51
+ // file that has been converted to a CommonJS file using a Babel-
52
+ // compatible transform (i.e. "__esModule" has not been set), then set
53
+ // "default" to the CommonJS "module.exports" for node compatibility.
54
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
55
+ mod
56
+ ));
18
57
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
58
 
20
59
  // src/index.ts
21
60
  var src_exports = {};
22
61
  __export(src_exports, {
62
+ FileSelector: () => FileSelector,
23
63
  UploadProgressBar: () => UploadProgressBar
24
64
  });
25
65
  module.exports = __toCommonJS(src_exports);
26
66
 
27
- // src/lib/UploadProgressBar.tsx
67
+ // src/lib/FileSelector.tsx
68
+ var import_react3 = require("react");
69
+ var import_react_hook_form3 = require("react-hook-form");
70
+ var import_react_query2 = require("@tanstack/react-query");
71
+ var import_mui_button2 = require("@availity/mui-button");
72
+ var import_mui_layout3 = require("@availity/mui-layout");
73
+ var import_mui_typography4 = require("@availity/mui-typography");
74
+
75
+ // src/lib/Dropzone.tsx
28
76
  var import_react = require("react");
29
- var import_mui_progress = require("@availity/mui-progress");
30
- var import_mui_typography = require("@availity/mui-typography");
77
+ var import_react_dropzone = require("react-dropzone");
78
+ var import_mui_divider = require("@availity/mui-divider");
31
79
  var import_mui_icon = require("@availity/mui-icon");
80
+ var import_mui_layout = require("@availity/mui-layout");
81
+ var import_mui_typography = require("@availity/mui-typography");
82
+
83
+ // src/lib/FilePickerBtn.tsx
84
+ var import_react_hook_form = require("react-hook-form");
85
+ var import_mui_button = require("@availity/mui-button");
86
+ var import_mui_form_utils = require("@availity/mui-form-utils");
32
87
  var import_jsx_runtime = require("react/jsx-runtime");
88
+ var FilePickerBtn = (_a) => {
89
+ var _b = _a, {
90
+ name,
91
+ children = "Browse Files",
92
+ color,
93
+ inputId,
94
+ inputProps = {},
95
+ maxSize,
96
+ onChange,
97
+ onClick
98
+ } = _b, rest = __objRest(_b, [
99
+ "name",
100
+ "children",
101
+ "color",
102
+ "inputId",
103
+ "inputProps",
104
+ "maxSize",
105
+ "onChange",
106
+ "onClick"
107
+ ]);
108
+ const { register } = (0, import_react_hook_form.useFormContext)();
109
+ const { accept, multiple, ref, style, type: inputType } = inputProps;
110
+ const field = register(name);
111
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ import_mui_form_utils.Input,
114
+ __spreadProps(__spreadValues({}, field), {
115
+ onChange,
116
+ value: "",
117
+ inputRef: ref,
118
+ type: inputType,
119
+ sx: style,
120
+ inputProps: {
121
+ accept,
122
+ size: maxSize != null ? maxSize : void 0,
123
+ multiple
124
+ },
125
+ id: inputId
126
+ })
127
+ ),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mui_button.Button, __spreadProps(__spreadValues({ color }, rest), { onClick, fullWidth: false, children }))
129
+ ] });
130
+ };
131
+
132
+ // src/lib/Dropzone.tsx
133
+ var import_react_hook_form2 = require("react-hook-form");
134
+ var import_jsx_runtime2 = require("react/jsx-runtime");
135
+ var outerBoxStyles = {
136
+ backgroundColor: "background.canvas",
137
+ border: "1px dotted",
138
+ borderRadius: "4px",
139
+ padding: "2rem"
140
+ };
141
+ var innerBoxStyles = {
142
+ width: "100%",
143
+ height: "100%"
144
+ };
145
+ var createCounter = () => {
146
+ let id = 0;
147
+ const increment = () => id += 1;
148
+ return {
149
+ id,
150
+ increment
151
+ };
152
+ };
153
+ var counter = createCounter();
154
+ var Dropzone = ({
155
+ allowedFileTypes = [],
156
+ disabled,
157
+ maxFiles,
158
+ maxSize,
159
+ multiple,
160
+ name,
161
+ onChange,
162
+ onClick,
163
+ setFileRejections,
164
+ setTotalSize
165
+ }) => {
166
+ const { setValue, watch } = (0, import_react_hook_form2.useFormContext)();
167
+ const validator = (0, import_react.useCallback)(
168
+ (file) => {
169
+ var _a;
170
+ const previous = (_a = watch(name)) != null ? _a : [];
171
+ const isDuplicate = previous.some((prev) => prev.name === file.name);
172
+ if (isDuplicate) {
173
+ return {
174
+ code: "duplicate-name",
175
+ message: "A file with this name already exists"
176
+ };
177
+ }
178
+ const hasMaxFiles = maxFiles && previous.length >= maxFiles;
179
+ if (hasMaxFiles) {
180
+ return {
181
+ code: "too-many-files",
182
+ message: `Too many files. You may only upload ${maxFiles} file(s).`
183
+ };
184
+ }
185
+ return null;
186
+ },
187
+ [maxFiles]
188
+ );
189
+ const onDrop = (0, import_react.useCallback)(
190
+ (acceptedFiles, fileRejections) => {
191
+ var _a;
192
+ let newSize = 0;
193
+ for (const file of acceptedFiles) {
194
+ newSize += file.size;
195
+ }
196
+ setTotalSize((prev) => prev + newSize);
197
+ const previous = (_a = watch(name)) != null ? _a : [];
198
+ setValue(name, previous.concat(acceptedFiles));
199
+ if (fileRejections.length > 0) {
200
+ for (const rejection of fileRejections) {
201
+ rejection.id = counter.increment();
202
+ }
203
+ }
204
+ if (setFileRejections)
205
+ setFileRejections(fileRejections);
206
+ },
207
+ [setFileRejections]
208
+ );
209
+ const accept = allowedFileTypes.join(",");
210
+ const { getRootProps, getInputProps } = (0, import_react_dropzone.useDropzone)({
211
+ onDrop,
212
+ maxSize,
213
+ maxFiles,
214
+ disabled,
215
+ multiple,
216
+ accept,
217
+ validator
218
+ });
219
+ const inputProps = getInputProps({
220
+ multiple,
221
+ accept,
222
+ onChange
223
+ });
224
+ const handleOnChange = (event) => {
225
+ if (inputProps.onChange) {
226
+ inputProps.onChange(event);
227
+ }
228
+ };
229
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_layout.Box, __spreadProps(__spreadValues({ sx: outerBoxStyles }, getRootProps()), { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_layout.Box, { sx: innerBoxStyles, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_layout.Stack, { spacing: 2, divider: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_divider.Divider, { children: "OR" }), alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_icon.CloudDownloadIcon, { fontSize: "xlarge", color: "secondary" }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_mui_typography.Typography, { variant: "subtitle2", fontWeight: "700", children: "Drag and Drop Files Here" }),
232
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
233
+ FilePickerBtn,
234
+ {
235
+ name,
236
+ color: "primary",
237
+ disabled,
238
+ maxSize,
239
+ onClick,
240
+ inputProps,
241
+ onChange: handleOnChange
242
+ }
243
+ )
244
+ ] }) }) }) }));
245
+ };
246
+
247
+ // src/lib/ErrorAlert.tsx
248
+ var import_mui_alert = require("@availity/mui-alert");
249
+ var import_jsx_runtime3 = require("react/jsx-runtime");
250
+ var codes = {
251
+ "file-too-large": "File exceeds maximum size",
252
+ "file-invalid-type": "File has an invalid type",
253
+ "file-too-small": "File is smaller than minimum size",
254
+ "too-many-file": "Too many files",
255
+ "duplicate-name": "Duplicate file selected"
256
+ };
257
+ var ErrorAlert = ({ errors, fileName, id, onClose }) => {
258
+ if (errors.length === 0)
259
+ return null;
260
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: errors.map((error) => {
261
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_mui_alert.Alert, { severity: "error", onClose, children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_mui_alert.AlertTitle, { children: [
263
+ codes[error.code] || "Error",
264
+ ": ",
265
+ fileName
266
+ ] }),
267
+ error.message
268
+ ] }, `${id}-${error.code}`);
269
+ }) });
270
+ };
271
+
272
+ // src/lib/FileList.tsx
273
+ var import_mui_list = require("@availity/mui-list");
274
+ var import_mui_icon3 = require("@availity/mui-icon");
275
+ var import_mui_layout2 = require("@availity/mui-layout");
276
+
277
+ // src/lib/UploadProgressBar.tsx
278
+ var import_react2 = require("react");
279
+ var import_mui_progress = require("@availity/mui-progress");
280
+ var import_mui_typography2 = require("@availity/mui-typography");
281
+ var import_mui_icon2 = require("@availity/mui-icon");
282
+ var import_jsx_runtime4 = require("react/jsx-runtime");
33
283
  var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
34
- const [statePercentage, setStatePercentage] = (0, import_react.useState)(upload.percentage || 0);
35
- const [error, setError] = (0, import_react.useState)(false);
284
+ const [statePercentage, setStatePercentage] = (0, import_react2.useState)(upload.percentage || 0);
285
+ const [error, setError] = (0, import_react2.useState)(false);
36
286
  const handleOnProgress = () => {
37
287
  setStatePercentage(upload.percentage);
38
288
  setError(false);
@@ -53,13 +303,226 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
53
303
  upload.onProgress.push(handleOnProgress);
54
304
  upload.onSuccess.push(handleOnSuccess);
55
305
  upload.onError.push(handleOnError);
56
- return upload.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_mui_typography.Typography, { color: "text.error", children: [
57
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mui_icon.WarningTriangleIcon, {}),
306
+ return upload.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_mui_typography2.Typography, { color: "text.error", children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_mui_icon2.WarningTriangleIcon, {}),
58
308
  " ",
59
309
  upload.errorMessage
60
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mui_progress.LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
310
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_mui_progress.LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
311
+ };
312
+
313
+ // src/lib/util.ts
314
+ function formatBytes(bytes, decimals = 2) {
315
+ if (!+bytes)
316
+ return "0 Bytes";
317
+ const k = 1024;
318
+ const dm = decimals < 0 ? 0 : decimals;
319
+ const sizes = ["Bytes", "KB", "MB", "GB"];
320
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
321
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
322
+ }
323
+ var FILE_EXT_ICONS = {
324
+ png: "file-image",
325
+ jpg: "file-image",
326
+ jpeg: "file-image",
327
+ gif: "file-image",
328
+ ppt: "file-powerpoint",
329
+ pptx: "file-powerpoint",
330
+ xls: "file-excel",
331
+ xlsx: "file-excel",
332
+ doc: "file-word",
333
+ docx: "file-word",
334
+ txt: "doc-alt",
335
+ text: "doc-alt",
336
+ zip: "file-archive",
337
+ "7zip": "file-archive",
338
+ xml: "file-code",
339
+ html: "file-code",
340
+ pdf: "file-pdf"
341
+ };
342
+ var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
343
+ var getFileExtIcon = (fileName) => {
344
+ var _a;
345
+ const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
346
+ const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : "doc";
347
+ return { ext, icon };
348
+ };
349
+
350
+ // src/lib/useUploadCore.tsx
351
+ var import_react_query = require("@tanstack/react-query");
352
+ var import_upload_core = __toESM(require("@availity/upload-core"));
353
+ function startUpload(file, options) {
354
+ const upload = new import_upload_core.default(file, options);
355
+ upload.start();
356
+ return upload;
357
+ }
358
+ function useUploadCore(file, options) {
359
+ const isQueryEnabled = !!file;
360
+ return (0, import_react_query.useQuery)(["upload", file.name, options], () => startUpload(file, options), {
361
+ enabled: isQueryEnabled,
362
+ retry: false
363
+ });
364
+ }
365
+
366
+ // src/lib/FileList.tsx
367
+ var import_jsx_runtime5 = require("react/jsx-runtime");
368
+ var FileRow = ({ file, options, onRemoveFile }) => {
369
+ const { ext, icon } = getFileExtIcon(file.name);
370
+ console.log("ext, icon:", ext, icon);
371
+ const { data: upload } = useUploadCore(file, options);
372
+ if (!upload)
373
+ return null;
374
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_mui_layout2.Grid, { container: true, spacing: 2, alignItems: "center", justifyContent: "space-between", width: "100%", children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_layout2.Grid, { xs: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_list.ListItemIcon, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_icon3.FileIcon, {}) }) }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_layout2.Grid, { xs: 4, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_list.ListItemText, { children: upload.trimFileName(upload.file.name) }) }),
377
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_layout2.Grid, { xs: 2, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_list.ListItem, { children: formatBytes(upload.file.size) }) }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_layout2.Grid, { xs: 4, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UploadProgressBar, { upload }) }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_layout2.Grid, { xs: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
380
+ import_mui_list.ListItemButton,
381
+ {
382
+ onClick: () => {
383
+ onRemoveFile(upload.id, upload);
384
+ },
385
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_list.ListItemIcon, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_icon3.DeleteIcon, {}) })
386
+ }
387
+ ) })
388
+ ] }) });
389
+ };
390
+ var FileList = ({ files, options, onRemoveFile }) => {
391
+ if (files.length === 0)
392
+ return null;
393
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_mui_list.List, { children: files.map((file) => {
394
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FileRow, { file, options, onRemoveFile }, file.name);
395
+ }) });
396
+ };
397
+
398
+ // src/lib/FileTypesMessage.tsx
399
+ var import_mui_typography3 = require("@availity/mui-typography");
400
+ var import_jsx_runtime6 = require("react/jsx-runtime");
401
+ var FileTypesMessage = ({ allowedFileTypes, maxFileSize }) => {
402
+ const fileSizeMsg = typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null;
403
+ const fileTypesMsg = allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}.` : "All file types allowed.";
404
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_mui_typography3.Typography, { variant: "caption", children: [
405
+ fileSizeMsg,
406
+ fileTypesMsg
407
+ ] });
408
+ };
409
+
410
+ // src/lib/FileSelector.tsx
411
+ var import_jsx_runtime7 = require("react/jsx-runtime");
412
+ var CLOUD_URL = "/cloud/web/appl/vault/upload/v1/resumable";
413
+ var FileSelector = ({
414
+ name,
415
+ allowedFileNameCharacters,
416
+ allowedFileTypes = [],
417
+ bucketId,
418
+ clientId,
419
+ children,
420
+ customerId,
421
+ disabled = false,
422
+ endpoint,
423
+ isCloud,
424
+ label = "Upload file",
425
+ maxFiles,
426
+ maxSize,
427
+ multiple = true,
428
+ onChange,
429
+ onSubmit,
430
+ onSuccess,
431
+ onError,
432
+ onFilePreUpload = [],
433
+ onUploadRemove,
434
+ retryDelays
435
+ }) => {
436
+ const [totalSize, setTotalSize] = (0, import_react3.useState)(0);
437
+ const [fileRejections, setFileRejections] = (0, import_react3.useState)([]);
438
+ const client = (0, import_react_query2.useQueryClient)();
439
+ const methods = (0, import_react_hook_form3.useForm)({
440
+ defaultValues: {
441
+ [name]: []
442
+ }
443
+ });
444
+ const options = {
445
+ bucketId,
446
+ customerId,
447
+ clientId,
448
+ fileTypes: allowedFileTypes,
449
+ maxSize,
450
+ allowedFileNameCharacters,
451
+ onError,
452
+ onSuccess,
453
+ retryDelays
454
+ };
455
+ if (onFilePreUpload)
456
+ options.onPreStart = onFilePreUpload;
457
+ if (endpoint)
458
+ options.endpoint = endpoint;
459
+ if (isCloud)
460
+ options.endpoint = CLOUD_URL;
461
+ const handleOnRemoveFile = (uploadId, upload) => {
462
+ const prevFiles = methods.watch(name);
463
+ const newFiles = prevFiles.filter((file) => file.name !== upload.file.name);
464
+ if (newFiles.length !== prevFiles.length) {
465
+ const removedFile = prevFiles.find((file) => file.name === upload.file.name);
466
+ methods.setValue(name, newFiles);
467
+ if (removedFile == null ? void 0 : removedFile.size)
468
+ setTotalSize(totalSize - removedFile.size);
469
+ if (onUploadRemove)
470
+ onUploadRemove(newFiles, uploadId);
471
+ }
472
+ };
473
+ const files = methods.watch(name);
474
+ const handleOnSubmit = (values) => {
475
+ if (values[name].length === 0)
476
+ return;
477
+ const queries = client.getQueriesData(["upload"]);
478
+ const uploads = [];
479
+ for (const [, data] of queries) {
480
+ if (data)
481
+ uploads.push(data);
482
+ }
483
+ if (onSubmit)
484
+ onSubmit(uploads, values);
485
+ };
486
+ const handleRemoveRejection = (id) => {
487
+ const rejections = fileRejections.filter((value) => value.id !== id);
488
+ setFileRejections(rejections);
489
+ };
490
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_hook_form3.FormProvider, __spreadProps(__spreadValues({}, methods), { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit: methods.handleSubmit(handleOnSubmit), children: [
491
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_mui_typography4.Typography, { children: label }),
493
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
494
+ Dropzone,
495
+ {
496
+ name,
497
+ allowedFileTypes,
498
+ disabled,
499
+ maxFiles,
500
+ maxSize,
501
+ multiple,
502
+ onChange,
503
+ setFileRejections,
504
+ setTotalSize
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FileTypesMessage, { allowedFileTypes, maxFileSize: maxSize })
508
+ ] }),
509
+ children,
510
+ fileRejections.length > 0 ? fileRejections.map((rejection) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
511
+ ErrorAlert,
512
+ {
513
+ errors: rejection.errors,
514
+ fileName: rejection.file.name,
515
+ id: rejection.id,
516
+ onClose: () => handleRemoveRejection(rejection.id)
517
+ },
518
+ rejection.id
519
+ )) : null,
520
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FileList, { files, options, onRemoveFile: handleOnRemoveFile }),
521
+ files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_mui_layout3.Grid, { xs: 12, justifyContent: "end", display: "flex", paddingTop: 2.5, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_mui_button2.Button, { type: "submit", sx: { marginLeft: "auto", marginRight: 0 }, children: "Submit" }) })
522
+ ] }) }));
61
523
  };
62
524
  // Annotate the CommonJS export names for ESM import in node:
63
525
  0 && (module.exports = {
526
+ FileSelector,
64
527
  UploadProgressBar
65
528
  });
package/dist/index.mjs CHANGED
@@ -1,9 +1,251 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+
33
+ // src/lib/FileSelector.tsx
34
+ import { useState as useState2 } from "react";
35
+ import { useForm, FormProvider } from "react-hook-form";
36
+ import { useQueryClient } from "@tanstack/react-query";
37
+ import { Button as Button2 } from "@availity/mui-button";
38
+ import { Grid as Grid2 } from "@availity/mui-layout";
39
+ import { Typography as Typography4 } from "@availity/mui-typography";
40
+
41
+ // src/lib/Dropzone.tsx
42
+ import { useCallback } from "react";
43
+ import { useDropzone } from "react-dropzone";
44
+ import { Divider } from "@availity/mui-divider";
45
+ import { CloudDownloadIcon } from "@availity/mui-icon";
46
+ import { Box, Stack } from "@availity/mui-layout";
47
+ import { Typography } from "@availity/mui-typography";
48
+
49
+ // src/lib/FilePickerBtn.tsx
50
+ import { useFormContext } from "react-hook-form";
51
+ import { Button } from "@availity/mui-button";
52
+ import { Input } from "@availity/mui-form-utils";
53
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
54
+ var FilePickerBtn = (_a) => {
55
+ var _b = _a, {
56
+ name,
57
+ children = "Browse Files",
58
+ color,
59
+ inputId,
60
+ inputProps = {},
61
+ maxSize,
62
+ onChange,
63
+ onClick
64
+ } = _b, rest = __objRest(_b, [
65
+ "name",
66
+ "children",
67
+ "color",
68
+ "inputId",
69
+ "inputProps",
70
+ "maxSize",
71
+ "onChange",
72
+ "onClick"
73
+ ]);
74
+ const { register } = useFormContext();
75
+ const { accept, multiple, ref, style, type: inputType } = inputProps;
76
+ const field = register(name);
77
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
78
+ /* @__PURE__ */ jsx(
79
+ Input,
80
+ __spreadProps(__spreadValues({}, field), {
81
+ onChange,
82
+ value: "",
83
+ inputRef: ref,
84
+ type: inputType,
85
+ sx: style,
86
+ inputProps: {
87
+ accept,
88
+ size: maxSize != null ? maxSize : void 0,
89
+ multiple
90
+ },
91
+ id: inputId
92
+ })
93
+ ),
94
+ /* @__PURE__ */ jsx(Button, __spreadProps(__spreadValues({ color }, rest), { onClick, fullWidth: false, children }))
95
+ ] });
96
+ };
97
+
98
+ // src/lib/Dropzone.tsx
99
+ import { useFormContext as useFormContext2 } from "react-hook-form";
100
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
101
+ var outerBoxStyles = {
102
+ backgroundColor: "background.canvas",
103
+ border: "1px dotted",
104
+ borderRadius: "4px",
105
+ padding: "2rem"
106
+ };
107
+ var innerBoxStyles = {
108
+ width: "100%",
109
+ height: "100%"
110
+ };
111
+ var createCounter = () => {
112
+ let id = 0;
113
+ const increment = () => id += 1;
114
+ return {
115
+ id,
116
+ increment
117
+ };
118
+ };
119
+ var counter = createCounter();
120
+ var Dropzone = ({
121
+ allowedFileTypes = [],
122
+ disabled,
123
+ maxFiles,
124
+ maxSize,
125
+ multiple,
126
+ name,
127
+ onChange,
128
+ onClick,
129
+ setFileRejections,
130
+ setTotalSize
131
+ }) => {
132
+ const { setValue, watch } = useFormContext2();
133
+ const validator = useCallback(
134
+ (file) => {
135
+ var _a;
136
+ const previous = (_a = watch(name)) != null ? _a : [];
137
+ const isDuplicate = previous.some((prev) => prev.name === file.name);
138
+ if (isDuplicate) {
139
+ return {
140
+ code: "duplicate-name",
141
+ message: "A file with this name already exists"
142
+ };
143
+ }
144
+ const hasMaxFiles = maxFiles && previous.length >= maxFiles;
145
+ if (hasMaxFiles) {
146
+ return {
147
+ code: "too-many-files",
148
+ message: `Too many files. You may only upload ${maxFiles} file(s).`
149
+ };
150
+ }
151
+ return null;
152
+ },
153
+ [maxFiles]
154
+ );
155
+ const onDrop = useCallback(
156
+ (acceptedFiles, fileRejections) => {
157
+ var _a;
158
+ let newSize = 0;
159
+ for (const file of acceptedFiles) {
160
+ newSize += file.size;
161
+ }
162
+ setTotalSize((prev) => prev + newSize);
163
+ const previous = (_a = watch(name)) != null ? _a : [];
164
+ setValue(name, previous.concat(acceptedFiles));
165
+ if (fileRejections.length > 0) {
166
+ for (const rejection of fileRejections) {
167
+ rejection.id = counter.increment();
168
+ }
169
+ }
170
+ if (setFileRejections)
171
+ setFileRejections(fileRejections);
172
+ },
173
+ [setFileRejections]
174
+ );
175
+ const accept = allowedFileTypes.join(",");
176
+ const { getRootProps, getInputProps } = useDropzone({
177
+ onDrop,
178
+ maxSize,
179
+ maxFiles,
180
+ disabled,
181
+ multiple,
182
+ accept,
183
+ validator
184
+ });
185
+ const inputProps = getInputProps({
186
+ multiple,
187
+ accept,
188
+ onChange
189
+ });
190
+ const handleOnChange = (event) => {
191
+ if (inputProps.onChange) {
192
+ inputProps.onChange(event);
193
+ }
194
+ };
195
+ return /* @__PURE__ */ jsx2(Box, __spreadProps(__spreadValues({ sx: outerBoxStyles }, getRootProps()), { children: /* @__PURE__ */ jsx2(Box, { sx: innerBoxStyles, children: /* @__PURE__ */ jsx2(Stack, { spacing: 2, divider: /* @__PURE__ */ jsx2(Divider, { children: "OR" }), alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs2(Fragment2, { children: [
196
+ /* @__PURE__ */ jsx2(CloudDownloadIcon, { fontSize: "xlarge", color: "secondary" }),
197
+ /* @__PURE__ */ jsx2(Typography, { variant: "subtitle2", fontWeight: "700", children: "Drag and Drop Files Here" }),
198
+ /* @__PURE__ */ jsx2(
199
+ FilePickerBtn,
200
+ {
201
+ name,
202
+ color: "primary",
203
+ disabled,
204
+ maxSize,
205
+ onClick,
206
+ inputProps,
207
+ onChange: handleOnChange
208
+ }
209
+ )
210
+ ] }) }) }) }));
211
+ };
212
+
213
+ // src/lib/ErrorAlert.tsx
214
+ import { Alert, AlertTitle } from "@availity/mui-alert";
215
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
216
+ var codes = {
217
+ "file-too-large": "File exceeds maximum size",
218
+ "file-invalid-type": "File has an invalid type",
219
+ "file-too-small": "File is smaller than minimum size",
220
+ "too-many-file": "Too many files",
221
+ "duplicate-name": "Duplicate file selected"
222
+ };
223
+ var ErrorAlert = ({ errors, fileName, id, onClose }) => {
224
+ if (errors.length === 0)
225
+ return null;
226
+ return /* @__PURE__ */ jsx3(Fragment3, { children: errors.map((error) => {
227
+ return /* @__PURE__ */ jsxs3(Alert, { severity: "error", onClose, children: [
228
+ /* @__PURE__ */ jsxs3(AlertTitle, { children: [
229
+ codes[error.code] || "Error",
230
+ ": ",
231
+ fileName
232
+ ] }),
233
+ error.message
234
+ ] }, `${id}-${error.code}`);
235
+ }) });
236
+ };
237
+
238
+ // src/lib/FileList.tsx
239
+ import { List, ListItem, ListItemText, ListItemIcon, ListItemButton } from "@availity/mui-list";
240
+ import { DeleteIcon, FileIcon } from "@availity/mui-icon";
241
+ import { Grid } from "@availity/mui-layout";
242
+
1
243
  // src/lib/UploadProgressBar.tsx
2
244
  import { useState } from "react";
3
245
  import { LinearProgress } from "@availity/mui-progress";
4
- import { Typography } from "@availity/mui-typography";
246
+ import { Typography as Typography2 } from "@availity/mui-typography";
5
247
  import { WarningTriangleIcon } from "@availity/mui-icon";
6
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
248
+ import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
7
249
  var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
8
250
  const [statePercentage, setStatePercentage] = useState(upload.percentage || 0);
9
251
  const [error, setError] = useState(false);
@@ -27,12 +269,225 @@ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
27
269
  upload.onProgress.push(handleOnProgress);
28
270
  upload.onSuccess.push(handleOnSuccess);
29
271
  upload.onError.push(handleOnError);
30
- return upload.errorMessage ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Typography, { color: "text.error", children: [
31
- /* @__PURE__ */ jsx(WarningTriangleIcon, {}),
272
+ return upload.errorMessage ? /* @__PURE__ */ jsx4(Fragment4, { children: /* @__PURE__ */ jsxs4(Typography2, { color: "text.error", children: [
273
+ /* @__PURE__ */ jsx4(WarningTriangleIcon, {}),
32
274
  " ",
33
275
  upload.errorMessage
34
- ] }) }) : /* @__PURE__ */ jsx(LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
276
+ ] }) }) : /* @__PURE__ */ jsx4(LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
277
+ };
278
+
279
+ // src/lib/util.ts
280
+ function formatBytes(bytes, decimals = 2) {
281
+ if (!+bytes)
282
+ return "0 Bytes";
283
+ const k = 1024;
284
+ const dm = decimals < 0 ? 0 : decimals;
285
+ const sizes = ["Bytes", "KB", "MB", "GB"];
286
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
287
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
288
+ }
289
+ var FILE_EXT_ICONS = {
290
+ png: "file-image",
291
+ jpg: "file-image",
292
+ jpeg: "file-image",
293
+ gif: "file-image",
294
+ ppt: "file-powerpoint",
295
+ pptx: "file-powerpoint",
296
+ xls: "file-excel",
297
+ xlsx: "file-excel",
298
+ doc: "file-word",
299
+ docx: "file-word",
300
+ txt: "doc-alt",
301
+ text: "doc-alt",
302
+ zip: "file-archive",
303
+ "7zip": "file-archive",
304
+ xml: "file-code",
305
+ html: "file-code",
306
+ pdf: "file-pdf"
307
+ };
308
+ var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
309
+ var getFileExtIcon = (fileName) => {
310
+ var _a;
311
+ const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
312
+ const icon = isValidKey(ext) ? FILE_EXT_ICONS[ext] : "doc";
313
+ return { ext, icon };
314
+ };
315
+
316
+ // src/lib/useUploadCore.tsx
317
+ import { useQuery } from "@tanstack/react-query";
318
+ import Upload from "@availity/upload-core";
319
+ function startUpload(file, options) {
320
+ const upload = new Upload(file, options);
321
+ upload.start();
322
+ return upload;
323
+ }
324
+ function useUploadCore(file, options) {
325
+ const isQueryEnabled = !!file;
326
+ return useQuery(["upload", file.name, options], () => startUpload(file, options), {
327
+ enabled: isQueryEnabled,
328
+ retry: false
329
+ });
330
+ }
331
+
332
+ // src/lib/FileList.tsx
333
+ import { Fragment as Fragment5, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
334
+ var FileRow = ({ file, options, onRemoveFile }) => {
335
+ const { ext, icon } = getFileExtIcon(file.name);
336
+ console.log("ext, icon:", ext, icon);
337
+ const { data: upload } = useUploadCore(file, options);
338
+ if (!upload)
339
+ return null;
340
+ return /* @__PURE__ */ jsx5(Fragment5, { children: /* @__PURE__ */ jsxs5(Grid, { container: true, spacing: 2, alignItems: "center", justifyContent: "space-between", width: "100%", children: [
341
+ /* @__PURE__ */ jsx5(Grid, { xs: 1, children: /* @__PURE__ */ jsx5(ListItemIcon, { children: /* @__PURE__ */ jsx5(FileIcon, {}) }) }),
342
+ /* @__PURE__ */ jsx5(Grid, { xs: 4, children: /* @__PURE__ */ jsx5(ListItemText, { children: upload.trimFileName(upload.file.name) }) }),
343
+ /* @__PURE__ */ jsx5(Grid, { xs: 2, children: /* @__PURE__ */ jsx5(ListItem, { children: formatBytes(upload.file.size) }) }),
344
+ /* @__PURE__ */ jsx5(Grid, { xs: 4, children: /* @__PURE__ */ jsx5(UploadProgressBar, { upload }) }),
345
+ /* @__PURE__ */ jsx5(Grid, { xs: 1, children: /* @__PURE__ */ jsx5(
346
+ ListItemButton,
347
+ {
348
+ onClick: () => {
349
+ onRemoveFile(upload.id, upload);
350
+ },
351
+ children: /* @__PURE__ */ jsx5(ListItemIcon, { children: /* @__PURE__ */ jsx5(DeleteIcon, {}) })
352
+ }
353
+ ) })
354
+ ] }) });
355
+ };
356
+ var FileList = ({ files, options, onRemoveFile }) => {
357
+ if (files.length === 0)
358
+ return null;
359
+ return /* @__PURE__ */ jsx5(List, { children: files.map((file) => {
360
+ return /* @__PURE__ */ jsx5(FileRow, { file, options, onRemoveFile }, file.name);
361
+ }) });
362
+ };
363
+
364
+ // src/lib/FileTypesMessage.tsx
365
+ import { Typography as Typography3 } from "@availity/mui-typography";
366
+ import { jsxs as jsxs6 } from "react/jsx-runtime";
367
+ var FileTypesMessage = ({ allowedFileTypes, maxFileSize }) => {
368
+ const fileSizeMsg = typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null;
369
+ const fileTypesMsg = allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}.` : "All file types allowed.";
370
+ return /* @__PURE__ */ jsxs6(Typography3, { variant: "caption", children: [
371
+ fileSizeMsg,
372
+ fileTypesMsg
373
+ ] });
374
+ };
375
+
376
+ // src/lib/FileSelector.tsx
377
+ import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
378
+ var CLOUD_URL = "/cloud/web/appl/vault/upload/v1/resumable";
379
+ var FileSelector = ({
380
+ name,
381
+ allowedFileNameCharacters,
382
+ allowedFileTypes = [],
383
+ bucketId,
384
+ clientId,
385
+ children,
386
+ customerId,
387
+ disabled = false,
388
+ endpoint,
389
+ isCloud,
390
+ label = "Upload file",
391
+ maxFiles,
392
+ maxSize,
393
+ multiple = true,
394
+ onChange,
395
+ onSubmit,
396
+ onSuccess,
397
+ onError,
398
+ onFilePreUpload = [],
399
+ onUploadRemove,
400
+ retryDelays
401
+ }) => {
402
+ const [totalSize, setTotalSize] = useState2(0);
403
+ const [fileRejections, setFileRejections] = useState2([]);
404
+ const client = useQueryClient();
405
+ const methods = useForm({
406
+ defaultValues: {
407
+ [name]: []
408
+ }
409
+ });
410
+ const options = {
411
+ bucketId,
412
+ customerId,
413
+ clientId,
414
+ fileTypes: allowedFileTypes,
415
+ maxSize,
416
+ allowedFileNameCharacters,
417
+ onError,
418
+ onSuccess,
419
+ retryDelays
420
+ };
421
+ if (onFilePreUpload)
422
+ options.onPreStart = onFilePreUpload;
423
+ if (endpoint)
424
+ options.endpoint = endpoint;
425
+ if (isCloud)
426
+ options.endpoint = CLOUD_URL;
427
+ const handleOnRemoveFile = (uploadId, upload) => {
428
+ const prevFiles = methods.watch(name);
429
+ const newFiles = prevFiles.filter((file) => file.name !== upload.file.name);
430
+ if (newFiles.length !== prevFiles.length) {
431
+ const removedFile = prevFiles.find((file) => file.name === upload.file.name);
432
+ methods.setValue(name, newFiles);
433
+ if (removedFile == null ? void 0 : removedFile.size)
434
+ setTotalSize(totalSize - removedFile.size);
435
+ if (onUploadRemove)
436
+ onUploadRemove(newFiles, uploadId);
437
+ }
438
+ };
439
+ const files = methods.watch(name);
440
+ const handleOnSubmit = (values) => {
441
+ if (values[name].length === 0)
442
+ return;
443
+ const queries = client.getQueriesData(["upload"]);
444
+ const uploads = [];
445
+ for (const [, data] of queries) {
446
+ if (data)
447
+ uploads.push(data);
448
+ }
449
+ if (onSubmit)
450
+ onSubmit(uploads, values);
451
+ };
452
+ const handleRemoveRejection = (id) => {
453
+ const rejections = fileRejections.filter((value) => value.id !== id);
454
+ setFileRejections(rejections);
455
+ };
456
+ return /* @__PURE__ */ jsx6(FormProvider, __spreadProps(__spreadValues({}, methods), { children: /* @__PURE__ */ jsxs7("form", { onSubmit: methods.handleSubmit(handleOnSubmit), children: [
457
+ /* @__PURE__ */ jsxs7(Fragment6, { children: [
458
+ /* @__PURE__ */ jsx6(Typography4, { children: label }),
459
+ /* @__PURE__ */ jsx6(
460
+ Dropzone,
461
+ {
462
+ name,
463
+ allowedFileTypes,
464
+ disabled,
465
+ maxFiles,
466
+ maxSize,
467
+ multiple,
468
+ onChange,
469
+ setFileRejections,
470
+ setTotalSize
471
+ }
472
+ ),
473
+ /* @__PURE__ */ jsx6(FileTypesMessage, { allowedFileTypes, maxFileSize: maxSize })
474
+ ] }),
475
+ children,
476
+ fileRejections.length > 0 ? fileRejections.map((rejection) => /* @__PURE__ */ jsx6(
477
+ ErrorAlert,
478
+ {
479
+ errors: rejection.errors,
480
+ fileName: rejection.file.name,
481
+ id: rejection.id,
482
+ onClose: () => handleRemoveRejection(rejection.id)
483
+ },
484
+ rejection.id
485
+ )) : null,
486
+ /* @__PURE__ */ jsx6(FileList, { files, options, onRemoveFile: handleOnRemoveFile }),
487
+ files.length > 0 && /* @__PURE__ */ jsx6(Grid2, { xs: 12, justifyContent: "end", display: "flex", paddingTop: 2.5, children: /* @__PURE__ */ jsx6(Button2, { type: "submit", sx: { marginLeft: "auto", marginRight: 0 }, children: "Submit" }) })
488
+ ] }) }));
35
489
  };
36
490
  export {
491
+ FileSelector,
37
492
  UploadProgressBar
38
493
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@availity/mui-file-selector",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Availity MUI file-selector Component - part of the @availity/element design system",
5
5
  "keywords": [
6
6
  "react",
@@ -36,7 +36,7 @@
36
36
  "@availity/mui-alert": "^0.7.0",
37
37
  "@availity/mui-button": "^0.6.12",
38
38
  "@availity/mui-divider": "^0.4.0",
39
- "@availity/mui-form-utils": "^0.15.1",
39
+ "@availity/mui-form-utils": "^0.16.0",
40
40
  "@availity/mui-icon": "^0.11.1",
41
41
  "@availity/mui-layout": "^0.2.0",
42
42
  "@availity/mui-list": "^0.2.2",
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
+ export * from './lib/FileSelector';
1
2
  export * from './lib/UploadProgressBar';