analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
338 lines (336 loc) • 11.3 kB
JavaScript
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