Componentes
Vazio, carregando & erro
Vazio
Nenhum paciente ainda
Conecte o WhatsApp ou importe sua base pra começar a organizar a agenda.
Nenhum paciente com esse filtro
Tente outro termo ou volte pra lista completa.
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
| icon | LucideIcon | — | Opcional. Com ícone, o gap sobe pra 4 e nasce o selo circular ink-100 de 56px; sem ícone, gap-3 (é a variante de filtro). |
| title | string | — | Uma frase curta, 16/500. |
| description | string | — | O próximo passo, em max-w-xs. Não descreve o vazio: aponta a saída. |
| children | ReactNode | — | As ações. Primária no onboarding, outline no filtro. |
| Peça | Receita |
|---|---|
| Moldura | rounded-xl border border-dashed border-border |
| Respiro | py-phi-4 (fluido) |
| Selo do ícone | size-14 rounded-full bg-ink-100 · ícone size-6 em fg-subtle |
| Texto | centrado, description em max-w-xs |
NotaA borda tracejada é semântica
Carregando
| Regra | Detalhe |
|---|---|
| O esqueleto imita a FORMA real | Campos são pílulas no app, então o esqueleto deles é rounded-full. Retângulo no lugar de pílula faz a tela “trocar de roupa” quando carrega. |
| Nunca um spinner no meio do nada | Spinner só dentro de um botão que está trabalhando (o ícone Loader2 do toast de loading). |
| aria-busy e role=status | A região carregando se anuncia: role="status" aria-busy="true" aria-label="Carregando". |
| loading.tsx embrulha a PAGE, nunca o layout do mesmo segmento | Um await no layout faz o boundary de cima piscar antes do palco aparecer. |
| Sem fade de entrada | O conteúdo entra no lugar do esqueleto. Fade duplicado faz a tela piscar. |
@utility skeleton { @apply animate-pulse bg-ink-100 motion-reduce:animate-none }
/* NÃO combinar com outro animate-* na mesma classe: utility composto sai antes
no CSS, então o animate-* de fora venceria o animation:none do reduced-motion. */
<div className="skeleton h-9 w-full rounded-full" /> ✓
<div className="skeleton animate-bounce" /> ✗Erro
Algo deu errado
A tela não conseguiu carregar. Tente de novo. Se continuar, recarregue a página.
código a1b2c3
Verificação em duas etapas ativa nesta conta.
| Altura | Peça | Comportamento |
|---|---|---|
| A rota falhou | RouteError (error.tsx) | Selo, copy calma, o digest do erro em mono e um retry que faz router.refresh() + reset(). |
| O formulário recusou | FormAlert | role="alert" + rolagem até ficar visível quando o erro SURGE ou MUDA — em form longo no celular ele nascia acima da dobra e passava batido. |
| O campo está errado | FieldError | 12px destructive, ligado ao input por aria-describedby, e some ao primeiro toque. |
| Deu certo | toast.success / PositiveBanner | Toast pro que acabou de acontecer; banner pro estado que permanece. |
Sim
Diz o que houve e dá o caminho. Painel discreto, sem ícone de alarme, sem borda vermelha grossa.
Não
Vermelho cheio, caixa alta e vocabulário de servidor. Assusta e não ajuda: quem marca consulta não conhece código HTTP.
Toast
| Regra | Detalhe |
|---|---|
| Título curto + descrição opcional | “Consulta confirmada” + “Marina Alvez confirmou para amanhã, 14h.” |
| O tema vem por prop | O root layout lê o cookie e passa; nunca useTheme/next-themes — sem provider o hook cai em “system” e o toast herdaria o escuro do sistema num app claro. |
| Toast não substitui estado | Se a mudança fica na tela (o item sumiu da lista), o toast é confirmação; se não fica, a informação precisa de outro lugar. |
| Desfazer vive dentro do toast | O timer do Sonner PAUSA no hover: um setTimeout paralelo comete a ação enquanto o toast ainda está na tela. Comitar no onAutoClose/onDismiss, com uma flag de desfeito. |
| Erro no toast só pro que é passageiro | Falha de envio, timeout. Erro de formulário mora no FormAlert, perto do campo. |
Um toast.loading pode virar sucesso reusando o mesmo id (o botão acima demonstra). Hoje nenhuma tela do app usa esse caminho — as ações longas desabilitam o botão e trocam o rótulo (“Salvando…”). Se for usar, lembre que ele ocupa a fila de toasts durante toda a espera.
A copy dos três estados
Fórmulas
| Estado | Fórmula | Exemplo |
|---|---|---|
| Vazio (onboarding) | O que falta + o próximo passo | “Nenhum paciente ainda. Conecte o WhatsApp e comece a organizar a agenda.” |
| Vazio (filtro) | O que não bateu + como voltar | “Nenhum paciente com esse filtro. Tente outro termo ou volte pra lista completa.” |
| Erro | O que houve + o que fazer (+ o código, discreto) | “A tela não conseguiu carregar. Tente de novo. Se continuar, recarregue a página.” |
| Sucesso | O que aconteceu, com o objeto | “207 cadastros importados.” |
NuncaNunca culpar quem está lendo