// components/Fileinput.js import { $, $html, $if, $for } from "../sigpro.js"; import { ui, getIcon } from "../core/utils.js"; /** * Fileinput component * * daisyUI classes used: * - fieldset, w-full, p-0 * - tooltip, tooltip-top, before:z-50, after:z-50 * - relative, flex, items-center, justify-between, w-full, h-12, px-4 * - border-2, border-dashed, rounded-lg, cursor-pointer, transition-all, duration-200 * - border-primary, bg-primary/10, border-base-content/20, bg-base-100, hover:bg-base-200 * - text-sm, opacity-70, truncate, grow, text-left * - text-[10px], opacity-40, shrink-0 * - text-error, text-[10px], mt-1, px-1, font-medium * - mt-2, space-y-1 * - flex, items-center, justify-between, p-1.5, pl-3, text-xs, bg-base-200/50, rounded-md, border, border-base-300 * - gap-2, truncate, opacity-50, font-medium, max-w-[200px] * - btn, btn-ghost, btn-xs, btn-circle */ export const Fileinput = (props) => { const { class: className, tooltip, max = 2, accept = "*", onSelect, ...rest } = props; const selectedFiles = $([]); const isDragging = $(false); const error = $(null); const MAX_BYTES = max * 1024 * 1024; const handleFiles = (files) => { const fileList = Array.from(files); error(null); const oversized = fileList.find((f) => f.size > MAX_BYTES); if (oversized) { error(`Máx ${max}MB`); return; } selectedFiles([...selectedFiles(), ...fileList]); onSelect?.(selectedFiles()); }; const removeFile = (index) => { const updated = selectedFiles().filter((_, i) => i !== index); selectedFiles(updated); onSelect?.(updated); }; return $html("fieldset", { ...rest, class: ui('fieldset w-full p-0', className) }, [ $html( "div", { class: () => `w-full ${tooltip ? "tooltip tooltip-top before:z-50 after:z-50" : ""}`, "data-tip": tooltip, }, [ $html( "label", { class: () => ` relative flex items-center justify-between w-full h-12 px-4 border-2 border-dashed rounded-lg cursor-pointer transition-all duration-200 ${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"} `, ondragover: (e) => { e.preventDefault(); isDragging(true); }, ondragleave: () => isDragging(false), ondrop: (e) => { e.preventDefault(); isDragging(false); handleFiles(e.dataTransfer.files); }, }, [ $html("div", { class: "flex items-center gap-3 w-full" }, [ getIcon("icon-[lucide--upload]"), $html("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."), $html("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`), ]), $html("input", { type: "file", multiple: true, accept: accept, class: "hidden", onchange: (e) => handleFiles(e.target.files), }), ], ), ], ), () => (error() ? $html("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()) : null), $if( () => selectedFiles().length > 0, () => $html("ul", { class: "mt-2 space-y-1" }, [ $for( selectedFiles, (file, index) => $html("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [ $html("div", { class: "flex items-center gap-2 truncate" }, [ $html("span", { class: "opacity-50" }, "📄"), $html("span", { class: "truncate font-medium max-w-[200px]" }, file.name), $html("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`), ]), $html( "button", { type: "button", class: "btn btn-ghost btn-xs btn-circle", onclick: (e) => { e.preventDefault(); e.stopPropagation(); removeFile(index); }, }, [getIcon("icon-[lucide--x]")] ), ]), (file) => file.name + file.lastModified, ), ]), ), ]); };