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:
parent
6b793677b9
commit
f7c04cc2cb
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user