Merge pull request #57 from spotify/alund/new-nav

New collapsing navigation
This commit is contained in:
Stefan Ålund
2020-02-06 09:52:57 +01:00
committed by GitHub
3 changed files with 219 additions and 52 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ const useStyles = makeStyles(theme => ({
root: {
display: 'grid',
// FIXME: Don't used a fixed width here
gridTemplateColumns: '224px auto',
gridTemplateColumns: '64px auto',
gridTemplateRows: 'auto 1fr',
width: '100%',
height: '100vh',
@@ -1,60 +1,221 @@
import Avatar from '@material-ui/core/Avatar';
import Grid from '@material-ui/core/Grid';
import Link from '@material-ui/core/Link';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import React, { FC } from 'react';
import React, { FC, useState, createContext, useContext } from 'react';
import clsx from 'clsx';
import {
makeStyles,
SvgIcon,
Typography,
Link,
styled,
} from '@material-ui/core';
import HomeIcon from '@material-ui/icons/Home';
import ServiceIcon from '@material-ui/icons/DeviceHub';
import WebIcon from '@material-ui/icons/Language';
import LibIcon from '@material-ui/icons/LocalLibrary';
import CreateIcon from '@material-ui/icons/AddCircleOutline';
const drawerWidthClosed = 64;
const drawerWidthOpen = 220;
const Context = createContext<boolean>(false);
const useSidebarItemStyles = makeStyles({
root: {
color: '#b5b5b5',
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
height: 40,
cursor: 'pointer',
},
closed: {
width: drawerWidthClosed,
justifyContent: 'center',
},
open: {
width: drawerWidthOpen,
},
iconContainer: {
height: '100%',
width: drawerWidthClosed,
marginRight: -16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
});
type SidebarItemProps = {
icon: typeof SvgIcon;
text: string;
to?: string;
onClick?: () => void;
};
const SidebarItem: FC<SidebarItemProps> = ({
icon: Icon,
text,
to,
onClick,
}) => {
const classes = useSidebarItemStyles();
const open = useContext(Context);
if (!open) {
return (
<Link
className={clsx(classes.root, classes.closed)}
href={to}
onClick={onClick}
underline="none"
>
<Icon fontSize="small" />
</Link>
);
}
return (
<Link
className={clsx(classes.root, classes.open)}
href={to}
onClick={onClick}
underline="none"
>
<div className={classes.iconContainer}>
<Icon fontSize="small" />
</div>
<Typography variant="subtitle2">{text}</Typography>
</Link>
);
};
const useSidebarLogoStyles = makeStyles({
root: {
height: drawerWidthClosed,
color: '#fff',
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
},
logoContainer: {
width: drawerWidthClosed,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
logo: {
fontSize: 32,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginLeft: 22,
whiteSpace: 'nowrap',
},
titleDot: {
color: '#1DB954',
},
});
const SidebarLogo: FC<{}> = () => {
const classes = useSidebarLogoStyles();
const open = useContext(Context);
return (
<div className={classes.root}>
<Typography variant="h6" color="inherit" className={classes.title}>
{open ? 'Backstage' : 'B'}
<span className={classes.titleDot}>.</span>
</Typography>
</div>
);
};
const Space = styled('div')({
flex: 1,
});
const Spacer = styled('div')({
height: 8,
});
const Divider = styled('hr')({
height: 1,
width: '100%',
background: '#383838',
border: 'none',
});
const useStyles = makeStyles(theme => ({
root: {
background: '#181818',
color: 'white',
zIndex: 1000,
position: 'relative',
overflow: 'visible',
width: theme.spacing(7) + 1,
height: '100vh',
paddingLeft: theme.spacing(2),
paddingTop: theme.spacing(2),
},
drawer: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'flex-start',
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
padding: 0,
background: '#171717',
overflowX: 'hidden',
width: drawerWidthClosed,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
},
drawerOpen: {
width: drawerWidthOpen,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shorter,
}),
},
}));
const SideBar: FC<{}> = () => {
const classes = useStyles();
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<Link href="#" variant="body1">
[Co-brand Logo]
</Link>
<List component="nav">
<ListItem button>
<ListItemText primary="Home" />
</ListItem>
<ListItem button>
<ListItemText primary="Services" />
</ListItem>
<ListItem button>
<ListItemText primary="Libraries" />
</ListItem>
<ListItem button>
<ListItemText primary="Websites" />
</ListItem>
<ListItem button>
<ListItemText primary="+ Create..." />
</ListItem>
</List>
<Grid
container
direction="row"
justify="flex-start"
alignItems="center"
spacing={2}
>
<Grid item>
<Avatar>A</Avatar>
</Grid>
<Typography variant="caption">$userName</Typography>
</Grid>
<div
className={classes.root}
onMouseEnter={handleOpen}
onFocus={handleOpen}
onMouseLeave={handleClose}
onBlur={handleClose}
data-testid="sidebar-root"
>
<Context.Provider value={open}>
<div className={clsx(classes.drawer, { [classes.drawerOpen]: open })}>
<SidebarLogo />
<Spacer />
<Divider />
<SidebarItem icon={HomeIcon} to="/" text="Home" />
<Divider />
<SidebarItem icon={ServiceIcon} to="/services" text="Services" />
<SidebarItem icon={WebIcon} to="/websites" text="Websites" />
<SidebarItem icon={LibIcon} to="/libraries" text="Libraries" />
<Divider />
<Space />
<SidebarItem icon={CreateIcon} text="Create..." />
</div>
</Context.Provider>
</div>
);
};
@@ -26,9 +26,9 @@ class Header extends Component {
return typeLink ? (
// <Link to={typeLink}>
<Typography className={classes.type}>{type}</Typography>
// </Link>
<Typography className={classes.type}>{type}</Typography>
) : (
// </Link>
<Typography className={classes.type}>{type}</Typography>
);
}
@@ -69,12 +69,17 @@ class Header extends Component {
const { title, pageTitleOverride, children, style, classes } = this.props;
const pageTitle = pageTitleOverride || title;
if (typeof pageTitle !== 'string') {
console.warn('<Header/> title prop is not a string, pageTitleOverride should be provided.');
console.warn(
'<Header/> title prop is not a string, pageTitleOverride should be provided.',
);
}
return (
<Fragment>
<Helmet titleTemplate={`${pageTitle} | %s | Backstage`} defaultTitle={`${pageTitle} | Backstage`} />
<Helmet
titleTemplate={`${pageTitle} | %s | Backstage`}
defaultTitle={`${pageTitle} | Backstage`}
/>
<Theme.Consumer>
{theme => (
<header style={style} className={classes.header}>
@@ -98,7 +103,7 @@ const styles = theme => ({
gridArea: 'pageHeader',
padding: theme.spacing(3),
minHeight: 118,
width: 'calc(100vw - 224px)',
width: '100vw',
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
position: 'relative',
zIndex: 100,
@@ -117,6 +122,7 @@ const styles = theme => ({
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
marginRight: theme.spacing(6),
},
title: {
color: theme.palette.bursts.fontColor,