Documentação/Visão geral

Tabela de dados

Uma tabela do Nuxt UI com 2.000 tarefas, virtualizada e pesquisável, ordenada arrastando as linhas pelas alças.

Editar esta página

Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês

Live

Carregando a demo…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import TaskTable from './TaskTable.vue';
</script>

<template>
  <!-- The row in hand: a table row can't have a shadow, so its overlay content has it. -->
  <DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
    <TaskTable />
  </DnDProvider>
</template>

O UTable do Nuxt UI renderiza a tabela, as linhas e o virtualizador dela: só as linhas que estão na tela ficam no DOM. O Vue DnD Kit as ordena. A tabela é a da biblioteca do jeito que vem, com os seus slots e as suas classes ui; nada dela é reescrito.

O que observar

  • TaskTable.vue: a lista ordenável é o <tbody> da tabela, encontrado pelo tableRef que o UTable expõe. A tabela recebe o rascunho da lista como data, então as linhas se movem enquanto você arrasta, e get-row-id dá às linhas a key da tarefa, como a lista faz. axis: 'y' mantém o arraste na coluna dele, e restrictToFirstScrollableAncestor, dentro da tabela: o contêiner de rolagem dela é o primeiro elemento rolável em volta das linhas. Expanda a demo: a tabela preenche a tela.
  • RowGrip.vue: o UTable renderiza as linhas, então nenhum componente seu é uma linha. A alça na primeira célula (o slot #grip-cell) torna a linha em volta dela o item, e é o handle dele.
  • A busca é o filter da lista: a tabela mostra as tarefas que correspondem, e um drop grava de volta o array inteiro, com as tarefas ocultas onde estavam.
  • tasks.ts: 2.000 tarefas em um shallowRef: a biblioteca nunca olha dentro delas.

Uma linha que a tabela renderiza

A alça chama makeSortableItem com um getter para a linha: o <tr> em volta dela, depois de renderizado. Como handle, a alça recebe o foco e os atributos ARIA, e o resto da linha fica livre para cliques e seleção de texto.

RowGrip.vue
const grip = useTemplateRef<HTMLElement>('grip');
makeSortableItem(() => grip.value?.closest('tr') ?? null, {
  id: () => props.task.id,
  handle: grip,
  label: () => props.task.title,
});
TaskTable.vue
<UTable ref="table" :data="items" :get-row-id="(task: Task) => task.id" …>
  <template #grip-cell="{ row }">
    <RowGrip :task="row.original" />
  </template>
</UTable>

O overlay é a cópia da linha feita pela biblioteca, em uma tabela própria com as larguras das células. Uma linha de tabela não desenha sombra, então o overlay-class do provider dá uma ao conteúdo do overlay, com o fundo da tabela e um anel. As classes ui da linha tingem o lugar dela enquanto ela viaja (data-[dnd-placeholder]:bg-primary/5).

index.vue
<DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
  <TaskTable />
</DnDProvider>

Linhas virtualizadas

virtualize dá à tabela o TanStack Virtual: as linhas na tela e mais 8 em volta delas (overscan) são renderizadas, entre duas linhas espaçadoras.

TaskTable.vue
<UTable
  :data="items"
  :watch-options="{ deep: false }"
  :virtualize="{ estimateSize: 45, overscan: 8, onChange: () => captureLayout() }"
  class="min-h-0 flex-1"
/>
  • estimateSize é um primeiro palpite: o virtualizador mede cada linha conforme a renderiza. Uma medição move as linhas abaixo dela, e onChange chama o captureLayout() da lista, então a biblioteca anima esse movimento durante um arraste em vez de deixá-lo pular.
  • A tabela é o contêiner de rolagem, e a linha na mão fica dentro dela: perto da borda de cima ou de baixo, um arraste a rola, e novas linhas são renderizadas conforme entram na tela.
  • O rascunho é um array novo depois de cada movimento, então a tabela não precisa observar dentro dele (deep: false).
  • Role para longe da linha que você está segurando, e ela sai do intervalo renderizado e é desmontada. O arraste continua: o overlay ainda a mostra, e ela pousa onde o rascunho a coloca. Uma lista virtual sua pode mantê-la montada com rangeExtractor (veja Listas virtuais). Uma tabela dispõe as linhas entre linhas espaçadoras, onde uma linha renderizada longe das outras as empurraria para o lado, então esta a deixa ir.

Pesquise, depois arraste

TaskTable.vue
const { items, captureLayout } = makeSortableList(() => table.value?.tableRef?.tBodies[0] ?? null, {
  items: tasks,
  filter: (task) => {
    const text = query.value.trim().toLowerCase();
    return !text || `${task.id} ${task.title} ${task.assignee}`.toLowerCase().includes(text);
  },
  axis: 'y',
  modifiers: [restrictToFirstScrollableAncestor],
  label: 'Tasks',
});

Pesquise "Mia", mova uma das tarefas dela para cima de outra e depois limpe a busca: as tarefas dela estão na nova ordem, e todas as outras tarefas estão onde estavam. O e.to.index do drop conta as linhas mostradas; e.result contém o array inteiro.

Outras bibliotecas de UI. O Nuxt UI renderiza elementos simples e repassa as suas classes e atributos, como fazem as primitivas do Reka UI sobre as quais ele é construído: um ref, um slot e uma classe são tudo o que é preciso. Em outras bibliotecas, uma tabela também renderiza as linhas, e a mesma receita funciona:
  • A linha. Um slot que renderiza a linha inteira (#item do Vuetify, #body do Quasar) dá a você o elemento dela: torne-o o item. Onde você só recebe células (o #body de uma Column do PrimeVue, o slot padrão de uma coluna do Element Plus, o render do Naive UI), coloque uma alça em uma delas e torne o closest('tr') dela o item, como aqui.
  • A lista. O <tbody> dentro do elemento raiz da tabela: um template ref na tabela dá a você o componente, e o $el dele, o elemento.
  • As keys e os dados. Dê às linhas a key do seu id (item-value, row-key, dataKey) e passe para a tabela o rascunho da lista como os dados dela.
  • Ordenação própria desligada. Deixe de fora a reordenação de linhas da própria tabela e a ordenação por colunas: a ordem é a da lista.
  • Tabelas virtuais posicionam as linhas entre espaçadores ou em caixas absolutas: os dois funcionam, e um arraste sobrevive à desmontagem da sua linha.
O botão do StackBlitz abre os arquivos em um projeto Vite configurado para o Nuxt UI, como a documentação dele faz para Vue: o plugin @nuxt/ui/vite, o Tailwind CSS e UApp em volta do exemplo. Em um app Nuxt com o módulo @nuxt/ui, copie a pasta e renderize index.vue em uma página.

Teclado e leitores de tela

Chegue com Tab à alça de uma tarefa e pressione Space para pegar a linha. ArrowUp e ArrowDown a movem, Space a solta e Escape a devolve ao lugar. As linhas têm um label, e a lista também: os leitores de tela ouvem "Fix the login form dropped at position 3 of 2000 in Tasks.", não keys.

Leia mais: Listas virtuais · Mostrar parte dos itens · Estilização: Tailwind e kits de UI · Acessibilidade