From b65e034b045af948a6b5878db511219d1a2b930c Mon Sep 17 00:00:00 2001 From: Aleksandr Gumroian Date: Mon, 4 Nov 2024 22:27:14 +0100 Subject: [PATCH 1/5] Add @tanstack/react-table v8.20.5 to dependencies --- package-lock.json | 51 +++++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3cb34d6..92e1fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", + "@tanstack/react-table": "^8.20.5", "axios": "^1.7.2", "immutability-helper": "^3.1.1", "moment": "^2.30.1", @@ -3583,6 +3584,39 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz", @@ -15493,7 +15527,6 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -16247,7 +16280,6 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -21102,6 +21134,19 @@ "@sinonjs/commons": "^3.0.0" } }, + "@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "requires": { + "@tanstack/table-core": "8.20.5" + } + }, + "@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==" + }, "@testing-library/dom": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz", @@ -30083,7 +30128,6 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -30648,7 +30692,6 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index 7fa058e..3599f3e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", + "@tanstack/react-table": "^8.20.5", "axios": "^1.7.2", "immutability-helper": "^3.1.1", "moment": "^2.30.1", From 42294316d9a02b5d5c1b9eb4562881dc55223519 Mon Sep 17 00:00:00 2001 From: Aleksandr Gumroian Date: Mon, 4 Nov 2024 22:27:21 +0100 Subject: [PATCH 2/5] Add RichTable component with header, body, and pagination --- src/common/RichTable/RichTable.js | 70 +++++++++++ src/common/RichTable/RichTableBody.js | 48 ++++++++ src/common/RichTable/RichTableHeader.js | 96 +++++++++++++++ src/common/RichTable/RichTablePagination.js | 128 ++++++++++++++++++++ src/index.js | 1 + 5 files changed, 343 insertions(+) create mode 100644 src/common/RichTable/RichTable.js create mode 100644 src/common/RichTable/RichTableBody.js create mode 100644 src/common/RichTable/RichTableHeader.js create mode 100644 src/common/RichTable/RichTablePagination.js 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 ? ( + + ) : ( + + )} + + ))} + + ))} + + ); +} + +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 { From babdf92ddd185b699d9a33a2c8d6bc441666eb18 Mon Sep 17 00:00:00 2001 From: Aleksandr Gumroian Date: Fri, 8 Nov 2024 16:39:56 +0100 Subject: [PATCH 3/5] Fix import path for CustomizationContextMock in customTestRender.js --- src/testUtils/customTestRender.js | 2 +- .../{cutomizationContextMock.js => customizationContextMock.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/testUtils/{cutomizationContextMock.js => customizationContextMock.js} (100%) diff --git a/src/testUtils/customTestRender.js b/src/testUtils/customTestRender.js index 87166c5..6a94409 100644 --- a/src/testUtils/customTestRender.js +++ b/src/testUtils/customTestRender.js @@ -14,7 +14,7 @@ import { render } from "@testing-library/react"; import PropTypes from "prop-types"; import { AlertContextMock } from "./alertContextMock"; -import { CustomizationContextMock } from "./cutomizationContextMock"; +import { CustomizationContextMock } from "./customizationContextMock"; Wrapper.propTypes = { children: PropTypes.oneOfType([ diff --git a/src/testUtils/cutomizationContextMock.js b/src/testUtils/customizationContextMock.js similarity index 100% rename from src/testUtils/cutomizationContextMock.js rename to src/testUtils/customizationContextMock.js From e57722caa095e828ff26aa884b05109060a3c302 Mon Sep 17 00:00:00 2001 From: Aleksandr Gumroian Date: Fri, 8 Nov 2024 16:40:38 +0100 Subject: [PATCH 4/5] Add RebootButton and RichTable components to documentation --- src/common/RebootButton.js | 12 ++- src/common/RebootButton.md | 24 ++++++ src/common/RichTable/RichTable.js | 18 +++- src/common/RichTable/RichTable.md | 135 ++++++++++++++++++++++++++++++ styleguide.config.js | 36 ++++---- 5 files changed, 207 insertions(+), 18 deletions(-) create mode 100644 src/common/RebootButton.md create mode 100644 src/common/RichTable/RichTable.md diff --git a/src/common/RebootButton.js b/src/common/RebootButton.js index 9b9f85e..380b30d 100644 --- a/src/common/RebootButton.js +++ b/src/common/RebootButton.js @@ -16,6 +16,11 @@ import { Modal, ModalHeader, ModalBody, ModalFooter } from "../bootstrap/Modal"; import { useAlert } from "../context/alertContext/AlertContext"; import { ForisURLs } from "../utils/forisUrls"; +RebootButton.propTypes = { + /** Additional props to be passed to the button */ + props: PropTypes.object, +}; + function RebootButton(props) { const [triggered, setTriggered] = useState(false); const [modalShown, setModalShown] = useState(false); @@ -68,7 +73,12 @@ function RebootModal({ shown, setShown, onReboot }) {

    {_("Are you sure you want to restart the router?")}

    - + diff --git a/src/common/RebootButton.md b/src/common/RebootButton.md new file mode 100644 index 0000000..58a328a --- /dev/null +++ b/src/common/RebootButton.md @@ -0,0 +1,24 @@ +RebootButton component is a button that opens a modal dialog to confirm the +reboot of the device. + +## Usage + +```jsx +import React, { useEffect, createContext } from "react"; +import RebootButton from "./RebootButton"; +import { AlertContextProvider } from "../context/alertContext/AlertContext"; + +window.AlertContext = React.createContext(); + +const RebootButtonExample = () => { + return ( + +