Pular para o conteúdo

Table

templates/components/Table

A Table é uma grade de dados: um <table> semântico dentro de um container overflow-x-auto, montado por subcomponentes composáveis (Header/Body/Footer/Row/Head/Cell/Caption). Sua pele congelada já ANTECIPA comportamento de data-grid — a Row traz data-[state=selected] e has-aria-expanded:bg-muted/50, a Cell trata [role=checkbox], o tfoot vem pré-estilizado para totais e a caption fica embaixo — então os enriquecedores são comportamentais (ordenar/filtrar/paginar/selecionar/congelar) e de conteúdo aditivo, encaixando nos ganchos que a base já oferece.

Base congelada

A list of your recent invoices.
Invoice Status Method Amount
INV001 Paid Credit Card $250.00
INV002 Pending PayPal $150.00
INV003 Unpaid Bank Transfer $350.00
INV004 Paid Credit Card $450.00
INV005 Paid PayPal $550.00
INV006 Pending Bank Transfer $200.00
INV007 Unpaid Credit Card $300.00
Total $2,500.00

Conteúdo

Paginação

Quebra as linhas em páginas com…

Quebra as linhas em páginas com « ‹ › », 'ir para página' e contador 'N–M de T'; tamanho de página escolhível. posicionável

Congelar ao rolar

Fixa a 1ª coluna e/ou o…

Fixa a 1ª coluna e/ou o cabeçalho via position:sticky dentro do container overflow da base, mantendo a referência visível ao rolar.

Customizar colunas

Menu 'Colunas' pelo qual o usuário…

Menu 'Colunas' pelo qual o usuário final mostra/oculta cada coluna em runtime, sem tocar no markup nem no visual da base. posicionável

Peso do texto ⚠️

Peso do cabeçalho e dos dados…

Peso do cabeçalho e dos dados (normal/médio/semi/negrito). É tipografia = DECISÃO DE BASE, não capacidade.

Cor do Status (token) ⚠️

Mapeia cada estado a um TOKEN…

Mapeia cada estado a um TOKEN semântico (success/warning/info/danger/neutral) do app.css. A cor é base; só a associação estado→token é escolhida.

Comportamento

Ordenar por coluna

Clicar no cabeçalho reordena as linhas…

Clicar no cabeçalho reordena as linhas e mostra seta de direção (▲/▼) na coluna ativa. Escolhe quais direções ficam habilitadas.

Buscar / filtrar linhas

Campo que filtra as linhas em…

Campo que filtra as linhas em tempo real, escondendo as que não casam com o termo. Escopo global ou por coluna. posicionável

Selecionar linhas

Adiciona coluna de seleção reaproveitando o…

Adiciona coluna de seleção reaproveitando o [role=checkbox] e o data-[state=selected] que a base já estiliza; exibe contagem 'N de M'.

Exportar dados

Botão que serializa os dados da…

Botão que serializa os dados da tabela nos formatos escolhidos; estilo do botão (ícone+texto / só texto / só ícone) configurável. posicionável

Linhas expansíveis

Disclosure por linha: um gatilho revela…

Disclosure por linha: um gatilho revela um painel de detalhe numa sub-linha, usando o realce has-aria-expanded:bg-muted/50 que a base já prevê.

Totais no rodapé

Calcula agregações por coluna numérica (respeitando…

Calcula agregações por coluna numérica (respeitando o filtro/página ativos) e as escreve no tfoot já pré-estilizado (bg-muted/50, font-medium) da base.
Código gerado

Cole no Claude Code — ele acerta de primeira.

Table (UI) — Design System (Symfony UX Toolkit / shadcn)

BASE CONGELADA (não mude por instância): Tipografia e layout: text-sm, caption-bottom (legenda embaixo). Espaçamento: Cell com p-2 align-middle whitespace-nowrap; Head h-10 px-2 font-medium com text-left/rtl:text-start e cor text-foreground. Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça; container relative w-full overflow-x-auto. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Rodapé: border-t bg-muted/50 font-medium. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa. Nada disso (fonte, cor, cantos, padding, bordas) muda ao ligar capacidade.

USO: <twig:Table />

CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Ordenar por coluna: Clicar no cabeçalho reordena as linhas e mostra seta de direção (▲/▼) na coluna ativa. Escolhe quais direções ficam habilitadas. [multi → Crescente (A→Z / 0→9) / Decrescente (Z→A / 9→0) / Restaurar ordem original]- Buscar / filtrar linhas: Campo que filtra as linhas em tempo real, escondendo as que não casam com o termo. Escopo global ou por coluna. [select → Global (todas as colunas) / Por coluna]- Paginação: Quebra as linhas em páginas com « ‹ › », 'ir para página' e contador 'N–M de T'; tamanho de página escolhível. [select → 5 por página / 10 por página / 20 por página / 50 por página / Mostrar tudo]- Selecionar linhas: Adiciona coluna de seleção reaproveitando o [role=checkbox] e o data-[state=selected] que a base já estiliza; exibe contagem 'N de M'. [select → Seleção única (radio) / Seleção múltipla (checkbox + selecionar tudo)]- Congelar ao rolar: Fixa a 1ª coluna e/ou o cabeçalho via position:sticky dentro do container overflow da base, mantendo a referência visível ao rolar. [multi → Primeira coluna / Cabeçalho (rolagem vertical)]- Exportar dados: Botão que serializa os dados da tabela nos formatos escolhidos; estilo do botão (ícone+texto / só texto / só ícone) configurável. [multi → PDF / CSV / Excel / Copiar / Imprimir]- Customizar colunas: Menu 'Colunas' pelo qual o usuário final mostra/oculta cada coluna em runtime, sem tocar no markup nem no visual da base. [toggle]- Linhas expansíveis: Disclosure por linha: um gatilho revela um painel de detalhe numa sub-linha, usando o realce has-aria-expanded:bg-muted/50 que a base já prevê. [toggle]- Totais no rodapé: Calcula agregações por coluna numérica (respeitando o filtro/página ativos) e as escreve no tfoot já pré-estilizado (bg-muted/50, font-medium) da base. [multi → Soma / Média / Contagem / Mínimo / Máximo]- Peso do texto: Peso do cabeçalho e dos dados (normal/médio/semi/negrito). É tipografia = DECISÃO DE BASE, não capacidade. [select → normal / médio / semi / negrito]  (⚠️ borderline: revisar se não é decisão de base)- Cor do Status (token): Mapeia cada estado a um TOKEN semântico (success/warning/info/danger/neutral) do app.css. A cor é base; só a associação estado→token é escolhida. [select → success / warning / info / danger / neutral]  (⚠️ borderline: revisar se não é decisão de base)FAÇA:
- Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.
- Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.
- Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}.
NÃO FAÇA:
- Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.
- Não crie variante/fork para a mesma coisa — existe UMA base por componente.
- Não reimplemente o componente nem adicione toolchain Node.
TESTE ANTES DE MUDAR: "é função ou é base?" — função = capacidade opt-in; base = mude o token na fonte única (assets/styles/app.css), para todos.

A11Y (herdada da base): Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa

Via MCP: tool get_component com {"id": "table"} · list_capabilities("table").

Spec crua: config/ds-specs/table.json · Conectar o MCP.

Spec machine-readable (JSON)

{
    "$schema_version": "1.0",
    "id": "table",
    "component": "Table",
    "eixo": "ui",
    "particularidade": "A Table é uma grade de dados: um &lt;table&gt; semântico dentro de um container overflow-x-auto, montado por subcomponentes composáveis (Header/Body/Footer/Row/Head/Cell/Caption). Sua pele congelada já ANTECIPA comportamento de data-grid — a Row traz data-[state=selected] e has-aria-expanded:bg-muted/50, a Cell trata [role=checkbox], o tfoot vem pré-estilizado para totais e a caption fica embaixo — então os enriquecedores são comportamentais (ordenar/filtrar/paginar/selecionar/congelar) e de conteúdo aditivo, encaixando nos ganchos que a base já oferece.",
    "base_congelada": "Tipografia e layout: text-sm, caption-bottom (legenda embaixo). Espaçamento: Cell com p-2 align-middle whitespace-nowrap; Head h-10 px-2 font-medium com text-left/rtl:text-start e cor text-foreground. Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça; container relative w-full overflow-x-auto. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Rodapé: border-t bg-muted/50 font-medium. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa. Nada disso (fonte, cor, cantos, padding, bordas) muda ao ligar capacidade.",
    "props": [],
    "capacidades": [
        {
            "id": "ordenar-por-coluna",
            "nome": "Ordenar por coluna",
            "descricao": "Clicar no cabeçalho reordena as linhas e mostra seta de direção (▲/▼) na coluna ativa. Escolhe quais direções ficam habilitadas.",
            "controle": "multi",
            "opcoes": [
                "Crescente (A→Z / 0→9)",
                "Decrescente (Z→A / 9→0)",
                "Restaurar ordem original"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "buscar-filtrar-linhas",
            "nome": "Buscar / filtrar linhas",
            "descricao": "Campo que filtra as linhas em tempo real, escondendo as que não casam com o termo. Escopo global ou por coluna.",
            "controle": "select",
            "opcoes": [
                "Global (todas as colunas)",
                "Por coluna"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "pagina-o",
            "nome": "Paginação",
            "descricao": "Quebra as linhas em páginas com « ‹ › », 'ir para página' e contador 'N–M de T'; tamanho de página escolhível.",
            "controle": "select",
            "opcoes": [
                "5 por página",
                "10 por página",
                "20 por página",
                "50 por página",
                "Mostrar tudo"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "selecionar-linhas",
            "nome": "Selecionar linhas",
            "descricao": "Adiciona coluna de seleção reaproveitando o [role=checkbox] e o data-[state=selected] que a base já estiliza; exibe contagem 'N de M'.",
            "controle": "select",
            "opcoes": [
                "Seleção única (radio)",
                "Seleção múltipla (checkbox + selecionar tudo)"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "congelar-ao-rolar",
            "nome": "Congelar ao rolar",
            "descricao": "Fixa a 1ª coluna e/ou o cabeçalho via position:sticky dentro do container overflow da base, mantendo a referência visível ao rolar.",
            "controle": "multi",
            "opcoes": [
                "Primeira coluna",
                "Cabeçalho (rolagem vertical)"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "exportar-dados",
            "nome": "Exportar dados",
            "descricao": "Botão que serializa os dados da tabela nos formatos escolhidos; estilo do botão (ícone+texto / só texto / só ícone) configurável.",
            "controle": "multi",
            "opcoes": [
                "PDF",
                "CSV",
                "Excel",
                "Copiar",
                "Imprimir"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "customizar-colunas",
            "nome": "Customizar colunas",
            "descricao": "Menu 'Colunas' pelo qual o usuário final mostra/oculta cada coluna em runtime, sem tocar no markup nem no visual da base.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "linhas-expans-veis",
            "nome": "Linhas expansíveis",
            "descricao": "Disclosure por linha: um gatilho revela um painel de detalhe numa sub-linha, usando o realce has-aria-expanded:bg-muted/50 que a base já prevê.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "totais-no-rodap",
            "nome": "Totais no rodapé",
            "descricao": "Calcula agregações por coluna numérica (respeitando o filtro/página ativos) e as escreve no tfoot já pré-estilizado (bg-muted/50, font-medium) da base.",
            "controle": "multi",
            "opcoes": [
                "Soma",
                "Média",
                "Contagem",
                "Mínimo",
                "Máximo"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "peso-do-texto",
            "nome": "Peso do texto",
            "descricao": "Peso do cabeçalho e dos dados (normal/médio/semi/negrito). É tipografia = DECISÃO DE BASE, não capacidade.",
            "controle": "select",
            "opcoes": [
                "normal",
                "médio",
                "semi",
                "negrito"
            ],
            "posicionavel": false,
            "e_funcao": false,
            "como_plugar": "REPROVADO no teste 'é função ou é base' — NÃO plugar como capacidade. É decisão de BASE: mude o token na fonte única assets/styles/app.css, valendo para TODOS os componentes."
        },
        {
            "id": "cor-do-status-token",
            "nome": "Cor do Status (token)",
            "descricao": "Mapeia cada estado a um TOKEN semântico (success/warning/info/danger/neutral) do app.css. A cor é base; só a associação estado→token é escolhida.",
            "controle": "select",
            "opcoes": [
                "success",
                "warning",
                "info",
                "danger",
                "neutral"
            ],
            "posicionavel": false,
            "e_funcao": false,
            "como_plugar": "REPROVADO no teste 'é função ou é base' — NÃO plugar como capacidade. É decisão de BASE: mude o token na fonte única assets/styles/app.css, valendo para TODOS os componentes."
        }
    ],
    "snippet_uso": "<twig:Table />",
    "exemplo_demo": "{%- set invoices = [\n    {invoice: 'INV001', paymentStatus: 'Paid', totalAmount: '$250.00', paymentMethod: 'Credit Card'},\n    {invoice: 'INV002', paymentStatus: 'Pending', totalAmount: '$150.00', paymentMethod: 'PayPal'},\n    {invoice: 'INV003', paymentStatus: 'Unpaid', totalAmount: '$350.00', paymentMethod: 'Bank Transfer'},\n    {invoice: 'INV004', paymentStatus: 'Paid', totalAmount: '$450.00', paymentMethod: 'Credit Card'},\n    {invoice: 'INV005', paymentStatus: 'Paid', totalAmount: '$550.00', paymentMethod: 'PayPal'},\n    {invoice: 'INV006', paymentStatus: 'Pending', totalAmount: '$200.00', paymentMethod: 'Bank Transfer'},\n    {invoice: 'INV007', paymentStatus: 'Unpaid', totalAmount: '$300.00', paymentMethod: 'Credit Card'},\n] -%}\n<twig:Table>\n    <twig:Table:Caption>A list of your recent invoices.</twig:Table:Caption>\n    <twig:Table:Header>\n        <twig:Table:Row>\n            <twig:Table:Head class=\"w-[100px]\">Invoice</twig:Table:Head>\n            <twig:Table:Head>Status</twig:Table:Head>\n            <twig:Table:Head>Method</twig:Table:Head>\n            <twig:Table:Head class=\"text-right\">Amount</twig:Table:Head>\n        </twig:Table:Row>\n    </twig:Table:Header>\n    <twig:Table:Body>\n        {% for invoice in invoices %}\n            <twig:Table:Row>\n                <twig:Table:Cell class=\"font-medium\">{{ invoice.invoice }}</twig:Table:Cell>\n                <twig:Table:Cell>{{ invoice.paymentStatus }}</twig:Table:Cell>\n                <twig:Table:Cell>{{ invoice.paymentMethod }}</twig:Table:Cell>\n                <twig:Table:Cell class=\"text-right\">{{ invoice.totalAmount }}</twig:Table:Cell>\n            </twig:Table:Row>\n        {% endfor %}\n    </twig:Table:Body>\n    <twig:Table:Footer>\n        <twig:Table:Row>\n            <twig:Table:Cell colspan=\"3\">Total</twig:Table:Cell>\n            <twig:Table:Cell class=\"text-right\">$2,500.00</twig:Table:Cell>\n        </twig:Table:Row>\n    </twig:Table:Footer>\n</twig:Table>",
    "regras": {
        "faca": [
            "Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.",
            "Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.",
            "Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}."
        ],
        "nao_faca": [
            "Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.",
            "Não crie variante/fork para a mesma coisa — existe UMA base por componente.",
            "Não reimplemente o componente nem adicione toolchain Node."
        ]
    },
    "a11y": "Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa",
    "mcp": {
        "tool": "get_component",
        "args": {
            "id": "table"
        }
    },
    "props_nota": "Sem props próprias declaradas: a base repassa atributos nativos via {{ attributes }} (id, name, type, value, aria-*, data-*). Props específicas, quando existem, ficam nos subcomponentes."
}
  1. v0.1.0 Camada de IA: scaffold de página + specs íntegras
    • Novo tool MCP get_page_scaffold: devolve a MOLDURA de uma página nova (layout + blocks reais, contrato do renderPage, receita de rota + buildNav, tokens de layout lidos de app.css e snippet .html.twig válido) — dá pra montar página sem ler o controller/layout.
    • Fonte única do scaffold em config/ds-specs/_page-scaffold.json (padrão v1.0); tokens semânticos derivados de assets/styles/app.css em runtime.
    • Fix: exemplo_demo estava truncado em 1600 bytes em 5 specs (card, field, table, tabs, typography) — reconstituídos íntegros a partir dos demos, fechando todas as tags.
    • Nova seção Updates (este changelog versionado) como 9º eixo do shell.
    • Docs: CLAUDE.md e AI-LAYER.md citam o novo tool; contadores de tools atualizados (6 → 7).

Changelog completo em Updates.