InícioBoas práticasTemaIdiomas e RTLApp e cabeçalhoLayoutTipografiaAbasMenu e âncoraDropdown e popoverCommand e togglesBotãoCamposSeleção e valoresCombobox e mençõesDatas e calendárioÁrvore e cascataTransferUploadFiltrosTabelaPlanilhaKPI e estatísticaBadge e tagListas e detalhesGráficosImagem e carrosselÍconesDiagramasAlertas e estadosProgresso e passosDiálogo e toastDrawer e notificaçãoUtilitáriosOverviewWork queueReportReport com htmxForm / requestTabela administrativaPágina de detalheFormulário em diálogoGráfico com dados do servidorBuscar… llms.txtTemaTemaRestaurarCopiar promptCopiar snippetPrompt: para as instruções de um agente. Snippet: direto no templates/base.html.v0.11.0File upload with a drag-and-drop zone (click, drag or keyboard), validation and a file list.Notas fiscais com progressoO componente só seleciona e valida. Você envia com abCall e informa o andamento com setStatus(file, 'uploading' | 'done' | 'error'). Aqui o envio é simulado; arquivos com 'erro' no nome falham.Enviar notasLimpar<ab-stack gap="3">
<ab-upload id="nf-upload" name="notas" label="Notas fiscais" accept=".pdf,.xml" multiple max-size="5" max-files="10"></ab-upload>
<ab-cluster>
<ab-button id="nf-send" variant="primary" icon="upload-simple">Enviar notas</ab-button>
<ab-button id="nf-clear" variant="ghost">Limpar</ab-button>
</ab-cluster>
</ab-stack>
<script type="module">
// ab-upload is loaded on demand: wait for it before calling its methods at page load.
await customElements.whenDefined("ab-upload");
const up = document.getElementById("nf-upload");
const pdf = (name, kb) => new File([new Uint8Array(kb * 1024)], name, { type: "application/pdf" });
const xml = (name) => new File(["<nfeProc/>"], name, { type: "text/xml" });
// Estado inicial para ilustrar: uma enviada, uma enviando, uma com erro e um arquivo grande recusado.
const [a, b, c] = [pdf("NF-e 4512 - Fornecedor ABC.pdf", 312), xml("NF-e 4513.xml"), pdf("NF-e 4514 (erro).pdf", 840)];
up.files = [a, b, c];
up.setStatus(a, "done");
up.setStatus(b, "uploading", { progress: 62 });
up.setStatus(c, "error", { message: "Chave de acesso inválida" });
up.addFiles([pdf("Lote completo março.pdf", 6 * 1024)]);
async function send(file) {
// Na sua Page: await abCall("salvar_nota", { arquivo: file });
up.setStatus(file, "uploading", { progress: 0 });
for (let p = 15; p <= 100; p += 15 + Math.random() * 20) {
await new Promise((r) => setTimeout(r, 250));
up.setStatus(file, "uploading", { progress: Math.min(p, 99) });
}
if (/erro/i.test(file.name)) up.setStatus(file, "error", { message: "Chave de acesso inválida" });
else up.setStatus(file, "done");
}
document.getElementById("nf-send").addEventListener("click", async (e) => {
const btn = e.currentTarget;
const pending = up.files.filter((f) => up.getStatus(f)?.status !== "done");
if (!pending.length) return abToast("Nada para enviar", { tone: "info" });
btn.loading = true;
await Promise.all(pending.map(send));
btn.loading = false;
const failed = up.files.filter((f) => up.getStatus(f)?.status === "error").length;
abToast(failed ? `${failed} nota(s) com erro` : "Notas enviadas", { tone: failed ? "danger" : "ok" });
});
document.getElementById("nf-clear").addEventListener("click", () => up.clear());
</script>Comprovantes com miniaturalist-type picture mostra a imagem de cada recibo. PDFs ficam com o ícone do tipo.<ab-upload id="receipts" label="Comprovantes de despesa" accept="image/*,.pdf" multiple list-type="picture" max-size="10" help="Foto legível do cupom ou nota. Até 10 MB por arquivo."></ab-upload>
<script type="module">
const svg = (bg, label) => `<svg xmlns="http://www.w3.org/2000/svg" width="120" height="160"><rect width="120" height="160" fill="${bg}"/><rect x="14" y="16" width="92" height="10" rx="3" fill="#ffffffcc"/><rect x="14" y="36" width="70" height="6" rx="3" fill="#ffffff99"/><rect x="14" y="48" width="80" height="6" rx="3" fill="#ffffff99"/><rect x="14" y="60" width="60" height="6" rx="3" fill="#ffffff99"/><text x="14" y="140" font-family="sans-serif" font-size="16" font-weight="700" fill="#fff">${label}</text></svg>`;
const img = (name, bg, label) => new File([svg(bg, label)], name, { type: "image/svg+xml" });
const up = document.getElementById("receipts");
up.files = [
img("almoco-cliente.svg", "#2f855a", "R$ 186,40"),
img("taxi-aeroporto.svg", "#3d79d8", "R$ 74,90"),
new File([new Uint8Array(220 * 1024)], "hotel-recife.pdf", { type: "application/pdf" }),
];
</script>Em um formulárioab-upload participa do FormData: new FormData(form).getAll(name) devolve os File. Ou passe upload.files direto para abCall.<form id="contrato-form">
<ab-stack gap="3">
<ab-input name="fornecedor" label="Fornecedor" value="Acme Ltda"></ab-input>
<ab-upload name="contrato" label="Contrato assinado" accept=".pdf" required max-size="20" compact></ab-upload>
<ab-cluster><ab-button type="submit" variant="primary">Cadastrar</ab-button><code id="contrato-out">—</code></ab-cluster>
</ab-stack>
</form>
<script type="module">
const form = document.getElementById("contrato-form");
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!form.reportValidity()) return;
const fd = new FormData(form);
// await abCall("cadastrar_contrato", { fornecedor: fd.get("fornecedor"), contrato: fd.get("contrato") });
document.getElementById("contrato-out").textContent = `fornecedor=${fd.get("fornecedor")}, contrato=${fd.get("contrato").name}`;
});
</script>Desabilitado<ab-upload label="Anexos" disabled hint="Aguardando aprovação do pedido"></ab-upload>APIAtributos, propriedades, métodos, eventos e slots de cada tag.<ab-upload>File upload with a drag-and-drop zone (click, drag or keyboard), validation and a file list.
Form-associated: inside a <form>, new FormData(form).getAll(name) returns the File objects.
It does NOT upload by itself — send the files with abCall("fn", { files: upload.files }) or your
own fetch, and report progress back with upload.setStatus(file, "uploading", { progress: 40 }),
then "done" or "error" (with { message }).
Validation (accept, max-size, max-files) rejects files with an inline error; accepted files are in files.