Attachments
Renderiza arquivos e documentos de origem anexados a um turno de chat — thumbnails em grid, chips inline ou uma lista detalhada, com previews em hover e remoção.
Visão geral
Attachments renderiza os arquivos e documentos de origem vinculados a um turno de chat — imagens, PDFs, áudio, vídeo ou fontes citadas. É um primitive composto e orientado por contexto: o container Attachments define a variant (grid, inline ou list), e cada filho Attachment lê essa variant para se organizar, escolher seu ícone de mídia e dimensionar seu preview. Inclua apenas as sub-partes que você precisa (AttachmentPreview, AttachmentInfo, AttachmentRemove).
Cada Attachment recebe um AttachmentData — um FileUIPart ou SourceDocumentUIPart do pacote ai, mais um id. A categoria de mídia (image / video / audio / document / source) é inferida a partir de mediaType, então o ícone e o preview corretos são renderizados sem config extra. Passe onRemove para exibir o botão de remoção; omita e o botão não renderiza nada.
Preview
'use client';import { Attachment, AttachmentInfo, AttachmentPreview, AttachmentRemove, Attachments, type AttachmentData,} from '@gremorie/rx-ai';const files: AttachmentData[] = [ { id: '1', type: 'file', mediaType: 'image/png', filename: 'dashboard-mock.png', url: '', }, { id: '2', type: 'file', mediaType: 'application/pdf', filename: 'spec-v3.pdf', url: '', }, { id: '3', type: 'file', mediaType: 'audio/mpeg', filename: 'voice-note.mp3', url: '', },];export function AttachmentsPreview() { return ( <div className="flex flex-col gap-8"> <div className="flex flex-col gap-2"> <span className="text-muted-foreground text-xs">grid</span> <Attachments variant="grid" className="ml-0"> {files.map((data) => ( <Attachment key={data.id} data={data} onRemove={() => undefined}> <AttachmentPreview /> <AttachmentRemove /> </Attachment> ))} </Attachments> </div> <div className="flex flex-col gap-2"> <span className="text-muted-foreground text-xs">inline</span> <Attachments variant="inline"> {files.map((data) => ( <Attachment key={data.id} data={data} onRemove={() => undefined}> <AttachmentPreview /> <AttachmentInfo /> <AttachmentRemove /> </Attachment> ))} </Attachments> </div> <div className="flex flex-col gap-2"> <span className="text-muted-foreground text-xs">list</span> <Attachments variant="list"> {files.map((data) => ( <Attachment key={data.id} data={data} onRemove={() => undefined}> <AttachmentPreview /> <AttachmentInfo showMediaType /> <AttachmentRemove /> </Attachment> ))} </Attachments> </div> </div> );}Anatomia
Attachments container; define a variant (grid · inline · list) via context
└─ Attachment um arquivo/fonte; lê a variant, infere a categoria de mídia
├─ AttachmentPreview thumbnail (image/video) ou ícone de categoria de mídia
├─ AttachmentInfo nome do arquivo + tipo de mídia opcional (oculto no grid)
├─ AttachmentRemove botão de remoção (renderiza só quando onRemove está definido)
├─ AttachmentHoverCard wrapper opcional de preview em hover
│ ├─ AttachmentHoverCardTrigger
│ └─ AttachmentHoverCardContent
└─ AttachmentEmpty mensagem de estado vazioInstalação
bash npx gremorie@latest add rx-attachments bash pnpm dlx gremorie@latest add rx-attachments
bash yarn dlx gremorie@latest add rx-attachments
bash bunx --bun gremorie@latest add rx-attachments
Traz rx-forms (para Button) e rx-overlays (para HoverCard) como dependências de registry, mais o pacote ai para os tipos de attachment.
Uso
import {
Attachment,
AttachmentInfo,
AttachmentPreview,
AttachmentRemove,
Attachments,
type AttachmentData,
} from "@gremorie/rx-ai";
export function Example({
files,
remove,
}: {
files: AttachmentData[];
remove: (id: string) => void;
}) {
return (
<Attachments variant="inline">
{files.map((data) => (
<Attachment key={data.id} data={data} onRemove={() => remove(data.id)}>
<AttachmentPreview />
<AttachmentInfo />
<AttachmentRemove />
</Attachment>
))}
</Attachments>
);
}npx gremorie@latest add ng-attachmentsimport { Component, signal } from '@angular/core';
import {
AttachmentList,
AttachmentItem,
AttachmentPreview,
AttachmentInfo,
AttachmentRemove,
type AttachmentData,
} from '@gremorie/ng-ai';
@Component({
selector: 'app-example',
standalone: true,
imports: [
AttachmentList,
AttachmentItem,
AttachmentPreview,
AttachmentInfo,
AttachmentRemove,
],
template: `
<attachment-list variant="grid">
@for (file of files(); track file.id) {
<attachment-item [data]="file" (removed)="remove($event)">
<attachment-preview />
<attachment-info />
<attachment-remove />
</attachment-item>
}
</attachment-list>
`,
})
export class ExampleComponent {
readonly files = signal<AttachmentData[]>([]);
remove(file: AttachmentData) {
this.files.update((current) => current.filter((f) => f.id !== file.id));
}
}API
<Attachments>
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "grid" | "inline" | "list" | "grid" | Modo de layout compartilhado com cada filho via context. grid flutua para o fim (ml-auto). |
className | string | - | Classes extras no container. |
Estende todos os HTMLAttributes<HTMLDivElement>. Provê o context de attachments.
<Attachment>
| Prop | Type | Default | Description |
|---|---|---|---|
data | AttachmentData | - | Um FileUIPart ou SourceDocumentUIPart com um id. |
onRemove | () => void | - | Quando definido, AttachmentRemove renderiza um botão de remoção funcional. |
className | string | - | Classes extras no item. |
Provê o context por item (data, mediaCategory inferido, onRemove, variant).
<AttachmentPreview>
| Prop | Type | Default | Description |
|---|---|---|---|
fallbackIcon | ReactNode | - | Sobrescreve o ícone de categoria de mídia mostrado quando não há thumbnail. |
className | string | - | Classes extras na caixa de preview. |
Renderiza um <img> para imagens, um <video> para vídeos, caso contrário o ícone de categoria de mídia.
<AttachmentInfo>
| Prop | Type | Default | Description |
|---|---|---|---|
showMediaType | boolean | false | Renderiza o mediaType bruto abaixo do nome do arquivo. |
className | string | - | Classes extras. |
Retorna null na variant grid (apenas thumbnails).
<AttachmentRemove>
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Remove" | Label acessível (aria-label + sr-only). |
Estende todas as props de Button. Renderiza null a menos que o Attachment pai tenha onRemove. Interrompe a propagação do clique para que remover não dispare o item.
<AttachmentHoverCard> / <AttachmentHoverCardTrigger> / <AttachmentHoverCardContent>
Wrappers finos sobre HoverCard do @gremorie/rx-overlays, pré-ajustados para previews de attachment (delay zero de abrir/fechar, align="start", padding compacto). Use para mostrar um preview grande no hover.
<AttachmentEmpty>
Placeholder centralizado em tom muted. Padrão é "No attachments"; passe children para sobrescrever.
Composição
<Attachments>escolhe o layout viavariante o compartilha pelo context.<Attachment>envolve um arquivo/fonte e infere sua categoria de mídia a partir demediaType.<AttachmentPreview>mostra o thumbnail ou o ícone de mídia.<AttachmentInfo>adiciona o nome do arquivo (e o tipo de mídia opcional) parainline/list.<AttachmentRemove>aparece só quandoonRemoveestá conectado.
Variações
Grid (thumbnails)
O padrão. Thumbnails quadrados que flutuam para a borda final — ideal acima de um PromptInput.
<Attachments variant="grid">
{files.map((data) => (
<Attachment key={data.id} data={data} onRemove={() => remove(data.id)}>
<AttachmentPreview />
<AttachmentRemove />
</Attachment>
))}
</Attachments>Inline (chips)
Chips compactos com um ícone pequeno e o nome do arquivo — bons dentro de um balão de mensagem.
<Attachments variant="inline">
{files.map((data) => (
<Attachment key={data.id} data={data}>
<AttachmentPreview />
<AttachmentInfo />
</Attachment>
))}
</Attachments>List (linhas detalhadas)
Linhas de largura total com um preview maior, nome do arquivo e tipo de mídia — bom para uma superfície de revisão.
<Attachments variant="list">
{files.map((data) => (
<Attachment key={data.id} data={data} onRemove={() => remove(data.id)}>
<AttachmentPreview />
<AttachmentInfo showMediaType />
<AttachmentRemove />
</Attachment>
))}
</Attachments>Acessibilidade
- Botão de remoção:
AttachmentRemovecarrega umaria-label(padrão"Remove") mais um labelsr-only, então o controle apenas com ícone é anunciado. - Imagens: previews definem o
altcomo o nome do arquivo (com fallback para"Image"), então leitores de tela descrevem o thumbnail. - Isolamento de clique: remover interrompe a propagação, então uma linha de attachment interativa não é também disparada quando o usuário a remove.
- Estado vazio:
AttachmentEmptyrenderiza texto de verdade, então uma região de attachments vazia não é uma lacuna silenciosa.
Relacionados
- PromptInput - a doca de input acima da qual os attachments são preparados
- Message - attachments frequentemente renderizam dentro de um turno de mensagem
- Sources - documentos de origem citados, um conceito irmão