Data Table

1.2.5

DataTable is a highly versatile component used to organize large quantities of tabular data. It supports pagination to help navigate through the data easily and efficiently, and also allows the user to sort and filter the data by columns. Additionally, it offers the ability to select one or multiple rows using checkboxes, which can be useful for performing batch actions or manipulating the data in other ways.

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

Additional props are passed to the <DataTable> element. See div docs for a list of props accepted by the <DataTable> element.

DataTable

NameTypeDefaultDescription

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

NameTypeDefaultDescription

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

NameTypeDefaultDescription

checkbox*

object

Checkbox element rendered on the table header that controls all rows.

children*

React.ReactNode

Row content.

DataTable.Footer

NameTypeDefaultDescription

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

NameTypeDefaultDescription

checkbox*

object

Checkbox element rendered on the row that controls whether the row is selected.

children*

React.ReactNode

Content of the row.

DataTable.Cell

NameTypeDefaultDescription

children*

React.ReactNode

Content of the List component.