From 4303700bb2d5fcc26f6a251c864b3894c6a3d262 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 24 Mar 2021 15:17:56 +0200 Subject: [PATCH] Fix: highlight sidebar's active section (#2366) * - fix: highlight active sidebar section / link - fix: caching crd definitions to prevent sidebar's scroll jumps - refactored & simplified `Sidebar` and `SidebarItem` Signed-off-by: Roman * responding to comments Signed-off-by: Roman * adding @jsdoc comments to SidebarItem Signed-off-by: Roman --- .../+custom-resources/custom-resources.tsx | 4 +- .../components/layout/sidebar-item.tsx | 56 +++++++------- src/renderer/components/layout/sidebar.tsx | 74 ++++++++++++------- 3 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/renderer/components/+custom-resources/custom-resources.tsx b/src/renderer/components/+custom-resources/custom-resources.tsx index 3224b08fd3..1772694137 100644 --- a/src/renderer/components/+custom-resources/custom-resources.tsx +++ b/src/renderer/components/+custom-resources/custom-resources.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route"; +import { crdDefinitionsRoute, crdResourcesRoute, crdURL } from "./crd.route"; import { CrdList } from "./crd-list"; import { CrdResources } from "./crd-resources"; @@ -14,7 +14,7 @@ export class CustomResources extends React.Component { title: "Definitions", component: CustomResources, url: crdURL(), - routePath: crdRoute.path.toString(), + routePath: String(crdDefinitionsRoute.path), } ]; } diff --git a/src/renderer/components/layout/sidebar-item.tsx b/src/renderer/components/layout/sidebar-item.tsx index e2eb942db4..da36b43c17 100644 --- a/src/renderer/components/layout/sidebar-item.tsx +++ b/src/renderer/components/layout/sidebar-item.tsx @@ -6,19 +6,27 @@ import { cssNames, prevDefault } from "../../utils"; import { observer } from "mobx-react"; import { NavLink } from "react-router-dom"; import { Icon } from "../icon"; -import { TabLayoutRoute } from "./tab-layout"; import { sidebarStorage } from "./sidebar-storage"; import { isActiveRoute } from "../../navigation"; interface SidebarItemProps { - id: string; // Used to save nav item collapse/expand state in local storage + /** + * Unique id, used in storage and integration tests + */ + id: string; url: string; - text: React.ReactNode | string; className?: string; + text: React.ReactNode; icon?: React.ReactNode; isHidden?: boolean; + /** + * Forces this item to be also show as active or not. + * + * Default: dynamically checks the location against the `url` props to determine if + * this item should be shown as active + */ isActive?: boolean; - subMenus?: TabLayoutRoute[]; + subMenus?: React.ReactNode | React.ComponentType[]; } @observer @@ -26,22 +34,30 @@ export class SidebarItem extends React.Component { static displayName = "SidebarItem"; get id(): string { - return this.props.id; // unique id, used in storage and integration tests + return this.props.id; } - get compact(): boolean { + @computed get compact(): boolean { return Boolean(sidebarStorage.get().compact); } - get expanded(): boolean { + @computed get expanded(): boolean { return Boolean(sidebarStorage.get().expanded[this.id]); } - @computed get isExpandable(): boolean { - const { subMenus, children } = this.props; - const hasContent = subMenus?.length > 0 || children; + @computed get isActive(): boolean { + return this.props.isActive ?? isActiveRoute({ + path: this.props.url, + exact: true, + }); + } - return Boolean(hasContent && !this.compact) /*not available in compact-mode*/; + @computed get isExpandable(): boolean { + if (this.compact) { + return false; // not available currently + } + + return Boolean(this.props.subMenus || this.props.children); } toggleExpand = () => { @@ -51,11 +67,11 @@ export class SidebarItem extends React.Component { }; render() { - const { isHidden, isActive, subMenus = [], icon, text, children, url, className } = this.props; + const { isHidden, icon, text, children, url, className, subMenus } = this.props; if (isHidden) return null; - const { id, compact, expanded, isExpandable, toggleExpand } = this; + const { isActive, id, compact, expanded, isExpandable, toggleExpand } = this; const classNames = cssNames(SidebarItem.displayName, className, { compact, }); @@ -76,19 +92,7 @@ export class SidebarItem extends React.Component { {isExpandable && expanded && (
    - {subMenus.map(({ title, routePath, url = routePath }) => { - const subItemId = `${id}${routePath}`; - - return ( - - ); - })} + {subMenus} {children}
)} diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 7f0ddd818f..522e06674c 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -1,7 +1,8 @@ import "./sidebar.scss"; +import type { TabLayoutRoute } from "./tab-layout"; import React from "react"; -import type { TabLayoutRoute } from "./tab-layout"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { NavLink } from "react-router-dom"; import { cssNames } from "../../utils"; @@ -22,7 +23,7 @@ import { UserManagement } from "../+user-management"; import { Storage } from "../+storage"; import { Network } from "../+network"; import { crdStore } from "../+custom-resources/crd.store"; -import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources"; +import { crdRoute, crdURL } from "../+custom-resources"; import { CustomResources } from "../+custom-resources/custom-resources"; import { isActiveRoute } from "../../navigation"; import { isAllowedResource } from "../../../common/rbac"; @@ -44,29 +45,50 @@ export class Sidebar extends React.Component { crdStore.reloadAll(); } - renderCustomResources() { - if (crdStore.isLoading) { + @computed get crdSubMenus(): React.ReactNode { + if (!crdStore.isLoaded && crdStore.isLoading) { return ; } return Object.entries(crdStore.groups).map(([group, crds]) => { - const submenus: TabLayoutRoute[] = crds.map((crd) => { - return { - title: crd.getResourceKind(), - component: CrdList, - url: crd.getResourceUrl(), - routePath: String(crdResourcesRoute.path), - }; + const crdGroupSubMenu: React.ReactNode = crds.map((crd) => { + return ( + + ); }); return ( + ); + }); + } + + renderTreeFromTabRoutes(tabRoutes: TabLayoutRoute[] = []): React.ReactNode { + if (!tabRoutes.length) { + return null; + } + + return tabRoutes.map(({ title, routePath, url = routePath, exact = true }) => { + const subMenuItemId = `tab-route-item-${url}`; + + return ( + ); }); @@ -122,10 +144,10 @@ export class Sidebar extends React.Component { key={id} id={id} url={pageUrl} + isActive={isActive} text={menuItem.title} icon={} - isActive={isActive} - subMenus={tabRoutes} + subMenus={this.renderTreeFromTabRoutes(tabRoutes)} /> ); }); @@ -172,7 +194,7 @@ export class Sidebar extends React.Component { isActive={isActiveRoute(workloadsRoute)} isHidden={Workloads.tabRoutes.length == 0} url={workloadsURL({ query })} - subMenus={Workloads.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(Workloads.tabRoutes)} text="Workloads" icon={} /> @@ -181,7 +203,7 @@ export class Sidebar extends React.Component { isActive={isActiveRoute(configRoute)} isHidden={Config.tabRoutes.length == 0} url={configURL({ query })} - subMenus={Config.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(Config.tabRoutes)} text="Configuration" icon={} /> @@ -190,7 +212,7 @@ export class Sidebar extends React.Component { isActive={isActiveRoute(networkRoute)} isHidden={Network.tabRoutes.length == 0} url={networkURL({ query })} - subMenus={Network.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(Network.tabRoutes)} text="Network" icon={} /> @@ -199,7 +221,7 @@ export class Sidebar extends React.Component { isActive={isActiveRoute(storageRoute)} isHidden={Storage.tabRoutes.length == 0} url={storageURL({ query })} - subMenus={Storage.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(Storage.tabRoutes)} icon={} text="Storage" /> @@ -223,7 +245,7 @@ export class Sidebar extends React.Component { id="apps" isActive={isActiveRoute(appsRoute)} url={appsURL({ query })} - subMenus={Apps.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(Apps.tabRoutes)} icon={} text="Apps" /> @@ -231,20 +253,20 @@ export class Sidebar extends React.Component { id="users" isActive={isActiveRoute(usersManagementRoute)} url={usersManagementURL({ query })} - subMenus={UserManagement.tabRoutes} + subMenus={this.renderTreeFromTabRoutes(UserManagement.tabRoutes)} icon={} text="Access Control" /> } - text="Custom Resources" > - {this.renderCustomResources()} + {this.renderTreeFromTabRoutes(CustomResources.tabRoutes)} + {this.crdSubMenus} {this.renderRegisteredMenus()}