Skip to content

Commit

Permalink
feature/deseng661: Implemented new admin navigation design. (#2565)
Browse files Browse the repository at this point in the history
* feature/deseng661: Implemented new admin navigation design.

* feature/deseng661: Separated close button component, modified types, disabled default button animations.

* [To Feature] DESENG-661 Patch: Optimizations and Accessibility (#2566)

* Patch for DESENG-661: Optimizations and Accessibility

* Unify route item styles

* Sonarcloud issues? Fixed

* feature/deseng661: Enhanced accessibility of admin nav bar.

* feature/deseng661: Further accessibility enhancements.

* feature/deseng661: Changed aria label of nav menu.

* feature/deseng661: Changed aria label again.

---------

Co-authored-by: Nat² <[email protected]>
  • Loading branch information
jareth-whitney and NatSquared committed Jul 25, 2024
1 parent bcbdcdc commit 3443e95
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 99 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## July 25, 2024

- **Feature** New admin navigation [🎟️ DESENG-661](https://citz-gdx.atlassian.net/browse/DESENG-661)
- Implemented new admin navigation design for desktop and mobile viewports.
- Implemented additional accessibility for admin navigation

## July 17, 2024

- **Feature** Admin authoring experience - language selector [🎟️ DESENG-657](https://citz-gdx.atlassian.net/browse/DESENG-657)
Expand Down
2 changes: 1 addition & 1 deletion met-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface Translations {
}

const App = () => {
const drawerWidth = 280;
const drawerWidth = 300;
const dispatch = useAppDispatch();
const roles = useAppSelector((state) => state.user.roles);
const authenticationLoading = useAppSelector((state) => state.user.authentication.loading);
Expand Down
250 changes: 176 additions & 74 deletions met-web/src/components/layout/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,204 @@
import React from 'react';
import { ListItemButton, List, ListItem, Box, Drawer, Toolbar, Divider, ThemeProvider } from '@mui/material';
import { ListItemButton, List, ListItem, Box, Drawer, Toolbar, Divider } from '@mui/material';
import { useLocation, useNavigate } from 'react-router-dom';
import { Routes } from './SideNavElements';
import { DarkTheme, Palette } from '../../../styles/Theme';
import { SideNavProps } from './types';
import { MetHeader4 } from 'components/common';
import { Routes, Route } from './SideNavElements';
import { Palette, colors } from '../../../styles/Theme';
import { SideNavProps, DrawerBoxProps, CloseButtonProps } from './types';
import { When, Unless } from 'react-if';
import { useAppSelector } from 'hooks';
import UserGuideNav from './UserGuideNav';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLinkSlash } from '@fortawesome/pro-regular-svg-icons/faLinkSlash';
import { faCheck } from '@fortawesome/pro-solid-svg-icons/faCheck';
import { faArrowLeft } from '@fortawesome/pro-solid-svg-icons/faArrowLeft';

const DrawerBox = () => {
export const routeItemStyle = {
padding: 0,
backgroundColor: Palette.background.default,
'&:hover, &:focus': {
filter: 'brightness(96%)',
},
'&:active': {
filter: 'brightness(92%)',
},
'&:has(.MuiButtonBase-root:focus-visible)': {
boxShadow: `inset 0px 0px 0px 2px ${colors.focus.regular.outer}`,
},
'&:first-of-type': {
borderTopRightRadius: '16px', //round outline to match border radius of parent
},
'&:last-of-type': {
borderBottomRightRadius: '16px',
},
};

const CloseButton = ({ setOpen }: CloseButtonProps) => {
return (
<>
<ListItem key={'closeMenu'} sx={routeItemStyle}>
<ListItemButton
onClick={() => setOpen(false)}
disableRipple
sx={{
padding: 2,
pr: 4,
justifyContent: 'flex-end',
color: Palette.text.primary,
'&:hover, &:active, &:focus': {
backgroundColor: 'transparent',
},
}}
>
<FontAwesomeIcon icon={faArrowLeft} style={{ paddingRight: '0.75rem' }} />
Close Menu
</ListItemButton>
</ListItem>
<Divider sx={{ backgroundColor: colors.surface.gray[30] }} />
</>
);
};

const DrawerBox = ({ isMediumScreenOrLarger, setOpen }: DrawerBoxProps) => {
const navigate = useNavigate();
const location = useLocation();
const permissions = useAppSelector((state) => state.user.roles);

const getCurrentBaseRoute = () => {
return Routes.map((route) => route.base)
.filter((route) => location.pathname.includes(route))
.reduce((prev, curr) => (prev.length > curr.length ? prev : curr));
};

const currentBaseRoute = getCurrentBaseRoute();
const currentBaseRoute = Routes.map((route) => route.base)
.filter((route) => location.pathname.includes(route))
.reduce((prev, curr) => (prev.length > curr.length ? prev : curr));

const filteredRoutes = Routes.filter((route) => {
if (route.authenticated) {
return route.allowedRoles.some((role) => permissions.includes(role));
}
return true;
const allowedRoutes = Routes.filter((route) => {
return !route.authenticated || route.allowedRoles.some((role) => permissions.includes(role));
});

const renderListItem = (route: Route, itemType: string) => {
return (
<>
<When condition={'Tenant Admin' === route.name}>
<Divider sx={{ backgroundColor: Palette.primary.light, height: '0.2rem' }} />
</When>
<ListItem
key={route.name}
sx={{
...routeItemStyle,
backgroundColor: 'selected' === itemType ? colors.surface.blue[10] : Palette.background.default,
}}
>
<ListItemButton
component="a"
disableRipple
sx={{
'&:hover, &:active, &:focus': {
backgroundColor: 'transparent',
},
padding: 2,
pl: 4,
}}
data-testid={`SideNav/${route.name}-button`}
onClick={() => {
navigate(route.path);
setOpen(false);
}}
>
<FontAwesomeIcon
style={{
fontSize: '1.1rem',
color: 'selected' === itemType ? Palette.primary.light : Palette.text.primary,
paddingRight: '0.75rem',
width: '1.1rem',
}}
icon={route.icon ?? faLinkSlash}
/>

<span
style={{
color: 'selected' === itemType ? Palette.primary.light : Palette.text.primary,
fontWeight: 'selected' === itemType ? 'bold' : '500',
fontSize: '1rem',
}}
>
{route.name}
<When condition={currentBaseRoute === route.base}>
<span style={{ position: 'absolute', right: '2rem' }}>
<FontAwesomeIcon icon={faCheck} />
</span>
</When>
</span>
</ListItemButton>
</ListItem>
<Divider sx={{ backgroundColor: colors.surface.gray[30] }} />
<When condition={'User Admin' === route.name}>
<UserGuideNav />
</When>
</>
);
};

return (
<Box
component="nav"
aria-label="Administration navigation"
sx={{
mr: isMediumScreenOrLarger ? '1.25rem' : '0',
overflow: 'auto',
height: '100%',
background: Palette.primary.main,
backgroundColor: Palette.background.default,
borderTopRightRadius: '16px',
borderBottomRightRadius: '16px',
boxShadow: '0 5px 10px rgba(0, 0, 0, 0.4)',
mt: '5.625rem',
}}
>
<List sx={{ paddingTop: '2.5em' }}>
{filteredRoutes.map((route) => (
<ListItem key={route.name}>
<ListItemButton
data-testid={`SideNav/${route.name}-button`}
onClick={() => navigate(route.path)}
>
<When condition={currentBaseRoute === route.base}>
<MetHeader4 color={Palette.secondary.main} bold>
{route.name}
</MetHeader4>
</When>
<Unless condition={currentBaseRoute === route.base}>
<MetHeader4 color={'white'}>{route.name}</MetHeader4>
</Unless>
</ListItemButton>
</ListItem>
))}
<Divider sx={{ backgroundColor: 'var(--bcds-surface-background-white)' }} />
<UserGuideNav />
<List sx={{ pt: '0', pb: '0' }}>
<Unless condition={isMediumScreenOrLarger}>
<CloseButton setOpen={setOpen} />
</Unless>
{allowedRoutes.map((route) =>
renderListItem(route, currentBaseRoute === route.base ? 'selected' : 'other'),
)}
</List>
</Box>
);
};

const SideNav = ({ open, setOpen, isMediumScreen, drawerWidth = 280 }: SideNavProps) => {
const SideNav = ({ open, setOpen, isMediumScreen, drawerWidth = 300 }: SideNavProps) => {
if (!drawerWidth) return <></>;
return (
<ThemeProvider theme={DarkTheme}>
{isMediumScreen ? (
<Drawer
variant="permanent"
sx={{
height: '100vh',
if (isMediumScreen)
return (
<Drawer
PaperProps={{
sx: {
border: 'none',
width: drawerWidth,
flexShrink: 0,
[`& .MuiDrawer-paper`]: {
width: drawerWidth,
boxSizing: 'border-box',
backgroundColor: Palette.primary.main,
},
}}
>
<Toolbar />
<DrawerBox />
</Drawer>
) : (
<Drawer
sx={{
width: '15%',
background: Palette.primary.main,
}}
onClose={() => setOpen(false)}
anchor={'left'}
open={open}
hideBackdrop={!open}
>
<DrawerBox />
</Drawer>
)}
</ThemeProvider>
boxSizing: 'border-box',
backgroundColor: Palette.background.default,
},
}}
elevation={0}
variant="permanent"
sx={{
height: '100vh',
width: drawerWidth,
flexShrink: 0,
}}
>
<Toolbar />
<DrawerBox isMediumScreenOrLarger={isMediumScreen} setOpen={setOpen} />
</Drawer>
);
return (
<Drawer
PaperProps={{ sx: { width: '100%' } }}
sx={{
mt: '80px',
width: '100%',
}}
onClose={() => setOpen(false)}
anchor={'left'}
open={open}
hideBackdrop={!open}
>
<DrawerBox isMediumScreenOrLarger={isMediumScreen} setOpen={setOpen} />
</Drawer>
);
};

Expand Down
55 changes: 37 additions & 18 deletions met-web/src/components/layout/SideNav/SideNavElements.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,81 @@
import { USER_ROLES } from 'services/userService/constants';

interface Route {
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
import {
faHouse,
faPeopleArrows,
faSquarePollHorizontal,
faTags,
faGlobe,
faUserGear,
faHouseUser,
faMessagePen,
} from '@fortawesome/pro-regular-svg-icons';
export interface Route {
name: string;
path: string;
base: string;
authenticated: boolean;
allowedRoles: string[];
icon?: IconDefinition;
customComponent?: React.ReactNode;
}

export const Routes: Route[] = [
{ name: 'Home', path: '/home', base: '/', authenticated: false, allowedRoles: [] },
{ name: 'Home', path: '/home', base: '/', authenticated: false, allowedRoles: [], icon: faHouse },
{
name: 'Engagements',
path: '/engagements',
base: '/engagements',
authenticated: false,
allowedRoles: [],
icon: faPeopleArrows,
},
{
name: 'Surveys',
path: '/surveys',
base: '/surveys',
authenticated: false,
allowedRoles: [],
icon: faSquarePollHorizontal,
},
{
name: 'Metadata Management',
name: 'Metadata',
path: '/metadatamanagement',
base: '/metadatamanagement',
authenticated: true,
allowedRoles: [USER_ROLES.MANAGE_METADATA],
},
{
name: 'User Management',
path: '/usermanagement',
base: 'usermanagement',
authenticated: true,
allowedRoles: [USER_ROLES.VIEW_USERS],
},
{
name: 'Feedback Tool',
path: '/feedback',
base: 'feedback',
authenticated: true,
allowedRoles: [USER_ROLES.VIEW_FEEDBACKS],
icon: faTags,
},
{
name: 'Languages',
path: '/languages',
base: 'languages',
authenticated: true,
allowedRoles: [USER_ROLES.VIEW_LANGUAGES],
icon: faGlobe,
},
{
name: 'User Admin',
path: '/usermanagement',
base: 'usermanagement',
authenticated: true,
allowedRoles: [USER_ROLES.VIEW_USERS],
icon: faUserGear,
},
{
name: 'Tenant Admin',
path: '/tenantadmin',
base: 'tenantadmin',
authenticated: true,
allowedRoles: [USER_ROLES.SUPER_ADMIN],
icon: faHouseUser,
},
{
name: 'MET Feedback',
path: '/feedback',
base: 'feedback',
authenticated: true,
allowedRoles: [USER_ROLES.VIEW_FEEDBACKS],
icon: faMessagePen,
},
];
Loading

0 comments on commit 3443e95

Please sign in to comment.