Pular para o conteúdo

Componentes e formulários

Você manda dados tipados; o Trivo desenha cada pixel. É o que impede um botão de terceiro de se parecer com um diálogo do sistema.

Onde eles são escritos

await interaction.reply("Escolha uma cor:", {
  components: [
    { type: "row", items: [
      { type: "button", id: "azul", label: "Azul", style: "primary" },
      { type: "button", id: "verde", label: "Verde" },
    ]},
    { type: "card", title: "Tempo agora", description: "Vinte e um graus.", color: "accent" },
  ],
});

O mesmo vale para deferredReply e followUp. Para pedir texto, o gesto é interaction.showModal(…), e o envio chega como uma interação nova, do tipo form, pelo mesmo onInteraction.

A régua, antes de qualquer campo

Nada de HTML, nada de CSS, nada de estilo embutido, nada de URL de imagem arbitrária. Um App não pinta a interface do Trivo — e a razão não é estética: um botão que pareça um aviso do sistema é phishing renderizado pelo nosso próprio cliente.

O que o parser não conhece é recusado na publicação, e não degradado em silêncio. Diferente do texto de mensagem, aqui não existe histórico a preservar: o primeiro componente já nasce validado.

Onde eles entram

components vai no corpo de qualquer uma das três portas de resposta — a confirmação, a resposta diferida e o seguimento — e também no corpo com que você responde ao POST assinado.

{
  "type": "reply",
  "content": "Escolha uma cor:",
  "components": [
    { "type": "row", "items": [
        { "type": "button", "id": "azul", "label": "Azul", "style": "primary" },
        { "type": "button", "id": "verde", "label": "Verde" }
    ]},
    { "type": "card", "title": "Tempo agora", "description": "Vinte e um graus.", "color": "accent" }
  ]
}

Os blocos

BlocoO que éCabe junto?
rowUma linha de até 5 itens: botão, link ou seleção.Sim, lado a lado.
cardTítulo, texto, cor, linhas de dado e rodapé.Ocupa a largura inteira.

No máximo 5 blocos por mensagem, e o conjunto inteiro tem teto de 8192 bytes — a conta é em bytes, não em caracteres, porque é isso que o banco e o fio carregam.

Os itens de uma linha

ItemCamposO que volta quando alguém aciona
buttonid, label, style (default | primary | danger), disabledcomponentId, sem valores
linklabel, url (só https:)Nada: link não aciona interação, e por isso não tem id
selectid, placeholder, options (até 25), min, maxcomponentId e values

A forma de uma opção de select

{ "type": "select", "id": "cor", "placeholder": "Escolha uma cor",
  "options": [
    { "value": "azul", "label": "Azul", "description": "o padrão" },
    { "value": "verde", "label": "Verde" }
  ] }
  • Cada opção é { value, label, description? } — e repare que a sugestão do autocompletar, que se parece com isto, usa name no lugar de label. São dois vocabulários vizinhos e diferentes: o de Interações existe para entrar na linha de texto de quem digita, e este existe para virar uma lista desenhada pelo Trivo.
  • Um select ocupa a linha inteira: ele abre uma lista por cima do vizinho, e dividir a linha produziria alvo de toque espremido no celular.
  • O id é único na mensagem toda, e não por linha: o acionamento volta com o id e mais nada, então dois botões com o mesmo id são a mesma interação para você.
  • O id é roteamento, nunca autorização. Ele voltou do cliente e é tão confiável quanto qualquer coisa que voltou do cliente. Quem prova de quem o componente é são a mensagem e a linha dela, do nosso lado — mas o que o botão FAZ é decisão sua, e a mensagem com botões é pública: qualquer um a vê.

O cartão

{
  "type": "card",
  "title": "Chamado #418",
  "description": "Aberto por **Ana** há dois dias.",
  "color": "danger",
  "fields": [
    { "name": "Estado", "value": "Em análise", "inline": true },
    { "name": "Prioridade", "value": "Alta", "inline": true }
  ],
  "footer": "Atualizado agora"
}
  • A cor sai de um conjunto fechadoneutral, accent, success, danger —, e nunca de um hexadecimal. Cada nome vira um token do nosso design system e acompanha o tema de quem lê; um valor fixo sumiria em metade dos temas.
  • O texto usa a gramática do Trivo, a mesma do resto do produto: negrito, link https: e lista. Não há um segundo markdown aqui.
  • inline é pedido, não ordem: quem decide se cabe lado a lado é a largura de quem lê. Num celular estreito os campos empilham.
  • Um cartão precisa de title, description ou fields. Sem os três ele é um retângulo colorido, e é recusado.
  • Imagem ainda não existe. Ela será por chave de anexo, nunca por URL externa — senão o cliente de quem lê vira rastreador de terceiro —, e a chave depende de um caminho de upload que os Apps ainda não têm.

O formulário

Responda com type: "modal" para abrir um formulário na tela de quem acionou. A interação fecha em answered — você respondeu, e respondeu com um formulário —, e o envio é uma interação nova, do tipo form, que chega pelo caminho de sempre.

{
  "type": "modal",
  "modal": {
    "id": "sugestao",
    "title": "Mandar sugestão",
    "fields": [
      { "type": "text", "id": "titulo", "label": "Título", "required": true, "maxLength": 60 },
      { "type": "paragraph", "id": "corpo", "label": "Detalhes", "placeholder": "O que você faria?" }
    ]
  }
}
CampoVale paraObservação
typetext | paragraphUma linha ou um parágrafo. São os dois.
requiredAmbosConferido de novo aqui, contra a definição que guardamos.
minLengthAmbosSó se o campo não veio vazio.
maxLengthAmbosAté 4000 caracteres.
  • Até 5 campos, e pelo menos um.
  • Guardamos a definição. O que chega no envio é conferido contra ela — um campo que você não declarou é recusado, e os valores voltam para você na ordem em que você os declarou, não na que o cliente mandou.
  • Um formulário, um envio. O segundo leva FORM_NOT_FOUND, que é a mesma resposta para "já foi enviado", "venceu", "não existe" e "é de outra pessoa".
  • modal não vale sobre autocomplete nem sobre form: um formulário que abre outro formulário é laço sem saída.

O que chega quando alguém envia

{
  "type": "form",
  "data": {
    "modalId": "sugestao",
    "fields": [
      { "id": "titulo", "value": "Botão de exportar" },
      { "id": "corpo", "value": "" }
    ]
  }
}

Campo que ninguém preencheu chega como texto vazio, e não ausente: duas formas de dizer "em branco" obrigariam você a lidar com as duas.