gbs-add-block 0.0.23-beta → 0.0.24

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/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # GBS Building Blocks 2.0 (v0.0.23)
2
+
3
+ Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
+
5
+ ## Documentation
6
+
7
+ For detailed documentation on usage and props, Please visit: [Building Block Documentation v2.0](https://blackmax-designs.gitbook.io/building-block-v2.0)
8
+
9
+ ## What's New 🎉 (Ver 0.0.23)
10
+
11
+ - Updated Uploader
12
+ - Global Style Customisation is Now available(beta)
13
+
14
+ ## Authors
15
+
16
+ - [@anandhuremanan](https://www.github.com/anandhuremanan)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.23-beta",
3
+ "version": "0.0.24",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -20,7 +20,7 @@ import { check, search, upDown, x } from "../icon/iconPaths";
20
20
  import { getSourceData } from "../utils";
21
21
  import type { ItemsProps, SelectProps } from "./types";
22
22
  import { selectStyle } from "./style";
23
- import { popUp, primary } from "../../globalStyle";
23
+ import { popUp, primary } from "../globalStyle";
24
24
 
25
25
  const Select = forwardRef<any, SelectProps>((props, ref) => {
26
26
  const {
@@ -0,0 +1,130 @@
1
+ import React from "react";
2
+ import Icon from "../icon/Icon";
3
+ import { cloudDownload, x } from "../icon/iconPaths";
4
+ import { GetFileIcon } from "./uploaderIcon";
5
+ import type { UploadedFilePreview } from "./types";
6
+
7
+ export default function UploadedFilePreview({
8
+ files = [],
9
+ UploadedFileData,
10
+ apiURL,
11
+ showImagePreview,
12
+ previewUrls,
13
+ onFileRemove,
14
+ isRemovable,
15
+ removedId,
16
+ }: UploadedFilePreview) {
17
+ const handleDownload = (fileId: any, fileName: any) => {
18
+ const downloadUrl = constructDownloadUrl(fileId);
19
+ window.open(downloadUrl, "_blank");
20
+ };
21
+
22
+ const constructDownloadUrl = (fileId: any) => {
23
+ const url = new URL("download", apiURL);
24
+ url.searchParams.append("id", fileId);
25
+ return url.href;
26
+ };
27
+
28
+ const isImageFile = (fileName: any) => {
29
+ const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];
30
+ return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
31
+ };
32
+ return (
33
+ <div>
34
+ {/* Rendering Uploded Local Files */}
35
+ {files.length > 0 && (
36
+ <div className="mt-4 space-y-2">
37
+ {files.map((file: any, index: number) => (
38
+ <div
39
+ key={index}
40
+ className="flex items-center justify-between bg-gray-50 p-2 rounded-md"
41
+ >
42
+ <div className="flex items-center space-x-2">
43
+ <GetFileIcon
44
+ file={file}
45
+ showImagePreview={showImagePreview}
46
+ previewUrls={previewUrls}
47
+ />
48
+ <div>
49
+ <p className="text-sm font-medium text-gray-700">
50
+ {file.name}
51
+ </p>
52
+ <p className="text-xs text-gray-500">
53
+ {(file.size / 1024 / 1024).toFixed(2)} MB
54
+ </p>
55
+ </div>
56
+ </div>
57
+ <button
58
+ type="button"
59
+ onClick={() => {
60
+ if (onFileRemove) onFileRemove(index);
61
+ }}
62
+ className="text-red-500 hover:text-red-700 transition-colors"
63
+ >
64
+ <Icon
65
+ dimensions={{ width: "16", height: "16" }}
66
+ elements={x}
67
+ svgClass={"stroke-red-500 fill-none dark:stroke-white"}
68
+ />
69
+ </button>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ )}
74
+
75
+ {/* Rendering Uploded Remote Files Preview */}
76
+ {UploadedFileData.length > 0 &&
77
+ UploadedFileData.map((item: any, index: any) => (
78
+ <div
79
+ key={index}
80
+ className="flex items-center justify-between bg-gray-50 p-2 rounded-md mt-2"
81
+ >
82
+ <div className="flex items-center space-x-2">
83
+ {isImageFile(item.FileName) ? (
84
+ <img
85
+ src={constructDownloadUrl(item.FileID)}
86
+ alt={item.FileName}
87
+ className="mt-2 w-8 h-8 object-cover rounded-lg"
88
+ />
89
+ ) : (
90
+ <GetFileIcon file={{ type: item.FileName.split(".").pop() }} />
91
+ )}
92
+ <div>
93
+ <p className="text-sm font-medium text-gray-700">
94
+ {item.FileName}
95
+ </p>
96
+ <p className="text-xs text-gray-500">
97
+ {(item.FileSize / 1024 / 1024).toFixed(2)} MB
98
+ </p>
99
+ </div>
100
+ </div>
101
+ <div className="flex gap-2">
102
+ <button
103
+ onClick={() => handleDownload(item.FileID, item.FileName)}
104
+ >
105
+ <Icon
106
+ dimensions={{ width: "18", height: "18" }}
107
+ elements={cloudDownload}
108
+ svgClass={"stroke-blue-800 fill-none dark:stroke-white"}
109
+ />
110
+ </button>
111
+ {isRemovable && (
112
+ <button
113
+ aria-label="Remove"
114
+ onClick={() => {
115
+ if (removedId) removedId(item.FileID);
116
+ }}
117
+ >
118
+ <Icon
119
+ dimensions={{ width: "16", height: "16" }}
120
+ elements={x}
121
+ svgClass={"stroke-red-500 fill-none dark:stroke-white"}
122
+ />
123
+ </button>
124
+ )}
125
+ </div>
126
+ </div>
127
+ ))}
128
+ </div>
129
+ );
130
+ }
@@ -9,13 +9,12 @@
9
9
  */
10
10
 
11
11
  import React, { useState, useRef, useEffect } from "react";
12
- import { upload, x, circleCheck } from "../icon/iconPaths";
12
+ import { upload, circleCheck } from "../icon/iconPaths";
13
13
  import Icon from "../icon/Icon";
14
- import { GetFileIcon } from "./uploaderIcon";
15
14
  import AestheticProcessingAnimationWithStyles from "./ProgressAnimation";
16
- import { sendFiles } from "./uploaderService";
17
- import { cloudDownload } from "../icon/iconPaths";
15
+ import { getFilesById, sendFiles } from "./uploaderService";
18
16
  import type { FileUploadProps } from "./types";
17
+ import UploadedFilePreview from "./UploadedFilePreview";
19
18
 
20
19
  export const FileUploader = ({
21
20
  showImagePreview = false,
@@ -23,14 +22,16 @@ export const FileUploader = ({
23
22
  onChange,
24
23
  selectedFiles = [],
25
24
  accept,
26
- fileCount = 1,
25
+ fileCount = Infinity,
27
26
  disabled = false,
28
27
  inputFileSize, // Max file size in MB
29
28
  startUpload = false,
30
29
  apiURL = "",
31
30
  chunk_size = 1024 * 1024,
32
31
  uploadedFileIdArray = () => {},
33
- fileData = [],
32
+ documentId = [],
33
+ isRemovable = true,
34
+ removedIds,
34
35
  }: FileUploadProps) => {
35
36
  const [files, setFiles] = useState<any[]>([]);
36
37
  const [previewUrls, setPreviewUrls] = useState<{ [key: string]: string }>({});
@@ -42,32 +43,48 @@ export const FileUploader = ({
42
43
  undefined
43
44
  );
44
45
  const [progress, setProgress] = useState(0);
46
+ const [fileData, setFileData] = useState<any[]>([]);
45
47
  const fileInputRef = useRef<any>(null);
46
48
  const dropZoneRef = useRef<any>(null);
47
49
 
50
+ // Upload Handler
51
+ const handleUpload = async () => {
52
+ if (files.length > 0) {
53
+ setFileUploadStatus(undefined);
54
+ await sendFiles(
55
+ files,
56
+ chunk_size,
57
+ apiURL,
58
+ ({ uploading, progress, uploadedFileIds }) => {
59
+ setIsUploading(uploading);
60
+ if (progress !== undefined) setProgress(progress);
61
+ // Returns Uploaded File ID's
62
+ if (uploadedFileIds) uploadedFileIdArray(uploadedFileIds);
63
+ }
64
+ );
65
+ setFileUploadStatus("Upload completed");
66
+ setFiles([]);
67
+ } else {
68
+ setFileUploadStatus("No files selected");
69
+ }
70
+ };
71
+
72
+ // Getting Files by ID
73
+ const getFiles = async () => {
74
+ const files = await getFilesById(apiURL, documentId);
75
+ setFileData(files);
76
+ };
77
+
78
+ // Fetching by FileId
79
+ useEffect(() => {
80
+ if (documentId.length) {
81
+ getFiles();
82
+ }
83
+ }, [documentId]);
84
+
48
85
  // Starts uploads to server
49
86
  useEffect(() => {
50
87
  if (startUpload) {
51
- // Upload Handler
52
- const handleUpload = async () => {
53
- if (files.length > 0) {
54
- setFileUploadStatus(undefined);
55
- await sendFiles(
56
- files,
57
- chunk_size,
58
- apiURL,
59
- ({ uploading, progress, uploadedFileIds }) => {
60
- setIsUploading(uploading);
61
- if (progress !== undefined) setProgress(progress);
62
- // Returns Uploaded File ID's
63
- if (uploadedFileIds) uploadedFileIdArray(uploadedFileIds);
64
- }
65
- );
66
- setFileUploadStatus("Upload completed");
67
- } else {
68
- setFileUploadStatus("No files selected");
69
- }
70
- };
71
88
  handleUpload();
72
89
  }
73
90
  }, [startUpload]);
@@ -81,9 +98,34 @@ export const FileUploader = ({
81
98
 
82
99
  // Adds files to file array
83
100
  const handleFileChange = (newFiles: File[]) => {
101
+ // First check for duplicates
102
+ const duplicateFiles: string[] = [];
103
+ const nonDuplicateFiles = newFiles.filter((newFile) => {
104
+ const isDuplicate = files.some(
105
+ (existingFile) => existingFile.name === newFile.name
106
+ );
107
+ if (isDuplicate) {
108
+ duplicateFiles.push(newFile.name);
109
+ }
110
+ return !isDuplicate;
111
+ });
112
+
113
+ // If there are duplicates, set error messages
114
+ if (duplicateFiles.length > 0) {
115
+ setFileSizeErrors((prev) => [
116
+ ...prev,
117
+ ...duplicateFiles.map(
118
+ (fileName) => `File "${fileName}" has already been uploaded`
119
+ ),
120
+ ]);
121
+
122
+ // If all files are duplicates, exit early
123
+ if (nonDuplicateFiles.length === 0) return;
124
+ }
125
+
84
126
  let updatedFiles: File[] = multiple
85
- ? [...files, ...newFiles]
86
- : [newFiles[0]];
127
+ ? [...files, ...nonDuplicateFiles]
128
+ : [nonDuplicateFiles[0]];
87
129
 
88
130
  // Ensure the total number of files doesn't exceed the fileCount limit
89
131
  if (updatedFiles.length > fileCount) {
@@ -127,7 +169,7 @@ export const FileUploader = ({
127
169
  if (onChange) onChange(validFiles);
128
170
  }
129
171
 
130
- // Set file size errors
172
+ // Set file size errors (now includes both size and duplicate errors)
131
173
  setFileSizeErrors(newErrors);
132
174
  };
133
175
 
@@ -170,22 +212,6 @@ export const FileUploader = ({
170
212
  handleFileChange(multiple ? droppedFiles : [droppedFiles[0]]);
171
213
  };
172
214
 
173
- const handleDownload = (fileId: any, fileName: any) => {
174
- const downloadUrl = constructDownloadUrl(fileId);
175
- window.open(downloadUrl, "_blank");
176
- };
177
-
178
- const constructDownloadUrl = (fileId: any) => {
179
- const url = new URL("download", apiURL);
180
- url.searchParams.append("id", fileId);
181
- return url.href;
182
- };
183
-
184
- const isImageFile = (fileName: any) => {
185
- const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];
186
- return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
187
- };
188
-
189
215
  return (
190
216
  <div className="w-full max-w-md mx-auto">
191
217
  <div
@@ -261,81 +287,20 @@ export const FileUploader = ({
261
287
  <AestheticProcessingAnimationWithStyles progressPercentage={progress} />
262
288
  )}
263
289
 
264
- {files.length > 0 && (
265
- <div className="mt-4 space-y-2">
266
- {files.map((file: any, index) => (
267
- <div
268
- key={index}
269
- className="flex items-center justify-between bg-gray-50 p-2 rounded-md"
270
- >
271
- <div className="flex items-center space-x-2">
272
- <GetFileIcon
273
- file={file}
274
- showImagePreview={showImagePreview}
275
- previewUrls={previewUrls}
276
- />
277
- <div>
278
- <p className="text-sm font-medium text-gray-700">
279
- {file.name}
280
- </p>
281
- <p className="text-xs text-gray-500">
282
- {(file.size / 1024 / 1024).toFixed(2)} MB
283
- </p>
284
- </div>
285
- </div>
286
- <button
287
- type="button"
288
- onClick={() => removeFile(index)}
289
- className="text-red-500 hover:text-red-700 transition-colors"
290
- >
291
- <Icon
292
- dimensions={{ width: "16", height: "16" }}
293
- elements={x}
294
- svgClass={"stroke-red-500 fill-none dark:stroke-white"}
295
- />
296
- </button>
297
- </div>
298
- ))}
299
- </div>
300
- )}
301
-
302
- {fileData.length > 0 &&
303
- fileData.map((item: any, index: any) => (
304
- <div
305
- key={index}
306
- className="flex items-center justify-between bg-gray-50 p-2 rounded-md mt-2"
307
- >
308
- <div className="flex items-center space-x-2">
309
- {/* <GetFileIcon
310
- file={item.FileName}
311
- showImagePreview={showImagePreview}
312
- previewUrls={previewUrls}
313
- /> */}
314
- {isImageFile(item.FileName) && (
315
- <img
316
- src={constructDownloadUrl(item.FileID)}
317
- alt={item.FileName}
318
- className="mt-2 w-8 h-8 object-cover rounded-lg"
319
- />
320
- )}
321
- <div>
322
- <p className="text-sm font-medium text-gray-700">
323
- {item.FileName}
324
- </p>
325
- <p className="text-xs text-gray-500">
326
- {(item.FileSize / 1024 / 1024).toFixed(2)} MB
327
- </p>
328
- </div>
329
- </div>
330
- <button onClick={() => handleDownload(item.FileID, item.FileName)}>
331
- <Icon
332
- dimensions={{ width: "18", height: "18" }}
333
- elements={cloudDownload}
334
- svgClass={"stroke-red-500 fill-none dark:stroke-white"}
335
- />
336
- </button>
337
- </div>
338
- ))}
290
+ <UploadedFilePreview
291
+ UploadedFileData={fileData}
292
+ apiURL={apiURL}
293
+ files={files}
294
+ showImagePreview={showImagePreview}
295
+ previewUrls={previewUrls}
296
+ onFileRemove={(index: any) => {
297
+ removeFile(index);
298
+ }}
299
+ isRemovable={isRemovable}
300
+ removedId={(fileId: any) => {
301
+ if (removedIds) removedIds(fileId);
302
+ }}
303
+ />
339
304
  </div>
340
305
  );
341
306
  };
@@ -1,7 +1,7 @@
1
1
  export type FileUploadProps = {
2
2
  showImagePreview?: boolean;
3
3
  multiple?: boolean;
4
- onChange: (files: FileList | null) => void;
4
+ onChange?: (files: FileList | null) => void;
5
5
  selectedFiles?: File[];
6
6
  accept?: string;
7
7
  fileCount?: number;
@@ -12,4 +12,19 @@ export type FileUploadProps = {
12
12
  chunk_size?: number;
13
13
  uploadedFileIdArray?: (ids: string[]) => void;
14
14
  fileData?: File[];
15
+ documentId?: string[];
16
+ isRemovable?: boolean;
17
+ removedIds?: (fileId: string | null) => void;
18
+ };
19
+
20
+ export type UploadedFilePreview = {
21
+ files?: any[];
22
+ UploadedFileData: any[];
23
+ apiURL: string;
24
+ previewType?: boolean;
25
+ showImagePreview: boolean;
26
+ previewUrls: { [key: string]: string };
27
+ onFileRemove: any;
28
+ isRemovable: boolean;
29
+ removedId?: any;
15
30
  };
@@ -11,8 +11,8 @@ import {
11
11
  filetext,
12
12
  film,
13
13
  music,
14
- } from "../../icon/iconPaths";
15
- import Icon from "../../icon/Icon";
14
+ } from "../icon/iconPaths";
15
+ import Icon from "../icon/Icon";
16
16
 
17
17
  // returns an icon based on the selected file type
18
18
  export const GetFileIcon = ({ file, showImagePreview, previewUrls }: any) => {
@@ -30,7 +30,7 @@ export const GetFileIcon = ({ file, showImagePreview, previewUrls }: any) => {
30
30
  svgClass={"stroke-blue-500 fill-none dark:stroke-white"}
31
31
  />
32
32
  );
33
- if (file.type === "application/pdf")
33
+ if (file.type === "application/pdf" || file.type.includes("pdf"))
34
34
  return (
35
35
  <Icon
36
36
  dimensions={{ width: "26", height: "26" }}
@@ -118,7 +118,17 @@ export async function getAllFiles(apiUrl: string) {
118
118
  }
119
119
 
120
120
  // returns files by ID
121
- export async function getFilesById(apiUrl: string, documentId: string) {
122
- let files: any[] = [];
123
- return files;
121
+ export async function getFilesById(
122
+ apiUrl: string,
123
+ documentIds: string[]
124
+ ): Promise<any[]> {
125
+ return Promise.all(
126
+ documentIds.map(async (id) => {
127
+ const response = await fetch(`${apiUrl}?id=${id}`);
128
+ if (!response.ok) {
129
+ throw new Error(`Failed to fetch document with ID: ${id}`);
130
+ }
131
+ return response.json();
132
+ })
133
+ );
124
134
  }