diff --git a/src/common/RichTable/RichTable.js b/src/common/RichTable/RichTable.js
new file mode 100644
index 0000000..a45451a
--- /dev/null
+++ b/src/common/RichTable/RichTable.js
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React, { useMemo, useState } from "react";
+
+import {
+ flexRender,
+ getCoreRowModel,
+ getSortedRowModel,
+ getPaginationRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+
+import RichTableBody from "./RichTableBody";
+import RichTableHeader from "./RichTableHeader";
+import RichTablePagination from "./RichTablePagination";
+
+const fallbackData = [];
+
+const RichTable = ({
+ columns,
+ data,
+ withPagination,
+ pageSize = 5,
+ pageIndex = 0,
+}) {
+ const tableColumns = useMemo(() => columns, [columns]);
+ const [tableData] = useState(data ?? fallbackData);
+ const [sorting, setSorting] = useState([]);
+ const [pagination, setPagination] = useState({
+ pageIndex,
+ pageSize,
+ });
+
+ const table = useReactTable({
+ data: tableData,
+ columns: tableColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ onPaginationChange: setPagination,
+ onSortingChange: setSorting,
+ state: {
+ sorting,
+ pagination,
+ },
+ });
+
+ return (
+
+
+ {withPagination && (
+
+ )}
+
+ );
+};
+
+export default RichTable;
diff --git a/src/common/RichTable/RichTableBody.js b/src/common/RichTable/RichTableBody.js
new file mode 100644
index 0000000..2aca395
--- /dev/null
+++ b/src/common/RichTable/RichTableBody.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React from "react";
+
+import propTypes from "prop-types";
+
+RichTableBody.propTypes = {
+ table: propTypes.shape({
+ getRowModel: propTypes.func.isRequired,
+ }).isRequired,
+ flexRender: propTypes.func.isRequired,
+};
+
+function RichTableBody({ table, flexRender }) {
+ return (
+
+ {table.getRowModel().rows.map((row) => {
+ return (
+
+ {row.getVisibleCells().map((cell) => {
+ return (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ |
+ );
+ })}
+
+ );
+ })}
+
+ );
+}
+
+export default RichTableBody;
diff --git a/src/common/RichTable/RichTableHeader.js b/src/common/RichTable/RichTableHeader.js
new file mode 100644
index 0000000..79e6ca9
--- /dev/null
+++ b/src/common/RichTable/RichTableHeader.js
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React from "react";
+
+import {
+ faSquareCaretUp,
+ faSquareCaretDown,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import propTypes from "prop-types";
+
+RichTableHeader.propTypes = {
+ table: propTypes.shape({
+ getHeaderGroups: propTypes.func.isRequired,
+ }).isRequired,
+ flexRender: propTypes.func.isRequired,
+};
+
+function RichTableHeader({ table, flexRender }) {
+ const getThTitle = (header) => {
+ if (!header.column.getCanSort()) return undefined;
+
+ const nextSortingOrder = header.column.getNextSortingOrder();
+ if (nextSortingOrder === "asc") return _("Sort ascending");
+ if (nextSortingOrder === "desc") return _("Sort descending");
+ return _("Clear sort");
+ };
+
+ return (
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ||
+ header.column.columnDef.headerIsHidden ? (
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ ) : (
+
+ )}
+ |
+ ))}
+
+ ))}
+
+ );
+}
+
+export default RichTableHeader;
diff --git a/src/common/RichTable/RichTablePagination.js b/src/common/RichTable/RichTablePagination.js
new file mode 100644
index 0000000..c6821f6
--- /dev/null
+++ b/src/common/RichTable/RichTablePagination.js
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React, { useMemo } from "react";
+
+import {
+ faAngleLeft,
+ faAnglesLeft,
+ faAngleRight,
+ faAnglesRight,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import propTypes from "prop-types";
+
+RichTablePagination.propTypes = {
+ table: propTypes.shape({
+ getState: propTypes.func.isRequired,
+ getCanPreviousPage: propTypes.func.isRequired,
+ getCanNextPage: propTypes.func.isRequired,
+ firstPage: propTypes.func.isRequired,
+ previousPage: propTypes.func.isRequired,
+ nextPage: propTypes.func.isRequired,
+ lastPage: propTypes.func.isRequired,
+ setPageSize: propTypes.func.isRequired,
+ getPageCount: propTypes.func.isRequired,
+ }).isRequired,
+ tablePageSize: propTypes.number,
+ allRows: propTypes.number,
+};
+
+function RichTablePagination({ table, tablePageSize, allRows }) {
+ const { pagination } = table.getState();
+ const prevPagBtnDisabled = !table.getCanPreviousPage();
+ const nextPagBtnDisabled = !table.getCanNextPage();
+
+ const pageSizes = useMemo(() => {
+ return [tablePageSize ?? 5, 10, 25].filter(
+ (value, index, self) => self.indexOf(value) === index
+ );
+ }, [tablePageSize]);
+
+ const renderPaginationButton = (icon, ariaLabel, onClick, disabled) => (
+
+
+
+ );
+
+ return (
+
+ );
+}
+
+export default RichTablePagination;
diff --git a/src/index.js b/src/index.js
index 4e7839f..8052fb1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -43,6 +43,7 @@ export { Modal, ModalBody, ModalFooter, ModalHeader } from "./bootstrap/Modal";
export { default as RebootButton } from "./common/RebootButton";
export { default as WiFiSettings } from "./common/WiFiSettings/WiFiSettings";
export { default as ResetWiFiSettings } from "./common/WiFiSettings/ResetWiFiSettings";
+export { default as RichTable } from "./common/RichTable/RichTable";
// Form
export { default as ForisForm } from "./form/components/ForisForm";
export {