From 869fb58fe123fbcccee0a3ef0bd78af7a9db519a Mon Sep 17 00:00:00 2001 From: Griffin Sullivan <48397354+Griffin-Sullivan@users.noreply.github.com> Date: Thu, 8 Aug 2024 08:31:41 -0400 Subject: [PATCH] Refactor Navigation to use react-router-dom v6 (#250) Signed-off-by: Griffin-Sullivan --- clients/ui/frontend/package-lock.json | 183 +++++----------- clients/ui/frontend/package.json | 2 +- clients/ui/frontend/src/app/App.tsx | 48 ++++ .../frontend/src/app/AppLayout/AppLayout.tsx | 107 --------- clients/ui/frontend/src/app/AppRoutes.tsx | 71 ++++++ clients/ui/frontend/src/app/NavSidebar.tsx | 67 ++++++ .../ui/frontend/src/app/NotFound/NotFound.tsx | 6 +- .../ui/frontend/src/app/Settings/Admin.tsx | 31 +++ .../src/app/__snapshots__/app.test.tsx.snap | 206 ------------------ clients/ui/frontend/src/app/index.tsx | 16 -- clients/ui/frontend/src/app/routes.tsx | 94 -------- .../src/app/utils/useDocumentTitle.ts | 13 -- clients/ui/frontend/src/index.tsx | 7 +- 13 files changed, 285 insertions(+), 566 deletions(-) create mode 100644 clients/ui/frontend/src/app/App.tsx delete mode 100644 clients/ui/frontend/src/app/AppLayout/AppLayout.tsx create mode 100644 clients/ui/frontend/src/app/AppRoutes.tsx create mode 100644 clients/ui/frontend/src/app/NavSidebar.tsx create mode 100644 clients/ui/frontend/src/app/Settings/Admin.tsx delete mode 100644 clients/ui/frontend/src/app/__snapshots__/app.test.tsx.snap delete mode 100644 clients/ui/frontend/src/app/index.tsx delete mode 100644 clients/ui/frontend/src/app/routes.tsx delete mode 100644 clients/ui/frontend/src/app/utils/useDocumentTitle.ts diff --git a/clients/ui/frontend/package-lock.json b/clients/ui/frontend/package-lock.json index 716abcc32..16ab1210c 100644 --- a/clients/ui/frontend/package-lock.json +++ b/clients/ui/frontend/package-lock.json @@ -51,7 +51,7 @@ "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^5.3.4", + "react-router-dom": "^6.4.1", "regenerator-runtime": "^0.14.1", "rimraf": "^5.0.7", "serve": "^14.2.1", @@ -3353,6 +3353,15 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, + "node_modules/@remix-run/router": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3521,12 +3530,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@testing-library/cypress/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, "node_modules/@testing-library/cypress/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3686,12 +3689,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, "node_modules/@testing-library/dom/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5225,9 +5222,9 @@ } }, "node_modules/aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", + "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", "dev": true }, "node_modules/axe-core": { @@ -5432,14 +5429,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5988,9 +5985,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001646", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz", - "integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==", + "version": "1.0.30001649", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz", + "integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==", "dev": true, "funding": [ { @@ -6686,13 +6683,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", + "integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==", "dev": true, "peer": true, "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -8202,9 +8199,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", - "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", "dev": true }, "node_modules/emittery": { @@ -10301,29 +10298,6 @@ "he": "bin/he" } }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -11467,12 +11441,6 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -15942,13 +15910,10 @@ } }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -16694,6 +16659,11 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -16965,46 +16935,41 @@ "dev": true }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", "dev": true, "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "@remix-run/router": "1.19.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", "dev": true, "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/read-pkg": { @@ -17361,12 +17326,6 @@ "node": ">=8" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "dev": true - }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -17812,12 +17771,6 @@ "node": ">= 0.6" } }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", - "dev": true - }, "node_modules/serve-handler/node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -18908,18 +18861,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -19760,12 +19701,6 @@ "node": ">= 0.10" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "dev": true - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/clients/ui/frontend/package.json b/clients/ui/frontend/package.json index b2049ed3f..ee043a6ea 100644 --- a/clients/ui/frontend/package.json +++ b/clients/ui/frontend/package.json @@ -63,7 +63,7 @@ "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^5.3.4", + "react-router-dom": "^6.4.1", "regenerator-runtime": "^0.14.1", "rimraf": "^5.0.7", "serve": "^14.2.1", diff --git a/clients/ui/frontend/src/app/App.tsx b/clients/ui/frontend/src/app/App.tsx new file mode 100644 index 000000000..d9be35e50 --- /dev/null +++ b/clients/ui/frontend/src/app/App.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import '@patternfly/react-core/dist/styles/base.css'; +import AppRoutes from '@app/AppRoutes'; +import '@app/app.css'; +import { + Flex, + Masthead, + MastheadContent, + MastheadToggle, + Page, + PageToggleButton, + Title +} from '@patternfly/react-core'; +import NavSidebar from './NavSidebar'; +import { BarsIcon } from '@patternfly/react-icons'; + +const App: React.FC = () => { + const masthead = ( + + + + + + + + + + + Kubeflow Model Registry UI + + + + + ); + + return ( + } + > + + + ); +}; + +export default App; diff --git a/clients/ui/frontend/src/app/AppLayout/AppLayout.tsx b/clients/ui/frontend/src/app/AppLayout/AppLayout.tsx deleted file mode 100644 index 66e3b9967..000000000 --- a/clients/ui/frontend/src/app/AppLayout/AppLayout.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import * as React from 'react'; -import { NavLink, useLocation } from 'react-router-dom'; -import { - Button, - Flex, - Masthead, - MastheadContent, - MastheadToggle, - Nav, - NavExpandable, - NavItem, - NavList, - Page, - PageSidebar, - PageSidebarBody, - SkipToContent, - Title -} from '@patternfly/react-core'; -import { IAppRoute, IAppRouteGroup, routes } from '@app/routes'; -import { BarsIcon } from '@patternfly/react-icons'; - -interface IAppLayout { - children: React.ReactNode; -} - -const AppLayout: React.FunctionComponent = ({ children }) => { - const [sidebarOpen, setSidebarOpen] = React.useState(true); - const masthead = ( - - - - - - - - - Kubeflow Model Registry UI - - - - - ); - - const location = useLocation(); - - const renderNavItem = (route: IAppRoute, index: number) => ( - - - {route.label} - - - ); - - const renderNavGroup = (group: IAppRouteGroup, groupIndex: number) => ( - route.path === location.pathname)} - > - {group.routes.map((route, idx) => route.label && renderNavItem(route, idx))} - - ); - - const Navigation = ( - - ); - - const Sidebar = ( - - - {Navigation} - - - ); - - const pageId = 'primary-app-container'; - - const PageSkipToContent = ( - { - event.preventDefault(); - const primaryContentContainer = document.getElementById(pageId); - primaryContentContainer && primaryContentContainer.focus(); - }} href={`#${pageId}`}> - Skip to Content - - ); - return ( - - {children} - - ); -}; - -export { AppLayout }; diff --git a/clients/ui/frontend/src/app/AppRoutes.tsx b/clients/ui/frontend/src/app/AppRoutes.tsx new file mode 100644 index 000000000..da6fc5c2c --- /dev/null +++ b/clients/ui/frontend/src/app/AppRoutes.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { Dashboard } from '@app/Dashboard/Dashboard'; +import { Support } from '@app/Support/Support'; +import { NotFound } from '@app/NotFound/NotFound'; +import { Admin } from '@app/Settings/Admin'; + +export const isNavDataGroup = (navItem: NavDataItem): navItem is NavDataGroup => 'children' in navItem; + +type NavDataCommon = { + label: string; +}; + +export type NavDataHref = NavDataCommon & { + path: string; +}; + +export type NavDataGroup = NavDataCommon & { + children: NavDataHref[]; +}; + +type NavDataItem = NavDataHref | NavDataGroup; + +export const useAdminSettings = (): NavDataItem[] => { + // get auth access for example set admin as true + const isAdmin = true; //this should be a call to getting auth / role access + + if (!isAdmin) { + return []; + } + + return [{ + label: 'Settings', + children: [ + { label: 'Setting 1', path: '/admin' }, + { label: 'Setting 2', path: '/admin' }, + { label: 'Setting 3', path: '/admin'} + ] + }] +} + +export const useNavData = (): NavDataItem[] => { + return ([ + { + label: 'Dashboard', + path: '/' + }, + { + label: 'Support', + path: '/support' + }, + ...useAdminSettings() + ]) +} + +const AppRoutes: React.FC = () => { + const isAdmin = true; + + return ( + + } /> + } /> + } /> + {isAdmin && + } /> + } + + ); +}; + +export default AppRoutes; diff --git a/clients/ui/frontend/src/app/NavSidebar.tsx b/clients/ui/frontend/src/app/NavSidebar.tsx new file mode 100644 index 000000000..297be6109 --- /dev/null +++ b/clients/ui/frontend/src/app/NavSidebar.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { NavLink } from 'react-router-dom'; +import { + Nav, + NavExpandable, + NavItem, + NavList, + PageSidebar, + PageSidebarBody, +} from '@patternfly/react-core'; +import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from '@app/AppRoutes'; + +const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => ( + + {item.label} + +); + +const NavGroup: React.FC<{ item: NavDataGroup }> = ({ item }) => { + const { children } = item; + const [expanded, setExpanded] = React.useState(false); + + return ( + setExpanded(val)} + aria-label={item.label} + > + {children.map((childItem) => ( + + ))} + + ); +}; + +const NavSidebar: React.FC = () => { + const navData = useNavData() + + return ( + + + + + + ); +}; + +export default NavSidebar; diff --git a/clients/ui/frontend/src/app/NotFound/NotFound.tsx b/clients/ui/frontend/src/app/NotFound/NotFound.tsx index 5fdc35ee6..bc78c6a75 100644 --- a/clients/ui/frontend/src/app/NotFound/NotFound.tsx +++ b/clients/ui/frontend/src/app/NotFound/NotFound.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { Button, EmptyState, EmptyStateBody, EmptyStateFooter, PageSection } from '@patternfly/react-core'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; const NotFound: React.FunctionComponent = () => { function GoHomeBtn() { - const history = useHistory(); + const navigate = useNavigate(); function handleClick() { - history.push('/'); + navigate('/'); } return ( diff --git a/clients/ui/frontend/src/app/Settings/Admin.tsx b/clients/ui/frontend/src/app/Settings/Admin.tsx new file mode 100644 index 000000000..47fa98442 --- /dev/null +++ b/clients/ui/frontend/src/app/Settings/Admin.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { CubesIcon } from '@patternfly/react-icons'; +import { + Button, + EmptyState, + EmptyStateBody, + EmptyStateFooter, + EmptyStateVariant, + PageSection, + Text, + TextContent +} from '@patternfly/react-core'; + +const Admin: React.FunctionComponent = () => ( + + + + + + This represents an the empty state pattern in Patternfly 6. Hopefully it's simple enough to use but + flexible enough to meet a variety of needs. + + + + + + + +); + +export { Admin }; \ No newline at end of file diff --git a/clients/ui/frontend/src/app/__snapshots__/app.test.tsx.snap b/clients/ui/frontend/src/app/__snapshots__/app.test.tsx.snap deleted file mode 100644 index 266dc2f49..000000000 --- a/clients/ui/frontend/src/app/__snapshots__/app.test.tsx.snap +++ /dev/null @@ -1,206 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`App tests should render default App component 1`] = ` - -
- -
- - - -
-
-

- Kubeflow Model Registry UI -

-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-

- Empty State (Dashboard Module) -

-
-
-
-
-

- This represents an the empty state pattern in Patternfly 6. Hopefully it's simple enough to use but flexible enough to meet a variety of needs. -

-
-
- -
-
-
-
-
-
-
-`; diff --git a/clients/ui/frontend/src/app/index.tsx b/clients/ui/frontend/src/app/index.tsx deleted file mode 100644 index 82b7700b7..000000000 --- a/clients/ui/frontend/src/app/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import '@patternfly/react-core/dist/styles/base.css'; -import { BrowserRouter as Router } from 'react-router-dom'; -import { AppLayout } from '@app/AppLayout/AppLayout'; -import { AppRoutes } from '@app/routes'; -import '@app/app.css'; - -const App: React.FunctionComponent = () => ( - - - - - -); - -export default App; diff --git a/clients/ui/frontend/src/app/routes.tsx b/clients/ui/frontend/src/app/routes.tsx deleted file mode 100644 index d254afac7..000000000 --- a/clients/ui/frontend/src/app/routes.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import * as React from 'react'; -import { Route, RouteComponentProps, Switch, useLocation } from 'react-router-dom'; -import { Dashboard } from '@app/Dashboard/Dashboard'; -import { Support } from '@app/Support/Support'; -import { NotFound } from '@app/NotFound/NotFound'; -import { useDocumentTitle } from '@app/utils/useDocumentTitle'; - -let routeFocusTimer: number; - -export interface IAppRoute { - label?: string; // Excluding the label will exclude the route from the nav sidebar in AppLayout - /* eslint-disable @typescript-eslint/no-explicit-any */ - component: React.ComponentType> | React.ComponentType; - /* eslint-enable @typescript-eslint/no-explicit-any */ - exact?: boolean; - path: string; - title: string; - routes?: undefined; -} - -export interface IAppRouteGroup { - label: string; - routes: IAppRoute[]; -} - -export type AppRouteConfig = IAppRoute | IAppRouteGroup; - -const routes: AppRouteConfig[] = [ - { - component: Dashboard, - exact: true, - label: 'Dashboard', - path: '/', - title: 'Kubeflow Model Registry | Main Dashboard', - }, - { - component: Support, - exact: true, - label: 'Support', - path: '/support', - title: 'Kubeflow Model Registry | Support Page', - }, -]; - -// a custom hook for sending focus to the primary content container -// after a view has loaded so that subsequent press of tab key -// sends focus directly to relevant content -// may not be necessary if https://github.com/ReactTraining/react-router/issues/5210 is resolved -const useA11yRouteChange = () => { - const { pathname } = useLocation(); - React.useEffect(() => { - routeFocusTimer = window.setTimeout(() => { - const mainContainer = document.getElementById('primary-app-container'); - if (mainContainer) { - mainContainer.focus(); - } - }, 50); - return () => { - window.clearTimeout(routeFocusTimer); - }; - }, [pathname]); -}; - -const RouteWithTitleUpdates = ({ component: Component, title, ...rest }: IAppRoute) => { - useA11yRouteChange(); - useDocumentTitle(title); - - function routeWithTitle(routeProps: RouteComponentProps) { - return ; - } - - return ; -}; - -const PageNotFound = ({ title }: { title: string }) => { - useDocumentTitle(title); - return ; -}; - -const flattenedRoutes: IAppRoute[] = routes.reduce( - (flattened, route) => [...flattened, ...(route.routes ? route.routes : [route])], - [] as IAppRoute[], -); - -const AppRoutes = (): React.ReactElement => ( - - {flattenedRoutes.map(({ path, exact, component, title }, idx) => ( - - ))} - - -); - -export { AppRoutes, routes }; diff --git a/clients/ui/frontend/src/app/utils/useDocumentTitle.ts b/clients/ui/frontend/src/app/utils/useDocumentTitle.ts deleted file mode 100644 index 0442ab4af..000000000 --- a/clients/ui/frontend/src/app/utils/useDocumentTitle.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from 'react'; - -// a custom hook for setting the page title -export function useDocumentTitle(title: string) { - React.useEffect(() => { - const originalTitle = document.title; - document.title = title; - - return () => { - document.title = originalTitle; - }; - }, [title]); -} diff --git a/clients/ui/frontend/src/index.tsx b/clients/ui/frontend/src/index.tsx index aca5ab6e3..aa2febc9f 100644 --- a/clients/ui/frontend/src/index.tsx +++ b/clients/ui/frontend/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from '@app/index'; +import { BrowserRouter as Router } from 'react-router-dom'; +import App from '@app/App'; if (process.env.NODE_ENV !== 'production') { const config = { @@ -18,6 +19,8 @@ const root = ReactDOM.createRoot(document.getElementById('root') as Element); root.render( - + + + );