@availity/mui-file-selector 0.1.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 ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## 0.1.0 (2024-10-31)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `mui-button` updated to version `0.1.0`
10
+ * `mui-divider` updated to version `0.1.0`
11
+ * `mui-form-utils` updated to version `0.1.0`
12
+ * `mui-icon` updated to version `0.1.0`
13
+ * `mui-layout` updated to version `0.1.0`
14
+ * `mui-list` updated to version `0.1.0`
15
+ * `mui-progress` updated to version `0.1.0`
16
+ * `mui-typography` updated to version `0.1.0`
17
+ * `mui-paper` updated to version `0.1.0`
18
+
19
+ ### Features
20
+
21
+ * add FileSelector ([7ad1f7b](https://github.com/Availity/element/commit/7ad1f7bb364bbeb2048d2ff4c9b0a2b1a1e33777))
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * **mui-file-selector:** update types ([477c992](https://github.com/Availity/element/commit/477c9921792ef35302b1b21d17056d16434c7d98))
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @availity/mui-file-selector
2
+
3
+ > Availity MUI File Selector component to be used with @availity/element design system.
4
+
5
+ [![Version](https://img.shields.io/npm/v/@availity/mui-file-selector.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-file-selector)
6
+ [![NPM Downloads](https://img.shields.io/npm/dt/@availity/mui-file-selector.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-file-selector)
7
+ [![Dependency Status](https://img.shields.io/librariesio/release/npm/@availity/mui-file-selector?style=for-the-badge)](https://github.com/Availity/element/blob/main/packages/mui-file-selector/package.json)
8
+
9
+ ## Documentation
10
+
11
+ This package extends the MUI File Selector component: [MUI File Selector Docs](https://mui.com/components/file-selector/)
12
+
13
+ Live demo and documentation in our [Storybook](https://availity.github.io/element/?path=/docs/components-file-selector-introduction--docs)
14
+
15
+ Availity standards for design and usage can be found in the [Availity Design Guide](https://zeroheight.com/2e36e50c7)
16
+
17
+ ## Installation
18
+
19
+ ### Import Through @availity/element (Recommended)
20
+
21
+ #### NPM
22
+
23
+ ```bash
24
+ npm install @availity/element
25
+ ```
26
+
27
+ #### Yarn
28
+
29
+ ```bash
30
+ yarn add @availity/element
31
+ ```
32
+
33
+ ### Direct Import
34
+
35
+ #### NPM
36
+
37
+ _This package has a few peer dependencies. Add `@mui/material` & `@emotion/react` to your project if not already installed._
38
+
39
+ ```bash
40
+ npm install @availity/mui-file-selector
41
+ ```
42
+
43
+ #### Yarn
44
+
45
+ ```bash
46
+ yarn add @availity/mui-file-selector
47
+ ```
48
+
49
+ ### Usage
50
+
51
+ #### Import through @availity/element
52
+
53
+ ```tsx
54
+ import { FileSelector } from '@availity/element';
55
+ ```
56
+
57
+ #### Direct import
58
+
59
+ ```tsx
60
+ import { FileSelector } from '@availity/mui-file-selector';
61
+ ```
@@ -0,0 +1,16 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import Upload from '@availity/upload-core';
3
+
4
+ type UploadProgressBarProps = {
5
+ /** The upload instance returned by creating a new Upload via @availity/upload-core. */
6
+ upload: Upload;
7
+ /** Callback function to hook into the onProgress within the Upload instance provided in the upload prop. */
8
+ onProgress?: (upload: Upload) => void;
9
+ /** Callback function to hook into the onSuccess within the Upload instance provided in the upload prop. */
10
+ onSuccess?: (upload: Upload) => void;
11
+ /** Callback function to hook into the onError within the Upload instance provided in the upload prop. */
12
+ onError?: (upload: Upload) => void;
13
+ };
14
+ declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
15
+
16
+ export { UploadProgressBar, type UploadProgressBarProps };
@@ -0,0 +1,16 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import Upload from '@availity/upload-core';
3
+
4
+ type UploadProgressBarProps = {
5
+ /** The upload instance returned by creating a new Upload via @availity/upload-core. */
6
+ upload: Upload;
7
+ /** Callback function to hook into the onProgress within the Upload instance provided in the upload prop. */
8
+ onProgress?: (upload: Upload) => void;
9
+ /** Callback function to hook into the onSuccess within the Upload instance provided in the upload prop. */
10
+ onSuccess?: (upload: Upload) => void;
11
+ /** Callback function to hook into the onError within the Upload instance provided in the upload prop. */
12
+ onError?: (upload: Upload) => void;
13
+ };
14
+ declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
15
+
16
+ export { UploadProgressBar, type UploadProgressBarProps };
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ UploadProgressBar: () => UploadProgressBar
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/lib/UploadProgressBar.tsx
28
+ var import_react = require("react");
29
+ var import_mui_progress = require("@availity/mui-progress");
30
+ var import_mui_typography = require("@availity/mui-typography");
31
+ var import_mui_icon = require("@availity/mui-icon");
32
+ var import_jsx_runtime = require("react/jsx-runtime");
33
+ 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);
36
+ const handleOnProgress = () => {
37
+ setStatePercentage(upload.percentage);
38
+ setError(false);
39
+ if (onProgress)
40
+ onProgress(upload);
41
+ };
42
+ const handleOnError = () => {
43
+ setError(true);
44
+ if (onError)
45
+ onError(upload);
46
+ };
47
+ const handleOnSuccess = () => {
48
+ setStatePercentage(100);
49
+ setError(false);
50
+ if (onSuccess)
51
+ onSuccess(upload);
52
+ };
53
+ upload.onProgress.push(handleOnProgress);
54
+ upload.onSuccess.push(handleOnSuccess);
55
+ 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, {}),
58
+ " ",
59
+ upload.errorMessage
60
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mui_progress.LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
61
+ };
62
+ // Annotate the CommonJS export names for ESM import in node:
63
+ 0 && (module.exports = {
64
+ UploadProgressBar
65
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,38 @@
1
+ // src/lib/UploadProgressBar.tsx
2
+ import { useState } from "react";
3
+ import { LinearProgress } from "@availity/mui-progress";
4
+ import { Typography } from "@availity/mui-typography";
5
+ import { WarningTriangleIcon } from "@availity/mui-icon";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ var UploadProgressBar = ({ upload, onProgress, onError, onSuccess }) => {
8
+ const [statePercentage, setStatePercentage] = useState(upload.percentage || 0);
9
+ const [error, setError] = useState(false);
10
+ const handleOnProgress = () => {
11
+ setStatePercentage(upload.percentage);
12
+ setError(false);
13
+ if (onProgress)
14
+ onProgress(upload);
15
+ };
16
+ const handleOnError = () => {
17
+ setError(true);
18
+ if (onError)
19
+ onError(upload);
20
+ };
21
+ const handleOnSuccess = () => {
22
+ setStatePercentage(100);
23
+ setError(false);
24
+ if (onSuccess)
25
+ onSuccess(upload);
26
+ };
27
+ upload.onProgress.push(handleOnProgress);
28
+ upload.onSuccess.push(handleOnSuccess);
29
+ upload.onError.push(handleOnError);
30
+ return upload.errorMessage ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Typography, { color: "text.error", children: [
31
+ /* @__PURE__ */ jsx(WarningTriangleIcon, {}),
32
+ " ",
33
+ upload.errorMessage
34
+ ] }) }) : /* @__PURE__ */ jsx(LinearProgress, { value: statePercentage, "aria-label": `${upload.file.name}-progress` });
35
+ };
36
+ export {
37
+ UploadProgressBar
38
+ };
@@ -0,0 +1,7 @@
1
+ import { Markdown } from '@storybook/blocks';
2
+ import { Meta } from '@storybook/addon-docs';
3
+ import ReadMe from './README.md?raw';
4
+
5
+ <Meta title="Components/File Selector/Introduction" />
6
+
7
+ <Markdown>{ReadMe}</Markdown>
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ const global = require('../../jest.config.global');
2
+
3
+ module.exports = {
4
+ ...global,
5
+ displayName: 'file-selector',
6
+ coverageDirectory: '../../coverage/file-selector',
7
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@availity/mui-file-selector",
3
+ "version": "0.1.0",
4
+ "description": "Availity MUI file-selector Component - part of the @availity/element design system",
5
+ "keywords": [
6
+ "react",
7
+ "typescript",
8
+ "availity",
9
+ "mui"
10
+ ],
11
+ "homepage": "https://availity.github.io/element/?path=/docs/components-file-selector-introduction--docs",
12
+ "bugs": {
13
+ "url": "https://github.com/Availity/element/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/Availity/element.git",
18
+ "directory": "packages/file-selector"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Availity Developers <AVOSS@availity.com>",
22
+ "browser": "./dist/index.js",
23
+ "main": "./dist/index.js",
24
+ "module": "./dist/index.mjs",
25
+ "types": "./dist/index.d.ts",
26
+ "scripts": {
27
+ "build": "tsup src/index.ts --format esm,cjs --dts",
28
+ "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
29
+ "clean": "rm -rf dist",
30
+ "clean:nm": "rm -rf node_modules",
31
+ "publish": "yarn npm publish --tolerate-republish --access public",
32
+ "publish:canary": "yarn npm publish --access public --tag canary"
33
+ },
34
+ "dependencies": {
35
+ "@availity/api-axios": "^9.0.4",
36
+ "@availity/mui-button": "^0.6.11",
37
+ "@availity/mui-divider": "^0.4.0",
38
+ "@availity/mui-form-utils": "^0.14.0",
39
+ "@availity/mui-icon": "^0.11.0",
40
+ "@availity/mui-layout": "^0.2.0",
41
+ "@availity/mui-list": "^0.2.0",
42
+ "@availity/mui-progress": "^0.4.0",
43
+ "@availity/mui-typography": "^0.2.1",
44
+ "@availity/upload-core": "^6.1.1",
45
+ "@tanstack/react-query": "^4.36.1",
46
+ "react-dropzone": "^11.7.1",
47
+ "react-hook-form": "^7.51.3",
48
+ "uuid": "^9.0.1"
49
+ },
50
+ "devDependencies": {
51
+ "@mui/material": "^5.15.15",
52
+ "@types/tus-js-client": "^1.8.0",
53
+ "react": "18.2.0",
54
+ "react-dom": "18.2.0",
55
+ "tsup": "^8.0.2",
56
+ "typescript": "^5.4.5"
57
+ },
58
+ "peerDependencies": {
59
+ "@mui/material": "^5.11.9",
60
+ "react": ">=16.3.0"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ }
65
+ }
package/project.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "mui-file-selector",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/file-selector/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "lint": {
9
+ "executor": "@nx/eslint:lint",
10
+ "options": {
11
+ "eslintConfig": ".eslintrc.json",
12
+ "silent": false,
13
+ "fix": false,
14
+ "cache": true,
15
+ "cacheLocation": "./node_modules/.cache/file-selector/.eslintcache",
16
+ "maxWarnings": -1,
17
+ "quiet": false,
18
+ "noEslintrc": false,
19
+ "hasTypeAwareRules": true,
20
+ "cacheStrategy": "metadata"
21
+ }
22
+ },
23
+ "test": {
24
+ "executor": "@nx/jest:jest",
25
+ "outputs": ["{workspaceRoot}/coverage/file-selector"],
26
+ "options": {
27
+ "jestConfig": "packages/file-selector/jest.config.js"
28
+ }
29
+ },
30
+ "version": {
31
+ "executor": "@jscutlery/semver:version",
32
+ "options": {
33
+ "preset": "conventional",
34
+ "commitMessageFormat": "chore({projectName}): release version ${version} [skip ci]",
35
+ "tagPrefix": "@availity/{projectName}@",
36
+ "trackDeps": true,
37
+ "skipCommitTypes": ["docs"]
38
+ }
39
+ }
40
+ }
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './lib/UploadProgressBar';
@@ -0,0 +1,28 @@
1
+ import { ReactNode } from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { useForm, FormProvider } from 'react-hook-form';
5
+
6
+ import { Dropzone } from './Dropzone';
7
+
8
+ const TestForm = ({ children }: { children: ReactNode }) => {
9
+ const methods = useForm();
10
+
11
+ return <FormProvider {...methods}>{children}</FormProvider>;
12
+ };
13
+
14
+ describe('Dropzone', () => {
15
+ test('should render successfully', () => {
16
+ const client = new QueryClient();
17
+
18
+ render(
19
+ <QueryClientProvider client={client}>
20
+ <TestForm>
21
+ <Dropzone name="test" bucketId="test" customerId="123" clientId="test" maxSize={1000} />
22
+ </TestForm>
23
+ </QueryClientProvider>
24
+ );
25
+
26
+ expect(screen.getByText('Drag and Drop Files Here')).toBeTruthy();
27
+ });
28
+ });
@@ -0,0 +1,185 @@
1
+ import { ChangeEvent, MouseEvent, useCallback, useState } from 'react';
2
+ import { useDropzone, FileRejection, DropEvent } from 'react-dropzone';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { Divider } from '@availity/mui-divider';
5
+ import { CloudDownloadIcon } from '@availity/mui-icon';
6
+ import { Box, Stack } from '@availity/mui-layout';
7
+ import { Typography } from '@availity/mui-typography';
8
+ import Upload, { Options } from '@availity/upload-core';
9
+
10
+ import { FilePickerBtn } from './FilePickerBtn';
11
+
12
+ const outerBoxStyles = {
13
+ backgroundColor: 'background.canvas',
14
+ border: '1px dotted',
15
+ borderRadius: '4px',
16
+ padding: '2rem',
17
+ };
18
+
19
+ const innerBoxStyles = {
20
+ width: '100%',
21
+ height: '100%',
22
+ };
23
+
24
+ const CLOUD_URL = '/cloud/web/appl/vault/upload/v1/resumable';
25
+
26
+ export type DropzoneProps = {
27
+ name: string;
28
+ bucketId: string;
29
+ clientId: string;
30
+ customerId: string;
31
+ allowedFileNameCharacters?: string;
32
+ allowedFileTypes?: `.${string}`[];
33
+ deliverFileOnSubmit?: boolean;
34
+ deliveryChannel?: string;
35
+ disabled?: boolean;
36
+ endpoint?: string;
37
+ fileDeliveryMetadata?: Record<string, unknown> | ((file: Upload) => Record<string, unknown>);
38
+ getDropRejectionMessages?: (fileRejectsions: FileRejection[]) => void;
39
+ isCloud?: boolean;
40
+ maxFiles?: number;
41
+ maxSize?: number;
42
+ multiple?: boolean;
43
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
44
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
45
+ // onDeliveryError?: (responses: unknown[]) => void;
46
+ // onDeliverySuccess?: (responses: unknown[]) => void;
47
+ onFileDelivery?: (upload: Upload) => void;
48
+ onFilePreUpload?: ((upload: Upload) => boolean)[];
49
+ };
50
+
51
+ export const Dropzone = ({
52
+ allowedFileNameCharacters,
53
+ allowedFileTypes = [],
54
+ bucketId,
55
+ clientId,
56
+ customerId,
57
+ deliveryChannel,
58
+ // deliverFileOnSubmit,
59
+ fileDeliveryMetadata,
60
+ disabled,
61
+ endpoint,
62
+ getDropRejectionMessages,
63
+ isCloud,
64
+ maxFiles,
65
+ maxSize,
66
+ multiple,
67
+ name,
68
+ onChange,
69
+ onClick,
70
+ onFilePreUpload,
71
+ onFileDelivery,
72
+ }: DropzoneProps) => {
73
+ const [totalSize, setTotalSize] = useState(0);
74
+ const [files, setFiles] = useState<Upload[]>([]);
75
+
76
+ const onDrop = useCallback(
77
+ (acceptedFiles: File[], fileRejections: FileRejection[], dropEvent: DropEvent) => {
78
+ // Do something with the files
79
+ console.log('Dropzone acceptedFiles:', acceptedFiles);
80
+ console.log('Dropzone fileRejections:', fileRejections);
81
+ console.log('Dropzone dropEvent:', dropEvent);
82
+
83
+ // Verify we have not exceeded max number of files
84
+ if (maxFiles && acceptedFiles.length > maxFiles) {
85
+ acceptedFiles.slice(0, Math.max(9, maxFiles));
86
+ }
87
+
88
+ const uploads = acceptedFiles.map((file) => {
89
+ const options: Options = {
90
+ bucketId,
91
+ customerId,
92
+ clientId,
93
+ fileTypes: allowedFileTypes,
94
+ maxSize,
95
+ allowedFileNameCharacters,
96
+ };
97
+
98
+ if (onFilePreUpload) options.onPreStart = onFilePreUpload;
99
+ if (endpoint) options.endpoint = endpoint;
100
+ if (isCloud) options.endpoint = CLOUD_URL;
101
+
102
+ const upload = new Upload(file, options);
103
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
104
+ // @ts-ignore
105
+ upload.id = `${upload.id}-${uuid()}`;
106
+
107
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
+ // @ts-ignore
109
+ if (file.dropRejectionMessage) {
110
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
111
+ // @ts-ignore
112
+ upload.errorMessage = file.dropRejectionMessage;
113
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
114
+ // @ts-ignore
115
+ } else if (maxSize && totalSize + newFilesTotalSize + upload.file.size > maxSize) {
116
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
117
+ // @ts-ignore
118
+ upload.errorMessage = 'Total documents size is too large';
119
+ } else {
120
+ upload.start();
121
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
122
+ // @ts-ignore
123
+ newFilesTotalSize += upload.file.size;
124
+ }
125
+ if (onFileDelivery) {
126
+ onFileDelivery(upload);
127
+ } else if (deliveryChannel && fileDeliveryMetadata) {
128
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
129
+ // @ts-ignore
130
+ // upload.onSuccess.push(() => {
131
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
132
+ // @ts-ignore
133
+ // if (upload?.references?.[0]) {
134
+ // allow form to revalidate when upload is complete
135
+ // setFieldTouched(name, true);
136
+ // deliver upon upload complete, not form submit
137
+ // if (!deliverFileOnSubmit) {
138
+ // callFileDelivery(upload);
139
+ // }
140
+ // }
141
+ // });
142
+ }
143
+
144
+ return upload;
145
+ });
146
+
147
+ // Set uploads somewhere. state?
148
+ setFiles(files);
149
+
150
+ if (getDropRejectionMessages) getDropRejectionMessages(fileRejections);
151
+ },
152
+ [getDropRejectionMessages]
153
+ );
154
+
155
+ const { getRootProps, getInputProps } = useDropzone({ onDrop });
156
+
157
+ const accept = allowedFileTypes.join(',');
158
+
159
+ return (
160
+ <Box sx={outerBoxStyles} {...getRootProps()}>
161
+ <Box sx={innerBoxStyles}>
162
+ <Stack spacing={2} divider={<Divider>OR</Divider>} alignItems="center" justifyContent="center">
163
+ <>
164
+ <CloudDownloadIcon fontSize="xlarge" color="secondary" />
165
+ <Typography variant="subtitle2" fontWeight="700">
166
+ Drag and Drop Files Here
167
+ </Typography>
168
+ <FilePickerBtn
169
+ name={name}
170
+ color="primary"
171
+ disabled={disabled}
172
+ maxSize={maxSize}
173
+ onClick={onClick}
174
+ inputProps={getInputProps({
175
+ multiple,
176
+ accept,
177
+ onChange,
178
+ })}
179
+ />
180
+ </>
181
+ </Stack>
182
+ </Box>
183
+ </Box>
184
+ );
185
+ };
@@ -0,0 +1,16 @@
1
+ import { render } from '@testing-library/react';
2
+
3
+ import { FileList } from './FileList';
4
+
5
+ describe('FileList', () => {
6
+ test('should render successfully', () => {
7
+ render(
8
+ <FileList
9
+ uploads={[]}
10
+ onRemoveFile={() => {
11
+ // noop
12
+ }}
13
+ />
14
+ );
15
+ });
16
+ });
@@ -0,0 +1,70 @@
1
+ import type Upload from '@availity/upload-core';
2
+ import { List, ListItem, ListItemText, ListItemIcon, ListItemButton } from '@availity/mui-list';
3
+ import { DeleteIcon, FileIcon } from '@availity/mui-icon';
4
+ import { Grid } from '@availity/mui-layout';
5
+
6
+ import { UploadProgressBar } from './UploadProgressBar';
7
+ import { formatBytes, getFileExtIcon } from './util';
8
+
9
+ type FileRowProps = {
10
+ /** The upload instance returned by creating a new Upload via @availity/upload-core. */
11
+ upload: Upload;
12
+ /** Callback called when file is removed. The callback is passed the id of the file that was removed. */
13
+ onRemoveFile: (id: string) => void;
14
+ };
15
+
16
+ const FileRow = ({ upload, onRemoveFile }: FileRowProps) => {
17
+ const { ext, icon } = getFileExtIcon(upload.file.name);
18
+ console.log('ext, icon:', ext, icon);
19
+
20
+ return (
21
+ <ListItem>
22
+ <Grid container spacing={2} alignItems="center" justifyContent="space-between" width="100%">
23
+ <Grid xs={1}>
24
+ <ListItemIcon>
25
+ <FileIcon />
26
+ </ListItemIcon>
27
+ </Grid>
28
+ <Grid xs={4}>
29
+ <ListItemText>{upload.trimFileName(upload.file.name)}</ListItemText>
30
+ </Grid>
31
+ <Grid xs={2}>
32
+ <ListItem>{formatBytes(upload.file.size)}</ListItem>
33
+ </Grid>
34
+ <Grid xs={4}>
35
+ <UploadProgressBar upload={upload} />
36
+ </Grid>
37
+ <Grid xs={1}>
38
+ <ListItemButton>
39
+ <ListItemIcon
40
+ onClick={() => {
41
+ onRemoveFile(upload.id);
42
+ }}
43
+ >
44
+ <DeleteIcon />
45
+ </ListItemIcon>
46
+ </ListItemButton>
47
+ </Grid>
48
+ </Grid>
49
+ </ListItem>
50
+ );
51
+ };
52
+
53
+ export type FileListProps = {
54
+ /** List of Upload objects */
55
+ uploads: Upload[];
56
+ /** Callback called when file is removed. The callback is passed the id of the file that was removed. */
57
+ onRemoveFile: (id: string) => void;
58
+ };
59
+
60
+ export const FileList = ({ uploads, onRemoveFile }: FileListProps) => {
61
+ if (uploads.length === 0) return null;
62
+
63
+ return (
64
+ <List>
65
+ {uploads.map((upload) => {
66
+ return <FileRow key={upload.id} upload={upload} onRemoveFile={onRemoveFile} />;
67
+ })}
68
+ </List>
69
+ );
70
+ };