Data Table
A **Data Table** é um padrão que organiza informações estruturadas em linhas e colunas, facilitando sua exploração, comparação e manipulação. É utilizada para listar grandes volumes de dados (como produtos, pedidos ou clientes), permitindo realizar ações de forma eficiente e precisa a partir da mesma 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;
Instale o componente via terminal.
npm install @nimbus-ds/data-table
Data Table
- Para mostrar listas extensas de objetos com múltiplos atributos comparáveis (produtos, pedidos, usuários, etc.).
- Quando se requer ordenamento, filtrado, busca ou edição em linha de dados.
- Ideal em fluxos de gestão intensiva onde se espera trabalhar com muitos itens de uma vez.
- Quando os dados são muito poucos (menos de 3 itens) ou não têm atributos comparáveis.
- Se os itens requerem visualizações enriquecidas ou elementos visuais destacados (usar cards sempre que sejam poucos elementos para listar).
- Quando se busca uma experiência mais conversacional ou de assistência (usar wizard ou formulários sequenciais).
- Ordenável e filtrável: O usuário pode encontrar rapidamente o que busca.
- Suporte para ações em lote: Agiliza tarefas repetitivas como eliminar, mudar estado, exportar.
- Edição inline quando aplicável: Minimiza a mudança de interface e melhora a velocidade de edição.
- Persistência de filtros e estado entre sessões.
- Suporte para shortcuts e navegação com teclado.
- Feedback imediato ante ações (toast)
- Navegação completa com teclado: navegação por células, uso de Tab/Shift+Tab.
- Roles semânticos corretos: role="table", role="row", role="cell", aria-sort, etc.
- Alternativas de texto e estados acessíveis para leitores de tela.
- Foco visível e consistente ao interagir com elementos dentro da tabela.
- Áreas táteis adequadas em dispositivos móveis (mínimo 44x44px).
A estrutura de uma Data Table inclui:
- Header: Com nomes de colunas e controles de ordenamento.
- Rows: Cada linha representa um item.
- Cells: Contêm os valores ou ações.
- Bulk actions bar (opcional): Aparece ao selecionar itens.
- Paginação ou scroll infinito.
- Toolbar (opcional): Filtros, busca e botões de ação geral.
- Checkbox
- Icon button
- Tag
- Tooltip
- Search field
- Pagination
- Text
✅ Do
- Usar ordenamento e filtros consistentes com o modelo mental do usuário.
- Manter os headers sempre visíveis ao fazer scroll.
- Priorizar colunas chave à esquerda.
- Oferecer feedback imediato ao editar (usar toast)
- Limitar o número de colunas visíveis em mobile.
❌ Don't
- Não incluir muitas colunas sem permitir scroll ou colapso.
- Não usar ações sem ícones ou textos descritivos.
- Não modificar o layout ao aplicar filtros (preservar contexto).
- Não apresentar dados sem alinhamento coerente (texto, números, datas).
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. |