forstok-ui-lib 8.6.3 → 8.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forstok-ui-lib",
3
- "version": "8.6.3",
3
+ "version": "8.7.0",
4
4
  "description": "Forstok UI Components Library",
5
5
  "path": "dist",
6
6
  "main": "dist/index.js",
@@ -1130,3 +1130,142 @@ export const evCheckAllValidationByHeaderWithVariant = (
1130
1130
  }
1131
1131
  return { error: isError, data: _newData };
1132
1132
  };
1133
+
1134
+ export const getFileType = (file?: File | null): string => {
1135
+ if (!file) return "";
1136
+
1137
+ const mimeType = (file.type || "").toLowerCase();
1138
+ const fileName = file.name.toLowerCase();
1139
+ const extension = fileName.includes(".")
1140
+ ? fileName.split(".").pop() || ""
1141
+ : "";
1142
+
1143
+ if (mimeType.includes("image/")) return "image";
1144
+ if (mimeType.includes("video/")) return "video";
1145
+ if (mimeType.includes("audio/")) return "audio";
1146
+ if (mimeType.includes("application/pdf")) return "pdf";
1147
+ if (mimeType.includes("text/csv")) return "csv";
1148
+ if (mimeType.includes("text/plain")) return "text";
1149
+ if (mimeType.includes("text/html")) return "html";
1150
+ if (mimeType.includes("text/xml") || mimeType.includes("application/xml"))
1151
+ return "xml";
1152
+ if (mimeType.includes("application/json") || mimeType.includes("text/json"))
1153
+ return "json";
1154
+ if (
1155
+ mimeType.includes("application/zip") ||
1156
+ mimeType.includes("application/x-zip-compressed")
1157
+ )
1158
+ return "zip";
1159
+ if (
1160
+ mimeType.includes("application/x-rar") ||
1161
+ mimeType.includes("application/vnd.rar")
1162
+ )
1163
+ return "rar";
1164
+ if (mimeType.includes("application/x-7z-compressed")) return "7z";
1165
+ if (mimeType.includes("application/x-tar")) return "tar";
1166
+ if (
1167
+ mimeType.includes("application/gzip") ||
1168
+ mimeType.includes("application/x-gzip")
1169
+ )
1170
+ return "gzip";
1171
+
1172
+ if (
1173
+ mimeType.includes("application/vnd.ms-excel") ||
1174
+ mimeType.includes(
1175
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1176
+ ) ||
1177
+ mimeType.includes("application/vnd.oasis.opendocument.spreadsheet")
1178
+ ) {
1179
+ return "sheet";
1180
+ }
1181
+
1182
+ if (
1183
+ mimeType.includes("application/msword") ||
1184
+ mimeType.includes(
1185
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1186
+ ) ||
1187
+ mimeType.includes("application/vnd.oasis.opendocument.text") ||
1188
+ mimeType.includes("application/rtf")
1189
+ ) {
1190
+ return "word";
1191
+ }
1192
+
1193
+ if (
1194
+ mimeType.includes("application/vnd.ms-powerpoint") ||
1195
+ mimeType.includes(
1196
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1197
+ ) ||
1198
+ mimeType.includes("application/vnd.oasis.opendocument.presentation")
1199
+ ) {
1200
+ return "powerpoint";
1201
+ }
1202
+
1203
+ if (mimeType.includes("application/epub+zip")) return "epub";
1204
+
1205
+ switch (extension) {
1206
+ case "jpg":
1207
+ case "jpeg":
1208
+ case "png":
1209
+ case "gif":
1210
+ case "webp":
1211
+ case "bmp":
1212
+ case "svg":
1213
+ case "heic":
1214
+ case "heif":
1215
+ return "image";
1216
+ case "mp4":
1217
+ case "mov":
1218
+ case "avi":
1219
+ case "mkv":
1220
+ case "webm":
1221
+ case "m4v":
1222
+ return "video";
1223
+ case "mp3":
1224
+ case "wav":
1225
+ case "ogg":
1226
+ case "m4a":
1227
+ case "aac":
1228
+ case "flac":
1229
+ return "audio";
1230
+ case "pdf":
1231
+ return "pdf";
1232
+ case "csv":
1233
+ return "csv";
1234
+ case "txt":
1235
+ return "text";
1236
+ case "html":
1237
+ case "htm":
1238
+ return "html";
1239
+ case "xml":
1240
+ return "xml";
1241
+ case "json":
1242
+ return "json";
1243
+ case "xls":
1244
+ case "xlsx":
1245
+ case "ods":
1246
+ return "sheet";
1247
+ case "doc":
1248
+ case "docx":
1249
+ case "odt":
1250
+ case "rtf":
1251
+ return "word";
1252
+ case "ppt":
1253
+ case "pptx":
1254
+ case "odp":
1255
+ return "powerpoint";
1256
+ case "zip":
1257
+ return "zip";
1258
+ case "rar":
1259
+ return "rar";
1260
+ case "7z":
1261
+ return "7z";
1262
+ case "tar":
1263
+ return "tar";
1264
+ case "gz":
1265
+ return "gzip";
1266
+ case "epub":
1267
+ return "epub";
1268
+ default:
1269
+ return mimeType.includes("application/") ? "document" : "";
1270
+ }
1271
+ };
@@ -1,44 +1,101 @@
1
- import { type InputHTMLAttributes, type ReactNode, useState } from 'react';
2
- import { generateMessage } from '../../assets/javascripts/function';
3
- import { UploadDragDropContainer, UploadDragDropFileHelper, UploadDragDropFileContainer, UploadInput, UploadDragDropLabel, UploadDragDropFileName, UploadDragDropFileNamePlaceholder, UploadDragDropIcon } from './styles';
4
- import type { TChangeEvent, TDragEvent } from '../../typeds/base.typed';
5
- import type { TMessageFunction } from '../message/typed';
1
+ import {
2
+ type InputHTMLAttributes,
3
+ type ReactNode,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
8
+ import {
9
+ UploadDragDropContainer,
10
+ UploadDragDropFileHelper,
11
+ UploadDragDropFileContainer,
12
+ UploadDragDropFilePreviewCard,
13
+ UploadDragDropImagePreview,
14
+ UploadInput,
15
+ UploadDragDropLabel,
16
+ UploadDragDropFileName,
17
+ UploadDragDropFileNamePlaceholder,
18
+ UploadDragDropIcon,
19
+ } from "./styles";
20
+ import {
21
+ generateMessage,
22
+ getFileType,
23
+ } from "../../assets/javascripts/function";
24
+ import type { TMessageFunction } from "../message/typed";
25
+ import type { TChangeEvent, TDragEvent, TMouseEvent } from "../../typeds";
6
26
 
7
27
  type TUpload = InputHTMLAttributes<HTMLInputElement> & {
8
- children?: ReactNode
9
- evChange?: (arg0: FileList) => void
10
- evCreateMessage?: TMessageFunction
11
- label?: string
12
- }
28
+ children?: ReactNode;
29
+ evChange?: (arg0: FileList) => void;
30
+ evCreateMessage?: TMessageFunction;
31
+ label?: string;
32
+ subLabel?: string;
33
+ defaultValue?: string;
34
+ isValidFile?: boolean;
35
+ type?: string;
36
+ evRemove?: TMouseEvent;
37
+ };
13
38
 
14
- const UploadDragDropComponent = ({ children, id, name, evChange, evCreateMessage, label, ...props }: TUpload) => {
39
+ type TFilePreview = {
40
+ fileSrc: string;
41
+ fileType: string;
42
+ previewKind: "image" | "file";
43
+ fileLabel: string;
44
+ };
45
+
46
+ const UploadDragDropComponent = ({
47
+ children,
48
+ id,
49
+ name,
50
+ evChange,
51
+ evCreateMessage,
52
+ label,
53
+ subLabel,
54
+ type,
55
+ defaultValue,
56
+ isValidFile = true,
57
+ evRemove,
58
+ ...props
59
+ }: TUpload) => {
15
60
  const { accept } = props;
16
61
 
62
+ const inputRef = useRef<HTMLInputElement>(null);
63
+ const [valueFile, setValueFile] = useState<FileList>();
17
64
  const [dragActive, setDragActive] = useState<boolean>(false);
65
+ const [filePreview, setFilePreview] = useState<TFilePreview>({
66
+ fileSrc: "",
67
+ fileType: "",
68
+ previewKind: "file",
69
+ fileLabel: "",
70
+ });
18
71
 
19
- const handleDrag: TDragEvent = e => {
72
+ const handleDrag: TDragEvent = (e) => {
20
73
  e.preventDefault();
21
74
  e.stopPropagation();
22
75
  if (e.type === "dragenter" || e.type === "dragover") {
23
76
  setDragActive(true);
24
- }
25
- else if (e.type === "dragleave") {
77
+ } else if (e.type === "dragleave") {
26
78
  setDragActive(false);
27
79
  }
28
- }
80
+ };
29
81
 
30
- const handleDrop: TDragEvent = e => {
82
+ const handleDrop: TDragEvent = (e) => {
31
83
  e.preventDefault();
32
84
  e.stopPropagation();
33
85
  setDragActive(false);
34
86
  if (e.dataTransfer.files[0]) {
35
- const acceptedFormat = accept ? accept.split(',').map(_accept => _accept.trim().replace('.', '')) : null;
36
- for (let x= 0; x < e.dataTransfer.files.length; x++) {
87
+ const acceptedFormat = accept
88
+ ? accept.split(",").map((_accept) => _accept.trim().replace(".", ""))
89
+ : null;
90
+ for (let x = 0; x < e.dataTransfer.files.length; x++) {
37
91
  const curFile = e.dataTransfer.files[x];
38
- const curFormat = curFile.name.split('.').pop();
92
+ const curFormat = curFile.name.split(".").pop();
39
93
  if (acceptedFormat?.length && curFormat) {
40
94
  if (!acceptedFormat.includes(curFormat)) {
41
- const _failed_props = generateMessage('failed', 'File type is not allowed. Please check again');
95
+ const _failed_props = generateMessage(
96
+ "failed",
97
+ "File type is not allowed. Please check again",
98
+ );
42
99
  evCreateMessage && evCreateMessage(_failed_props);
43
100
  return false;
44
101
  }
@@ -46,41 +103,219 @@ const UploadDragDropComponent = ({ children, id, name, evChange, evCreateMessage
46
103
  }
47
104
  evChange && evChange(e.dataTransfer.files);
48
105
  }
49
- }
106
+ };
50
107
 
51
- const handleChange: TChangeEvent = e => {
108
+ const handleChange: TChangeEvent = (e) => {
52
109
  e.preventDefault();
53
110
  const target = e.target as HTMLInputElement;
54
111
  if (target.files && target.files[0]) {
55
112
  evChange && evChange(target.files);
113
+ setValueFile(target.files);
114
+ }
115
+ };
116
+
117
+ const handleRemove: TMouseEvent = (e) => {
118
+ e.preventDefault();
119
+ setValueFile(undefined);
120
+ setFilePreview({
121
+ fileSrc: "",
122
+ fileType: "",
123
+ previewKind: "file",
124
+ fileLabel: "",
125
+ });
126
+ if (inputRef.current) {
127
+ inputRef.current.value = "";
56
128
  }
57
- }
129
+ evRemove?.(e);
130
+ };
131
+
132
+ useEffect(() => {
133
+ let activeObjectUrl = "";
134
+ let isMounted = true;
135
+
136
+ const evGenerateThumbVideo = (videoFile: File): Promise<string> => {
137
+ return new Promise((resolve, reject) => {
138
+ const video = document.createElement("video");
139
+ const source = document.createElement("source");
140
+ const objectUrl = URL.createObjectURL(videoFile);
141
+
142
+ source.src = objectUrl;
143
+ source.type = videoFile.type;
144
+ activeObjectUrl = objectUrl;
145
+
146
+ video.appendChild(source);
147
+ video.muted = true;
148
+ video.playsInline = true;
149
+ video.preload = "metadata";
150
+
151
+ video.onloadedmetadata = () => {
152
+ const canvas = document.createElement("canvas");
153
+ canvas.width = video.videoWidth || 720;
154
+ canvas.height = video.videoHeight || 122;
155
+
156
+ video.currentTime = Math.min(0.1, video.duration || 0);
157
+
158
+ video.onseeked = () => {
159
+ const ctx = canvas.getContext("2d");
160
+ if (!ctx) {
161
+ reject(new Error("Canvas context is not available"));
162
+ return;
163
+ }
164
+
165
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
166
+ resolve(canvas.toDataURL("image/jpeg"));
167
+ };
168
+ };
169
+
170
+ video.onerror = () => {
171
+ reject(new Error("Failed to load video"));
172
+ };
173
+
174
+ video.load();
175
+ });
176
+ };
177
+
178
+ const evProcessFile = async () => {
179
+ let nextPreview: TFilePreview = {
180
+ fileSrc: "",
181
+ fileType: "",
182
+ previewKind: "file",
183
+ fileLabel: "",
184
+ };
185
+
186
+ if (!isValidFile) {
187
+ if (isMounted) {
188
+ setFilePreview(nextPreview);
189
+ }
190
+ return;
191
+ }
192
+
193
+ if (valueFile && valueFile.length) {
194
+ const currentFile = valueFile[0];
195
+ const currentFileType = getFileType(currentFile) || "document";
196
+
197
+ nextPreview = {
198
+ fileSrc: currentFile.name,
199
+ fileType: currentFileType,
200
+ previewKind: "file",
201
+ fileLabel: currentFile.name,
202
+ };
203
+
204
+ if (currentFileType === "image") {
205
+ activeObjectUrl = URL.createObjectURL(currentFile);
206
+ nextPreview = {
207
+ fileSrc: activeObjectUrl,
208
+ fileType: currentFileType,
209
+ previewKind: "image",
210
+ fileLabel: currentFile.name,
211
+ };
212
+ } else if (currentFileType === "video") {
213
+ try {
214
+ const thumbSrc = await evGenerateThumbVideo(currentFile);
215
+ nextPreview = {
216
+ fileSrc: thumbSrc,
217
+ fileType: currentFileType,
218
+ previewKind: "image",
219
+ fileLabel: currentFile.name,
220
+ };
221
+ } catch {
222
+ nextPreview = {
223
+ fileSrc: currentFile.name,
224
+ fileType: currentFileType,
225
+ previewKind: "file",
226
+ fileLabel: currentFile.name,
227
+ };
228
+ }
229
+ }
230
+ } else if (defaultValue) {
231
+ nextPreview = {
232
+ fileSrc: defaultValue,
233
+ fileType: type ?? "image",
234
+ previewKind: (type ?? "image") === "image" ? "image" : "file",
235
+ fileLabel: defaultValue.split("/").pop() || defaultValue,
236
+ };
237
+ }
238
+
239
+ if (isMounted) {
240
+ setFilePreview(nextPreview);
241
+ }
242
+ };
243
+
244
+ void evProcessFile();
245
+
246
+ return () => {
247
+ isMounted = false;
248
+ if (activeObjectUrl) {
249
+ URL.revokeObjectURL(activeObjectUrl);
250
+ }
251
+ };
252
+ }, [defaultValue, isValidFile, type, valueFile]);
253
+
254
+ const { fileSrc, fileType, previewKind, fileLabel } = filePreview;
58
255
 
59
256
  return (
60
- <UploadDragDropContainer onDragEnter={handleDrag} className={dragActive ? "drag-active" : "" }>
61
- <UploadInput id={id} name={name} onChange={handleChange} {...props} />
257
+ <UploadDragDropContainer
258
+ onDragEnter={handleDrag}
259
+ className={dragActive ? "drag-active" : ""}
260
+ >
261
+ <UploadInput
262
+ ref={inputRef}
263
+ id={id}
264
+ name={name}
265
+ onChange={handleChange}
266
+ {...props}
267
+ />
62
268
  <UploadDragDropLabel id="label-file-dragdrop-upload" htmlFor={id}>
63
269
  <UploadDragDropFileContainer>
64
- {
65
- children ? (
66
- <UploadDragDropFileName>
67
- <UploadDragDropIcon />
68
- <span>
69
- {children}
70
- </span>
71
- </UploadDragDropFileName>
72
- ) :
73
- (
74
- <UploadDragDropFileNamePlaceholder>
75
- {label ?? 'Drag & Drop file here to upload, or '}<span>browse</span>.
76
- </UploadDragDropFileNamePlaceholder>
77
- )
78
- }
270
+ {fileSrc ? (
271
+ <UploadDragDropImagePreview>
272
+ <UploadDragDropFilePreviewCard
273
+ $backgroundSrc={previewKind === "image" ? fileSrc : undefined}
274
+ >
275
+ <span className="file-type">{fileType || "document"}</span>
276
+ <span className="file-name">{fileLabel}</span>
277
+ </UploadDragDropFilePreviewCard>
278
+ {evRemove !== undefined && (
279
+ <i
280
+ className="remove"
281
+ onClick={handleRemove}
282
+ title="Remove File"
283
+ >
284
+ X
285
+ </i>
286
+ )}
287
+ </UploadDragDropImagePreview>
288
+ ) : children ? (
289
+ <UploadDragDropFileName>
290
+ <UploadDragDropIcon />
291
+ <span>{children}</span>
292
+ </UploadDragDropFileName>
293
+ ) : (
294
+ <UploadDragDropFileNamePlaceholder>
295
+ <span>
296
+ {label ?? "Drag & Drop file here to upload, or "}
297
+ <aside>browse</aside>
298
+ </span>
299
+ {subLabel && (
300
+ <>
301
+ <br />
302
+ <span className="_refSubLabel">{subLabel}</span>
303
+ </>
304
+ )}
305
+ </UploadDragDropFileNamePlaceholder>
306
+ )}
79
307
  </UploadDragDropFileContainer>
80
308
  </UploadDragDropLabel>
81
- { dragActive && <UploadDragDropFileHelper onDragEnter={handleDrag} onDragLeave={handleDrag} onDragOver={handleDrag} onDrop={handleDrop} /> }
309
+ {dragActive && (
310
+ <UploadDragDropFileHelper
311
+ onDragEnter={handleDrag}
312
+ onDragLeave={handleDrag}
313
+ onDragOver={handleDrag}
314
+ onDrop={handleDrop}
315
+ />
316
+ )}
82
317
  </UploadDragDropContainer>
83
- )
84
- }
318
+ );
319
+ };
85
320
 
86
- export default UploadDragDropComponent
321
+ export default UploadDragDropComponent;