1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-17 01:17:29 +02:00

feat: admin menu for mobile (#9626)

This commit is contained in:
David Leek 2025-03-27 13:28:44 +01:00 committed by GitHub
parent 6b793677b9
commit f7c04cc2cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 210 additions and 179 deletions

View File

@ -72,6 +72,7 @@ interface IMenuGroupProps {
icon: ReactNode;
activeIcon: ReactNode;
isActiveMenu: boolean;
staticExpanded?: true | undefined;
}
export const MenuGroup = ({
@ -80,10 +81,12 @@ export const MenuGroup = ({
icon,
activeIcon,
isActiveMenu,
staticExpanded,
}: IMenuGroupProps) => {
return (
<StyledAccordion
disableGutters={true}
expanded={staticExpanded}
sx={{
boxShadow: 'none',
'&:before': {
@ -92,7 +95,7 @@ export const MenuGroup = ({
}}
>
<StyledAccordionSummary
expandIcon={<ExpandMoreIcon />}
expandIcon={staticExpanded ? null : <ExpandMoreIcon />}
aria-controls='configure-content'
id='configure-header'
sx={listItemButtonStyle}

View File

@ -1,38 +1,8 @@
import {
Grid,
styled,
Paper,
Typography,
Button,
List,
useMediaQuery,
useTheme,
} from '@mui/material';
import { Grid, styled, Paper, useMediaQuery, useTheme } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
import type { ReactNode } from 'react';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import StopRoundedIcon from '@mui/icons-material/StopRounded';
import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem';
import { useLocation } from 'react-router-dom';
import { Sticky } from 'component/common/Sticky/Sticky';
import { adminRoutes, adminGroups } from 'component/admin/adminRoutes';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { filterByConfig } from 'component/common/util';
import { filterAdminRoutes } from 'component/admin/filterAdminRoutes';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { IconRenderer } from './AdminMenuIcons';
interface IMenuLinkItem {
href: string;
text: string;
icon: ReactNode;
}
interface IMenuItem {
href: string;
text: string;
items?: IMenuLinkItem[];
}
import { AdminMenuNavigation } from './AdminNavigationItems';
const StyledAdminMainGrid = styled(Grid)(({ theme }) => ({
minWidth: 0, // this is a fix for overflowing flex
@ -79,20 +49,6 @@ const StickyContainer = styled(Sticky)(({ theme }) => ({
transition: 'padding 0.3s ease',
}));
const SettingsHeader = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
fontWeight: theme.fontWeight.bold,
}));
const StyledButton = styled(Button)(({ theme }) => ({
paddingLeft: theme.spacing(0),
marginBottom: theme.spacing(3),
}));
const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({
color: theme.palette.primary.main,
}));
interface IWrapIfAdminSubpageProps {
children: ReactNode;
}
@ -113,29 +69,11 @@ export const WrapIfAdminSubpage = ({ children }: IWrapIfAdminSubpageProps) => {
return <>{children}</>;
};
const DashboardLink = () => {
return (
<>
<StyledButton
href='/personal'
rel='noreferrer'
startIcon={<ArrowBackIcon />}
>
Back to Unleash
</StyledButton>
</>
);
};
interface IAdminMenuProps {
children: ReactNode;
}
export const AdminMenu = ({ children }: IAdminMenuProps) => {
const { uiConfig, isPro, isEnterprise } = useUiConfig();
const { isBilling } = useInstanceStatus();
const isActiveItem = (item?: string) =>
item !== undefined && location.pathname === item;
const theme = useTheme();
const isBreakpoint = useMediaQuery(theme.breakpoints.down(1350));
const onClick = () => {
@ -144,109 +82,13 @@ export const AdminMenu = ({ children }: IAdminMenuProps) => {
behavior: 'smooth',
});
};
const location = useLocation();
const routes = adminRoutes
.filter(filterByConfig(uiConfig))
.filter((route) =>
filterAdminRoutes(route?.menu, {
enterprise: isEnterprise(),
pro: isPro(),
billing: isBilling,
}),
);
const menuStructure = routes.reduce(
(acc: Record<string, IMenuItem>, route) => {
if (route.group && adminGroups[route.group]) {
if (!acc[route.group]) {
acc[route.group] = {
href: route.group,
text: adminGroups[route.group],
items: [],
};
}
acc[route.group].items?.push({
href: route.path,
text: route.title,
icon: <StopRoundedIcon />,
});
}
if (!route.group) {
acc[route.path] = {
href: route.path,
text: route.title,
};
}
return acc;
},
{},
);
const items = Object.values(menuStructure);
return (
<StyledAdminMainGrid container spacing={1}>
<Grid item>
<StickyContainer>
<StyledMenuPaper>
<SettingsHeader>Admin settings</SettingsHeader>
<DashboardLink />
<List>
{items.map((item) => {
if (item.items) {
const isActiveMenu = item.items.find(
(itm) => isActiveItem(itm.href),
);
return (
<MenuGroup
title={item.text}
icon={
<IconRenderer
path={item.href}
active={false}
/>
}
activeIcon={
<IconRenderer
path={item.href}
active={true}
/>
}
isActiveMenu={Boolean(isActiveMenu)}
key={item.text}
>
{item.items.map((subItem) => (
<AdminSubListItem
href={subItem.href}
text={subItem.text}
selected={isActiveItem(
subItem.href,
)}
onClick={onClick}
key={subItem.href}
>
<StyledStopRoundedIcon />
</AdminSubListItem>
))}
</MenuGroup>
);
}
return (
<AdminListItem
href={item.href}
text={item.text}
selected={isActiveItem(item.href)}
onClick={onClick}
key={item.href}
>
<IconRenderer
path={item.href}
active={false}
/>
</AdminListItem>
);
})}
</List>
<AdminMenuNavigation onClick={onClick} />
</StyledMenuPaper>
</StickyContainer>
</Grid>

View File

@ -0,0 +1,185 @@
import { Button, styled, Typography, List } from '@mui/material';
import { OtherLinksList } from '../NavigationSidebar/NavigationList';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import StopRoundedIcon from '@mui/icons-material/StopRounded';
import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem';
import { IconRenderer } from './AdminMenuIcons';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { useLocation } from 'react-router-dom';
import { filterByConfig } from 'component/common/util';
import { filterAdminRoutes } from 'component/admin/filterAdminRoutes';
import { adminGroups, adminRoutes } from 'component/admin/adminRoutes';
import type { ReactNode } from 'react';
interface IMenuLinkItem {
href: string;
text: string;
icon: ReactNode;
}
interface IMenuItem {
href: string;
text: string;
items?: IMenuLinkItem[];
}
const SettingsHeader = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
fontWeight: theme.fontWeight.bold,
}));
const StyledButton = styled(Button)(({ theme }) => ({
paddingLeft: theme.spacing(0),
marginBottom: theme.spacing(3),
}));
const StyledDiv = styled('div')(({ theme }) => ({
padding: theme.spacing(2, 2.5, 0, 2.5),
}));
const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({
color: theme.palette.primary.main,
}));
export const DashboardLink = () => {
return (
<StyledButton
href='/personal'
rel='noreferrer'
startIcon={<ArrowBackIcon />}
>
Back to Unleash
</StyledButton>
);
};
export const AdminMobileNavigation = ({ onClick }: { onClick: () => void }) => {
return (
<>
<StyledDiv>
<AdminNavigationHeader />
</StyledDiv>
<AdminNavigationItems staticExpanded={true} onClick={onClick} />
<OtherLinksList />
</>
);
};
export const AdminMenuNavigation = ({ onClick }: { onClick: () => void }) => {
return (
<>
<AdminNavigationHeader />
<AdminNavigationItems onClick={onClick} />
</>
);
};
export const AdminNavigationHeader = () => {
return (
<>
<SettingsHeader>Admin settings</SettingsHeader>
<DashboardLink />
</>
);
};
export const AdminNavigationItems = ({
onClick,
staticExpanded,
}: { onClick: () => void; staticExpanded?: true | undefined }) => {
const { uiConfig, isPro, isEnterprise } = useUiConfig();
const { isBilling } = useInstanceStatus();
const isActiveItem = (item?: string) =>
item !== undefined && location.pathname === item;
const location = useLocation();
const routes = adminRoutes
.filter(filterByConfig(uiConfig))
.filter((route) =>
filterAdminRoutes(route?.menu, {
enterprise: isEnterprise(),
pro: isPro(),
billing: isBilling,
}),
);
const menuStructure = routes.reduce(
(acc: Record<string, IMenuItem>, route) => {
if (route.group && adminGroups[route.group]) {
if (!acc[route.group]) {
acc[route.group] = {
href: route.group,
text: adminGroups[route.group],
items: [],
};
}
acc[route.group].items?.push({
href: route.path,
text: route.title,
icon: <StopRoundedIcon />,
});
}
if (!route.group) {
acc[route.path] = {
href: route.path,
text: route.title,
};
}
return acc;
},
{},
);
const items = Object.values(menuStructure);
return (
<List>
{items.map((item) => {
if (item.items) {
const isActiveMenu = item.items.find((itm) =>
isActiveItem(itm.href),
);
return (
<MenuGroup
title={item.text}
icon={
<IconRenderer path={item.href} active={false} />
}
activeIcon={
<IconRenderer path={item.href} active={true} />
}
isActiveMenu={Boolean(isActiveMenu)}
key={item.text}
staticExpanded={staticExpanded}
>
{item.items.map((subItem) => (
<AdminSubListItem
href={subItem.href}
text={subItem.text}
selected={isActiveItem(subItem.href)}
onClick={onClick}
key={subItem.href}
>
<StyledStopRoundedIcon />
</AdminSubListItem>
))}
</MenuGroup>
);
}
return (
<AdminListItem
href={item.href}
text={item.text}
selected={isActiveItem(item.href)}
onClick={onClick}
key={item.href}
>
<IconRenderer path={item.href} active={false} />
</AdminListItem>
);
})}
</List>
);
};

View File

@ -1,4 +1,4 @@
import type { ReactNode, VFC } from 'react';
import type { VFC } from 'react';
import { Link } from 'react-router-dom';
import { Divider, Drawer, styled } from '@mui/material';
import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg';
@ -8,6 +8,8 @@ import theme from 'themes/theme';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
import { MobileNavigationSidebar } from 'component/layout/MainLayout/NavigationSidebar/NavigationSidebar';
import { NewInUnleash } from 'component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash';
import { useUiFlag } from 'hooks/useUiFlag';
import { AdminMobileNavigation } from 'component/layout/MainLayout/AdminMenu/AdminNavigationItems';
const StyledDrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
@ -25,19 +27,18 @@ interface IDrawerMenuProps {
title?: string;
open?: boolean;
toggleDrawer: () => void;
links: Array<{
value: string;
icon: ReactNode;
href: string;
title: string;
}>;
}
export const DrawerMenu: VFC<IDrawerMenuProps> = ({
links = [],
open = false,
toggleDrawer,
}) => {
const newAdminUIEnabled = useUiFlag('adminNavUI');
const showOnlyAdminMenu =
newAdminUIEnabled && location.pathname.indexOf('/admin') === 0;
const onClick = () => {
toggleDrawer();
};
return (
<Drawer
className={styles.drawer}
@ -65,10 +66,14 @@ export const DrawerMenu: VFC<IDrawerMenuProps> = ({
</Link>
</StyledDrawerHeader>
<Divider />
<MobileNavigationSidebar
onClick={toggleDrawer}
NewInUnleash={NewInUnleash}
/>
{showOnlyAdminMenu ? (
<AdminMobileNavigation onClick={onClick} />
) : (
<MobileNavigationSidebar
onClick={toggleDrawer}
NewInUnleash={NewInUnleash}
/>
)}
</nav>
</Drawer>
);

View File

@ -118,11 +118,7 @@ const Header = () => {
<MenuIcon />
</IconButton>
</Tooltip>
<DrawerMenu
links={uiConfig.links}
open={openDrawer}
toggleDrawer={toggleDrawer}
/>
<DrawerMenu open={openDrawer} toggleDrawer={toggleDrawer} />
<StyledUserContainer>
<UserProfile />
</StyledUserContainer>