UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

338 lines (336 loc) 11.3 kB
import { Button_default } from "./chunk-LAYB7IKW.mjs"; import { Text_default } from "./chunk-IMCIR6TJ.mjs"; import { cn } from "./chunk-53ICLDGS.mjs"; // src/components/FileDropzone/FileDropzone.tsx import { useRef, useState, useCallback, useId, useEffect } from "react"; import { UploadIcon } from "@phosphor-icons/react/dist/csr/Upload"; import { WarningCircleIcon } from "@phosphor-icons/react/dist/csr/WarningCircle"; import { CheckCircleIcon } from "@phosphor-icons/react/dist/csr/CheckCircle"; import { FileVideoIcon } from "@phosphor-icons/react/dist/csr/FileVideo"; import { FileAudioIcon } from "@phosphor-icons/react/dist/csr/FileAudio"; import { FilePdfIcon } from "@phosphor-icons/react/dist/csr/FilePdf"; import { ClosedCaptioningIcon } from "@phosphor-icons/react/dist/csr/ClosedCaptioning"; import { PaperclipIcon } from "@phosphor-icons/react/dist/csr/Paperclip"; import { PencilSimpleIcon } from "@phosphor-icons/react/dist/csr/PencilSimple"; import { XIcon } from "@phosphor-icons/react/dist/csr/X"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var FILE_TYPE_CONFIG = { image: { icon: UploadIcon, defaultPlaceholder: "para subir a imagem ou arraste aqui" }, video: { icon: FileVideoIcon, defaultPlaceholder: "para subir o v\xEDdeo ou arraste aqui" }, audio: { icon: FileAudioIcon, defaultPlaceholder: "para subir o \xE1udio ou arraste aqui" }, pdf: { icon: FilePdfIcon, defaultPlaceholder: "para subir o PDF ou arraste aqui" }, subtitle: { icon: ClosedCaptioningIcon, defaultPlaceholder: "para subir a legenda ou arraste aqui" } }; function FileDropzone({ label, helperText, errorMessage, fileUrl, selectedFile, onFileSelect, onRemoveFile, accept, fileType, actionText = "Clique aqui", placeholder, changeText, changeButtonText = "Trocar", disabled = false, required = false, className, maxSize, onSizeError, onTypeError, showPreview = true, previewMaxHeight = "max-h-48" }) { const fileInputRef = useRef(null); const dragCounterRef = useRef(0); const [isDragging, setIsDragging] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const generatedId = useId(); const inputId = `file-dropzone-${generatedId}`; const hasError = Boolean(errorMessage); const config = FILE_TYPE_CONFIG[fileType]; const IconComponent = config.icon; const defaultPlaceholder = placeholder || config.defaultPlaceholder; const defaultChangeText = changeText || (fileType === "image" ? "Clique para alterar a imagem" : "Clique para trocar o arquivo"); useEffect(() => { if (!selectedFile || fileType !== "image") { setPreviewUrl(null); return; } const url = URL.createObjectURL(selectedFile); setPreviewUrl(url); return () => { URL.revokeObjectURL(url); }; }, [selectedFile, fileType]); const displayUrl = fileType === "image" ? previewUrl || fileUrl : null; const hasFile = Boolean(selectedFile || fileUrl); const validateFile = useCallback( (file) => { const acceptedTypes = accept.split(",").map((type) => type.trim()); const isValidType = acceptedTypes.some((type) => { if (type.startsWith(".")) { return file.name.toLowerCase().endsWith(type.toLowerCase()); } if (!file.type) { return false; } if (type.endsWith("/*")) { const mainType = type.split("/")[0]; return file.type.startsWith(mainType + "/"); } return file.type === type; }); if (!isValidType) { onTypeError?.(file); return false; } if (maxSize != null && file.size > maxSize) { onSizeError?.(file, maxSize); return false; } return true; }, [accept, maxSize, onSizeError, onTypeError] ); const handleFileChange = (event) => { const file = event.target.files?.[0]; if (!file || disabled) return; if (validateFile(file)) { onFileSelect?.(file); } event.target.value = ""; }; const handleDragEnter = (e) => { e.preventDefault(); e.stopPropagation(); dragCounterRef.current += 1; if (!disabled && dragCounterRef.current === 1) { setIsDragging(true); } }; const handleDragLeave = (e) => { e.preventDefault(); e.stopPropagation(); dragCounterRef.current -= 1; if (dragCounterRef.current === 0) { setIsDragging(false); } }; const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); dragCounterRef.current = 0; setIsDragging(false); if (disabled) return; const file = e.dataTransfer.files?.[0]; if (!file) return; if (validateFile(file)) { onFileSelect?.(file); } }; const handleRemove = (e) => { e.preventDefault(); e.stopPropagation(); if (!disabled) { onRemoveFile?.(); } }; const handleChangeClick = (e) => { e.preventDefault(); e.stopPropagation(); if (!disabled) { fileInputRef.current?.click(); } }; const fileName = selectedFile?.name || (fileUrl ? "Arquivo carregado" : ""); const getBorderClasses = () => { if (hasError) { return disabled ? "border-indicator-error" : "border-indicator-error hover:border-indicator-error"; } if (isDragging) { return "border-primary-500 bg-primary-50"; } return disabled ? "border-border-200" : "border-border-200 hover:border-primary-500"; }; const dropzoneClasses = cn( "flex flex-col items-center justify-center p-6 border-2 border-dashed rounded-lg cursor-pointer transition-colors", getBorderClasses(), disabled && "cursor-not-allowed opacity-50" ); const isImagePreview = fileType === "image" && showPreview && Boolean(displayUrl); const imageCardClasses = cn( "flex flex-col items-center justify-center gap-3 p-6 rounded-lg bg-background-100", hasError && "border-2 border-indicator-error", disabled && "opacity-50" ); const getAriaDescribedBy = () => { if (errorMessage) return `${inputId}-error`; if (helperText) return `${inputId}-helper`; return void 0; }; const renderImagePreview = () => /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( "img", { src: displayUrl ?? void 0, alt: "Preview do arquivo", className: cn("max-w-full rounded-lg object-contain", previewMaxHeight) } ), /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-full border border-border-200 bg-background px-3 py-1.5", children: [ /* @__PURE__ */ jsx(PaperclipIcon, { size: 16, className: "text-text-600" }), /* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-text-700 max-w-[200px] truncate", children: fileName }), onRemoveFile && !disabled && /* @__PURE__ */ jsx( Button_default, { variant: "raw", type: "button", onClick: handleRemove, className: "p-0.5 rounded-full hover:bg-error-100 transition-colors", "aria-label": "Remover arquivo", children: /* @__PURE__ */ jsx(XIcon, { size: 14, className: "text-indicator-error" }) } ) ] }), !disabled && /* @__PURE__ */ jsx( Button_default, { variant: "outline", action: "primary", size: "small", iconLeft: /* @__PURE__ */ jsx(PencilSimpleIcon, { size: 16 }), onClick: handleChangeClick, children: changeButtonText } ) ] }); const renderFilePreview = () => { if (hasFile) { return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-12 h-12 rounded-full bg-success-100", children: /* @__PURE__ */ jsx( CheckCircleIcon, { size: 28, className: "text-success-600", weight: "fill" } ) }), /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsx(IconComponent, { size: 20, className: "text-text-600" }), /* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-text-700 max-w-[200px] truncate", children: fileName }), onRemoveFile && !disabled && /* @__PURE__ */ jsx( Button_default, { variant: "raw", type: "button", onClick: handleRemove, className: "p-1 rounded-full hover:bg-error-100 transition-colors", "aria-label": "Remover arquivo", children: /* @__PURE__ */ jsx(XIcon, { size: 14, className: "text-indicator-error" }) } ) ] }), /* @__PURE__ */ jsx(Text_default, { size: "xs", className: "text-text-500", children: defaultChangeText }) ] }); } return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(IconComponent, { size: 24, className: "text-primary-500 mb-2" }), /* @__PURE__ */ jsxs(Text_default, { size: "sm", className: "text-text-600 text-center", children: [ /* @__PURE__ */ jsx("span", { className: "text-primary-500 font-medium", children: actionText }), " ", defaultPlaceholder ] }) ] }); }; return /* @__PURE__ */ jsxs("div", { className, children: [ label && /* @__PURE__ */ jsxs( "label", { htmlFor: inputId, className: "block font-bold text-text-900 mb-2 text-sm", children: [ label, " ", required && /* @__PURE__ */ jsx("span", { className: "text-indicator-error", children: "*" }) ] } ), /* @__PURE__ */ jsx( "input", { ref: fileInputRef, id: inputId, type: "file", accept, onChange: handleFileChange, className: "hidden", disabled, "aria-invalid": hasError, "aria-describedby": getAriaDescribedBy() } ), isImagePreview ? /* @__PURE__ */ jsx( "div", { className: imageCardClasses, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: renderImagePreview() } ) : /* @__PURE__ */ jsx( "label", { htmlFor: inputId, className: dropzoneClasses, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: renderFilePreview() } ), /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [ helperText && !errorMessage && /* @__PURE__ */ jsx(Text_default, { id: `${inputId}-helper`, size: "xs", className: "text-text-500", children: helperText }), errorMessage && /* @__PURE__ */ jsxs(Text_default, { id: `${inputId}-error`, size: "xs", color: "text-indicator-error", children: [ /* @__PURE__ */ jsx(WarningCircleIcon, { size: 14 }), " ", errorMessage ] }) ] }) ] }); } export { FileDropzone }; //# sourceMappingURL=chunk-JPP2X7BZ.mjs.map