gbs-add-block 1.0.5 → 1.0.7
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 +4 -7
- package/index.cjs +1 -0
- package/package.json +1 -1
- package/source/components/select/index.tsx +112 -86
- package/source/components/useUploader/useUploader.tsx +303 -0
- package/source/globalStyle.ts +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v1.0.
|
|
1
|
+
# GBS Building Blocks 2.0 (v1.0.7)
|
|
2
2
|
|
|
3
3
|
Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
|
|
4
4
|
|
|
@@ -6,13 +6,10 @@ Latest and upgraded version of GBS building blocks with headless UI and removed
|
|
|
6
6
|
|
|
7
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
8
|
|
|
9
|
-
## What's New 🎉 (Ver 1.0.
|
|
9
|
+
## What's New 🎉 (Ver 1.0.7)
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- More UI/UX Optimization.
|
|
14
|
-
- Animation improvements.
|
|
15
|
-
- Custom Components in SideBar and Mobile Screen Support
|
|
11
|
+
- Uploader Enhancement
|
|
12
|
+
- Select Bug Fix
|
|
16
13
|
|
|
17
14
|
## Authors
|
|
18
15
|
|
package/index.cjs
CHANGED
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import React, {
|
|
|
13
13
|
useImperativeHandle,
|
|
14
14
|
useRef,
|
|
15
15
|
} from "react";
|
|
16
|
+
import { createPortal } from "react-dom";
|
|
16
17
|
import Icon from "../icon/Icon";
|
|
17
18
|
import { check, search, upDown, x } from "../icon/iconPaths";
|
|
18
19
|
import type { ItemsProps, SelectHandle, SelectProps } from "./types";
|
|
@@ -21,9 +22,11 @@ import { iconClass, popUp, primary } from "../globalStyle";
|
|
|
21
22
|
import {
|
|
22
23
|
useSelectState,
|
|
23
24
|
useSelectData,
|
|
24
|
-
useClickOutside,
|
|
25
25
|
applyScrollbarStyles,
|
|
26
26
|
useKeyboardNavigation,
|
|
27
|
+
usePortalPopup,
|
|
28
|
+
getPortalStyles,
|
|
29
|
+
getAnimationClasses,
|
|
27
30
|
} from "@grampro/headless-helpers";
|
|
28
31
|
|
|
29
32
|
const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
|
|
@@ -68,9 +71,16 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
|
|
|
68
71
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
69
72
|
const selectRef = useRef<HTMLDivElement>(null);
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
// Use the custom portal hooks
|
|
75
|
+
const position = usePortalPopup({
|
|
76
|
+
isOpen: showPopover,
|
|
77
|
+
onClose: () => setShowPopover(false),
|
|
78
|
+
triggerRef: selectRef,
|
|
79
|
+
popupHeight: 250,
|
|
80
|
+
offset: 4,
|
|
81
|
+
minSpaceRequired: 50,
|
|
82
|
+
portalSelector: '[data-select-portal="true"]',
|
|
83
|
+
});
|
|
74
84
|
|
|
75
85
|
// Update the handleSelect to accept a SelectItem
|
|
76
86
|
const handleSelect = useCallback(
|
|
@@ -120,97 +130,113 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
|
|
|
120
130
|
selected: selectedItem,
|
|
121
131
|
}));
|
|
122
132
|
|
|
123
|
-
|
|
133
|
+
// Portal popup content with smart positioning
|
|
134
|
+
const popupContent = showPopover && !disabled && (
|
|
124
135
|
<div
|
|
125
|
-
className="
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
className={`${popUp["pop-up-style"]} ${getAnimationClasses(
|
|
137
|
+
position.placement
|
|
138
|
+
)}`}
|
|
139
|
+
style={getPortalStyles(position, 250)}
|
|
140
|
+
data-select-portal="true"
|
|
129
141
|
>
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
{showSearch && (
|
|
143
|
+
<div className={selectStyle["input-parent"]}>
|
|
144
|
+
<Icon elements={search} svgClass={iconClass["grey-common"]} />
|
|
145
|
+
<input
|
|
146
|
+
autoComplete="off"
|
|
147
|
+
type="text"
|
|
148
|
+
name="search"
|
|
149
|
+
id="search"
|
|
150
|
+
placeholder="Search a value"
|
|
151
|
+
className="w-full outline-none dark:bg-transparent"
|
|
152
|
+
ref={inputRef}
|
|
153
|
+
value={searchTerm}
|
|
154
|
+
onChange={(e) => {
|
|
155
|
+
setSearchTerm(e.target.value);
|
|
156
|
+
if (onFiltering) onFiltering(e.target.value);
|
|
157
|
+
setFocusedIndex(-1);
|
|
158
|
+
}}
|
|
145
159
|
/>
|
|
146
|
-
</
|
|
147
|
-
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
{filteredItems.length > 0 ? (
|
|
163
|
+
filteredItems.map(({ value, label }, index: number) => (
|
|
148
164
|
<button
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
key={value}
|
|
166
|
+
ref={(el: any) => (itemRefs.current[index] = el)}
|
|
167
|
+
className={`${selectStyle["filter-button"]} ${
|
|
168
|
+
focusedIndex === index ? "bg-gray-100 dark:bg-gray-700" : ""
|
|
169
|
+
}`}
|
|
170
|
+
onClick={() => handleSelect({ value, label })}
|
|
171
|
+
onMouseEnter={() => setFocusedIndex(index)}
|
|
151
172
|
>
|
|
152
|
-
<Icon
|
|
173
|
+
<Icon
|
|
174
|
+
elements={check}
|
|
175
|
+
svgClass={`h-4 w-4 fill-none ${
|
|
176
|
+
selectedItem === value ? "stroke-gray-500" : ""
|
|
177
|
+
}`}
|
|
178
|
+
/>
|
|
179
|
+
{label}
|
|
153
180
|
</button>
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
))
|
|
182
|
+
) : (
|
|
183
|
+
<div className="text-sm text-center p-2">No Data Found</div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
157
187
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
ref={(el: any) => (itemRefs.current[index] = el)}
|
|
193
|
-
className={`${selectStyle["filter-button"]} ${
|
|
194
|
-
focusedIndex === index ? "bg-gray-100" : ""
|
|
195
|
-
}`}
|
|
196
|
-
onClick={() => handleSelect({ value, label })}
|
|
197
|
-
onMouseEnter={() => setFocusedIndex(index)}
|
|
198
|
-
>
|
|
199
|
-
<Icon
|
|
200
|
-
elements={check}
|
|
201
|
-
svgClass={`h-4 w-4 fill-none ${
|
|
202
|
-
selectedItem === value ? "stroke-gray-500" : ""
|
|
203
|
-
}`}
|
|
204
|
-
/>
|
|
205
|
-
{label}
|
|
206
|
-
</button>
|
|
207
|
-
))
|
|
208
|
-
) : (
|
|
209
|
-
<div className="text-sm text-center">No Data Found</div>
|
|
188
|
+
return (
|
|
189
|
+
<>
|
|
190
|
+
<div
|
|
191
|
+
className="relative w-full mt-2"
|
|
192
|
+
ref={selectRef}
|
|
193
|
+
id={id}
|
|
194
|
+
onKeyDown={handleKeyDown}
|
|
195
|
+
>
|
|
196
|
+
<div className="w-full relative">
|
|
197
|
+
<button
|
|
198
|
+
className={`${error ? primary["error-border"] : "border"} ${
|
|
199
|
+
selectStyle["select-button"]
|
|
200
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
201
|
+
onClick={togglePopover}
|
|
202
|
+
type="button"
|
|
203
|
+
disabled={disabled}
|
|
204
|
+
>
|
|
205
|
+
{selectedDisplay || placeholder}
|
|
206
|
+
<Icon
|
|
207
|
+
elements={upDown}
|
|
208
|
+
svgClass={`${iconClass["grey-common"]} ${
|
|
209
|
+
disabled ? "opacity-50" : ""
|
|
210
|
+
} ${
|
|
211
|
+
showPopover && position.placement === "top" ? "rotate-180" : ""
|
|
212
|
+
}`}
|
|
213
|
+
/>
|
|
214
|
+
</button>
|
|
215
|
+
{selectedDisplay && !disabled && (
|
|
216
|
+
<button
|
|
217
|
+
className={selectStyle["selectedDisplay-Button"]}
|
|
218
|
+
onClick={handleClear}
|
|
219
|
+
>
|
|
220
|
+
<Icon elements={x} svgClass={iconClass["grey-common"]} />
|
|
221
|
+
</button>
|
|
210
222
|
)}
|
|
223
|
+
{error && <p className={primary["error-primary"]}>{error}</p>}
|
|
211
224
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
|
|
226
|
+
<input
|
|
227
|
+
type="hidden"
|
|
228
|
+
name={name}
|
|
229
|
+
value={selectedItem || ""}
|
|
230
|
+
readOnly
|
|
231
|
+
disabled={disabled}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Render popup in portal to document.body */}
|
|
236
|
+
{typeof document !== "undefined" &&
|
|
237
|
+
popupContent &&
|
|
238
|
+
createPortal(popupContent, document.body)}
|
|
239
|
+
</>
|
|
214
240
|
);
|
|
215
241
|
});
|
|
216
242
|
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
interface ChunkInfo {
|
|
5
|
+
file: Blob;
|
|
6
|
+
originalFile: File;
|
|
7
|
+
chunkIndex: number;
|
|
8
|
+
totalChunks: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UploadProgress {
|
|
12
|
+
[fileName: string]: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface UploadResult {
|
|
16
|
+
fileName: string;
|
|
17
|
+
status: "complete" | "chunk_received" | "error";
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface UseChunkedUploadOptions {
|
|
22
|
+
chunkSize?: number;
|
|
23
|
+
maxConcurrentUploads?: number;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
onProgress?: (fileName: string, progress: number) => void;
|
|
26
|
+
onComplete?: (fileName: string, result: UploadResult) => void;
|
|
27
|
+
onError?: (fileName: string, error: Error) => void;
|
|
28
|
+
additionalParams?: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UseChunkedUploadReturn {
|
|
32
|
+
uploadFiles: (files: File[]) => Promise<void>;
|
|
33
|
+
isUploading: boolean;
|
|
34
|
+
uploadProgress: UploadProgress;
|
|
35
|
+
uploadStatus: string;
|
|
36
|
+
cancelUpload: () => void;
|
|
37
|
+
reset: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Helper function to process files into chunks
|
|
41
|
+
function processFiles(files: File[], chunkSize: number): ChunkInfo[] {
|
|
42
|
+
const finalFiles: ChunkInfo[] = [];
|
|
43
|
+
|
|
44
|
+
files.forEach((file) => {
|
|
45
|
+
if (file.size > chunkSize) {
|
|
46
|
+
const totalChunks = Math.ceil(file.size / chunkSize);
|
|
47
|
+
let offset = 0;
|
|
48
|
+
let chunkIndex = 0;
|
|
49
|
+
|
|
50
|
+
while (offset < file.size) {
|
|
51
|
+
const chunk = file.slice(offset, offset + chunkSize);
|
|
52
|
+
finalFiles.push({
|
|
53
|
+
file: chunk,
|
|
54
|
+
originalFile: file,
|
|
55
|
+
chunkIndex,
|
|
56
|
+
totalChunks,
|
|
57
|
+
});
|
|
58
|
+
offset += chunkSize;
|
|
59
|
+
chunkIndex++;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
finalFiles.push({
|
|
63
|
+
file,
|
|
64
|
+
originalFile: file,
|
|
65
|
+
chunkIndex: 0,
|
|
66
|
+
totalChunks: 1,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return finalFiles;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Helper function to upload a single chunk
|
|
75
|
+
async function uploadChunk(
|
|
76
|
+
chunk: Blob,
|
|
77
|
+
originalFile: File,
|
|
78
|
+
chunkIndex: number,
|
|
79
|
+
totalChunks: number,
|
|
80
|
+
baseUrl: string,
|
|
81
|
+
signal?: AbortSignal,
|
|
82
|
+
additionalParams: any = {}
|
|
83
|
+
): Promise<UploadResult> {
|
|
84
|
+
const formData = new FormData();
|
|
85
|
+
formData.append("chunk", chunk);
|
|
86
|
+
formData.append("fileName", originalFile.name);
|
|
87
|
+
formData.append("chunkIndex", chunkIndex.toString());
|
|
88
|
+
formData.append("totalChunks", totalChunks.toString());
|
|
89
|
+
formData.append("fileSize", originalFile.size.toString());
|
|
90
|
+
|
|
91
|
+
Object.entries(additionalParams).forEach(([key, value]: any) => {
|
|
92
|
+
formData.append(key, value);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const response = await fetch(`${baseUrl}`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: formData,
|
|
98
|
+
signal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return response.json();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Main custom hook
|
|
109
|
+
export function useChunkedUpload(
|
|
110
|
+
options: UseChunkedUploadOptions = {}
|
|
111
|
+
): UseChunkedUploadReturn {
|
|
112
|
+
const {
|
|
113
|
+
chunkSize = 1024 * 1024, // 1MB default
|
|
114
|
+
maxConcurrentUploads = 3,
|
|
115
|
+
baseUrl = "",
|
|
116
|
+
onProgress,
|
|
117
|
+
onComplete,
|
|
118
|
+
onError,
|
|
119
|
+
additionalParams = {},
|
|
120
|
+
} = options;
|
|
121
|
+
|
|
122
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
123
|
+
const [uploadProgress, setUploadProgress] = useState<UploadProgress>({});
|
|
124
|
+
const [uploadStatus, setUploadStatus] = useState("");
|
|
125
|
+
const [abortController, setAbortController] =
|
|
126
|
+
useState<AbortController | null>(null);
|
|
127
|
+
|
|
128
|
+
const updateProgress = useCallback(
|
|
129
|
+
(fileName: string, progress: number) => {
|
|
130
|
+
setUploadProgress((prev) => ({
|
|
131
|
+
...prev,
|
|
132
|
+
[fileName]: progress,
|
|
133
|
+
}));
|
|
134
|
+
onProgress?.(fileName, progress);
|
|
135
|
+
},
|
|
136
|
+
[onProgress]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const updateStatus = useCallback((status: string) => {
|
|
140
|
+
setUploadStatus(status);
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
const uploadFiles = useCallback(
|
|
144
|
+
async (files: File[]) => {
|
|
145
|
+
if (files.length === 0) {
|
|
146
|
+
throw new Error("No files selected for upload.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Create new abort controller
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
setAbortController(controller);
|
|
152
|
+
|
|
153
|
+
setIsUploading(true);
|
|
154
|
+
updateStatus("Processing files...");
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const processedFiles = processFiles(files, chunkSize);
|
|
158
|
+
|
|
159
|
+
// Group chunks by file
|
|
160
|
+
const fileChunks: { [key: string]: ChunkInfo[] } = {};
|
|
161
|
+
processedFiles.forEach((processedFile) => {
|
|
162
|
+
const fileName = processedFile.originalFile.name;
|
|
163
|
+
if (!fileChunks[fileName]) {
|
|
164
|
+
fileChunks[fileName] = [];
|
|
165
|
+
}
|
|
166
|
+
fileChunks[fileName].push(processedFile);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Upload files with concurrency control
|
|
170
|
+
const fileNames = Object.keys(fileChunks);
|
|
171
|
+
const uploadPromises = fileNames.map(async (fileName) => {
|
|
172
|
+
const chunks = fileChunks[fileName];
|
|
173
|
+
updateStatus(`Uploading ${fileName}...`);
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Create semaphore for concurrent chunk uploads
|
|
177
|
+
const semaphore = new Array(maxConcurrentUploads).fill(null);
|
|
178
|
+
let chunkIndex = 0;
|
|
179
|
+
const results: UploadResult[] = [];
|
|
180
|
+
|
|
181
|
+
const uploadNextChunk = async (): Promise<void> => {
|
|
182
|
+
if (chunkIndex >= chunks.length) return;
|
|
183
|
+
|
|
184
|
+
const currentChunk = chunks[chunkIndex++];
|
|
185
|
+
const result = await uploadChunk(
|
|
186
|
+
currentChunk.file,
|
|
187
|
+
currentChunk.originalFile,
|
|
188
|
+
currentChunk.chunkIndex,
|
|
189
|
+
currentChunk.totalChunks,
|
|
190
|
+
baseUrl,
|
|
191
|
+
controller.signal,
|
|
192
|
+
additionalParams
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
results.push(result);
|
|
196
|
+
|
|
197
|
+
// Update progress
|
|
198
|
+
const progress = (results.length / chunks.length) * 100;
|
|
199
|
+
updateProgress(fileName, progress);
|
|
200
|
+
|
|
201
|
+
// Continue uploading if there are more chunks
|
|
202
|
+
if (chunkIndex < chunks.length) {
|
|
203
|
+
await uploadNextChunk();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Start concurrent uploads
|
|
208
|
+
await Promise.all(semaphore.map(() => uploadNextChunk()));
|
|
209
|
+
|
|
210
|
+
// Check if file is complete
|
|
211
|
+
const completeResult = results.find((r) => r.status === "complete");
|
|
212
|
+
if (completeResult) {
|
|
213
|
+
updateStatus(`${fileName} uploaded successfully!`);
|
|
214
|
+
onComplete?.(fileName, completeResult);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { fileName, success: true };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const errorObj =
|
|
220
|
+
error instanceof Error ? error : new Error(String(error));
|
|
221
|
+
updateStatus(`Failed to upload ${fileName}: ${errorObj.message}`);
|
|
222
|
+
onError?.(fileName, errorObj);
|
|
223
|
+
return { fileName, success: false, error: errorObj };
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const results = await Promise.all(uploadPromises);
|
|
228
|
+
|
|
229
|
+
const successCount = results.filter((r) => r.success).length;
|
|
230
|
+
const failCount = results.length - successCount;
|
|
231
|
+
|
|
232
|
+
if (failCount === 0) {
|
|
233
|
+
updateStatus("All files uploaded successfully!");
|
|
234
|
+
} else if (successCount === 0) {
|
|
235
|
+
updateStatus("All uploads failed!");
|
|
236
|
+
} else {
|
|
237
|
+
updateStatus(`${successCount} files uploaded, ${failCount} failed`);
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
241
|
+
updateStatus("Upload cancelled");
|
|
242
|
+
} else {
|
|
243
|
+
const errorMessage =
|
|
244
|
+
error instanceof Error ? error.message : String(error);
|
|
245
|
+
updateStatus(`Upload failed: ${errorMessage}`);
|
|
246
|
+
}
|
|
247
|
+
} finally {
|
|
248
|
+
setIsUploading(false);
|
|
249
|
+
setAbortController(null);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[
|
|
253
|
+
chunkSize,
|
|
254
|
+
maxConcurrentUploads,
|
|
255
|
+
baseUrl,
|
|
256
|
+
updateProgress,
|
|
257
|
+
updateStatus,
|
|
258
|
+
onComplete,
|
|
259
|
+
onError,
|
|
260
|
+
]
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const cancelUpload = useCallback(() => {
|
|
264
|
+
if (abortController) {
|
|
265
|
+
abortController.abort();
|
|
266
|
+
updateStatus("Cancelling upload...");
|
|
267
|
+
}
|
|
268
|
+
}, [abortController, updateStatus]);
|
|
269
|
+
|
|
270
|
+
const reset = useCallback(() => {
|
|
271
|
+
setUploadProgress({});
|
|
272
|
+
setUploadStatus("");
|
|
273
|
+
setIsUploading(false);
|
|
274
|
+
if (abortController) {
|
|
275
|
+
abortController.abort();
|
|
276
|
+
setAbortController(null);
|
|
277
|
+
}
|
|
278
|
+
}, [abortController]);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
uploadFiles,
|
|
282
|
+
isUploading,
|
|
283
|
+
uploadProgress,
|
|
284
|
+
uploadStatus,
|
|
285
|
+
cancelUpload,
|
|
286
|
+
reset,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Alternative hook with simplified API for basic use cases
|
|
291
|
+
export function useSimpleUpload(baseUrl?: string) {
|
|
292
|
+
const { uploadFiles, isUploading, uploadStatus } = useChunkedUpload({
|
|
293
|
+
baseUrl,
|
|
294
|
+
chunkSize: 1024 * 1024, // 1MB
|
|
295
|
+
maxConcurrentUploads: 3,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
upload: uploadFiles,
|
|
300
|
+
isUploading,
|
|
301
|
+
status: uploadStatus,
|
|
302
|
+
};
|
|
303
|
+
}
|
package/source/globalStyle.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const primary = {
|
|
|
5
5
|
|
|
6
6
|
export const popUp = {
|
|
7
7
|
"pop-up-style":
|
|
8
|
-
"
|
|
8
|
+
"overflow-y-auto px-2 border border-slate-200 rounded-lg scrollbar bg-white scrollbar h-auto dark:bg-black dark:text-white animate-fade-down animate-once animate-duration-200",
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const iconClass = {
|