Skip to content

MeDataTable

Tabela flexível para exibição de dados tabulares. As colunas são definidas declarativamente via <MeColumn> e a tabela suporta scroll, empty state, loading e muito mais.

Importação

vue
<script setup>
import { MeDataTable, MeColumn } from '@me/ui-vue3'
</script>
vue
<script>
import { MeDataTable, MeColumn } from '@me/ui-vue2'

export default {
  components: { MeDataTable, MeColumn }
}
</script>

Tabela Simples

Para definir as colunas, utilize o componente <MeColumn> como filho direto de <MeDataTable>. Cada <MeColumn> recebe field (chave do objeto) e header (rótulo do cabeçalho).

vue
<template>
  <MeDataTable :items="items">
    <MeColumn field="id" header="ID" />
    <MeColumn field="name" header="Nome" />
    <MeColumn field="email" header="E-mail" />
    <MeColumn field="registered" header="Data de Cadastro" />
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [
  { id: 1, name: 'John Doe', email: 'john@example.com', registered: '2025-01-10' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', registered: '2025-02-12' },
  { id: 3, name: 'Alice Johnson', email: 'alice@example.com', registered: '2025-03-15' },
  { id: 4, name: 'Bob Brown', email: 'bob@example.com', registered: '2025-04-20' },
]
</script>

Formato dos Itens

A prop items recebe um array de objetos. Cada chave do objeto que você quiser exibir deve corresponder ao field de um <MeColumn>.

js
const items = [
  { id: 1, name: 'John Doe', email: 'john@example.com' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
]
vue
<MeDataTable :items="items">
  <MeColumn field="id"    header="ID" />     <!-- exibe item.id -->
  <MeColumn field="name"  header="Nome" />   <!-- exibe item.name -->
  <MeColumn field="email" header="E-mail" /> <!-- exibe item.email -->
</MeDataTable>

Identificador de linha (id e rowIdProperty)

A tabela usa item.id internamente para rastrear seleção, expansão e estado das linhas. Cada objeto precisa ter um id único.

Se seus dados usam outro campo como identificador (uuid, codigo, _id), passe o nome do campo em rowIdProperty. Sem isso, seleção e expansão vão se comportar de forma incorreta pois item.id será undefined.

js
const portais = [
  { portal_id: 10, name: 'ComprasNet' },
  { portal_id: 11, name: 'BBMNet' },
]
vue
<!-- ❌ item.id é undefined — seleção e expansão não funcionam -->
<MeDataTable :items="portais" selectable />

<!-- ✅ usa portal_id como identificador -->
<MeDataTable :items="portais" rowIdProperty="portal_id" selectable />

Sub-linhas (childRows e childRowsArrayName)

Para usar linhas filhas expansíveis, adicione um array childRows nos itens que devem ter sub-linhas. Itens sem essa propriedade não recebem o botão de expansão.

js
const items = [
  {
    id: 1,
    name: 'Portal A',
    childRows: [
      { id: 11, name: 'Sub-Portal A1' },
      { id: 12, name: 'Sub-Portal A2' },
    ]
  },
  { id: 2, name: 'Portal B' } // sem childRows = sem botão de expansão
]

Se a sua API retorna as sub-linhas em um campo com nome diferente (ex: filhos, subitens), configure childRowsArrayName para que a tabela saiba onde buscá-las:

vue
<!-- API retorna { id, name, subitens: [...] } -->
<MeDataTable :items="items" childRowsArrayName="subitens" expandableChildRows>
  ...
</MeDataTable>

Estado Vazio

Quando items está vazio, a tabela exibe um ilustração com título e descrição configuráveis via emptyTitle e emptyDescription.

Nenhum registro encontrado

Nenhum registro encontrado

Tente ajustar os filtros ou adicionar novos dados.

vue
<template>
  <MeDataTable
    :items="[]"
    emptyTitle="Nenhum registro encontrado"
    emptyDescription="Tente ajustar os filtros ou adicionar novos dados."
  />
</template>

Estados de Vazio e Erro

Use emptyIcon para escolher a ilustração exibida quando items está vazio. São 4 opções disponíveis: empty, not-found, error e empty-folder.

Nenhum registro encontrado

Nenhum registro encontrado

Tente ajustar os filtros ou adicionar novos dados.

Não encontrado

Não encontrado

O item procurado não foi localizado.

Erro ao carregar dados

Erro ao carregar dados

Ocorreu um erro inesperado. Tente novamente mais tarde.

Pasta vazia

Pasta vazia

Nenhum arquivo nesta pasta.

vue
<template>
  <MeDataTable
    :items="[]"
    emptyIcon="error"
    emptyTitle="Erro ao carregar dados"
    emptyDescription="Ocorreu um erro inesperado. Tente novamente mais tarde."
  />
</template>
emptyIconQuando usar
emptyLista ou tabela sem registros
not-foundBusca sem resultados
errorFalha ao carregar dados da API
empty-folderDiretório ou pasta sem conteúdo

Estado Vazio Customizado (#empty)

Quando as props emptyTitle, emptyDescription e emptyIcon não forem suficientes, use o slot #empty para substituir toda a área vazia por conteúdo livre.

inbox

Nenhum portal cadastrado ainda.

vue
<template>
  <MeDataTable :items="[]">
    <template #empty>
      <div style="display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 32px 0;">
        <span class="material-icons-outlined" style="font-size: 48px; color: #9ca3af;">inbox</span>
        <p style="font-size: 14px; color: #6b7280; margin: 0;">Nenhum portal cadastrado ainda.</p>
        <MeButton icon="add" variant="primary">Adicionar portal</MeButton>
      </div>
    </template>
  </MeDataTable>
</template>

Estado de Loading

Use :loading="true" para exibir o spinner sobre a tabela. O texto abaixo do spinner é opcional — basta omitir loadingText. A tabela também pode receber items durante o loading: os dados anteriores ficam visíveis sob o overlay, evitando layout shift.

Com loadingText

Carregando dados...

Sem loadingText

vue
<template>
  <!-- Com texto -->
  <MeDataTable :items="items" :loading="true" loadingText="Carregando dados..." />

  <!-- Sem texto -->
  <MeDataTable :items="items" :loading="true" />
</template>

Loading Customizado (#loading)

Use o slot #loading para substituir o spinner padrão por qualquer conteúdo.

sync Buscando dados do servidor...
vue
<template>
  <MeDataTable :items="items" :loading="loading">
    <MeColumn field="id" header="ID" />
    <MeColumn field="name" header="Nome" />
    <MeColumn field="email" header="E-mail" />
    <template #loading>
      <div style="display: flex; align-items: center; gap: 10px;">
        <span class="material-icons-outlined">sync</span>
        Buscando dados do servidor...
      </div>
    </template>
  </MeDataTable>
</template>

MeColumn

O <MeColumn> define cada coluna da tabela. É sempre usado como filho direto de <MeDataTable> — fora desse contexto não renderiza nada.

O exemplo abaixo combina as principais capacidades: cabeçalho customizado com ícone, célula com lógica de cor por status, e coluna de ação fixada à direita.

vue
<template>
  <MeDataTable :items="items">
    <MeColumn field="id" header="ID" width="60px" sortable />

    <MeColumn field="name" header="Nome" :ellipsis="true">
      <template #header="{ header }">
        <span style="color: #1976d2; font-weight: bold; display: flex; align-items: center; gap: 4px;">
          <span class="material-icons" style="font-size: 16px;">person</span>
          {{ header }}
        </span>
      </template>
    </MeColumn>

    <MeColumn field="status" header="Status">
      <template #cell="{ rowData }">
        <span :style="{
          color: rowData.status === 'Ativo' ? 'green' : rowData.status === 'Inativo' ? 'red' : 'orange',
          fontWeight: 'bold'
        }">{{ rowData.status }}</span>
      </template>
    </MeColumn>

    <MeColumn header="Ação" width="120px" type="action" fixed="right">
      <template #cell="{ rowData }">
        <MeButton flat icon="star" variant="gray" @click="handleAction(rowData.name)">
          Favoritar
        </MeButton>
      </template>
    </MeColumn>
  </MeDataTable>
</template>

Props

PropTipoDefaultDescrição
fieldstringChave do objeto de dados exibida na célula
headerstring''Rótulo do cabeçalho
widthstringLargura fixa (ex: 120px)
minWidthstringLargura mínima
fixedstringFixa a coluna: left ou right
ellipsisbooleanfalseTrunca texto longo com reticências
sortablebooleanfalseHabilita ordenação ao clicar no cabeçalho
nonHideablebooleanfalseImpede que a coluna seja ocultada pelo controle de colunas
typestringTipo especial: action (impede propagação do click) ou date (sort correto de datas)

Slots

SlotParâmetrosDescrição
#cell{ rowData }Conteúdo customizado da célula
#header{ field, header }Conteúdo customizado do cabeçalho
#cellChildRow{ childData, parentData }Célula customizada para linhas filhas — ver Linhas Filhas

Seleção de Linhas

Ative com selectable. O modo padrão é single (uma linha por vez). Use selectionMode="multiple" para seleção múltipla com checkbox no header para selecionar tudo.

A prop :selection e o padrão controlado

:selection é o estado externo da seleção — você declara um ref([]) no seu componente e passa para a tabela. A partir daí:

  • A tabela :selection para saber quais checkboxes mostrar marcados.
  • A tabela emite @selectionChange com o novo array toda vez que o usuário seleciona ou deseleciona uma linha.
  • Você atualiza seu ref com o evento: @selectionChange="selectedRows = $event".

Esse par :selection + @selectionChange funciona como um v-model manual. Sem ele, a tabela não tem como saber qual é o estado atual e a seleção fica fora de sincronia — especialmente em cenários onde você atualiza items externamente (paginação, filtro, reload).

O benefício concreto: como você detém o estado, pode pré-selecionar linhas, limpar a seleção programaticamente (selectedRows.value = []) ou reagir à seleção em qualquer parte da sua lógica sem precisar de refs ou callbacks extras.

Seleção simples

O fluxo mínimo: declare selectedRows = ref([]), passe :selection="selectedRows" e atualize no @selectionChange. Sem toolbar, sem slot extra.

vue
<template>
  <MeDataTable
    :items="items"
    selectable
    selectionMode="multiple"
    selectableInRowClick
    :selection="selectedRows"
    @selectionChange="selectedRows = $event"
  >
    <MeColumn field="id" header="ID" />
    <MeColumn field="name" header="Nome" />
    <MeColumn field="email" header="E-mail" />
    <MeColumn field="registered" header="Data de Cadastro" />
  </MeDataTable>
</template>

<script setup>
import { ref } from 'vue'
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [ /* ... */ ]
const selectedRows = ref([])
</script>

Com toolbar de ações

Adicione showSelectedRowsToolbar para exibir uma toolbar flutuante sempre que houver linhas selecionadas. Use o slot #selectedButtonsActions com MeSelectedRowsActionButton para adicionar ações em lote.

Selecione uma ou mais linhas abaixo para ver a toolbar aparecer:

vue
<template>
  <MeDataTable
    :items="items"
    selectable
    selectionMode="multiple"
    selectableInRowClick
    showSelectedRowsToolbar
    :selection="selectedRows"
    @selectionChange="selectedRows = $event"
  >
    <MeColumn field="id" header="ID" />
    <MeColumn field="name" header="Nome" />
    <MeColumn field="email" header="E-mail" />
    <MeColumn field="registered" header="Data de Cadastro" />

    <template #selectedButtonsActions>
      <MeSelectedRowsActionButton
        v-if="selectedRows.length === 1"
        icon="edit_note"
        label="Editar"
        @click="handleEdit"
      />
      <MeSelectedRowsActionButton
        icon="delete"
        label="Excluir"
        variant="red"
        @click="handleDelete"
      />
    </template>
  </MeDataTable>
</template>

<script setup>
import { ref } from 'vue'
import { MeDataTable, MeColumn, MeSelectedRowsActionButton } from '@me/ui-vue3'

const items = [ /* ... */ ]
const selectedRows = ref([])

const handleEdit = () => alert(`Editando: ${selectedRows.value[0].name}`)
const handleDelete = () => alert(`Excluindo: ${selectedRows.value.map(r => r.name).join(', ')}`)
</script>

Props de seleção

PropTipoDefaultDescrição
selectablebooleanfalseExibe checkbox e ativa seleção de linhas
selectionMode"single" | "multiple""single"single: uma linha por vez. multiple: checkbox no header para tudo
selectableInRowClickbooleanfalseSeleciona/deseleciona ao clicar em qualquer parte da linha
selectionarray[]Estado externo da seleção. A tabela lê esse array para saber quais linhas marcar como selecionadas. Sempre use junto com @selectionChange para manter em sincronia
showSelectedRowsToolbarbooleanfalseExibe a toolbar flutuante quando há linhas selecionadas

Evento @selectionChange

Emitido sempre que a seleção muda. O payload é o array completo dos itens selecionados (não apenas o item que mudou). Use para atualizar seu ref:

js
@selectionChange="selectedRows = $event"

Com o array em mãos você pode exibir contagem, habilitar/desabilitar botões, enviar para uma API, etc.

MeSelectedRowsActionButton

Componente auxiliar para os botões dentro do slot #selectedButtonsActions. Já estilizado no padrão da toolbar.

PropTipoDefaultDescrição
iconstring''Ícone Material Icons
labelstring''Texto do botão
variantstring"gray"Variante de cor
disabledbooleanfalseDesabilita o botão

Ordenação

Adicione :sortable="true" em cada <MeColumn> que deve ser ordenável. A tabela ordena os dados automaticamente ao clicar no cabeçalho. O evento @onSort é disparado com { field, direction } sempre que a ordenação muda.

Para colunas de data, use type="date" — sem isso a ordenação compara strings e fica incorreta.

vue
<template>
  <MeDataTable :items="items" @onSort="handleSortChange">
    <MeColumn field="portalName" header="PORTAL"          :sortable="true" />
    <MeColumn field="value"      header="VALOR"           :sortable="true" />
    <MeColumn field="createdAt"  header="DATA DE CRIAÇÃO" :sortable="true" type="date" />
  </MeDataTable>
</template>

<script setup>
import { ref } from 'vue'
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [
  { id: 1, portalName: 'ComprasNet', value: 1000, createdAt: '01/08/2023' },
  { id: 2, portalName: 'BBMNet',     value: 2000, createdAt: '15/07/2025' },
  { id: 3, portalName: 'Bionexo',    value: 1500, createdAt: '10/08/2011' },
  { id: 4, portalName: 'LicitaMais', value: 1200, createdAt: '30/06/1999' },
]

const sortState = ref({ field: '', direction: '' })
function handleSortChange({ field, direction }) {
  sortState.value = { field, direction }
}
</script>

Ordenação remota (remoteSort)

Com remoteSort, a tabela não ordena os dados por conta própria. Ela apenas emite @onSort e aguarda você atualizar items. Use quando a ordenação é feita no servidor — normalmente junto com paginação.

O fluxo:

  1. Usuário clica no cabeçalho → tabela emite @onSort com { field, direction }
  2. Você faz a requisição para a API passando field e direction
  3. A API retorna os dados já ordenados
  4. Você atualiza items com o resultado

O demo abaixo simula o passo 3 com um setTimeout de 600ms. Note que enquanto aguarda, o spinner de loading aparece — basta ligar :loading ao mesmo estado.

vue
<template>
  <MeDataTable
    :items="items"
    :loading="loading"
    remoteSort
    @onSort="handleSort"
  >
    <MeColumn field="portalName" header="PORTAL"          :sortable="true" />
    <MeColumn field="value"      header="VALOR"           :sortable="true" />
    <MeColumn field="createdAt"  header="DATA DE CRIAÇÃO" :sortable="true" />
  </MeDataTable>
</template>

<script setup>
import { ref } from 'vue'
import { MeDataTable, MeColumn } from '@me/ui-vue3'
import axios from 'axios'

const items = ref([])
const loading = ref(false)

async function handleSort({ field, direction }) {
  loading.value = true
  try {
    const response = await axios.get('/api/portals', { params: { sortField: field, sortDirection: direction } })
    items.value = response.data
  } finally {
    loading.value = false
  }
}
</script>

Props de ordenação

PropOndeTipoDefaultDescrição
sortableMeColumnbooleanfalseHabilita a ordenação para a coluna
typeMeColumnstringUse "date" em colunas de data para ordenação correta (formatos DD/MM/YYYY, DD-MM-YYYY)
sortFunctionMeColumnfunctionFunção customizada (items, direction) => items[] para lógica de ordenação própria
remoteSortMeDataTablebooleanfalseDesativa a ordenação local; você ordena via @onSort e atualiza items externamente

Evento @onSort

Emitido ao clicar no cabeçalho de uma coluna ordenável. Payload: { field: string, direction: 'asc' | 'desc' }.

Use com remoteSort para ordenação no servidor (ex.: paginação com sort via API).

Paginação

A paginação do MeDataTable é controlada externamente: você fatia os dados a serem exibidos e controla a página atual. A tabela renderiza apenas o que está em items e usa totalItems para calcular o número de páginas.

1 - 2 de 6
vue
<template>
  <MeDataTable
    :items="pagedItems"
    :pagination="true"
    :totalItems="allItems.length"
    :rowsPerPage="rowsPerPage"
    :currentPage="currentPage"
    :rowsPerPageOptions="[2, 4, 6]"
    :showRowsPerPageSelect="true"
    @pageChange="handlePageChange"
    @rowsPerPageChange="handleRowsPerPageChange"
  >
    <MeColumn field="id"         header="ID" />
    <MeColumn field="name"       header="Nome" />
    <MeColumn field="email"      header="E-mail" />
    <MeColumn field="registered" header="Data de Cadastro" />
  </MeDataTable>
</template>

<script setup>
import { ref, computed } from 'vue'
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const allItems = [ /* todos os itens */ ]

const currentPage = ref(1)
const rowsPerPage = ref(2)

const pagedItems = computed(() =>
  allItems.slice((currentPage.value - 1) * rowsPerPage.value, currentPage.value * rowsPerPage.value)
)

function handlePageChange(page) {
  currentPage.value = page
}

function handleRowsPerPageChange(rpp) {
  rowsPerPage.value = rpp
  currentPage.value = 1 // volta para a primeira página ao mudar o tamanho
}
</script>

Paginação remota (API): passe :items já fatiados pela API, use totalItems com o total retornado pelo servidor e atualize items ao receber @pageChange e @rowsPerPageChange.

Props de paginação

PropTipoDefaultDescrição
paginationbooleanfalseAtiva o rodapé de paginação
totalItemsnumber0Total de itens (todos, não apenas a página atual) — usado para calcular o número de páginas
currentPagenumber1Página atualmente exibida
rowsPerPagenumber10Quantidade de itens exibidos por página
rowsPerPageOptionsnumber[]Opções disponíveis no seletor de itens por página (ex: [5, 10, 20])
showRowsPerPageSelectbooleantrueExibe ou oculta o seletor de itens por página

Eventos de paginação

EventoPayloadQuando é emitido
@pageChangenumberUsuário navega para outra página
@rowsPerPageChangenumberUsuário altera a quantidade de itens por página

Visibilidade e Reordenação de Colunas

Adicione showColumnsControl na tabela para exibir um botão no cabeçalho que permite ao usuário ocultar e reordenar colunas.

Regras:

  • Colunas com fixed não aparecem no controle (não podem ser reordenadas nem ocultadas).
  • Colunas com nonHideable aparecem no controle mas não podem ser ocultadas.
vue
<template>
  <MeDataTable :items="items" showColumnsControl>
    <MeColumn field="id"         header="ID" />
    <MeColumn field="name"       header="Nome" />
    <MeColumn field="email"      header="E-mail"           :nonHideable="true" />
    <MeColumn field="registered" header="Data de Cadastro" fixed="right" />
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [ /* ... */ ]
</script>

No exemplo acima: ID e Nome podem ser ocultados e reordenados; E-mail aparece no painel mas não pode ser ocultado (nonHideable); Data de Cadastro é fixa à direita e não aparece no painel de controle.

Props de visibilidade

PropOndeTipoDefaultDescrição
showColumnsControlMeDataTablebooleanfalseExibe o botão para ocultar e reordenar colunas
nonHideableMeColumnbooleanfalseImpede que a coluna seja ocultada pelo painel de controle

Colunas Fixas

Use fixed="left" ou fixed="right" em MeColumn para fixar uma coluna durante o scroll horizontal. Útil para manter identificadores ou ações sempre visíveis.

O demo abaixo tem 14 colunas — role horizontalmente para ver o efeito:

vue
<template>
  <MeDataTable :items="items">
    <MeColumn field="id"   header="ID"         fixed="left"  width="60px" />
    <MeColumn field="name" header="Nome"                     width="150px" />
    <!-- demais colunas -->
    <MeColumn field="acao" header="Ação"        fixed="right" width="100px" />
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [ /* ... */ ]
</script>

Comportamento

  • Colunas fixas permanecem visíveis durante o scroll horizontal.
  • Colunas com fixed não aparecem no painel de controle de colunas (showColumnsControl).
  • É possível fixar múltiplas colunas à esquerda e à direita ao mesmo tempo.

Atenção: sempre defina width em colunas fixas para evitar problemas de layout.

Prop fixed

PropOndeTipoDefaultDescrição
fixedMeColumn"left" | "right"Fixa a coluna à esquerda ou à direita

Cabeçalho Customizado (#header)

Use o slot #header para injetar uma linha de largura total acima dos cabeçalhos de coluna. Ideal para toolbars com título, contagem de registros, busca ou ações globais da tabela.

peopleUsuários(4 registros)
vue
<template>
  <MeDataTable :items="items">
    <template #header>
      <div style="display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;">
        <span class="material-icons-outlined">people</span>
        <span>Usuários</span>
        <span>({{ items.length }} registros)</span>
      </div>
    </template>
    <MeColumn field="id"         header="ID" />
    <MeColumn field="name"       header="Nome" />
    <MeColumn field="email"      header="E-mail" />
    <MeColumn field="registered" header="Data de Cadastro" />
  </MeDataTable>
</template>

Use o slot #footer para injetar conteúdo no <tfoot> da tabela, ocupando toda a largura. Útil para totalizadores, médias ou notas de rodapé.

Total:R$ 600
vue
<template>
  <MeDataTable :items="items">
    <MeColumn field="name"  header="Nome" />
    <MeColumn field="value" header="Valor" />
    <template #footer>
      <div style="display: flex; justify-content: flex-end; gap: 8px;">
        <span>Total:</span>
        <strong>R$ {{ total }}</strong>
      </div>
    </template>
  </MeDataTable>
</template>

<script setup>
import { computed } from 'vue'
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [ /* ... */ ]
const total = computed(() => items.reduce((s, r) => s + r.value, 0))
</script>

Coluna de Ações

A coluna de ações é uma MeColumn com type="action" e normalmente fixed="right". O type="action" impede que o clique na célula propague para a seleção da linha — essencial quando há botões dentro da célula.

Use o slot #cell com { rowData } para montar os controles de cada linha.

Com botões de ação diretos

O padrão mais simples: botões visíveis diretamente na célula, um para cada ação.

vue
<template>
  <MeDataTable :items="items" selectableInRowClick selectable>
    <MeColumn field="id"     header="ID" />
    <MeColumn field="name"   header="Nome" />
    <MeColumn field="status" header="Status" />

    <MeColumn width="50px" fixed="right" type="action">
      <template #cell="{ rowData }">
        <div style="display: flex; gap: 4px;">
          <MeButton flat icon="visibility" variant="gray" @click="handleView(rowData)" />
          <MeButton flat icon="edit"       variant="gray" @click="handleEdit(rowData)" />
          <MeButton flat icon="delete"     variant="red"  @click="handleDelete(rowData)" />
        </div>
      </template>
    </MeColumn>
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn, MeButton } from '@me/ui-vue3'

const items = [ /* ... */ ]
</script>

Com menu dropdown (MeDropdownDataTable)

Ideal quando há muitas ações ou quando o espaço da coluna é restrito. O menu é aberto pelo slot #trigger.

vue
<template>
  <MeDataTable :items="items" selectableInRowClick selectable>
    <MeColumn field="id"     header="ID" />
    <MeColumn field="name"   header="Nome" />
    <MeColumn field="status" header="Status" />

    <MeColumn width="10px" fixed="right" type="action">
      <template #cell="{ rowData }">
        <MeDropdownDataTable
          :options="[
            { label: 'Ver detalhes', icon: { icon: 'visibility', variant: 'gray' } },
            { label: 'Editar',       icon: { icon: 'edit',       variant: 'gray' } },
            { label: 'Excluir',      icon: { icon: 'delete',     variant: 'red'  } },
          ]"
          :rowData="rowData"
          @optionClick="handleOptionClick"
        >
          <template #trigger>
            <MeButton flat icon="more_vert" variant="gray" />
          </template>
        </MeDropdownDataTable>
      </template>
    </MeColumn>
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn, MeButton, MeDropdownDataTable } from '@me/ui-vue3'

const items = [ /* ... */ ]

function handleOptionClick({ option, rowData }) {
  console.log(option.label, rowData)
}
</script>

Por que type="action"?

Sem type="action", clicar em um botão dentro da célula propaga o evento para a linha, ativando seleção ou row-click indesejados. Com type="action", a tabela chama event.stopPropagation() automaticamente.

MeDropdownDataTable

Componente auxiliar para menus de contexto por linha. Usa um slot #trigger para o botão de abertura e recebe um array de options.

Props

PropTipoDefaultDescrição
optionsDropdownOption[][]Lista de opções do menu
rowDataobjectDados da linha — retornados no evento @optionClick
position"bottom-right" | "bottom-left" | "top-right" | "top-left""bottom-right"Posição do menu em relação ao trigger
closeOnClickbooleantrueFecha o menu ao clicar em uma opção

DropdownOption

ts
interface DropdownOption {
  label: string
  icon?: {
    icon: string          // nome Material Icons
    variant?: string      // "red" | "green" | "yellow" | "gray"
    color?: string        // qualquer valor CSS
    notificationDot?: boolean
  }
  disabled?: boolean
}

Eventos

EventoPayloadDescrição
@optionClick{ option, rowData }Opção clicada
@closeMenu fechado

Linhas Filhas Expansíveis

Adicione expandableChildRows na tabela e inclua childRows (array) em cada item que deve ter sub-linhas. Linhas sem childRows ficam sem o botão de expansão.

Use o slot #cellChildRow em qualquer MeColumn para customizar a célula das linhas filhas. Ele recebe { childData, parentData }.

vue
<template>
  <MeDataTable :items="items" expandableChildRows>
    <MeColumn field="portalName" header="Portal">
      <template #cellChildRow="{ childData, parentData }">
        <span>{{ childData.portalName }} ({{ parentData.portalName }})</span>
      </template>
    </MeColumn>
    <MeColumn field="bidding"  header="Licitação" />
    <MeColumn field="modality" header="Modalidade" />

    <MeColumn width="60px" fixed="right" type="action">
      <template #cell="{ rowData }">
        <MeButton flat icon="visibility" variant="gray" @click="handleView(rowData)" />
      </template>
      <template #cellChildRow="{ childData, parentData }">
        <MeButton flat icon="settings" variant="gray" @click="handleChildAction(childData, parentData)" />
      </template>
    </MeColumn>
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn, MeButton } from '@me/ui-vue3'

const items = [
  {
    id: 1,
    portalName: 'Portal A',
    bidding: 'LIC-2025-001',
    modality: 'Pregão',
    childRows: [
      { id: 11, portalName: 'Sub-Portal A1', bidding: 'LIC-2025-001-A', modality: 'Pregão' },
      { id: 12, portalName: 'Sub-Portal A2', bidding: 'LIC-2025-001-B', modality: 'Concorrência' },
    ]
  },
  {
    id: 2,
    portalName: 'Portal B',
    bidding: 'LIC-2025-002',
    modality: 'Concorrência',
    childRows: [
      { id: 21, portalName: 'Sub-Portal B1', bidding: 'LIC-2025-002-A', modality: 'Pregão' },
    ]
  },
  { id: 3, portalName: 'Portal C', bidding: 'LIC-2025-003', modality: 'Pregão' },
]
</script>

Estrutura dos dados

js
const items = [
  {
    id: 1,
    // campos normais...
    childRows: [          // array de sub-linhas (opcional por item)
      { id: 11, /* mesmos campos */ },
      { id: 12, /* mesmos campos */ },
    ]
  },
  { id: 2, /* sem childRows = sem botão de expansão */ }
]

Props e slots de linhas filhas

Prop / SlotOndeDescrição
expandableChildRowsMeDataTableHabilita a exibição de linhas filhas expansíveis
#cellChildRowMeColumnSlot para customizar a célula da linha filha. Params: { childData, parentData }

Expansão com Tabela Interna

Use expandableCustomContent para expandir cada linha com conteúdo livre via slot #expansion="{ row }". O caso mais comum é uma tabela interna (isInnerTable) com seleção coordenada com a tabela pai.

Clique em qualquer linha abaixo para expandir (expandInRowClick). Selecione logins nas tabelas internas para ver a toolbar de ações aparecer:

vue
<template>
  <MeDataTable
    ref="tableRef"
    :items="accessesList"
    expandableCustomContent
    expandInRowClick
    :selection="selectedRowsWithChildren"
  >
    <MeColumn field="portal_name"  header="Portal"  />
    <MeColumn field="company_name" header="Empresa" />

    <template #expansion="{ row }">
      <div style="padding: 12px 16px; width: 100%;">
        <MeDataTable
          isInnerTable
          :items="row.logins"
          selectable
          selectionMode="multiple"
          :selection="getSelectedForParent(row)"
          @selectionChange="(sel) => handleInnerSelectionChange(sel, row)"
        >
          <MeColumn field="login"  header="Login"  />
          <MeColumn field="system" header="Etapa"  />
          <MeColumn field="status" header="Status" />
        </MeDataTable>
      </div>
    </template>
  </MeDataTable>

  <MeSelectedRowsToolbar
    :parentRef="tableRef"
    :show="showInnerToolbar"
    :selectedRowsCount="getTotalSelected()"
    @close="closeInnerToolbar"
  >
    <template #buttons>
      <MeSelectedRowsActionButton
        v-if="getTotalSelected() === 1"
        icon="edit_note"
        label="Editar"
        @click="handleEdit"
      />
      <MeSelectedRowsActionButton
        icon="delete"
        label="Excluir"
        variant="red"
        @click="handleDelete"
      />
    </template>
  </MeSelectedRowsToolbar>
</template>

<script setup>
import { ref } from 'vue'
import { MeDataTable, MeColumn, MeSelectedRowsToolbar, MeSelectedRowsActionButton } from '@me/ui-vue3'

const accessesList = [
  {
    id: 1,
    portal_name: 'Portal XPTO',
    company_name: 'Empresa ABC',
    logins: [
      { login: 'user1', system: 'Etapa 1', status: 'Ativo'   },
      { login: 'user2', system: 'Etapa 2', status: 'Inativo' },
    ],
  },
  {
    id: 2,
    portal_name: 'Portal YZ',
    company_name: 'Empresa DEF',
    logins: [
      { login: 'user3', system: 'Etapa 1', status: 'Ativo' },
    ],
  },
]

const tableRef = ref(null)
const selectedRowsWithChildren = ref([])
const showInnerToolbar = ref(false)

function getSelectedForParent(row) {
  const found = selectedRowsWithChildren.value.find(item => item.id === row.id)
  return found ? found.selectedChildRows : []
}

function getTotalSelected() {
  return selectedRowsWithChildren.value.reduce(
    (total, item) => total + (item.selectedChildRows?.length || 0), 0
  )
}

function handleInnerSelectionChange(selectedChildRows, parentRow) {
  selectedRowsWithChildren.value = selectedRowsWithChildren.value
    .filter(item => item.id !== parentRow.id)
  if (selectedChildRows.length > 0)
    selectedRowsWithChildren.value.push({ ...parentRow, selectedChildRows })
  showInnerToolbar.value = getTotalSelected() > 0
}

function closeInnerToolbar() {
  selectedRowsWithChildren.value = []
  showInnerToolbar.value = false
}
</script>

Como funciona a coordenação de seleção

A tabela pai usa :selection="selectedRowsWithChildren" para destacar visualmente as linhas que têm filhos selecionados. Cada tabela interna usa :selection="getSelectedForParent(row)" para manter o estado independente por linha pai.

Quando o usuário seleciona um login, @selectionChange chama handleInnerSelectionChange que:

  1. Remove o registro anterior do pai no array de seleção
  2. Re-insere com os novos filhos selecionados
  3. Atualiza showInnerToolbar com base no total de selecionados

Props de expansão

PropOndeTipoDefaultDescrição
expandableCustomContentMeDataTablebooleanfalseHabilita expansão com conteúdo livre via slot #expansion
expandInRowClickMeDataTablebooleanfalseExpande/recolhe ao clicar em qualquer parte da linha
isInnerTableMeDataTablebooleanfalseAplica estilo compacto para uso como tabela interna

MeSelectedRowsToolbar externo

Quando há tabelas internas, use o MeSelectedRowsToolbar fora da tabela principal e passe a referência via :parentRef para o posicionamento correto:

vue
<MeDataTable ref="tableRef" ...>
  ...
</MeDataTable>

<MeSelectedRowsToolbar :parentRef="tableRef" :show="showToolbar" ... />

Slot #expansion

SlotParamsDescrição
#expansion{ row }Conteúdo expandido da linha. row é o objeto do item

Customização de Estilo

O MeDataTable expõe props para aplicar classes e estilos em cada nível da tabela — cabeçalho, corpo, linha e célula. As props de linha e célula aceitam funções para customização dinâmica baseada nos dados.

vue
<template>
  <MeDataTable
    :items="items"
    :rowClass="(row) => row.status === 'Inativo' ? 'row-inactive' : ''"
    :rowStyle="(row) => row.status === 'Inativo' ? { opacity: '0.6' } : {}"
    :cellStyle="({ column, value }) => {
      if (column.field === 'status' && value === 'Ativo')   return { color: '#16a34a', fontWeight: '600' }
      if (column.field === 'status' && value === 'Inativo') return { color: '#dc2626', fontWeight: '600' }
      return {}
    }"
  >
    <MeColumn field="id"     header="ID"     :columnStyle="{ color: '#6b7280' }" />
    <MeColumn field="name"   header="Nome" />
    <MeColumn field="status" header="Status" />
  </MeDataTable>
</template>

<script setup>
import { MeDataTable, MeColumn } from '@me/ui-vue3'

const items = [ /* ... */ ]
</script>

Props de estilo

PropOndeTipoDescrição
headerClassMeDataTablestring | string[]Classes CSS extras para o <thead>
headerStyleMeDataTableobjectEstilos inline para o <thead>
bodyClassMeDataTablestring | string[]Classes CSS extras para o <tbody>
bodyStyleMeDataTableobjectEstilos inline para o <tbody>
rowClassMeDataTablestring | string[] | (row, rowIndex) => stringClasse(s) por linha — aceita função para lógica dinâmica
rowStyleMeDataTableobject | (row, rowIndex) => objectEstilos por linha — aceita função para lógica dinâmica
cellClassMeDataTablestring | string[] | (ctx: CellContext) => stringClasse(s) por célula — aceita função com contexto completo
cellStyleMeDataTableobject | (ctx: CellContext) => objectEstilos por célula — aceita função com contexto completo
columnClassMeColumnstring | string[]Classes CSS extras para todas as células daquela coluna
columnStyleMeColumnobjectEstilos inline para todas as células daquela coluna

CellContext — parâmetro recebido pelas funções de cellClass e cellStyle:

ts
interface CellContext {
  row: object      // objeto completo da linha
  rowIndex: number // índice da linha
  column: object   // definição da coluna (field, header, ...)
  colIndex: number // índice da coluna
  value: any       // valor da célula (row[column.field])
}

API

Props — MeDataTable

PropTipoDefaultDescrição
itemsarray[]Lista de dados da tabela
loadingbooleanfalseExibe spinner de carregamento
loadingTextstringTexto exibido abaixo do spinner
emptyTitlestring"Nenhum dado encontrado"Título do estado vazio
emptyDescriptionstringDescrição do estado vazio
emptyIcon"empty" | "not-found" | "error" | "empty-folder""empty"Ilustração do estado vazio
maxHeightstring"440px"Altura máxima (ativa scroll vertical)
minHeightstring"80px"Altura mínima
tableStylestring""Estilo inline para o elemento <table>
hideHeaderbooleanfalseOculta o cabeçalho
idstring"me-datatable"ID do elemento raiz
rowIdPropertystringnullCampo do objeto usado como identificador de linha. Use quando os dados não têm id — ver Formato dos Itens
Seleção
selectablebooleanfalseExibe checkbox e ativa seleção de linhas
selectionMode"single" | "multiple""single"Modo de seleção
selectableInRowClickbooleanfalseSeleciona ao clicar em qualquer parte da linha
selectionarray[]Estado externo da seleção (usar com @selectionChange)
showSelectedRowsToolbarbooleanfalseExibe toolbar flutuante ao selecionar linhas
onlyParentTableSelectablebooleanfalseApenas a tabela pai é selecionável (uso com tabelas internas)
Paginação
paginationbooleanfalseAtiva rodapé de paginação
totalItemsnumber0Total de itens — usado para calcular páginas
currentPagenumber1Página atual
rowsPerPagenumber10Itens por página
rowsPerPageOptionsnumber[]Opções do seletor de itens por página
showRowsPerPageSelectbooleantrueExibe seletor de itens por página
Ordenação
remoteSortbooleanfalseDesativa ordenação local; você ordena via @onSort
Colunas
showColumnsControlbooleanfalseExibe painel para ocultar e reordenar colunas
Expansão
expandableChildRowsbooleanfalseHabilita linhas filhas (childRows) expansíveis
expandableCustomContentbooleanfalseHabilita expansão com conteúdo livre via slot #expansion
expandInRowClickbooleanfalseExpande/recolhe ao clicar na linha
showExpandAllbooleanfalseExibe botão para expandir/recolher todas as linhas
expandAllOnMountbooleanfalseExpande todas as linhas ao montar o componente
childRowsArrayNamestring"childRows"Nome da propriedade usada como array de sub-linhas
isInnerTablebooleanfalseAplica estilo compacto para uso como tabela interna
Estilo
headerClassstring | string[]""Classes CSS extras para o <thead>
headerStyleobject{}Estilos inline para o <thead>
bodyClassstring | string[]""Classes CSS extras para o <tbody>
bodyStyleobject{}Estilos inline para o <tbody>
rowClassstring | string[] | (row, rowIndex) => string""Classes por linha — aceita função dinâmica
rowStyleobject | (row, rowIndex) => object{}Estilos por linha — aceita função dinâmica
cellClassstring | string[] | (ctx: CellContext) => string""Classes por célula — aceita função dinâmica
cellStyleobject | (ctx: CellContext) => object{}Estilos por célula — aceita função dinâmica

Props — MeColumn

PropTipoDefaultDescrição
fieldstringChave do objeto de dados exibida na célula
headerstring""Rótulo do cabeçalho
widthstringLargura fixa (ex: "120px")
minWidthstringLargura mínima
fixed"left" | "right"Fixa a coluna à esquerda ou à direita
ellipsisbooleanfalseTrunca texto longo com reticências
sortablebooleanfalseHabilita ordenação ao clicar no cabeçalho
sortFunction(items, direction) => items[]Função de ordenação customizada
nonHideablebooleanfalseImpede que seja ocultada pelo painel de controle
type"action" | "date""action": bloqueia propagação de clique; "date": ordenação correta de datas
columnClassstring | string[]""Classes CSS extras para toda a coluna
columnStyleobject{}Estilos inline para toda a coluna

Eventos — MeDataTable

EventoPayloadQuando é emitido
@selectionChangearrayA seleção de linhas muda
@rowClick{ rowData, rowIndex }Clique em uma linha
@onSort{ field: string, direction: 'asc' | 'desc' }Clique no cabeçalho de coluna ordenável
@pageChangenumberUsuário navega para outra página
@rowsPerPageChangenumberUsuário altera a quantidade de itens por página

Slots — MeDataTable

SlotParamsDescrição
#emptySubstitui toda a área de estado vazio por conteúdo livre — ver Estado Vazio Customizado
#loadingSubstitui o spinner padrão por loading customizado — ver Loading Customizado
#headerLinha de largura total acima dos cabeçalhos de coluna — ver Cabeçalho Customizado
#footerConteúdo no <tfoot> da tabela — ver Rodapé Customizado
#selectedButtonsActionsBotões dentro da toolbar de seleção interna (showSelectedRowsToolbar)
#expansion{ row }Conteúdo expandido por linha com expandableCustomContent

Slots — MeColumn

SlotParamsDescrição
#cell{ rowData }Conteúdo customizado da célula da linha pai
#header{ field, header }Conteúdo customizado do cabeçalho
#cellChildRow{ childData, parentData }Conteúdo customizado da célula de linha filha