Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [P4ADEV-1568] Set main menu #7

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default tseslint.config(
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'indent': ['error', 2],
Expand Down
3 changes: 0 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ const router = createBrowserRouter([
element: <Home />,
handle: {
backButton: false,
sidebar: {
visibile: false
}
} as RouteHandleObject
},
{
Expand Down
4 changes: 2 additions & 2 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export const Header = (props: HeaderProps) => {

const product: ProductEntity = {
id: '0',
title: 'Area Riservata',
productUrl: '#area-riservata',
title: 'Piattaforma Unitaria',
productUrl: '#pu',
linkType: 'internal'

};
Expand Down
144 changes: 144 additions & 0 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useEffect } from 'react';
import {
Box,
Divider,
Grid,
IconButton,
List,
Typography,
useTheme,
Tooltip,
useMediaQuery,
type Theme,
} from '@mui/material';
import { SidebarMenuItem } from './SidebarMenuItem';
import { useTranslation } from 'react-i18next';
import MenuIcon from '@mui/icons-material/Menu';
import CloseIcon from '@mui/icons-material/Close';
import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
import AltRouteIcon from '@mui/icons-material/AltRoute';
import { sidebarStyles } from './sidebar.styles';
import { PageRoutes } from '../../routes/routes';
import { ISidebarMenuItem } from '../../models/SidebarMenuItem';
import useCollapseMenu from '../../hooks/useCollapseMenu';

export const Sidebar: React.FC = () => {

const { t } = useTranslation();
const theme = useTheme();
const lg = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg'));

const { collapsed, changeMenuState, setCollapsed, setOverlay, overlay } = useCollapseMenu(!lg);

useEffect(() => {
setOverlay(!(lg || collapsed));
}, [lg, collapsed]);
//This useEffect is needed, otherwise React will complain about the component being re rendered while another re render is in the queue.

const styles = sidebarStyles(theme, collapsed);

const RotatedAltRouteIcon = () => {
return (
<AltRouteIcon sx={{ transform: 'rotate(90deg)' }} />
);
};


const menuItems: Array<ISidebarMenuItem> = [
{
label: t('menu.homepage'),
icon: ViewSidebarIcon,
route: PageRoutes.HOME,
end: true
},
{
label: t('menu.debtpositions'),
icon: ReceiptLongIcon,
route: '/debtpositions',
end: true
},
{
label: t('menu.flows'),
icon: RotatedAltRouteIcon,
route: '/flows',
end: true,
items: [
{
label: t('menu.subitem'),
route: '/flows/item1',
end: true
},
]
}
];

return (
<>
<Box sx={styles.container} component="aside">
<Grid
alignItems="normal"
display="flex"
flexDirection="column"
item
component="nav"
aria-expanded={!collapsed}
aria-label={t('menu.navigationMenu')}
role="navigation"
sx={styles.nav}>
{overlay && (
<Box sx={styles.collapseIcon}>
<Tooltip
placement="left"
title={t(!collapsed ? 'sidebar.collapse' : 'sidebar.expand')}>
<IconButton
data-testid="collapseClose"
aria-label={t(!collapsed ? 'sidebar.collapse' : 'sidebar.expand')}
onClick={() => changeMenuState()}
size="large">
<CloseIcon />
</IconButton>
</Tooltip>
</Box>
)}
<List
sx={styles.list}
component="ol"
aria-hidden={collapsed && !lg}
aria-label={t('menu.description')}>
{menuItems.map((item, index) => (
<SidebarMenuItem
onClick={() => !lg && setCollapsed(true)}
collapsed={collapsed}
item={item}
key={index}
/>
))}
</List>
<Box sx={styles.hamburgerBox}>
<Divider orientation="horizontal" flexItem sx={{ display: lg ? 'block' : 'none' }} />
<Box sx={styles.hamburgerIcon}>
<Tooltip
placement="right"
title={t(!collapsed ? 'sidebar.collapse' : 'sidebar.expand')}>
<IconButton
data-testid="hamburgerButton"
aria-label={t(!collapsed ? 'sidebar.collapse' : 'sidebar.expand')}
onClick={() => changeMenuState()}
size="large">
<MenuIcon />
{!lg && (
<Typography variant="button" sx={styles.hamburgerTypography}>
{t('menu.menu')}
</Typography>
)}
</IconButton>
</Tooltip>
</Box>
</Box>
</Grid>
</Box>
{overlay && <Box sx={styles.overlay} />}
</>
);
};
84 changes: 84 additions & 0 deletions src/components/Sidebar/SidebarMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText, useTheme } from '@mui/material';
import { NavLink } from 'react-router-dom';
import { SvgIconComponent } from '@mui/icons-material';
import { alpha } from '@mui/material';
import { ISidebarMenuItem } from '../../models/SidebarMenuItem';
import ExpandLessRoundedIcon from '@mui/icons-material/ExpandLessRounded';
import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';

type Props = {
collapsed: boolean;
item: ISidebarMenuItem;
onClick: React.MouseEventHandler<HTMLAnchorElement> | undefined;
};

function renderIcon(Icon: SvgIconComponent | (() => JSX.Element)) {
return <Icon></Icon>;
}

export const SidebarMenuItem = ({ collapsed, item, onClick }: Props) => {
const theme = useTheme();
const [selectedTarget, setSelectedTarget] = React.useState('');
const [open, setOpen] = React.useState(false);
const handleCollapseClick = () => {
setOpen(!open);
};
const handleListItemClick = (target: string) => {
setSelectedTarget(target);
};

return (
<ListItem disablePadding sx={{flexDirection: 'column', alignItems: 'stretch'}}>
<ListItemButton
end={item.end || false}
component={NavLink}
to={item.route}
onClick={item.items ? handleCollapseClick : onClick}
sx={{
px: 3,
'&.hover': {
backgroundColor: 'none'
},
'&.active': {
fontWeight: 'bold',
backgroundColor: alpha(theme.palette.primary.main, 0.08),
borderRight: '2px solid',
borderColor: theme.palette.primary.dark,
'.MuiTypography-root': {
fontWeight: 600,
color: theme.palette.primary.dark
},
'.MuiListItemIcon-root': {
color: theme.palette.primary.dark
}
}
}}>
{item.icon && <ListItemIcon aria-hidden="true">{renderIcon(item.icon)}</ListItemIcon>}
{!collapsed && (
<ListItemText
id={`menu-item-${item.label.toLowerCase()}`}
sx={{ whiteSpace: 'nowrap', overflow: 'hidden' }}
primary={item.label}
/>
)}
{item.items &&
(open ? <ExpandLessRoundedIcon color="action" /> : <ExpandMoreRoundedIcon color="action" />)}
</ListItemButton>

{item.items &&
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{item.items.map((subitem, subindex) => (
<ListItemButton sx={{ pl: 6 }}
selected={selectedTarget === `subitem-${subindex}`}
onClick={() => handleListItemClick(`subitem-${subindex}`)}>
<ListItemText primary={subitem.label} key={subindex} />
</ListItemButton>
))}
</List>
</Collapse>
}
</ListItem>
);
};
61 changes: 61 additions & 0 deletions src/components/Sidebar/sidebar.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { SxProps, Theme } from '@mui/material';

export const sidebarStyles = (theme: Theme, collapsed: boolean): Record<string, SxProps> => ({
container: {
zIndex: collapsed ? 1 : 10,
position: collapsed ? 'relative' : 'fixed',
width: '100%',
top: 0,
height: '100vh',
transition: 'width 0.3s ease, height 0.3s ease', // Add transition for smooth resizing
[theme.breakpoints.between('sm', 'lg')]: { width: collapsed ? '100%' : 'fit-content' },
[theme.breakpoints.up('lg')]: { width: 'fit-content', position: 'sticky' },
[theme.breakpoints.down('lg')]: { height: collapsed ? 'fit-content' : '100%' }
},
nav: {
minHeight: collapsed ? '1vh' : '50vh',
height: '100%',
width: '100%',
bgcolor: 'background.paper',
transition: 'width 0.3s ease', // Add transition for smooth width change
[theme.breakpoints.up('sm')]: { width: collapsed ? '100%' : '300px' },
[theme.breakpoints.up('lg')]: { width: collapsed ? '88px' : '300px', minHeight: '50vh' }
},
overlay: {
bgcolor: 'rgba(23, 50, 77, 0.7)',
zIndex: 1,
position: 'fixed',
top: 0,
left: 0,
height: '100%',
width: '100%'
},
collapseIcon: {
textAlign: 'right',
pt: 1,
pr: 2
},
list: {
[theme.breakpoints.down('lg')]: {
display: collapsed ? 'none' : 'inline-block'
}
},
hamburgerBox: {
marginTop: 'auto',
position: 'sticky',
bottom: '0',
transition: 'opacity 0.3s ease', // Add transition for smooth visibility change
[theme.breakpoints.down('lg')]: {
marginTop: collapsed ? 0 : 'auto',
opacity: collapsed ? 1 : 0,
visibility: collapsed ? 'visible' : 'hidden'
}
},
hamburgerIcon: {
p: 2
},
hamburgerTypography: {
fontWeight: 600,
pl: 1
}
});
14 changes: 11 additions & 3 deletions src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { NavigateNext } from '@mui/icons-material';
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
import { RouteHandleObject } from '../../models/Breadcrumbs';
import { Header } from '../Header';
import { Sidebar } from '../Sidebar/Sidebar';
import utils from '../../utils';

const defaultRouteHandle: RouteHandleObject = {
sidebar: { visible: true },
Expand All @@ -16,11 +18,17 @@ const defaultRouteHandle: RouteHandleObject = {
export function Layout() {
const matches = useMatches();

const { crumbs, backButton, backButtonText, backButtonFunction } = {
const overlay = utils.sidemenu.status.overlay.value;

document.body.style.overflow = overlay ? 'hidden' : 'auto';

const { crumbs, sidebar, backButton, backButtonText, backButtonFunction } = {
...defaultRouteHandle,
...(matches.find((match) => Boolean(match.handle))?.handle || {})
} as RouteHandleObject;

const sidePadding = sidebar.visible ? 3 : { xs: 3, md: 12, lg: 27, xl: 34 };

return (
<>
<Container
Expand All @@ -42,8 +50,8 @@ export function Layout() {
flexWrap={'wrap'}
alignContent={'flex-start'}
flexBasis={'50vh'}>
<Box>SIDEBAR</Box>
<Grid item bgcolor={grey['100']} padding={3} height={'100%'} xs >
{sidebar?.visible ? <Sidebar /> : null}
<Grid item bgcolor={grey['100']} padding={3} height={'100%'} xs paddingX={sidePadding}>
{backButton && <BackButton onClick={backButtonFunction} text={backButtonText} />}
{crumbs && (
<Breadcrumbs crumbs={crumbs} separator={<NavigateNext fontSize="small" />} />
Expand Down
Loading
Loading