File Upload <mv-file-upload>

Zona para soltar arquivos: arrastar e soltar com destaque (e rejeição visual dos tipos não permitidos), uma lista com miniaturas de imagem, ícones, tamanhos e remoção, validação de tipo/tamanho/quantidade e progresso por arquivo; integrado a formulários (FormData).

CategoriaFormulários
TipoWeb Component (<mv-file-upload>)
Statusestável
Keywordsfile-upload, dropzone, drag-and-drop, upload, attachments, progress, form-associated

When to use

  • Users attach documents or images by drag and drop with type, size and count limits enforced
  • Uploads should show per-file progress, thumbnails and a way to remove a file before submitting
  • A form must submit selected files natively through FormData

Avoid when

  • A single optional file is picked in a dense form where a drop zone takes too much room → use Input instead
  • Files are dropped into a chat message composer → use AI Chat instead

Instalação

node scripts/add.mjs file-upload --out ./src/marvelous

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["file-upload"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/file-upload/file-upload.js, components/file-upload/file-upload.css.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:

<form id="file-upload-demo-form" style="display:grid;gap:.5rem;width:min(100%,30rem)">
  <label for="file-upload-demo" style="font-size:.875rem;font-weight:500">Supporting documents</label>
  <mv-file-upload id="file-upload-demo" name="attachments" accept="image/*,.pdf,.docx,.xlsx" multiple max-size="5 MB" max-files="6"></mv-file-upload>
</form>
<script type="module">
  const up = document.getElementById("file-upload-demo");

  // Simulated upload: progress ticks until 100%, one in eight fails.
  let seeding = false;
  const simulate = (file, from = 0) => {
    let pct = from;
    const fail = Math.random() < 0.125;
    const tick = () => {
      if (!up.files.includes(file)) return;
      pct += 6 + Math.random() * 16;
      if (fail && pct > 55) return up.setError(file, "Connection lost");
      up.setProgress(file, Math.min(100, pct));
      if (pct < 100) setTimeout(tick, 180 + Math.random() * 260);
    };
    setTimeout(tick, 150);
  };
  up.addEventListener("mv-add", (e) => { if (!seeding) e.detail.files.forEach((f) => simulate(f)); });

  // Seed the demo with a few realistic files.
  customElements.whenDefined("mv-file-upload").then(async () => {
    const canvas = document.createElement("canvas");
    canvas.width = 160; canvas.height = 160;
    const ctx = canvas.getContext("2d");
    const g = ctx.createLinearGradient(0, 0, 160, 160);
    g.addColorStop(0, "#f6d365"); g.addColorStop(0.55, "#fda085"); g.addColorStop(1, "#8e7cc3");
    ctx.fillStyle = g; ctx.fillRect(0, 0, 160, 160);
    ctx.fillStyle = "rgba(255,255,255,.85)";
    ctx.beginPath(); ctx.arc(112, 48, 18, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = "rgba(40,30,70,.55)";
    ctx.beginPath(); ctx.moveTo(0, 160); ctx.lineTo(56, 84); ctx.lineTo(96, 128); ctx.lineTo(124, 100); ctx.lineTo(160, 140); ctx.lineTo(160, 160); ctx.fill();
    const blob = await new Promise((r) => canvas.toBlob(r, "image/jpeg", 0.8));
    const pad = (n) => new Uint8Array(n);
    const photo = new File([blob, pad(1_380_000)], "site-facade.jpg", { type: "image/jpeg" });
    const quote = new File([pad(842_000)], "signed-quote-2026-09.pdf", { type: "application/pdf" });
    const plan = new File([pad(2_310_000)], "floor-plan-level-2.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
    seeding = true;
    const [a, b, c] = up.addFiles([photo, quote, plan]);
    seeding = false;
    up.setProgress(a, 100);
    up.setProgress(b, 100);
    up.setProgress(c, 64);
    setTimeout(() => simulate(c, 64), 2500);
  });
</script>

API

Attributes

NameTipoDefaultDescription
namestringName of the form entries (one per file).
acceptstringAccepted types, like <input type=file>: “image/*,.pdf,application/zip”.
multiplebooleanMultiple files (otherwise a new file replaces the previous one).
max-sizestringMax size per file: “5 MB”, “500KB”, “1048576”.
max-filesnumberMax number of files (with multiple).
labelstringMain text of the zone (default: “Drag and drop files or browse”).
hintstringHint text (generated default: types · size · count).
layoutlist | gridlistDetailed list or thumbnail grid.
requiredbooleanNative validation (valueMissing).
disabledbooleanDisables the zone.
localestringen-USSize formatting (Intl: “2.4 MB”).

Properties

NameTipoDescription
filesFile[]Kept files (read-only).
itemsArray<{ id, file, status, progress, error }>State of each file: idle | uploading | done | error.

Methods

NameDescription
open()Opens the native file picker.
addFiles(files)Validates and adds files; returns the accepted ones.
remove(file | id)Removes a file (and releases its preview).
clear()Clears the list.
setProgress(file | id, pct)Updates the progress bar (100 = uploaded).
setError(file | id, message)Marks a file as failed.
checkValidity() / reportValidity()Native validation.

Events

NameDescription
mv-adddetail: { files }, accepted files (hook point to start the upload).
mv-removedetail: { file }.
mv-changedetail: { files }, full list after an add/remove.
mv-rejectdetail: { errors: [{ file, reason: type | size | count, message }] }.

Content structure

NameDescription
child contentReplaces the default content of the drop zone.

CSS classes

NameDescription
mv-file-upload-zone / -item / -thumb / -progress / -errorsGenerated parts; data-dragging / data-drag-reject on the host while dragging.

CSS variables

NameDefaultDescription
--mv-file-upload-width32remMax width.
--mv-file-upload-zone-height10remMin height of the zone.
--mv-file-upload-radiusvar(--mv-radius-xl)Zone radius.

Accessibility

The zone is a focusable role=button (Enter/Space open the picker), named by the <label for> and described by the hint text. Validation errors go in a role=alert region; additions, removals and completed uploads are announced through a polite live region. Each file has a “Remove <name>” button (focus moves to the next one) and a role=progressbar bar while uploading.

Esta página foi traduzida com IA. Informar um problema de tradução