Migrating new components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
This commit is contained in:
81
components/Fileinput.js
Normal file
81
components/Fileinput.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// components/Fileinput.js
|
||||
import { $, Tag, If, For } from "sigpro";
|
||||
|
||||
export const Fileinput = (props) => {
|
||||
const { class: className, 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 Tag("div", {
|
||||
...rest,
|
||||
class: `fieldset w-full p-0 ${className || ''}`.trim()
|
||||
}, [
|
||||
Tag("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); }
|
||||
}, [
|
||||
Tag("div", { class: "flex items-center gap-3 w-full" }, [
|
||||
Tag("span", { class: "icon-[lucide--upload]" }),
|
||||
Tag("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
|
||||
Tag("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`)
|
||||
]),
|
||||
Tag("input", {
|
||||
type: "file",
|
||||
multiple: true,
|
||||
accept,
|
||||
class: "hidden",
|
||||
onchange: (e) => handleFiles(e.target.files)
|
||||
})
|
||||
]),
|
||||
|
||||
() => error() && Tag("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()),
|
||||
|
||||
If(() => selectedFiles().length > 0, () =>
|
||||
Tag("ul", { class: "mt-2 space-y-1" }, [
|
||||
For(selectedFiles, (file, idx) =>
|
||||
Tag("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [
|
||||
Tag("div", { class: "flex items-center gap-2 truncate" }, [
|
||||
Tag("span", { class: "opacity-50" }, "📄"),
|
||||
Tag("span", { class: "truncate font-medium max-w-[200px]" }, file.name),
|
||||
Tag("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`)
|
||||
]),
|
||||
Tag("button", {
|
||||
type: "button",
|
||||
class: "btn btn-ghost btn-xs btn-circle",
|
||||
onclick: (e) => { e.preventDefault(); removeFile(idx); }
|
||||
}, Tag("span", { class: "icon-[lucide--x]" }))
|
||||
]),
|
||||
(file) => file.name + file.lastModified
|
||||
)
|
||||
])
|
||||
)
|
||||
]);
|
||||
};
|
||||
Reference in New Issue
Block a user