feat: spdv-995 (#225)

This commit is contained in:
rolandlor
2026-03-13 13:15:26 +01:00
committed by Bálint Ujvári
parent 3c4d618cc8
commit fa8a26e80d
3 changed files with 173 additions and 44 deletions
+92 -20
View File
@@ -1,6 +1,6 @@
import { BeeModes } from '@ethersphere/bee-js' import { BeeModes } from '@ethersphere/bee-js'
import { Box, Divider, Drawer, Grid, Link as MUILink, List, Typography } from '@mui/material' import { Box, Divider, Drawer, Grid, IconButton, Link as MUILink, List, Tooltip, Typography } from '@mui/material'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext, useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import FilesIcon from 'remixicon-react/ArrowUpDownLineIcon' import FilesIcon from 'remixicon-react/ArrowUpDownLineIcon'
import DocsIcon from 'remixicon-react/BookOpenLineIcon' import DocsIcon from 'remixicon-react/BookOpenLineIcon'
@@ -8,6 +8,8 @@ import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon'
import FileManagerIcon from 'remixicon-react/FolderOpenLineIcon' import FileManagerIcon from 'remixicon-react/FolderOpenLineIcon'
import GithubIcon from 'remixicon-react/GithubFillIcon' import GithubIcon from 'remixicon-react/GithubFillIcon'
import HomeIcon from 'remixicon-react/Home3LineIcon' import HomeIcon from 'remixicon-react/Home3LineIcon'
import MenuFoldIcon from 'remixicon-react/MenuFoldLineIcon'
import MenuUnfoldIcon from 'remixicon-react/MenuUnfoldLineIcon'
import SettingsIcon from 'remixicon-react/Settings2LineIcon' import SettingsIcon from 'remixicon-react/Settings2LineIcon'
import AccountIcon from 'remixicon-react/Wallet3LineIcon' import AccountIcon from 'remixicon-react/Wallet3LineIcon'
import { makeStyles } from 'tss-react/mui' import { makeStyles } from 'tss-react/mui'
@@ -23,26 +25,75 @@ import SideBarItem from './SideBarItem'
import SideBarStatus from './SideBarStatus' import SideBarStatus from './SideBarStatus'
const drawerWidth = 300 const drawerWidth = 300
const drawerWidthCollapsed = 72
const drawerHeaderHeight = 56
const useStyles = makeStyles()(theme => ({ const useStyles = makeStyles()(theme => ({
root: { root: {
flexWrap: 'nowrap', flexWrap: 'nowrap',
minHeight: '100vh', minHeight: `calc(100vh - ${drawerHeaderHeight}px)`,
paddingTop: theme.spacing(8),
paddingBottom: theme.spacing(8), paddingBottom: theme.spacing(8),
}, },
drawer: { drawer: {
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerCollapsed: {
width: drawerWidthCollapsed,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
}, },
drawerPaper: { drawerPaper: {
width: drawerWidth, width: drawerWidth,
backgroundColor: '#212121', backgroundColor: '#212121',
zIndex: 988, zIndex: 988,
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperCollapsed: {
width: drawerWidthCollapsed,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
header: {
display: 'flex',
alignItems: 'center',
height: drawerHeaderHeight,
borderBottom: '1px solid #2c2c2c',
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
flexShrink: 0,
}, },
logo: { logo: {
marginLeft: theme.spacing(8), flex: 1,
marginRight: theme.spacing(8), display: 'flex',
alignItems: 'center',
overflow: 'hidden',
},
logoImg: {
maxWidth: '100%',
display: 'block',
},
toggleButton: {
color: '#9f9f9f',
'&:hover': {
color: '#f9f9f9',
backgroundColor: '#2c2c2c',
},
},
toggleButtonCollapsed: {
marginLeft: 'auto',
}, },
icon: { icon: {
height: theme.spacing(4), height: theme.spacing(4),
@@ -70,6 +121,7 @@ export default function SideBar(): ReactElement {
const { classes } = useStyles() const { classes } = useStyles()
const { isDesktop } = useContext(SettingsContext) const { isDesktop } = useContext(SettingsContext)
const { nodeInfo } = useContext(BeeContext) const { nodeInfo } = useContext(BeeContext)
const [isCollapsed, setIsCollapsed] = useState(false)
const navBarItems = [ const navBarItems = [
{ {
@@ -103,13 +155,28 @@ export default function SideBar(): ReactElement {
] ]
return ( return (
<Drawer className={classes.drawer} variant="permanent" anchor="left" classes={{ paper: classes.drawerPaper }}> <Drawer
<Grid container direction="column" justifyContent="space-between" className={classes.root}> className={`${classes.drawer} ${isCollapsed ? classes.drawerCollapsed : ''}`}
<Grid className={classes.logo}> variant="permanent"
<Link to={ROUTES.INFO}> anchor="left"
<img alt="swarm" src={isDesktop ? DesktopLogo : DashboardLogo} /> classes={{ paper: `${classes.drawerPaper} ${isCollapsed ? classes.drawerPaperCollapsed : ''}` }}
>
<div className={classes.header}>
{!isCollapsed && (
<Link to={ROUTES.INFO} className={classes.logo}>
<img alt="swarm" className={classes.logoImg} src={isDesktop ? DesktopLogo : DashboardLogo} />
</Link> </Link>
</Grid> )}
<Tooltip title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'} placement="right">
<IconButton
onClick={() => setIsCollapsed(!isCollapsed)}
className={`${classes.toggleButton} ${isCollapsed ? classes.toggleButtonCollapsed : ''}`}
>
{isCollapsed ? <MenuUnfoldIcon /> : <MenuFoldIcon />}
</IconButton>
</Tooltip>
</div>
<Grid container direction="column" justifyContent="space-between" className={classes.root}>
<Grid> <Grid>
<List> <List>
{navBarItems.map(p => ( {navBarItems.map(p => (
@@ -120,6 +187,7 @@ export default function SideBar(): ReactElement {
path={p.path} path={p.path}
pathMatcherSubstring={p.pathMatcherSubstring} pathMatcherSubstring={p.pathMatcherSubstring}
label={p.label} label={p.label}
isCollapsed={isCollapsed}
/> />
</Link> </Link>
))} ))}
@@ -129,8 +197,9 @@ export default function SideBar(): ReactElement {
<MUILink href={BEE_DOCS_HOST} target="_blank" className={classes.link}> <MUILink href={BEE_DOCS_HOST} target="_blank" className={classes.link}>
<SideBarItem <SideBarItem
iconStart={<DocsIcon className={classes.icon} />} iconStart={<DocsIcon className={classes.icon} />}
iconEnd={<ExternalLinkIcon className={classes.icon} color="#595959" />} iconEnd={!isCollapsed ? <ExternalLinkIcon className={classes.icon} color="#595959" /> : undefined}
label={<span>Docs</span>} label={<span>Docs</span>}
isCollapsed={isCollapsed}
/> />
</MUILink> </MUILink>
</List> </List>
@@ -143,22 +212,25 @@ export default function SideBar(): ReactElement {
> >
<SideBarItem <SideBarItem
iconStart={<GithubIcon className={classes.icon} />} iconStart={<GithubIcon className={classes.icon} />}
iconEnd={<ExternalLinkIcon className={classes.icon} color="#595959" />} iconEnd={!isCollapsed ? <ExternalLinkIcon className={classes.icon} color="#595959" /> : undefined}
label={<span>GitHub</span>} label={<span>GitHub</span>}
isCollapsed={isCollapsed}
/> />
</MUILink> </MUILink>
</List> </List>
<Divider className={classes.divider} /> <Divider className={classes.divider} />
<Box mt={4}> {!isCollapsed && (
<Link to={ROUTES.TOP_UP_GIFT_CODE}> <Box mt={4}>
<Typography align="center">Redeem gift code</Typography> <Link to={ROUTES.TOP_UP_GIFT_CODE}>
</Link> <Typography align="center">Redeem gift code</Typography>
</Box> </Link>
</Box>
)}
</Grid> </Grid>
<Grid> <Grid>
<List> <List>
<Link to={ROUTES.STATUS} className={classes.link}> <Link to={ROUTES.STATUS} className={classes.link}>
<SideBarStatus path={ROUTES.STATUS} /> <SideBarStatus path={ROUTES.STATUS} isCollapsed={isCollapsed} />
</Link> </Link>
</List> </List>
</Grid> </Grid>
+33 -7
View File
@@ -1,4 +1,4 @@
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material' import { ListItemButton, ListItemIcon, ListItemText, Tooltip } from '@mui/material'
import type { ReactElement, ReactNode } from 'react' import type { ReactElement, ReactNode } from 'react'
import { matchPath, useLocation } from 'react-router-dom' import { matchPath, useLocation } from 'react-router-dom'
import { makeStyles } from 'tss-react/mui' import { makeStyles } from 'tss-react/mui'
@@ -24,14 +24,24 @@ const useItemStyles = makeStyles()(theme => ({
}, },
}, },
}, },
rootCollapsed: {
justifyContent: 'center',
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
},
})) }))
const useStyles = makeStyles()(theme => ({ const useStyles = makeStyles()(theme => ({
icon: { icon: {
color: 'inherit', color: 'inherit',
minWidth: 0,
}, },
activeIcon: { activeIcon: {
color: theme.palette.primary.main, color: theme.palette.primary.main,
minWidth: 0,
},
label: {
marginLeft: theme.spacing(2),
}, },
})) }))
@@ -41,9 +51,17 @@ interface Props {
path?: string path?: string
label: ReactNode label: ReactNode
pathMatcherSubstring?: string pathMatcherSubstring?: string
isCollapsed?: boolean
} }
export default function SideBarItem({ iconStart, iconEnd, path, label, pathMatcherSubstring }: Props): ReactElement { export default function SideBarItem({
iconStart,
iconEnd,
path,
label,
pathMatcherSubstring,
isCollapsed,
}: Props): ReactElement {
const { classes } = useStyles() const { classes } = useStyles()
const { classes: itemClasses } = useItemStyles() const { classes: itemClasses } = useItemStyles()
const location = useLocation() const location = useLocation()
@@ -52,10 +70,18 @@ export default function SideBarItem({ iconStart, iconEnd, path, label, pathMatch
: Boolean(path && matchPath(location.pathname, path)) : Boolean(path && matchPath(location.pathname, path))
return ( return (
<ListItemButton className={itemClasses.root} selected={isSelected} disableRipple> <Tooltip title={isCollapsed ? label : ''} placement="right">
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconStart}</ListItemIcon> <ListItemButton
<ListItemText primary={label} /> className={`${itemClasses.root} ${isCollapsed ? itemClasses.rootCollapsed : ''}`}
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconEnd}</ListItemIcon> selected={isSelected}
</ListItemButton> disableRipple
>
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconStart}</ListItemIcon>
{!isCollapsed && <ListItemText primary={label} className={classes.label} />}
{!isCollapsed && iconEnd && (
<ListItemIcon className={isSelected ? classes.activeIcon : classes.icon}>{iconEnd}</ListItemIcon>
)}
</ListItemButton>
</Tooltip>
) )
} }
+48 -17
View File
@@ -1,4 +1,4 @@
import { ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material' import { ListItemButton, ListItemIcon, ListItemText, Tooltip, Typography } from '@mui/material'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import { matchPath, useLocation } from 'react-router-dom' import { matchPath, useLocation } from 'react-router-dom'
import ArrowRight from 'remixicon-react/ArrowRightLineIcon' import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
@@ -18,8 +18,8 @@ const useStyles = makeStyles()(theme => ({
root: { root: {
height: theme.spacing(4), height: theme.spacing(4),
paddingLeft: theme.spacing(1), paddingLeft: theme.spacing(0),
paddingRight: theme.spacing(4), paddingRight: theme.spacing(0),
color: '#f9f9f9', color: '#f9f9f9',
borderLeft: '0px solid rgba(0,0,0,0)', borderLeft: '0px solid rgba(0,0,0,0)',
'&.Mui-selected, &.Mui-selected:hover': { '&.Mui-selected, &.Mui-selected:hover': {
@@ -46,31 +46,62 @@ const useStyles = makeStyles()(theme => ({
fontSize: '0.9rem', fontSize: '0.9rem',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}, },
rootCollapsed: {
justifyContent: 'center',
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
},
statusIcon: {
marginLeft: '30px',
minWidth: 0,
},
statusIconCollapsed: {
marginLeft: 0,
minWidth: 0,
},
statusText: {
marginLeft: theme.spacing(2),
},
})) }))
interface Props { interface Props {
path?: string path?: string
isCollapsed?: boolean
} }
export default function SideBarItem({ path }: Props): ReactElement { export default function SideBarItem({ path, isCollapsed }: Props): ReactElement {
const { status, isLoading } = useContext(Context) const { status, isLoading } = useContext(Context)
const { classes } = useStyles() const { classes } = useStyles()
const location = useLocation() const location = useLocation()
const isSelected = Boolean(path && matchPath(location.pathname, path)) const isSelected = Boolean(path && matchPath(location.pathname, path))
return ( return (
<ListItemButton <Tooltip title={isCollapsed ? `Node ${status.all}` : ''} placement="right">
classes={{ root: `${classes.root} ${status.all ? '' : classes.rootError}` }} <ListItemButton
selected={isSelected} classes={{
disableRipple root: `${classes.root} ${status.all ? '' : classes.rootError} ${isCollapsed ? classes.rootCollapsed : ''}`,
> }}
<ListItemIcon style={{ marginLeft: '30px' }}> selected={isSelected}
<StatusIcon checkState={status.all} isLoading={isLoading} /> disableRipple
</ListItemIcon> >
<ListItemText primary={<Typography className={classes.smallerText}>{`Node ${status.all}`}</Typography>} /> <ListItemIcon className={isCollapsed ? classes.statusIconCollapsed : classes.statusIcon}>
<ListItemIcon className={classes.icon}> <StatusIcon checkState={status.all} isLoading={isLoading} />
{status.all ? null : <ArrowRight className={classes.iconSmall} />} </ListItemIcon>
</ListItemIcon> {!isCollapsed && (
</ListItemButton> <>
<ListItemText
primary={
<Typography
className={`${classes.smallerText} ${classes.statusText}`}
>{`Node ${status.all}`}</Typography>
}
/>
<ListItemIcon className={classes.icon}>
{status.all ? null : <ArrowRight className={classes.iconSmall} />}
</ListItemIcon>
</>
)}
</ListItemButton>
</Tooltip>
) )
} }