Data Table
La Data Table es un patrón que organiza información estructurada en filas y columnas, facilitando su exploración, comparación y manipulación. Se utiliza para listar grandes volúmenes de datos (como productos, pedidos o clientes), permitiendo realizar acciones de forma eficiente y precisa desde la misma vista.
import React, { useEffect, useState } from "react";
import { DataTable } from "@nimbus-ds/patterns";
import { Tag, Box, IconButton, Chip } from "@nimbus-ds/components";
import {
ChevronDownIcon,
CheckCircleIcon,
ChevronUpIcon,
ExclamationTriangleIcon,
} from "@nimbus-ds/icons";
const pageSize = 5;
const orders = [
{
id: 10,
clientName: "Dr. Johnnie Bins",
total: "R$16.788,20",
qty: "9",
status: false,
},
{
id: 9,
clientName: "Earnest Berge",
total: "R$62.657,83",
qty: "3",
status: false,
},
{
id: 8,
clientName: "Irene Purdy",
total: "R$17.692,10",
qty: "4",
status: false,
},
{
id: 7,
clientName: "Owen Swift DVM",
total: "R$60.269,67",
qty: "5",
status: false,
},
{
id: 6,
clientName: "Felipe Ferry",
total: "R$75.058,94",
qty: "3",
status: false,
},
{
id: 5,
clientName: "Derek Kub",
total: "R$29.068,91",
qty: "1",
status: false,
},
{
id: 4,
clientName: "Elisa Vandervort",
total: "R$22.636,41",
qty: "5",
status: false,
},
{
id: 3,
clientName: "Rochelle Spencer",
total: "R$76.244,05",
qty: "9",
status: false,
},
{
id: 2,
clientName: "Angelina Koelpin",
total: "R$65.306,79",
qty: "4",
status: false,
},
{
id: 1,
clientName: "Edna Jacobi",
total: "R$97.025,32",
qty: "6",
status: false,
},
];
const Example: React.FC = () => {
interface RowProps {
id: number;
clientName: string;
total: string;
qty: string;
status: boolean;
}
const [rows, setRows] = useState<RowProps[]>(orders);
const [checkedRows, setCheckedRows] = useState<number[]>([]);
const [headerCheckboxStatus, setHeaderCheckboxStatus] = useState(false);
const [headerIndeterminateStatus, setHeaderIndeterminateStatus] =
useState(false);
const [currentPage, setCurrentPage] = useState<number>(1);
const [sortDirection, setSortDirection] = useState<
"ascending" | "descending"
>("descending");
const [sortColumn, setSortColumn] = useState<"id" | "clientName">("id");
useEffect(() => {
if (checkedRows.length === rows.length) {
setHeaderCheckboxStatus(true);
setHeaderIndeterminateStatus(false);
} else if (checkedRows.length > 0) {
setHeaderCheckboxStatus(false);
setHeaderIndeterminateStatus(true);
} else {
setHeaderCheckboxStatus(false);
setHeaderIndeterminateStatus(false);
}
}, [checkedRows.length, rows.length]);
const handleRowClick = (id: number) => {
if (checkedRows.includes(id)) {
setCheckedRows(checkedRows.filter((rowId) => rowId !== id));
} else {
setCheckedRows([...checkedRows, id]);
}
};
const handleHeaderCheckboxClick = () => {
if (headerCheckboxStatus) {
setCheckedRows([]);
} else {
const rowIds = rows.map((row) => row.id);
setCheckedRows(rowIds);
}
};
const handleBulkUpdateStatusClick = (status: boolean) => {
const updatedRows = rows.map((row) => {
const checked = checkedRows.includes(row.id);
return { ...row, status: checked ? status : row.status };
});
setRows(updatedRows);
};
const handlePageChange = (page: number): void => {
setCurrentPage(page);
};
const handleSort = (column: "id" | "clientName") => {
if (column === sortColumn) {
setSortDirection(
sortDirection === "ascending" ? "descending" : "ascending"
);
} else {
setSortColumn(column);
setSortDirection("ascending");
}
};
const sortCompareFunction = (rowA: RowProps, rowB: RowProps) => {
if (sortColumn === "id") {
return sortDirection === "ascending"
? rowA.id - rowB.id
: rowB.id - rowA.id;
}
if (sortColumn === "clientName") {
return sortDirection === "ascending"
? rowA.clientName.localeCompare(rowB.clientName)
: rowB.clientName.localeCompare(rowA.clientName);
}
return 0;
};
const getDisplayedRows = (): RowProps[] => {
const sortedRows = rows.slice().sort(sortCompareFunction);
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return sortedRows.slice(startIndex, endIndex);
};
const displayedRows = getDisplayedRows();
const totalRows = rows.length;
const firstRow = (currentPage - 1) * pageSize + 1;
const lastRow = Math.min(currentPage * pageSize, totalRows);
const tableHeader = (
<DataTable.Header
checkbox={{
name: "check-all-rows",
checked: headerCheckboxStatus,
onChange: handleHeaderCheckboxClick,
indeterminate: headerIndeterminateStatus,
}}
>
<DataTable.Cell width="120px">
<Box display="flex" gap="2" alignItems="center">
Order no.
<IconButton
source={
sortDirection === "ascending" ? (
<ChevronUpIcon size={10} />
) : (
<ChevronDownIcon size={10} />
)
}
size="1rem"
onClick={() => handleSort("id")}
/>
</Box>
</DataTable.Cell>
<DataTable.Cell width="auto">Client name</DataTable.Cell>
<DataTable.Cell width="120px">Total</DataTable.Cell>
<DataTable.Cell width="120px">Qty. of products</DataTable.Cell>
<DataTable.Cell width="120px">Order status</DataTable.Cell>
</DataTable.Header>
);
const tableFooter = (
<DataTable.Footer
itemCount={`Showing ${firstRow}-${lastRow} orders of ${totalRows}`}
pagination={{
pageCount: Math.ceil(totalRows / pageSize),
activePage: currentPage,
onPageChange: handlePageChange,
}}
/>
);
const hasBulkActions = checkedRows.length > 0 && (
<DataTable.BulkActions
checkbox={{
name: "check-all",
checked: headerCheckboxStatus,
onChange: handleHeaderCheckboxClick,
indeterminate: headerIndeterminateStatus,
}}
label={`${checkedRows.length} selected`}
action={
<Box display="flex" gap="1">
<Chip
onClick={() => handleBulkUpdateStatusClick(true)}
text="Fulfill orders"
/>
<Chip
onClick={() => handleBulkUpdateStatusClick(false)}
text="Unfulfill orders"
/>
</Box>
}
/>
);
return (
<DataTable
header={tableHeader}
footer={tableFooter}
bulkActions={hasBulkActions}
>
{displayedRows.map((row) => {
const { id, status } = row;
const statusIcon = status ? (
<CheckCircleIcon />
) : (
<ExclamationTriangleIcon />
);
const statusAppearance = status ? "success" : "warning";
const statusMsg = status ? "Fulfilled" : "Pending";
return (
<DataTable.Row
key={id}
backgroundColor={
checkedRows.includes(id)
? {
rest: "primary-surface",
hover: "primary-surfaceHighlight",
}
: {
rest: "neutral-background",
hover: "neutral-surface",
}
}
checkbox={{
name: `check-${id}`,
checked: checkedRows.includes(id),
onChange: () => handleRowClick(id),
}}
>
<DataTable.Cell>#{row.id}</DataTable.Cell>
<DataTable.Cell>{row.clientName}</DataTable.Cell>
<DataTable.Cell>{row.total}</DataTable.Cell>
<DataTable.Cell>{row.qty}</DataTable.Cell>
<DataTable.Cell>
<Tag appearance={statusAppearance}>
{statusIcon}
{statusMsg}
</Tag>
</DataTable.Cell>
</DataTable.Row>
);
})}
</DataTable>
);
};
export default Example;
Instalá el componente via terminal.
npm install @nimbus-ds/data-table
Data Table
- Para mostrar listas extensas de objetos con múltiples atributos comparables (productos, pedidos, usuarios, etc.).
- Cuando se requiere ordenamiento, filtrado, búsqueda o edición en línea de datos.
- Ideal en flujos de gestión intensiva donde se espera trabajar con muchos ítems a la vez.
- Cuando los datos son muy pocos (menos de 3 ítems) o no tienen atributos comparables.
- Si los ítems requieren visualizaciones enriquecidas o elementos visuales destacados (usar cards siempre que sean pocos elementos a enlistar).
- Cuando se busca una experiencia más conversacional o de asistencia (usar wizard o formularios secuenciales).
- Ordenable y filtrable: El usuario puede encontrar rápidamente lo que busca.
- Soporte para acciones en lote: Agiliza tareas repetitivas como eliminar, cambiar estado, exportar.
- Edición inline cuando aplica: Minimiza el cambio de interfaz y mejora la velocidad de edición.
- Persistencia de filtros y estado entre sesiones.
- Soporte para shortcuts y navegación con teclado.
- Feedback inmediato ante acciones (toast)
- Navegación completa con teclado: navegación por celdas, uso de Tab/Shift+Tab.
- Roles semánticos correctos: role="table", role="row", role="cell", aria-sort, etc.
- Alternativas de texto y estados accesibles para lectores de pantalla.
- Foco visible y consistente al interactuar con elementos dentro de la tabla.
- Áreas táctiles adecuadas en dispositivos mobiles (mínimo 44x44px).
La estructura de una Data Table incluye:
- Header: Con nombres de columnas y controles de ordenamiento.
- Rows: Cada fila representa un ítem.
- Cells: Contienen los valores o acciones.
- Bulk actions bar (opcional): Aparece al seleccionar ítems.
- Paginación o scroll infinito.
- Toolbar (opcional): Filtros, búsqueda y botones de acción general.
- Checkbox
- Icon button
- Tag
- Tooltip
- Search field
- Pagination
- Text
✅ Do
- Usar ordenamiento y filtros consistentes con el modelo mental del usuario.
- Mantener los headers siempre visibles al hacer scroll.
- Priorizar columnas clave a la izquierda.
- Ofrecer feedback inmediato al editar (usar toast)
- Limitar el número de columnas visibles en mobile.
❌ Don't
- No incluir demasiadas columnas sin permitir scroll o colapsado.
- No usar acciones sin íconos o textos descriptivos.
- No modificar el layout al aplicar filtros (preservar contexto).
- No presentar datos sin alineación coherente (texto, números, fechas).
Additional props are passed to the <DataTable> element. See div docs for a list of props accepted by the <DataTable> element.
DataTable
Name | Type | Default | Description |
---|---|---|---|
bulkActions | React.ReactNode | Bulk actions component rendered with a sticky position over the top of the table element. | |
header* | React.ReactNode | Table header content. | |
footer | React.ReactNode | Optional table footer content. | |
children* | React.ReactNode | Table body content. |
DataTable.BulkActions
Name | Type | Default | Description |
---|---|---|---|
checkbox* | object | Properties of the checkbox element rendered in the Bulk Actions component. | |
link | <Link /> | Optional link element rendered next to the Bulk Actions controller. | |
action* | React.ReactNode | Action component that controls the Bulk Actions. | |
label* | string | Lable for the checkbox element. |
DataTable.Header
Name | Type | Default | Description |
---|---|---|---|
checkbox* | object | Checkbox element rendered on the table header that controls all rows. | |
children* | React.ReactNode | Row content. |
DataTable.Footer
Name | Type | Default | Description |
---|---|---|---|
itemCount* | string | Left-hand side text intended for displaying an item count. | |
pagination | object | Pagination element rendered on the right-side of the footer. |
DataTable.Row
Name | Type | Default | Description |
---|---|---|---|
checkbox* | object | Checkbox element rendered on the row that controls whether the row is selected. | |
children* | React.ReactNode | Content of the row. |
DataTable.Cell
Name | Type | Default | Description |
---|---|---|---|
children* | React.ReactNode | Content of the List component. |