Files
sigpro/docs/ui.md
2026-05-14 12:11:02 +02:00

10 KiB

const fruta = $("");
const color = $("#3b82f6");
const fecha = $("");
const rango = $({ start: null, end: null });
const pais = $("");

const frutas = [
  "Manzana",
  "Pera",
  "Plátano",
  "Fresa",
  "Mango",
  "Sandía",
  "Melón",
  "Uva",
];
const paises = [
  { label: "🇪🇸 España", value: "ES" },
  { label: "🇲🇽 México", value: "MX" },
  { label: "🇦🇷 Argentina", value: "AR" },
  { label: "🇨🇴 Colombia", value: "CO" },
  { label: "🇨🇱 Chile", value: "CL" },
];

mount(
  () =>
    div({ class: "p-8 max-w-md mx-auto flex flex-col gap-4" }, [
      h1({ class: "text-2xl font-bold" }, "Field Components"),


      ui.autocomplete({
        label: "Fruta favorita",
        items: frutas,
        value: fruta,
        placeholder: "Buscar fruta...",
      }),


      ui.autocomplete({
        label: "País",
        items: paises,
        value: pais,
        placeholder: "Elige un país...",
      }),

    
      ui.datepicker({
        label: "Fecha de nacimiento",
        value: fecha,
        placeholder: "Selecciona fecha...",
      }),

  
      ui.datepicker({
        label: "Estancia",
        range: true,
        value: rango,
        placeholder: "Check-in → Check-out",
      }),


      ui.colorpicker({
        label: "Color favorito",
        value: color,
        placeholder: "Elige un color...",
      }),

      ui.password({}),
      ui.theme(),

      div(
        { class: "bg-base-200 rounded-box p-4 flex flex-col gap-2 text-sm" },
        [
          div({}, () => `🍎 Fruta: ${val(fruta) || "—"}`),
          div({}, () => `🌍 País: ${val(pais) || "—"}`),
          div({}, () => `📅 Fecha: ${val(fecha) || "—"}`),
          div({}, () => {
            const r = val(rango);
            return r.start && r.end
              ? `🏨 Estancia: ${r.start}${r.end}`
              : "🏨 Estancia: —";
          }),
          div({ class: "flex items-center gap-2" }, [
            span({}, "🎨 Color:"),
            div({
              class: "w-6 h-6 rounded border border-base-300",
              style: () => `background:${val(color)}`,
            }),
          ]),
        ],
      ),
    ]),
  "#ui",
);

const archivos = $([]);
const drag = $(false);
const error = $("");
const subiendo = $(false);
const progreso = $(0);

const MAX_SIZE = 5 * 1024 * 1024; 
const MAX_FILES = 3;

const handleFiles = (files) => {
  const arr = Array.from(files);
  if (arr.length > MAX_FILES) {
    error(`Máximo ${MAX_FILES} archivos`);
    return;
  }
  const big = arr.find(f => f.size > MAX_SIZE);
  if (big) {
    error(`"${big.name}" supera los 5MB`);
    return;
  }
  error("");
  archivos(arr);
};

const subirArchivos = async () => {
  const files = archivos();
  if (!files.length) return toast("Selecciona archivos primero", "alert-warning");

  subiendo(true);
  progreso(0);

  const formData = new FormData();
  files.forEach(f => formData.append('files', f));
  formData.append('carpeta', 'demo');

  try {
    const xhr = new XMLHttpRequest();
    
    const promise = new Promise((resolve, reject) => {
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          progreso(Math.round((e.loaded / e.total) * 100));
        }
      };
      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(new Error(`Error ${xhr.status}`));
        }
      };
      xhr.onerror = () => reject(new Error('Error de conexión'));
    });

    xhr.open('POST', '/api/upload');
    xhr.send(formData);
    
    await promise;
    toast(`✅ ${files.length} archivo(s) subidos`, "alert-success");
    archivos([]);
    progreso(0);
  } catch (err) {
    toast(`❌ ${err.message}`, "alert-error");
  } finally {
    subiendo(false);
  }
};

const fileInputProps = {
  type: "file",
  class: "hidden",
  multiple: true,
  accept: "image/*,.pdf,.doc,.docx",
  onchange: (e) => { handleFiles(e.target.files); e.target.value = ''; }
};

mount(
  () => div({ class: "p-8 max-w-md mx-auto flex flex-col gap-4" }, [
    h1({ class: "text-2xl font-bold" }, "📁 Upload Files"),

    // Zona drag & drop
    h("label", {
      class: () => `relative flex items-center justify-between h-14 px-4 border-2 border-dashed rounded-lg cursor-pointer transition-all ${
        drag() ? 'border-primary bg-primary/10' : 'border-base-content/20 bg-base-100'
      } ${subiendo() ? 'pointer-events-none opacity-50' : ''}`,
      ondragover: (e) => { e.preventDefault(); if (!subiendo()) drag(true); },
      ondragleave: () => drag(false),
      ondrop: (e) => {
        e.preventDefault();
        drag(false);
        if (subiendo()) return;
        handleFiles(e.dataTransfer.files);
      }
    }, [
      h("div", { class: "flex items-center gap-3" }, [
        h("span", { class: "icon-[lucide--upload] w-5 h-5 text-base-content/60" }),
        h("div", {}, [
          h("div", { class: "text-sm font-medium" }, "Arrastra archivos aquí"),
          h("div", { class: "text-xs text-base-content/50" }, `Máx ${MAX_FILES} archivos · ${MAX_SIZE / 1024 / 1024}MB c/u`),
        ]),
      ]),
      h("span", { class: "text-xs text-base-content/40" }, "o haz clic"),
      h("input", fileInputProps),
    ]),


    () => error() ? ui.fileError({ message: error() }) : null,


    () => archivos().length > 0 ? h("div", { class: "space-y-3" }, [
      h("div", { class: "flex flex-wrap gap-2" },
        archivos().map((f, i) => {
          const isImage = f.type?.startsWith('image/');
          const url = isImage ? URL.createObjectURL(f) : null;

          return h("div", {
            class: "relative group rounded-lg overflow-hidden border border-base-300 bg-base-200 w-20"
          }, [
            isImage ? h("img", {
              src: url,
              class: "w-20 h-20 object-cover",
              onload: () => url && URL.revokeObjectURL(url)
            }) : h("div", {
              class: "w-20 h-20 flex flex-col items-center justify-center gap-1"
            }, [
              h("span", { class: "text-2xl" }, f.type?.includes('pdf') ? "📕" : "📄"),
              h("span", { class: "text-[8px] uppercase opacity-50" }, f.name?.split('.').pop()),
            ]),
            h("div", { class: "p-1" }, [
              h("div", { class: "text-[9px] truncate font-medium leading-tight" }, f.name),
              h("div", { class: "text-[8px] opacity-50" }, `${~~(f.size / 1024)} KB`),
            ]),
            !subiendo() ? h("button", {
              class: "absolute top-0.5 right-0.5 btn btn-circle btn-ghost btn-xs opacity-0 group-hover:opacity-100 bg-base-100/80",
              onclick: () => archivos(archivos().filter((_, idx) => idx !== i))
            }, h("span", { class: "icon-[lucide--x] w-3 h-3" })) : null,
          ])
        })
      ),

   
      () => subiendo() ? h("div", { class: "space-y-1" }, [
        h("div", { class: "flex justify-between text-xs" }, [
          h("span", {}, "Subiendo..."),
          h("span", {}, () => `${progreso()}%`),
        ]),
        h("progress", { class: "progress progress-primary w-full", value: progreso, max: "100" }),
      ]) : null,

  
      h("div", { class: "flex gap-2" }, [
        h("button", {
          class: "btn btn-ghost btn-sm",
          onclick: () => { archivos([]); error(""); }
        }, "🗑 Limpiar"),
        h("button", {
          class: "btn btn-primary btn-sm",
          disabled: subiendo,
          onclick: subirArchivos
        }, () => subiendo() ? "⏳ Subiendo..." : "☁️ Subir al servidor"),
      ]),
    ]) : null,
  ]),
  "#ui",
);


const tabsSignal = $([
  { id: "a", label: "Tab A", content: "Content of tab A", open: true },
  { id: "b", label: "Tab B", content: "Content of tab B", closable: true },
  { id: "c", label: "Tab C", content: "Content of tab C" },
]);

mount(
  () => ui.tabs(
    { class: "tabs-box" },
    () => tabsSignal().flatMap((tab, i) =>
      ui.tab({
        name: "demo-tabs",
        classContent: "bg-base-100 border-base-300 p-6",
        label: tab.label,
        content: tab.content,
        checked: tab.open || false,
        tabs: tabsSignal,
        index: i,
        closable: tab.closable || false,
      })
    )
  ),
  "#tab",
);

mount(
  div({ class: "flex flex-wrap gap-2" }, [
    button({ class: "btn", onclick: () => toast("File saved!") }, "Simple"),
    button(
      { class: "btn", onclick: () => toast("Error!", "alert-error", 5000) },
      "Error (5s)",
    ),
    button(
      {
        class: "btn",
        onclick: () =>
          toast(
            div({ class: "flex items-center gap-2" }, [
              span({ class: "icon-[lucide--check] text-lg" }),
              span({}, "Report generated"),
            ]),
            "alert-success",
          ),
      },
      "With icon",
    ),
    button(
      {
        class: "btn",
        onclick: () =>
          toast(
            div({ class: "flex flex-col" }, [
              strong({}, "ATTENTION!"),
              span({}, "Error saving!"),
              button(
                {
                  class: "btn btn-xs mt-1",
                  onclick: () => console.log("Retry"),
                },
                "Retry",
              ),
            ]),
            "alert-warning",
            7000,
          ),
      },
      "Complex",
    ),
  ]),
  "#toast",
);

mount(
  div({ class: "flex flex-wrap gap-2" }, [
    button({ class: "btn", onclick: () => toast("File saved!") }, "Simple"),
    button(
      { class: "btn", onclick: () => toast("Error!", "alert-error", 5000) },
      "Error (5s)",
    ),
    button(
      {
        class: "btn",
        onclick: () =>
          toast(
            div({ class: "flex items-center gap-2" }, [
              ui.icon("icon-[lucide--check]"),
              span({}, "Report generated"),
            ]),
            "alert-success",
          ),
      },
      "With icon",
    ),
    button(
      {
        class: "btn",
        onclick: () =>
          toast(
            div({ class: "flex flex-col" }, [
              strong({}, "ATTENTION!"),
              span({}, "Error saving!"),
              button(
                {
                  class: "btn btn-xs mt-1",
                  onclick: () => console.log("Retry"),
                },
                "Retry",
              ),
            ]),
            "alert-warning",
            7000,
          ),
      },
      "Complex",
    ),
  ]),
  "#demo-toast",
);