1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-27 01:19:00 +02:00

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'
This commit is contained in:
Tymoteusz Czech 2025-05-28 12:00:28 +02:00 committed by GitHub
parent a302055bea
commit b111abc96f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 169 additions and 109 deletions

View File

@ -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 }) => {

View File

@ -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 ? <NewInUnleash /> : null}
<PrimaryNavigationList mode='full' onClick={onClick} />
<SecondaryNavigationList
routes={routes.mainNavRoutes}
mode='full'
onClick={onClick}
/>
<AdminSettingsLink mode={'full'} onClick={onClick} />
<OtherLinksList />
</>
);
};

View File

@ -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 (
<List>
{routes.map((route) => (
<DynamicListItem
key={route.title}
onClick={() => onClick(route.path)}
href={route.path}
text={route.title}
selected={activeItem === route.path}
badge={
showBadge(route?.menu?.mode) ? (
<EnterprisePlanBadge />
) : null
}
>
<IconRenderer path={route.path} />
</DynamicListItem>
))}
</List>
);
};
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 (
<List>
@ -165,7 +129,7 @@ export const PrimaryNavigationList: FC<{
{!isOss() ? (
<DynamicListItem
href='/insights'
text='Insights'
text={sideMenuCleanup ? 'Analytics' : 'Insights'}
onClick={() => onClick('/insights')}
selected={activeItem === '/insights'}
>
@ -176,47 +140,6 @@ export const PrimaryNavigationList: FC<{
);
};
const AccordionHeader: FC<{ children?: React.ReactNode }> = ({ children }) => {
return (
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='configure-content'
id='configure-header'
>
<Typography sx={{ fontWeight: 'bold', fontSize: 'small' }}>
{children}
</Typography>
</AccordionSummary>
);
};
export const SecondaryNavigation: FC<{
expanded: boolean;
onExpandChange: (expanded: boolean) => void;
mode: NavigationMode;
title: string;
children?: React.ReactNode;
}> = ({ mode, expanded, onExpandChange, title, children }) => {
return (
<Accordion
disableGutters={true}
sx={{
boxShadow: 'none',
'&:before': {
display: 'none',
},
}}
expanded={expanded}
onChange={(_, expand) => {
onExpandChange(expand);
}}
>
{mode === 'full' && <AccordionHeader>{title}</AccordionHeader>}
<AccordionDetails sx={{ p: 0 }}>{children}</AccordionDetails>
</Accordion>
);
};
export const AdminSettingsNavigation: FC<{
onClick: (activeItem: string) => void;
onSetFullMode: () => void;

View File

@ -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 ? <NewInUnleash /> : null}
<PrimaryNavigationList mode='full' onClick={onClick} />
<SecondaryNavigationList
routes={routes.mainNavRoutes}
mode='full'
onClick={onClick}
/>
<AdminSettingsLink mode={'full'} onClick={onClick} />
<OtherLinksList />
</>
);
};
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(() => {

View File

@ -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 (
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='configure-content'
id='configure-header'
>
<Typography sx={{ fontWeight: 'bold', fontSize: 'small' }}>
{children}
</Typography>
</AccordionSummary>
);
};
export const SecondaryNavigation: FC<{
expanded: boolean;
onExpandChange: (expanded: boolean) => void;
mode: NavigationMode;
title: string;
children?: React.ReactNode;
}> = ({ mode, expanded, onExpandChange, title, children }) => {
return (
<Accordion
disableGutters={true}
sx={{
boxShadow: 'none',
'&:before': {
display: 'none',
},
}}
expanded={expanded}
onChange={(_, expand) => {
onExpandChange(expand);
}}
>
{mode === 'full' && <AccordionHeader>{title}</AccordionHeader>}
<AccordionDetails sx={{ p: 0 }}>{children}</AccordionDetails>
</Accordion>
);
};

View File

@ -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 (
<List>
{routes.map((route) => (
<DynamicListItem
key={route.title}
onClick={() => onClick(route.path)}
href={route.path}
text={route.title}
selected={activeItem === route.path}
badge={
showBadge(route?.menu?.mode) ? (
<EnterprisePlanBadge />
) : null
}
>
{sideMenuCleanup ? (
<StopRoundedIcon fontSize='small' color='primary' />
) : (
<IconRenderer path={route.path} />
)}
</DynamicListItem>
))}
</List>
);
};

View File

@ -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';

View File

@ -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": {},

View File

@ -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

View File

@ -93,6 +93,7 @@ export type UiFlags = {
projectLinkTemplates?: boolean;
customMetrics?: boolean;
lifecycleMetrics?: boolean;
sideMenuCleanup?: boolean;
};
export interface IVersionInfo {

View File

@ -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 = {

View File

@ -59,6 +59,7 @@ process.nextTick(async () => {
reportUnknownFlags: true,
customMetrics: true,
lifecycleMetrics: true,
sideMenuCleanup: true,
},
},
authentication: {