From 67b96442a3849f0543f3c235ef60da9c5242cd84 Mon Sep 17 00:00:00 2001 From: Miguel Ortega Date: Mon, 14 Sep 2026 00:05:36 -0500 Subject: [PATCH] refactor(ui): migrar componente ImageUpload a HeroUI --- src/components/ui/ImageUpload.tsx | 170 +++++++++++++++++++++++++----- 1 file changed, 142 insertions(+), 28 deletions(-) diff --git a/src/components/ui/ImageUpload.tsx b/src/components/ui/ImageUpload.tsx index e1ea729..c8cf558 100644 --- a/src/components/ui/ImageUpload.tsx +++ b/src/components/ui/ImageUpload.tsx @@ -1,6 +1,8 @@ "use client"; + import { useRef, useState } from "react"; import { FiUpload, FiX } from "react-icons/fi"; +import { Button, CloseButton, Label } from "@heroui/react"; import { useTheme } from "@/context/ThemeContext"; export interface ImageUploadProps { @@ -11,8 +13,15 @@ export interface ImageUploadProps { hint?: string; } -export function ImageUpload({ label, value, onChange, aspectRatio = "square", hint }: ImageUploadProps) { +export function ImageUpload({ + label, + value, + onChange, + aspectRatio = "square", + hint, +}: ImageUploadProps) { const { isDark } = useTheme(); + const inputRef = useRef(null); const [dragging, setDragging] = useState(false); @@ -20,34 +29,118 @@ export function ImageUpload({ label, value, onChange, aspectRatio = "square", hi const handleFile = (file: File) => { if (!file.type.startsWith("image/")) return; + const reader = new FileReader(); - reader.onload = (e) => onChange(e.target?.result as string); + + reader.onload = (event) => { + const result = event.target?.result; + + if (typeof result === "string") { + onChange(result); + } + }; + reader.readAsDataURL(file); }; - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); setDragging(false); - const file = e.dataTransfer.files[0]; - if (file) handleFile(file); + + const file = event.dataTransfer.files[0]; + + if (file) { + handleFile(file); + } }; - const border = isDark ? "border-slate-700" : "border-slate-200"; - const bg = isDark ? "bg-slate-800/50" : "bg-slate-50"; - const active = dragging ? (isDark ? "border-violet-500 bg-violet-500/10" : "border-violet-400 bg-violet-50") : ""; - const muted = isDark ? "text-slate-400" : "text-slate-500"; + const borderColor = isDark + ? "border-slate-700" + : "border-slate-200"; + + const backgroundColor = isDark + ? "bg-slate-800/50" + : "bg-slate-50"; + + const dragActiveStyles = dragging + ? isDark + ? "border-violet-500 bg-violet-500/10" + : "border-violet-400 bg-violet-50" + : ""; + + const mutedColor = isDark + ? "text-slate-400" + : "text-slate-500"; + + const labelColor = isDark + ? "text-slate-300" + : "text-slate-700"; + + const pickerSize = isBanner + ? "h-32 w-full" + : "h-28 w-28"; + + const openFilePicker = () => { + inputRef.current?.click(); + }; + + const handleInputChange = ( + event: React.ChangeEvent + ) => { + const file = event.target.files?.[0]; + + if (file) { + handleFile(file); + } + }; + + const handleRemove = () => { + onChange(undefined); + + // Permite volver a seleccionar la misma imagen + // después de eliminarla. + if (inputRef.current) { + inputRef.current.value = ""; + } + }; return (
-
inputRef.current?.click()} - onDragOver={(e) => { e.preventDefault(); setDragging(true); }} - onDragLeave={() => setDragging(false)} + role="button" + tabIndex={0} + aria-label={`Seleccionar imagen para ${label}`} + className={[ + "relative cursor-pointer overflow-hidden rounded-xl", + "border-2 border-dashed", + "transition-all duration-200", + "focus-visible:outline-none focus-visible:ring-2", + "focus-visible:ring-violet-500", + borderColor, + backgroundColor, + dragActiveStyles, + pickerSize, + ].join(" ")} + onClick={openFilePicker} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + openFilePicker(); + } + }} + onDragOver={(event) => { + event.preventDefault(); + setDragging(true); + }} + onDragLeave={() => { + setDragging(false); + }} onDrop={handleDrop} > {value ? ( @@ -55,34 +148,55 @@ export function ImageUpload({ label, value, onChange, aspectRatio = "square", hi {label} - + + + +
) : ( -
- -

+

+ + +

{dragging ? "Suelta aquí" : "Click o arrastra"}

+ +
)}
- {hint &&

{hint}

} + {hint && ( +

+ {hint} +

+ )} { const f = e.target.files?.[0]; if (f) handleFile(f); }} + onChange={handleInputChange} />
);