Tema
BaseFileUpload
Recebe os anexos de um formulário: memória de cálculo, planilhas, fotos da obra. A pessoa arrasta os arquivos para a área ou usa o botão "Selecionar arquivos". Antes de qualquer erro, a área já diz o tipo e o tamanho aceitos; cada arquivo recusado aparece na lista com o motivo.
Uso
- Use para anexar arquivos num formulário: memória de cálculo na medição, fotos no relatório de fiscalização, planilha no cadastro do contrato.
labeldiz o que se anexa ("Memória de cálculo"), não "Arquivo" ou "Anexo".- Informe
acceptemax-sizesempre que houver regra. A área mostra a regra antes de errar ("PDF, até 20 MB") e o mesmo texto vale para o motivo da recusa. Prefira extensões (.pdf,.xlsx) a tipos MIME: elas aparecem melhor no texto. - Sem
multiple, o campo guarda um arquivo; escolher outro substitui o atual, como no campo nativo. Commultiple, os arquivos novos entram no fim da lista. v-modeltraz só os arquivos aceitos (File[]). Os recusados ficam na lista com erro até a pessoa removê-los ou escolher de novo, e chegam à tela pelo eventoreject.- O componente não envia nada. A tela envia os arquivos do
v-modelao salvar, ou mostra o envio em andamento comBaseFileItem(uploading,progress). - Use
invalidpara regras que só a tela confere, como um anexo obrigatório que faltou ao salvar. - Não coloque
BaseFileUploaddentro deBaseField: o componente já tem legenda, ajuda e erro, e oBaseFieldcriaria um segundo rótulo apontando para nenhum campo.
Regras e recusas
Arraste para a área abaixo um arquivo que não seja PDF, um PDF com mais de 1 MB ou o mesmo arquivo duas vezes.
| Situação | Mensagem na linha do arquivo |
|---|---|
Tipo fora de accept | "Tipo de arquivo não aceito. Envie PDF." |
Acima de max-size | "O arquivo tem 25 MB e passa do limite de 20 MB."; se o tamanho arredondado empatar com o limite, "O arquivo passa um pouco do limite de 20 MB." |
| Arquivo vazio (0 B) | "O arquivo está vazio. Escolha outro." |
| Já está na lista | "Este arquivo já está na lista." |
Mais de um sem multiple | "Este campo aceita um arquivo só. Escolha um por vez." |
O mesmo arquivo é reconhecido pelo nome, tamanho e data de modificação.
Estados
| Estado | Como |
|---|---|
| Padrão | Borda tracejada; no hover e com o foco no botão, a borda fica azul |
| Arrastando | Borda contínua, fundo de seleção e o texto muda para "Solte para adicionar". Só arquivos ativam a área; texto ou link arrastado não muda nada |
| Inválido | invalid, com invalid-text; sem texto, aparece "Anexe pelo menos um arquivo." A borda fica vermelha e a mensagem tem ícone |
| Aviso | warn com warn-text; sem texto, o aviso não aparece |
| Carregando | loading: o botão continua focável, mostra o indicador e não abre o seletor; soltar arquivos não faz nada. Remover um arquivo continua permitido, e a tela recebe a mudança por update:modelValue |
| Desabilitado | disabled: área apagada, botão e remoção desabilitados |
| Somente leitura | readonly: mostra só a lista, sem área nem remoção; sem arquivos, "Nenhum arquivo anexado." |
Tamanhos
Use sm em painéis laterais e cartões, como "Anexar outro PDF" na tela da medição: sem ícone, texto e botão na mesma linha, e linhas de arquivo compactas.
Estilo
| Propriedade | Token |
|---|---|
| Área: fundo | --tj-layer-02; hover e arrastando --tj-layer-selected-01; desabilitado --tj-field-disabled |
| Área: borda | 1,5 px tracejada --tj-border-strong-01; hover e foco --tj-border-interactive; arrastando contínua --tj-border-interactive; inválido --tj-support-error-text; aviso --tj-support-warning-text (3:1 sobre a área nos dois temas); desabilitado --tj-border-subtle-02. Arrastando vence inválido e aviso |
| Área: raio | --tj-radius-lg (12 px) |
| Área: espaço interno | --tj-space-6 (24 px); sm com --tj-space-4 (16 px) |
| Ícone | cloud-arrow-up, --tj-icon-lg (24 px), --tj-icon-interactive; arrastando file-earmark-arrow-down |
| Texto | --tj-text-md, --tj-text-secondary; sm com --tj-text-sm; arrastando --tj-text-primary e --tj-weight-medium |
| Regra (tipo e tamanho) | --tj-text-xs, --tj-text-helper |
| Botão | BaseButton secondary com ícone upload, do mesmo tamanho do componente |
| Foco do botão | Contorno sólido de 2 px --tj-border-interactive, afastado 2 px. O --tj-ring translúcido fica abaixo de 3:1 sobre o fundo claro da área |
| Rótulo e mensagens | Os mesmos de BaseField: --tj-text-sm, rótulo --tj-text-secondary semibold, erro --tj-support-error-text, aviso --tj-support-warning-text |
| Lista | BaseFileItem, com --tj-space-2 entre as linhas (sm: --tj-space-1) |
| Movimento | Borda e fundo com --tj-duration-fast-02 e --tj-easing-standard; sem transição com movimento reduzido. O indicador de loading é o do BaseButton, que gira devagar com movimento reduzido |
| Alto contraste | Borda CanvasText; arrastando, borda e contorno interno Highlight; foco com contorno Highlight |
Código
vue
<script setup lang="ts">
import { ref } from 'vue'
import { BaseFileUpload } from '@lab-inovacao/design-system'
const memoria = ref<File[]>([])
</script>
<template>
<BaseFileUpload
v-model="memoria"
label="Memória de cálculo"
accept=".pdf"
:max-size="20 * 1024 * 1024"
:invalid="tentouSalvar && memoria.length === 0"
required
@reject="registrarRecusa"
/>
</template>| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
v-model | File[] | [] | Arquivos aceitos, na ordem em que entraram |
label | string | — | O que se anexa; obrigatório. Vira a legenda do grupo |
accept | string | — | Tipos aceitos, na sintaxe do campo nativo: .pdf,.xlsx, image/* |
maxSize | number | — | Tamanho máximo em bytes |
multiple | boolean | false | Aceita vários arquivos; sem ele, um novo substitui o atual |
size | 'sm' | 'md' | 'md' | Densidade da área, do botão e das linhas |
buttonLabel | string | "Selecionar arquivos" (ou "Selecionar arquivo") | Texto do botão |
helperText | string | — | Ajuda abaixo da área |
invalid / invalidText | boolean / string | — / "Anexe pelo menos um arquivo." | Erro no lugar da ajuda |
warn / warnText | boolean / string | — | Aviso no lugar da ajuda |
required / optional | boolean | false | Marca na legenda |
disabled | boolean | false | Desabilita área, botão e remoção |
readonly | boolean | false | Mostra só a lista |
loading | boolean | false | Bloqueia novos arquivos enquanto a tela trabalha |
hideLabel | boolean | false | Legenda só para leitor de tela |
| Evento | Dados | Quando |
|---|---|---|
update:modelValue | File[] | Arquivos aceitos entraram ou um saiu |
reject | { file: File, reason: string }[] | Uma escolha ou um arraste teve arquivos recusados |
Para usar as mesmas regras fora do componente, src/lib/fileUpload.ts exporta selectFiles, fileRejectionReason, matchesAccept, describeAccept e uploadHint.
Acessibilidade
- Segue o padrão de botão da WAI-ARIA APG: "Selecionar arquivos" é um
<button>nativo, alcançado pelo Tab e acionado com Enter ou Espaço. Ele abre o seletor de arquivos do sistema, de um<input type="file">escondido. Arrastar é um atalho para o mouse; tudo o que ele faz, o botão também faz. - O nome do botão é o texto visível, então quem usa comando de voz diz "Selecionar arquivos" (WCAG 2.5.3). O botão é descrito pela regra ("PDF, até 20 MB") e pela ajuda, erro ou aviso (
aria-describedby). - Área, botão e lista ficam num
<fieldset>com a legenda dolabel: o leitor de tela anuncia "Memória de cálculo" ao entrar no grupo. A lista temrole="list", para o Safari não perder a semântica de lista sem marcadores, e o nome "Arquivos de" mais olabel, que distingue duas listas na mesma tela. Obrigatório tem asterisco visível e "(obrigatório)" para o leitor de tela, porque um botão não temrequired. - Uma região
aria-live="polite"anuncia o que mudou: "memoria-calculo-29.pdf adicionado.", "2 arquivos adicionados: a.pdf e b.pdf. 1 arquivo recusado.", "memoria-calculo-29.pdf removido.". Cada linha recusada lê o próprio motivo, peloBaseFileItem. A região é esvaziada antes de cada texto, então o mesmo resultado duas vezes seguidas ("1 arquivo recusado.") é lido de novo. - Ao remover um arquivo, o foco vai para o botão de remover que ocupou o lugar, para o anterior ou, com a lista vazia, para "Selecionar arquivos". O foco nunca se perde.
- Erro e aviso têm ícone, texto e borda; a mensagem começa com "Erro: " ou "Atenção: " escondido na tela. Arrastando, além da cor, a borda fica contínua e o texto muda. A cor nunca é o único sinal.
- O foco do botão é um contorno sólido com 3:1 sobre a área, que continua visível no alto contraste do Windows.
- Com
loading, o botão continua focável, em vez de sumir da ordem do Tab, e é marcado como indisponível (aria-disabled) e ocupado (aria-busy). O grupo não recebearia-busy, para não segurar os anúncios da regiãoaria-liveque fica dentro dele. - Com movimento reduzido, a área muda sem transição.
- Dentro de um
BaseField(o que não se recomenda), o componente não herda oiddo campo: o rótulo doBaseFieldfica sem campo, e a legenda do grupo continua sendo o nome. - Testado com axe em todos os estados: padrão, com regra e ajuda, com lista, obrigatório, opcional, inválido, aviso, desabilitado, somente leitura (com e sem arquivos), carregando, compacto, legenda escondida, arrastando, com arquivos recusados anunciados e com um segundo arquivo recusado sem
multiple. O contraste vem dos tokens de função, que mantêm AA nos dois temas; o jsdom não calcula cores.