@kopexa/sight
Version:
Kopexa Sight Design System — React component library for GRC applications
341 lines (337 loc) • 10.5 kB
JavaScript
"use client";
;
"use client";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/hooks/use-file-upload/index.ts
var use_file_upload_exports = {};
__export(use_file_upload_exports, {
useFileUpload: () => useFileUpload
});
module.exports = __toCommonJS(use_file_upload_exports);
var import_i18n2 = require("@kopexa/i18n");
var import_shared_utils = require("@kopexa/shared-utils");
var import_react = require("react");
// src/hooks/use-file-upload/messages.ts
var import_i18n = require("@kopexa/i18n");
var messages = (0, import_i18n.defineMessages)({
file_exceeds_max: {
id: "use_file_upload.file_exceeds_max",
defaultMessage: `File "{name}" exceeds the maximum size of {max}.`,
description: "Shown when a single file is larger than allowed"
},
file_not_accepted: {
id: "use_file_upload.file_not_accepted",
defaultMessage: `File "{name}" is not an accepted file type.`,
description: "Shown when file type/extension doesn't match accept"
},
too_many_files: {
id: "file_upload.too_many_files",
defaultMessage: "You can only upload a maximum of {max} files.",
description: "Shown when maxFiles would be exceeded"
}
});
// src/hooks/use-file-upload/index.ts
var useFileUpload = (options = {}) => {
const {
maxFiles = Number.POSITIVE_INFINITY,
maxSize = Number.POSITIVE_INFINITY,
accept = "*",
multiple = false,
initialFiles = [],
onFilesChange,
onFilesAdded,
onError
} = options;
const t = (0, import_i18n2.useSafeIntl)();
const [state, setState] = (0, import_react.useState)({
files: initialFiles.map((file) => ({
file,
id: file.id,
preview: file.url
})),
isDragging: false,
errors: []
});
const inputRef = (0, import_react.useRef)(null);
const validateFile = (0, import_react.useCallback)(
(file) => {
if (file instanceof File) {
if (file.size > maxSize) {
return t.formatMessage(messages.file_exceeds_max, {
name: file.name,
max: (0, import_shared_utils.formatBytes)(maxSize)
});
}
} else {
if (file.size > maxSize) {
return t.formatMessage(messages.file_exceeds_max, {
name: file.name,
max: (0, import_shared_utils.formatBytes)(maxSize)
});
}
}
if (accept !== "*") {
const acceptedTypes = accept.split(",").map((type) => type.trim());
const fileType = file instanceof File ? file.type || "" : file.type;
const fileExtension = `.${file instanceof File ? file.name.split(".").pop() : file.name.split(".").pop()}`;
const isAccepted = acceptedTypes.some((type) => {
if (type.startsWith(".")) {
return fileExtension.toLowerCase() === type.toLowerCase();
}
if (type.endsWith("/*")) {
const baseType = type.split("/")[0];
return fileType.startsWith(`${baseType}/`);
}
return fileType === type;
});
if (!isAccepted) {
return t.formatMessage(messages.file_not_accepted, {
name: file instanceof File ? file.name : file.name
});
}
}
return null;
},
[accept, maxSize, t.formatMessage]
);
const createPreview = (0, import_react.useCallback)(
(file) => {
if (file instanceof File) {
return URL.createObjectURL(file);
}
return file.url;
},
[]
);
const generateUniqueId = (0, import_react.useCallback)((file) => {
if (file instanceof File) {
return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
return file.id;
}, []);
const clearFiles = (0, import_react.useCallback)(() => {
setState((prev) => {
for (const file of prev.files) {
if (file.preview && file.file instanceof File && file.file.type.startsWith("image/")) {
URL.revokeObjectURL(file.preview);
}
}
if (inputRef.current) {
inputRef.current.value = "";
}
const newState = {
...prev,
files: [],
errors: []
};
onFilesChange == null ? void 0 : onFilesChange(newState.files);
return newState;
});
}, [onFilesChange]);
const addFiles = (0, import_react.useCallback)(
(newFiles) => {
if (!newFiles || newFiles.length === 0) return;
const newFilesArray = Array.from(newFiles);
const errors = [];
setState((prev) => ({ ...prev, errors: [] }));
if (!multiple) {
clearFiles();
}
if (multiple && maxFiles !== Number.POSITIVE_INFINITY && state.files.length + newFilesArray.length > maxFiles) {
errors.push(
t.formatMessage(messages.too_many_files, {
max: maxFiles
})
);
onError == null ? void 0 : onError(errors);
setState((prev) => ({ ...prev, errors }));
return;
}
const validFiles = [];
for (const file of newFilesArray) {
if (multiple) {
const isDuplicate = state.files.some(
(existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size
);
if (isDuplicate) {
return;
}
}
if (file.size > maxSize) {
errors.push(
multiple ? `Some files exceed the maximum size of ${(0, import_shared_utils.formatBytes)(maxSize)}.` : `File exceeds the maximum size of ${(0, import_shared_utils.formatBytes)(maxSize)}.`
);
continue;
}
const error = validateFile(file);
if (error) {
errors.push(error);
} else {
validFiles.push({
file,
id: generateUniqueId(file),
preview: createPreview(file)
});
}
}
if (validFiles.length > 0) {
onFilesAdded == null ? void 0 : onFilesAdded(validFiles);
setState((prev) => {
const newFiles2 = !multiple ? validFiles : [...prev.files, ...validFiles];
onFilesChange == null ? void 0 : onFilesChange(newFiles2);
return {
...prev,
files: newFiles2,
errors
};
});
} else if (errors.length > 0) {
onError == null ? void 0 : onError(errors);
setState((prev) => ({
...prev,
errors
}));
}
if (inputRef.current) {
inputRef.current.value = "";
}
},
[
onError,
state.files,
maxFiles,
multiple,
maxSize,
validateFile,
createPreview,
generateUniqueId,
clearFiles,
onFilesChange,
onFilesAdded,
t.formatMessage
]
);
const removeFile = (0, import_react.useCallback)(
(id) => {
setState((prev) => {
const fileToRemove = prev.files.find((file) => file.id === id);
if ((fileToRemove == null ? void 0 : fileToRemove.preview) && fileToRemove.file instanceof File && fileToRemove.file.type.startsWith("image/")) {
URL.revokeObjectURL(fileToRemove.preview);
}
const newFiles = prev.files.filter((file) => file.id !== id);
onFilesChange == null ? void 0 : onFilesChange(newFiles);
return {
...prev,
files: newFiles,
errors: []
};
});
},
[onFilesChange]
);
const clearErrors = (0, import_react.useCallback)(() => {
setState((prev) => ({
...prev,
errors: []
}));
}, []);
const handleDragEnter = (0, import_react.useCallback)((e) => {
e.preventDefault();
e.stopPropagation();
setState((prev) => ({ ...prev, isDragging: true }));
}, []);
const handleDragLeave = (0, import_react.useCallback)((e) => {
e.preventDefault();
e.stopPropagation();
if (e.currentTarget.contains(e.relatedTarget)) {
return;
}
setState((prev) => ({ ...prev, isDragging: false }));
}, []);
const handleDragOver = (0, import_react.useCallback)((e) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDrop = (0, import_react.useCallback)(
(e) => {
var _a;
e.preventDefault();
e.stopPropagation();
setState((prev) => ({ ...prev, isDragging: false }));
if ((_a = inputRef.current) == null ? void 0 : _a.disabled) {
return;
}
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
if (!multiple) {
const file = e.dataTransfer.files[0];
addFiles([file]);
} else {
addFiles(e.dataTransfer.files);
}
}
},
[addFiles, multiple]
);
const handleFileChange = (0, import_react.useCallback)(
(e) => {
if (e.target.files && e.target.files.length > 0) {
addFiles(e.target.files);
}
},
[addFiles]
);
const openFileDialog = (0, import_react.useCallback)(() => {
if (inputRef.current) {
inputRef.current.click();
}
}, []);
const getInputProps = (0, import_react.useCallback)(
(props = {}) => {
return {
...props,
type: "file",
onChange: handleFileChange,
accept: props.accept || accept,
multiple: props.multiple !== void 0 ? props.multiple : multiple,
ref: inputRef
};
},
[accept, multiple, handleFileChange]
);
return [
state,
{
addFiles,
removeFile,
clearFiles,
clearErrors,
handleDragEnter,
handleDragLeave,
handleDragOver,
handleDrop,
handleFileChange,
openFileDialog,
getInputProps
}
];
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
useFileUpload
});