Ir al contenido
Contenido y datos

DataTable

Una tabla de datos sobre la lista de Drive: columnas por definición, orden con la cabecera, búsqueda, páginas o «Cargar más», selección con casillas, vacío, esqueletos y grupos.

import { DataTable } from "sebs7n-ui/data-table"

Ejemplos

Facturas emitidas

Orden con la cabecera, búsqueda sin tildes ni mayúsculas, selección con casillas y páginas. La fila elegida va en el acento mientras la tabla tiene el foco, como la lista de Drive.

import { Button } from "sebs7n-ui"
import { DataTable } from "sebs7n-ui/data-table"
import { PlusIcon } from "lucide-react"
import { useState } from "react"

function Basico() {
  const [elegidas, setElegidas] = useState<string[]>([])
  return (
    <div className="w-full">
      <DataTable
        aria-label="Facturas emitidas"
        columns={COLUMNAS}
        data={FACTURAS}
        defaultSort={{ id: "fecha", direction: "desc" }}
        filter
        getRowId={(row) => row.id}
        getRowLabel={(row) => `${row.numero} de ${row.cliente}`}
        onSelectedChange={setElegidas}
        pageSize={8}
        selectable
        selected={elegidas}
        toolbar={
          <>
            {elegidas.length > 0 && <span className="text-callout text-label-secondary">{elegidas.length} elegidas</span>}
            <Button size="sm">
              <PlusIcon />
              Nueva factura
            </Button>
          </>
        }
      />
    </div>
  )
}

Por estado, con «Cargar más»

`groupBy` arma un grupo por estado con el título de Drive y su contador; `paging="more"` suma filas abajo en vez de paginar.

import { DataTable } from "sebs7n-ui/data-table"

function Agrupada() {
  return (
    <div className="w-full">
      <DataTable
        aria-label="Facturas por estado"
        columns={COLUMNAS.filter((column) => column.id !== "estado")}
        data={[...FACTURAS].sort((a, b) => ESTADOS.indexOf(a.estado) - ESTADOS.indexOf(b.estado))}
        getRowId={(row) => row.id}
        groupBy={(row) => row.estado}
        pageSize={8}
        paging="more"
      />
    </div>
  )
}

Cargando y vacía

Mientras llegan los datos, filas de esqueleto y `aria-busy`; sin filas, la tabla lo dice.

import { Button } from "sebs7n-ui"
import { DataTable } from "sebs7n-ui/data-table"
import { useState } from "react"

function Estados() {
  const [cargando, setCargando] = useState(true)
  return (
    <div className="flex w-full flex-col gap-4">
      <Button className="self-start" onClick={() => setCargando(!cargando)} size="sm" variant="secondary">
        {cargando ? "Mostrar vacía" : "Volver a cargar"}
      </Button>
      <DataTable
        aria-label="Facturas del mes"
        columns={COLUMNAS}
        data={[]}
        empty="Todavía no emitiste facturas este mes."
        getRowId={(row) => row.id}
        loading={cargando}
        loadingRows={4}
      />
    </div>
  )
}

Props

Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».

DataTable

PropTipoPor defectoDescripción
columns*DataTableColumn<T>[]—Las columnas: id, header, value (ordenar y buscar), cell (dibujar), sortable, numeric, searchable, className (el ancho).
data*T[]—Las filas.
getRowId*(row: T) => string—El id estable de cada fila: la key y lo que guarda la selección.
aria-labelstring—Nombre accesible del elemento.
defaultPagenumber1La página al arrancar.
defaultQuerystring""La búsqueda al arrancar.
defaultSelectedstring[][]Los elegidos al arrancar.
defaultSortDataTableSortnullEl orden al arrancar.
density"default" | "compact"—La densidad de la Table: default (filas de 41, la de Drive) o compact (32).
emptyReact.ReactNode—Lo que dice la tabla sin filas. Por defecto, «Sin resultados».
filterbooleanfalseMuestra la búsqueda arriba.
filtersReact.ReactNode—Lo que va pegado a la derecha de la búsqueda, en la misma fila mientras entre: los selectores de filtro.
getRowLabel(row: T) => string—El nombre de la fila para su casilla. Por defecto, el valor de la primera columna.
groupBy(row: T) => string—El título del grupo de cada fila: agrupa en el orden en que aparecen, con su contador.
labelsPartial<Labels["dataTable"]>—Textos: search, selectAll, selectRow, loadMore, empty, result, results, item, items.
loadingbooleanfalseFilas de esqueleto en lugar de los datos, y aria-busy.
loadingRowsnumber—Cuántas filas de esqueleto. Por defecto pageSize o 5.
localestring—El locale del orden alfabético.
onPageChange(page: number) => void—Se llama con la página nueva.
onQueryChange(query: string) => void—Se llama con el texto nuevo.
onSelectedChange(ids: string[]) => void—Se llama con los ids elegidos.
onSortChange(sort: DataTableSort) => void—Se llama con el orden nuevo.
pagenumber—La página, en base 1. Pasarla la vuelve controlada.
pageSizenumber—Filas por página. Sin él, todas.
paging"pages" | "more""pages"pages (default, con Pagination) o more (un botón «Cargar más»).
querystring—El texto de la búsqueda. Pasarlo lo vuelve controlado.
selectablebooleanfalseUna casilla por fila y la de todas en la cabecera.
selectedstring[]—Los ids elegidos, en el orden de data. Pasarlo lo vuelve controlado.
sortDataTableSort—El orden ({ id, direction } o null). Pasarlo lo vuelve controlado.
toolbarReact.ReactNode—Lo que va a la derecha de la búsqueda: filtros, «Nueva factura».
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

Teclado

Tab
Recorre la búsqueda, los botones de orden de la cabecera, las casillas y la paginación, en ese orden.
Enter / Espacio
En una cabecera, ordena: ascendente, descendente, sin orden. En una casilla, la marca.

Accesibilidad

  • Es una <table> de verdad: nombre obligatorio con aria-label. El lector anuncia columnas y filas y navega celda por celda con sus atajos de tabla.
  • La columna ordenada lleva aria-sort (ascending/descending) en su <th>; el orden se cambia con un <button> adentro de la cabecera. La flecha es decorativa.
  • La búsqueda es un searchbox con nombre («Buscar»), y el total («12 resultados») sale por una región status que se anuncia al cambiar.
  • Cada casilla se nombra con la fila (getRowLabel, por defecto la primera columna): «Seleccionar Acme S.A.». La de la cabecera, «Seleccionar todas», queda en mixed con algunas.
  • Cargando, la tabla está aria-busy y los esqueletos no tienen texto.
  • Los grupos son TableGroupHeader (<th scope="rowgroup">) en un <tbody> por grupo: el lector los asocia a sus filas.

Reglas de uso

  • Para datos que la persona ordena, busca o elige: facturas, clientes, movimientos. Una lista de pocas filas fijas es una Table a mano.
  • value es lo que se ordena y se busca; cell lo que se dibuja (un Badge, un importe formateado). Números y fechas se ordenan como tales; el texto, con el orden del idioma y sin distinguir tildes.
  • El orden y la búsqueda son del cliente, sobre data. Para datos del servidor, controlá sort, query y page y pasá en data la página que llegó.
  • «Seleccionar todas» marca las que se ven (la página actual), no las que el filtro esconde.
  • paging="more" para un historial que se lee de arriba abajo; pages para buscar una fila puntual.
  • Solo por subpath (sebs7n-ui/data-table): no está en el barrel, por peso.

Relacionados