From b111abc96f942ee8eb008a15c63e092f602b2553 Mon Sep 17 00:00:00 2001
From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
Date: Wed, 28 May 2025 12:00:28 +0200
Subject: [PATCH] feat: update sidebar navigation - refactors (#10037)
- added `sideMenuCleanup` flag
- extracted `SecondaryNavigation`, `SecondaryNavigationList` and
`MobileNavigationSidebar` into separate files
- hidden recent projects and flags
- renamed 'Insights' to 'Analytics'
---
.../NavigationSidebar/IconRenderer.tsx | 2 +
.../MobileNavigationSidebar.tsx | 30 +++++++
.../NavigationSidebar/NavigationList.tsx | 83 +------------------
.../NavigationSidebar/NavigationSidebar.tsx | 35 ++------
.../NavigationSidebar/SecondaryNavigation.tsx | 49 +++++++++++
.../SecondaryNavigationList.tsx | 46 ++++++++++
.../menu/Header/DrawerMenu/DrawerMenu.tsx | 2 +-
.../__snapshots__/routes.test.tsx.snap | 12 +++
frontend/src/component/menu/routes.ts | 10 +++
frontend/src/interfaces/uiConfig.ts | 1 +
src/lib/types/experimental.ts | 7 +-
src/server-dev.ts | 1 +
12 files changed, 169 insertions(+), 109 deletions(-)
create mode 100644 frontend/src/component/layout/MainLayout/NavigationSidebar/MobileNavigationSidebar.tsx
create mode 100644 frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigation.tsx
create mode 100644 frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigationList.tsx
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/IconRenderer.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/IconRenderer.tsx
index 27a59e9371..bec601a2af 100644
--- a/frontend/src/component/layout/MainLayout/NavigationSidebar/IconRenderer.tsx
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/IconRenderer.tsx
@@ -35,6 +35,7 @@ import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import PlaygroundIcon from '@mui/icons-material/AutoFixNormal';
import FlagOutlinedIcon from '@mui/icons-material/FlagOutlined';
import RocketLaunchIcon from '@mui/icons-material/RocketLaunchOutlined';
+import BuildIcon from '@mui/icons-material/Build';
// TODO: move to routes
const icons: Record<
@@ -86,6 +87,7 @@ const icons: Record<
'/custom-metrics': RocketLaunchIcon,
GitHub: GitHubIcon,
Documentation: LibraryBooksIcon,
+ Configure: BuildIcon,
};
export const IconRenderer: FC<{ path: string }> = ({ path }) => {
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/MobileNavigationSidebar.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/MobileNavigationSidebar.tsx
new file mode 100644
index 0000000000..7e1fcbc159
--- /dev/null
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/MobileNavigationSidebar.tsx
@@ -0,0 +1,30 @@
+import type { FC } from 'react';
+import {
+ PrimaryNavigationList,
+ AdminSettingsLink,
+ OtherLinksList,
+} from './NavigationList.tsx';
+import type { NewInUnleash } from './NewInUnleash/NewInUnleash.tsx';
+import { SecondaryNavigationList } from './SecondaryNavigationList.tsx';
+import { useRoutes } from './useRoutes.ts';
+
+export const MobileNavigationSidebar: FC<{
+ onClick: () => void;
+ NewInUnleash?: typeof NewInUnleash;
+}> = ({ onClick, NewInUnleash }) => {
+ const { routes } = useRoutes();
+
+ return (
+ <>
+ {NewInUnleash ? : null}
+
+
+
+
+ >
+ );
+};
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx
index 19cf17d78b..6dacf4358a 100644
--- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx
@@ -1,4 +1,3 @@
-import type React from 'react';
import type { FC } from 'react';
import type { INavigationMenuItem } from 'interfaces/route';
import type { NavigationMode } from './NavigationMode.tsx';
@@ -11,48 +10,12 @@ import {
import { Box, List, Typography } from '@mui/material';
import { IconRenderer } from './IconRenderer.tsx';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
-import Accordion from '@mui/material/Accordion';
-import AccordionDetails from '@mui/material/AccordionDetails';
-import AccordionSummary from '@mui/material/AccordionSummary';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import FlagIcon from '@mui/icons-material/OutlinedFlag';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
-import { useShowBadge } from 'component/layout/components/EnterprisePlanBadge/useShowBadge';
-import { EnterprisePlanBadge } from 'component/layout/components/EnterprisePlanBadge/EnterprisePlanBadge';
import { useNewAdminMenu } from 'hooks/useNewAdminMenu';
import { AdminMenuNavigation } from '../AdminMenu/AdminNavigationItems.tsx';
-
-export const SecondaryNavigationList: FC<{
- routes: INavigationMenuItem[];
- mode: NavigationMode;
- onClick: (activeItem: string) => void;
- activeItem?: string;
-}> = ({ routes, mode, onClick, activeItem }) => {
- const showBadge = useShowBadge();
- const DynamicListItem = mode === 'mini' ? MiniListItem : FullListItem;
-
- return (
-
- {routes.map((route) => (
- onClick(route.path)}
- href={route.path}
- text={route.title}
- selected={activeItem === route.path}
- badge={
- showBadge(route?.menu?.mode) ? (
-
- ) : null
- }
- >
-
-
- ))}
-
- );
-};
+import { useUiFlag } from 'hooks/useUiFlag.ts';
export const OtherLinksList = () => {
const { uiConfig } = useUiConfig();
@@ -126,6 +89,7 @@ export const PrimaryNavigationList: FC<{
}> = ({ mode, onClick, activeItem }) => {
const DynamicListItem = mode === 'mini' ? MiniListItem : FullListItem;
const { isOss } = useUiConfig();
+ const sideMenuCleanup = useUiFlag('sideMenuCleanup');
return (
@@ -165,7 +129,7 @@ export const PrimaryNavigationList: FC<{
{!isOss() ? (
onClick('/insights')}
selected={activeItem === '/insights'}
>
@@ -176,47 +140,6 @@ export const PrimaryNavigationList: FC<{
);
};
-const AccordionHeader: FC<{ children?: React.ReactNode }> = ({ children }) => {
- return (
- }
- aria-controls='configure-content'
- id='configure-header'
- >
-
- {children}
-
-
- );
-};
-
-export const SecondaryNavigation: FC<{
- expanded: boolean;
- onExpandChange: (expanded: boolean) => void;
- mode: NavigationMode;
- title: string;
- children?: React.ReactNode;
-}> = ({ mode, expanded, onExpandChange, title, children }) => {
- return (
- {
- onExpandChange(expand);
- }}
- >
- {mode === 'full' && {title}}
- {children}
-
- );
-};
-
export const AdminSettingsNavigation: FC<{
onClick: (activeItem: string) => void;
onSetFullMode: () => void;
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
index 0094a1dbc5..6d25fababb 100644
--- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
@@ -5,15 +5,13 @@ import { ShowHide } from './ShowHide.tsx';
import { useRoutes } from './useRoutes.ts';
import { useExpanded } from './useExpanded.ts';
import {
- OtherLinksList,
PrimaryNavigationList,
RecentFlagsNavigation,
RecentProjectsNavigation,
- SecondaryNavigation,
- SecondaryNavigationList,
AdminSettingsNavigation,
- AdminSettingsLink,
} from './NavigationList.tsx';
+import { SecondaryNavigationList } from './SecondaryNavigationList.tsx';
+import { SecondaryNavigation } from './SecondaryNavigation.tsx';
import { FullListItem, MiniListItem } from './ListItems.tsx';
import { useInitialPathname } from './useInitialPathname.ts';
import { useLastViewedProject } from 'hooks/useLastViewedProject';
@@ -31,27 +29,7 @@ import { ReactComponent as LogoOnly } from 'assets/img/logoDark.svg';
import { Link } from 'react-router-dom';
import { useFlag } from '@unleash/proxy-client-react';
import { useNewAdminMenu } from 'hooks/useNewAdminMenu';
-
-export const MobileNavigationSidebar: FC<{
- onClick: () => void;
- NewInUnleash?: typeof NewInUnleash;
-}> = ({ onClick, NewInUnleash }) => {
- const { routes } = useRoutes();
-
- return (
- <>
- {NewInUnleash ? : null}
-
-
-
-
- >
- );
-};
+import { useUiFlag } from 'hooks/useUiFlag.ts';
export const StretchContainer = styled(Box, {
shouldForwardProp: (propName) =>
@@ -118,12 +96,15 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({
const initialPathname = useInitialPathname();
const [activeItem, setActiveItem] = useState(initialPathname);
+ const sideMenuCleanup = useUiFlag('sideMenuCleanup');
const { lastViewed: lastViewedProject } = useLastViewedProject();
- const showRecentProject = mode === 'full' && lastViewedProject;
+ const showRecentProject =
+ !sideMenuCleanup && mode === 'full' && lastViewedProject;
const { lastViewed: lastViewedFlags } = useLastViewedFlags();
- const showRecentFlags = mode === 'full' && lastViewedFlags.length > 0;
+ const showRecentFlags =
+ !sideMenuCleanup && mode === 'full' && lastViewedFlags.length > 0;
const DynamicListItem = mode === 'mini' ? MiniListItem : FullListItem;
useEffect(() => {
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigation.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigation.tsx
new file mode 100644
index 0000000000..968229e570
--- /dev/null
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigation.tsx
@@ -0,0 +1,49 @@
+import type React from 'react';
+import type { FC } from 'react';
+import type { NavigationMode } from './NavigationMode.tsx';
+import { Typography } from '@mui/material';
+import Accordion from '@mui/material/Accordion';
+import AccordionDetails from '@mui/material/AccordionDetails';
+import AccordionSummary from '@mui/material/AccordionSummary';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+
+const AccordionHeader: FC<{ children?: React.ReactNode }> = ({ children }) => {
+ return (
+ }
+ aria-controls='configure-content'
+ id='configure-header'
+ >
+
+ {children}
+
+
+ );
+};
+
+export const SecondaryNavigation: FC<{
+ expanded: boolean;
+ onExpandChange: (expanded: boolean) => void;
+ mode: NavigationMode;
+ title: string;
+ children?: React.ReactNode;
+}> = ({ mode, expanded, onExpandChange, title, children }) => {
+ return (
+ {
+ onExpandChange(expand);
+ }}
+ >
+ {mode === 'full' && {title}}
+ {children}
+
+ );
+};
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigationList.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigationList.tsx
new file mode 100644
index 0000000000..ac2a433adf
--- /dev/null
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/SecondaryNavigationList.tsx
@@ -0,0 +1,46 @@
+import type { FC } from 'react';
+import type { INavigationMenuItem } from 'interfaces/route';
+import type { NavigationMode } from './NavigationMode.tsx';
+import { FullListItem, MiniListItem } from './ListItems.tsx';
+import { List } from '@mui/material';
+import { IconRenderer } from './IconRenderer.tsx';
+import { useUiFlag } from 'hooks/useUiFlag.ts';
+import StopRoundedIcon from '@mui/icons-material/StopRounded';
+import { useShowBadge } from 'component/layout/components/EnterprisePlanBadge/useShowBadge';
+import { EnterprisePlanBadge } from 'component/layout/components/EnterprisePlanBadge/EnterprisePlanBadge';
+
+export const SecondaryNavigationList: FC<{
+ routes: INavigationMenuItem[];
+ mode: NavigationMode;
+ onClick: (activeItem: string) => void;
+ activeItem?: string;
+}> = ({ routes, mode, onClick, activeItem }) => {
+ const showBadge = useShowBadge();
+ const DynamicListItem = mode === 'mini' ? MiniListItem : FullListItem;
+ const sideMenuCleanup = useUiFlag('sideMenuCleanup');
+
+ return (
+
+ {routes.map((route) => (
+ onClick(route.path)}
+ href={route.path}
+ text={route.title}
+ selected={activeItem === route.path}
+ badge={
+ showBadge(route?.menu?.mode) ? (
+
+ ) : null
+ }
+ >
+ {sideMenuCleanup ? (
+
+ ) : (
+
+ )}
+
+ ))}
+
+ );
+};
diff --git a/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx
index 90407aa760..cfcb6a474f 100644
--- a/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx
+++ b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx
@@ -6,7 +6,7 @@ import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText
import styles from './DrawerMenu.module.scss'; // FIXME: useStyle - theme
import theme from 'themes/theme';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
-import { MobileNavigationSidebar } from 'component/layout/MainLayout/NavigationSidebar/NavigationSidebar';
+import { MobileNavigationSidebar } from 'component/layout/MainLayout/NavigationSidebar/MobileNavigationSidebar';
import { NewInUnleash } from 'component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash';
import { AdminMobileNavigation } from 'component/layout/MainLayout/AdminMenu/AdminNavigationItems';
import { useNewAdminMenu } from 'hooks/useNewAdminMenu';
diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
index eeccfd38c5..50e27cca8d 100644
--- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
+++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
@@ -131,10 +131,22 @@ exports[`returns all baseRoutes 1`] = `
"menu": {
"primary": true,
},
+ "notFlag": "sideMenuCleanup",
"path": "/insights",
"title": "Insights",
"type": "protected",
},
+ {
+ "component": [Function],
+ "enterprise": true,
+ "flag": "sideMenuCleanup",
+ "menu": {
+ "primary": true,
+ },
+ "path": "/insights",
+ "title": "Analytics",
+ "type": "protected",
+ },
{
"component": [Function],
"menu": {},
diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts
index a1a8f9b51e..01af7b4d43 100644
--- a/frontend/src/component/menu/routes.ts
+++ b/frontend/src/component/menu/routes.ts
@@ -157,6 +157,16 @@ export const routes: IRoute[] = [
type: 'protected',
menu: { primary: true },
enterprise: true,
+ notFlag: 'sideMenuCleanup',
+ },
+ {
+ path: '/insights',
+ title: 'Analytics',
+ component: Insights,
+ type: 'protected',
+ menu: { primary: true },
+ enterprise: true,
+ flag: 'sideMenuCleanup',
},
// Applications
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index d1df42082d..9c3db9bd18 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -93,6 +93,7 @@ export type UiFlags = {
projectLinkTemplates?: boolean;
customMetrics?: boolean;
lifecycleMetrics?: boolean;
+ sideMenuCleanup?: boolean;
};
export interface IVersionInfo {
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 948f809a2e..98006fb575 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -66,7 +66,8 @@ export type IFlagKey =
| 'lastSeenBulkQuery'
| 'newGettingStartedEmail'
| 'lifecycleMetrics'
- | 'customMetrics';
+ | 'customMetrics'
+ | 'sideMenuCleanup';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@@ -312,6 +313,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_LIFECYCLE_METRICS,
false,
),
+ sideMenuCleanup: parseEnvVarBoolean(
+ process.env.UNLEASH_EXPERIMENTAL_SIDE_MENU_CLEANUP,
+ false,
+ ),
};
export const defaultExperimentalOptions: IExperimentalOptions = {
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 039178e7d8..1d04fd9200 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -59,6 +59,7 @@ process.nextTick(async () => {
reportUnknownFlags: true,
customMetrics: true,
lifecycleMetrics: true,
+ sideMenuCleanup: true,
},
},
authentication: {