# Mensagens Components V2
> Aprenda cada peça visual e combine containers, seções, mídia, action rows e selects em mensagens legíveis.

- Área: Componentes
- URL humana: https://ninenity.vercel.app/doc/mensagens-v2
- URL Markdown: https://ninenity.vercel.app/doc/markdown/mensagens-v2
- Pacotes e conceitos: Container, Section, Gallery, Select

## Mensagem básica com container

O container cria um bloco visual e pode receber cor de destaque, texto e divisores.

### status-card.ts

```text

import {
  ContainerBuilder,
  MessageBuilder,
  SeparatorBuilder,
  TextDisplayBuilder
} from '@ninenity/componentbuilder'

export const statusCard = new MessageBuilder()
  .addComponents(
    new ContainerBuilder()
      .setAccentColor(0xa571f4)
      .addTextDisplayComponents(
        new TextDisplayBuilder().setContent('## Status do serviço'),
        new TextDisplayBuilder().setContent('Todos os sistemas estão operacionais.')
      )
      .addSeparatorComponents(
        new SeparatorBuilder().setDivider(true).setSpacing('small')
      )
      .addTextDisplayComponents(
        new TextDisplayBuilder().setContent('- API: online
- Bot: online
- Banco: online')
      )
  )
  .template()
```


O conteúdo do `TextDisplayBuilder` aceita markdown suportado pelo Discord. Prefira blocos curtos e uma hierarquia clara em vez de uma única parede de texto.

## Enviar pelo Core

Converta o documento e combine as flags de Components V2 e resposta efêmera quando necessário.

### Discord/Commands/Status.ts

```text

import { MessageFlags, SlashCommandBuilder } from '@ninenity/core'
import { toDiscordMessagePayload } from '@ninenity/componentbuilder'
import { statusCard } from '../Interactions/StatusCard'
import Client from '../../Shared/Settings/Client'

Client.slash({
  data: new SlashCommandBuilder()
    .setName('status')
    .setDescription('Mostra o estado dos serviços.'),
  execute: async interaction => {
    await interaction.reply({
      ...toDiscordMessagePayload(statusCard.document()),
      flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral
    })
  }
})
```


> **Não misture content com Components V2 sem validar:** Monte todo o conteúdo visual nos componentes. O payload V2 possui regras diferentes das mensagens clássicas e o conversor já prepara a estrutura correta.

## Seção com thumbnail ou botão

Uma `SectionBuilder` combina texto com exatamente um acessório lateral.

### project-section.ts

```text

const withThumbnail = new SectionBuilder()
  .addTextDisplayComponents(
    new TextDisplayBuilder().setContent(
      '### Builder Studio
Crie e exporte interfaces Components V2.'
    )
  )
  .setThumbnailAccessory(
    new ThumbnailBuilder()
      .setURL('https://cdn.example.com/builder.png')
      .setDescription('Logo do Builder Studio')
  )

const withButton = new SectionBuilder()
  .setContent('### Documentação
Veja todos os exemplos do componente.')
  .setButtonAccessory(
    new ButtonBuilder()
      .setStyle(ButtonStyle.Link)
      .setLabel('Abrir docs')
      .setURL('https://ninenity.com/doc')
  )

const template = new MessageBuilder()
  .addComponents(
    new ContainerBuilder().addSectionComponents(withThumbnail, withButton)
  )
  .template()
```


Botões de link não possuem `customId` nem callback. Botões de ação precisam de `customId`, estilo diferente de `Link` e podem usar `.setCallback()`.

## Galeria e arquivo

Use galeria para mídia visual e arquivo quando o anexo faz parte da composição.

### media-card.ts

```text

const gallery = new MediaGalleryBuilder()
  .addItems(
    item => item
      .setURL('https://cdn.example.com/dashboard.png')
      .setDescription('Dashboard do projeto'),
    item => item
      .setURL('https://cdn.example.com/components.png')
      .setDescription('Componentes no Discord')
  )

const template = new MessageBuilder()
  .addComponents(
    new ContainerBuilder()
      .addTextDisplayComponents(
        new TextDisplayBuilder().setContent('## Visão do projeto')
      )
      .addMediaGalleryComponents(gallery)
      .addFileComponents(
        new FileBuilder().setURL('attachment://relatorio.pdf')
      )
  )
  .template()
```


- A galeria aceita no máximo 10 itens.
- Use `setSpoiler(true)` em uma imagem ou arquivo que não deve aparecer imediatamente.
- A URL `attachment://nome.ext` precisa corresponder a um arquivo realmente enviado no payload.
- Sempre forneça descrição útil para imagens importantes.

## Linha de botões

Uma action row aceita até cinco botões. Use estilos para significado, não apenas decoração.

### actions.ts

```text

const actions = new ActionRowBuilder<MessageActionRowComponentBuilder>()
  .addComponents(
    new ButtonBuilder()
      .setCustomId('project:approve')
      .setLabel('Aprovar')
      .setStyle(ButtonStyle.Success),
    new ButtonBuilder()
      .setCustomId('project:edit')
      .setLabel('Editar')
      .setStyle(ButtonStyle.Secondary),
    new ButtonBuilder()
      .setCustomId('project:delete')
      .setLabel('Excluir')
      .setStyle(ButtonStyle.Danger),
    new ButtonBuilder()
      .setLabel('Ver no HUB')
      .setStyle(ButtonStyle.Link)
      .setURL('https://hub.ninenity.com')
  )

const message = new MessageBuilder()
  .addComponents(new ContainerBuilder().addActionRowComponents(actions))
```


| Estilo | Use para |
| --- | --- |
| `Primary` | A ação principal daquela etapa |
| `Secondary` | Ações neutras, navegação e alternativas |
| `Success` | Confirmar, concluir ou ativar |
| `Danger` | Excluir, revogar ou outra ação destrutiva |
| `Link` | Abrir URL; não recebe callback |

## Menus de seleção

String select define opções; selects de usuário, cargo, canal e menção usam os seletores nativos do Discord.

### string-select.ts

```text

const projectSelect = new StringSelectMenuBuilder()
  .setCustomId('project:environment')
  .setPlaceholder('Selecione um ambiente')
  .setMinValues(1)
  .setMaxValues(1)
  .addOptions(
    option => option
      .setLabel('Produção')
      .setValue('production')
      .setDescription('Ambiente público'),
    option => option
      .setLabel('Desenvolvimento')
      .setValue('development')
      .setDescription('Ambiente de testes')
  )

const row = new ActionRowBuilder<MessageActionRowComponentBuilder>()
  .addComponents(projectSelect)
```


### seletores nativos

```text

const userRow = new ActionRowBuilder<MessageActionRowComponentBuilder>()
  .addComponents(
    new UserSelectMenuBuilder()
      .setCustomId('team:members')
      .setPlaceholder('Selecione até 3 membros')
      .setMinValues(1)
      .setMaxValues(3)
  )

const roleRow = new ActionRowBuilder<MessageActionRowComponentBuilder>()
  .addComponents(
    new RoleSelectMenuBuilder()
      .setCustomId('team:role')
      .setPlaceholder('Selecione o cargo da equipe')
  )
```


## Variáveis de template

Tokens deixam o mesmo layout renderizar dados diferentes sem reconstruir toda a árvore.

### member-template.ts

```text

const memberTemplate = new MessageBuilder()
  .addComponents(
    new ContainerBuilder().addTextDisplayComponents(
      new TextDisplayBuilder().setContent(
        '## Olá, ${user.name}!
Seu plano atual é **${account.plan}**.'
      )
    )
  )
  .template()

const rendered = memberTemplate.render({
  user: { name: interaction.user.displayName },
  account: { plan: 'Pro' }
})

await interaction.reply({
  ...toDiscordMessagePayload(rendered.document()),
  flags: MessageFlags.IsComponentsV2
})
```


> **Tokens de paginação:** O `Paginator` hidrata automaticamente `pages.current`, `pages.total`, `pages.hasNext` e `pages.hasBack`. Você não precisa calcular esses valores em páginas estáticas.
