UNPKG

ar-design

Version:

AR Design is a (react | nextjs) ui library.

181 lines (180 loc) 8.85 kB
"use client"; import React, { useCallback, useEffect, useRef, useState } from "react"; import "../../../assets/css/components/form/upload/styles.css"; import { ARIcon } from "../../icons"; import Dropzone from "./Dropzone"; import Button from "../button"; import List from "./List"; import Utils from "../../../libs/infrastructure/shared/Utils"; const Upload = ({ text, files, onChange, allowedTypes, maxSize, type = "list", direction = "column", size, fullWidth, multiple, }) => { // refs const _input = useRef(null); const _arUplaod = useRef(null); // refs -> File Data const _validationErrors = useRef([]); // variables const [className, setClassName] = useState(["button"]); // states const [selectedFiles, setSelectedFiles] = useState([]); const [validationErrors, setValidationErrors] = useState([]); // methods const handleFileChange = useCallback((files) => { const _files = Array.from(files ?? []); setSelectedFiles((prev) => { const previousFileNames = prev.map((f) => f.name); const newFiles = _files.filter((f) => !previousFileNames.includes(f.name)) ?? []; return [...prev, ...newFiles]; }); }, []); const handleFileRemove = useCallback((fileToRemove) => { const dataTransfer = new DataTransfer(); setSelectedFiles((prev) => { const newList = prev.filter((x) => x.name !== fileToRemove.name); newList.forEach((file) => dataTransfer.items.add(file)); if (_input.current) _input.current.files = dataTransfer.files; if (newList.length === 0) setClassName((prev) => prev.filter((c) => c !== "has-file")); return newList; }); }, []); const handleValidationFile = useCallback((file) => { const newErrors = []; if (allowedTypes) { if (!allowedTypes.includes(file.type)) { newErrors.push({ fileName: file.name, message: "Geçersiz dosya türü." }); _validationErrors.current.push(file.name); } } if (maxSize) { const _maxSize = maxSize * 1024 * 1024; // MB if (file.size > _maxSize) { newErrors.push({ fileName: file.name, message: "Dosya boyutu çok büyük." }); _validationErrors.current.push(file.name); } } setValidationErrors((prev) => [...prev, ...newErrors]); }, []); const handleFileToBase64 = useCallback((file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { if (reader.result && typeof reader.result === "string") { resolve(reader.result); } else { reject(new Error("Failed to read the file")); } }; reader.onerror = reject; reader.readAsDataURL(file); }); }, []); const handleDrag = useCallback((e) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setClassName((prev) => { const index = prev.findIndex((c) => c === "dragging"); if (index === -1) return [...prev, "dragging"]; return prev; }); } else { setClassName((prev) => prev.filter((c) => c !== "dragging")); } }, []); const handleDrop = useCallback((e) => { e.preventDefault(); e.stopPropagation(); const files = e.dataTransfer.files; if (files && files.length > 0) setSelectedFiles(Array.from(files)); setClassName((prev) => prev.filter((c) => c !== "dragging")); }, []); const renderUploadFile = (params) => { return (React.createElement("div", { ref: _arUplaod, className: "ar-upload" }, React.createElement("input", { ref: _input, type: "file", onChange: (event) => handleFileChange(event.target.files), multiple: multiple }), params.children)); }; // useEffects useEffect(() => { (async () => { const dataTransfer = new DataTransfer(); const fileFormData = new FormData(); setValidationErrors([]); _validationErrors.current = []; if (_input.current) { if (selectedFiles.length === 0) { if (_input.current) _input.current.files = dataTransfer.files; onChange(fileFormData, [], [], false); return; } // Seçilmiş olan dosyalar validasyona gönderiliyor. selectedFiles.forEach((f) => handleValidationFile(f)); const inValidFiles = Array.from(new Set(_validationErrors.current)); // Input içerisine dosyalar aktarılıyor. selectedFiles.forEach((f) => dataTransfer.items.add(f)); _input.current.files = dataTransfer.files; // Geçerli olan dosyalar alındı... const validFiles = [...selectedFiles.filter((x) => !inValidFiles.includes(x.name))]; validFiles.forEach((f) => fileFormData.append("file", f)); // Geçerli olan dosyalar base64'e dönüştürülüyor... const base64Array = await Promise.all(validFiles.map((validFile) => handleFileToBase64(validFile))); onChange(fileFormData, validFiles, base64Array, _validationErrors.current.length === 0); // Eğer dosya varsa. setClassName((prev) => { const index = prev.findIndex((c) => c === "has-file"); if (index === -1) return [...prev, "has-file"]; return prev; }); } })(); }, [selectedFiles]); useEffect(() => { if (Utils.DeepEqual(files, selectedFiles)) return; setSelectedFiles(files); }, [files]); useEffect(() => { if (type === "dropzone") setClassName((prev) => [...prev, "dropzone"]); }, []); switch (type) { case "list": case "grid": return renderUploadFile({ children: (React.createElement(React.Fragment, null, React.createElement(Button, { type: "button", variant: "outlined", color: "gray", icon: { element: React.createElement(ARIcon, { icon: "CloudUpload-Fill" }) }, onClick: () => { if (_input.current) _input.current.click(); }, fullWidth: fullWidth, size: size }, text && React.createElement("span", null, text)), React.createElement(List, { type: type, direction: direction, selectedFiles: selectedFiles ?? [], validationErrors: validationErrors, handleFileToBase64: handleFileToBase64, handleFileRemove: handleFileRemove }))), }); case "dropzone": return renderUploadFile({ children: (React.createElement("div", { className: "ar-upload-button" }, React.createElement("div", { className: className.map((c) => c).join(" "), onDragEnter: handleDrag, onDragLeave: handleDrag, onDragOver: handleDrag, onDrop: handleDrop, onClick: () => { if (_input.current) _input.current.click(); } }, React.createElement(Dropzone, { selectedFiles: selectedFiles ?? [], validationErrors: validationErrors, handleFileToBase64: handleFileToBase64, handleFileRemove: handleFileRemove }), selectedFiles && selectedFiles.length === 0 && (React.createElement(React.Fragment, null, React.createElement("div", { className: "upload" }, React.createElement(ARIcon, { icon: "CloudUpload-Fill", size: 32 }), React.createElement("div", { className: "properies" }, allowedTypes && (React.createElement("div", { className: "allow-types" }, allowedTypes?.map((allowedType) => allowedType.split("/")[1].toLocaleUpperCase()).join(", "))), maxSize && React.createElement("div", { className: "max-size" }, "up to ", maxSize, "MB"))), text && React.createElement("span", null, text)))))), }); default: return null; } }; export default Upload;