add AppLayout

This commit is contained in:
Dawid Wysokiński 2021-03-08 20:21:27 +01:00
parent ab2296af13
commit 41f2bb891b
11 changed files with 360 additions and 4 deletions

View File

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import clsx from 'clsx';
import { DRAWER_WIDTH } from './components/Sidebar/contants';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { useMediaQuery, Toolbar } from '@material-ui/core';
import Sidebar from './components/Sidebar/Sidebar';
import TopBar from './components/TopBar/TopBar';
export interface Props {
children?: React.ReactNode;
}
function AppLayout({ children }: Props) {
const [open, setOpen] = useState(false);
const classes = useStyles();
const theme = useTheme();
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'), {
defaultMatches: true,
});
const shouldOpenSidebar = isDesktop ? true : open;
const openSidebar = () => {
if (!isDesktop) {
setOpen(true);
}
};
const closeSidebar = () => {
if (!isDesktop) {
setOpen(false);
}
};
return (
<div
className={clsx({
[classes.shiftContent]: isDesktop,
})}
>
<TopBar openSidebar={open ? closeSidebar : openSidebar} />
<Toolbar />
<Sidebar
onClose={closeSidebar}
open={shouldOpenSidebar}
variant={isDesktop ? 'persistent' : 'temporary'}
onOpen={openSidebar}
/>
<main className={classes.content}>{children}</main>
</div>
);
}
const useStyles = makeStyles(theme => ({
shiftContent: {
paddingLeft: DRAWER_WIDTH,
},
content: {
height: '100%',
padding: theme.spacing(3),
},
}));
export default AppLayout;

View File

@ -0,0 +1,56 @@
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import clsx from 'clsx';
import useStyles from './useStyles';
import { ROUTE } from 'config/routing';
import { Route } from './components/Nav/types';
import { useTheme } from '@material-ui/core/styles';
import { SwipeableDrawer, DrawerProps, Toolbar } from '@material-ui/core';
import { Dashboard as DashboardIcon } from '@material-ui/icons';
import Nav from './components/Nav/Nav';
export interface Props {
className?: string;
open: boolean;
onClose: () => void;
onOpen: () => void;
variant?: DrawerProps['variant'];
}
const Sidebar = ({ className, open, variant, onClose, onOpen }: Props) => {
const classes = useStyles();
const theme = useTheme();
const { pathname } = useLocation();
const routes: Route[] = [
{
name: 'Dashboard',
to: ROUTE.DASHBOARD_PAGE,
Icon: <DashboardIcon color="inherit" />,
exact: true,
},
];
useEffect(() => {
onClose(); // eslint-disable-next-line
}, [pathname]);
return (
<SwipeableDrawer
anchor="left"
classes={{ paper: classes.drawerPaper }}
ModalProps={{ style: { zIndex: theme.zIndex.appBar - 1 } }}
onClose={onClose}
onOpen={onOpen}
open={open}
variant={variant}
>
<Toolbar />
<div className={clsx(classes.root, className)}>
<Nav routes={routes} />
</div>
</SwipeableDrawer>
);
};
export default Sidebar;

View File

@ -0,0 +1,100 @@
import React, { Fragment, useState, memo } from 'react';
import { useLocation, matchPath } from 'react-router-dom';
import { Route } from './types';
import { makeStyles } from '@material-ui/core/styles';
import {
ListItem as MUIListItem,
ListItemIcon,
ListItemText,
Collapse,
List,
} from '@material-ui/core';
import { ExpandLess, ExpandMore } from '@material-ui/icons';
import Link from 'common/Link/Link';
import clsx from 'clsx';
export interface Props {
route: Route;
nestedLevel: number;
}
function ListItem({ route, nestedLevel }: Props) {
const classes = useStyles();
const { pathname } = useLocation();
const hasNested = Array.isArray(route.nested) && route.nested.length > 0;
const isActive =
!!route.to && !!matchPath(pathname, { path: route.to, exact: route.exact });
const [open, setOpen] = useState(
(hasNested && !route.isExpandable) || isActive
);
const getItem = () => {
return (
<MUIListItem
onClick={
hasNested && route.isExpandable ? () => setOpen(!open) : undefined
}
disableGutters
button
style={{ paddingLeft: `${nestedLevel * 8}px` }}
>
<ListItemIcon
className={clsx({
[classes.activeLink]: isActive,
})}
>
{route.Icon}
</ListItemIcon>
<ListItemText
className={clsx({
[classes.activeLink]: isActive,
})}
primary={route.name}
/>
{hasNested && !!route.isExpandable && (
<Fragment>{open ? <ExpandLess /> : <ExpandMore />}</Fragment>
)}
</MUIListItem>
);
};
return (
<Fragment>
{!hasNested && route.to ? (
<Link to={route.to} params={route.params} color="inherit">
{getItem()}
</Link>
) : (
getItem()
)}
{hasNested && (
<Collapse in={open} timeout="auto">
<List component="div" disablePadding>
{route.nested?.map(route => {
return (
<ListItem
route={route}
nestedLevel={nestedLevel + 1}
key={route.name}
/>
);
})}
</List>
</Collapse>
)}
</Fragment>
);
}
const useStyles = makeStyles(theme => ({
link: {
display: 'block',
width: '100%',
},
activeLink: {
color: theme.palette.primary.main,
},
}));
export default memo(ListItem);

View File

@ -0,0 +1,21 @@
import React from 'react';
import { Route } from './types';
import { List } from '@material-ui/core';
import ListItem from './ListItem';
export interface Props {
routes: Route[];
}
const Nav = ({ routes }: Props) => {
return (
<List>
{routes.map(route => (
<ListItem nestedLevel={1} route={route} key={route.name} />
))}
</List>
);
};
export default Nav;

View File

@ -0,0 +1,11 @@
import { Props } from 'common/Link/Link';
export interface Route {
name: string;
to?: string;
exact?: boolean;
params?: Props['params'];
Icon: React.ReactElement;
nested?: Route[];
isExpandable?: boolean;
}

View File

@ -0,0 +1 @@
export const DRAWER_WIDTH = 250;

View File

@ -0,0 +1,18 @@
import { makeStyles } from '@material-ui/core/styles';
import { DRAWER_WIDTH } from './contants';
const useStyles = makeStyles(theme => ({
drawerPaper: {
zIndex: `${theme.zIndex.appBar - 1} !important` as any,
width: DRAWER_WIDTH,
overflowY: 'hidden',
},
root: {
backgroundColor: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
height: '100%',
overflowY: 'auto',
},
}));
export default useStyles;

View File

@ -0,0 +1,77 @@
import React from 'react';
import clsx from 'clsx';
import { useAuth } from 'libs/auth';
import { ROUTE } from 'config/routing';
import { makeStyles } from '@material-ui/core/styles';
import {
AppBar,
Toolbar,
Hidden,
IconButton,
Typography,
Button,
Container,
} from '@material-ui/core';
import { Menu as MenuIcon } from '@material-ui/icons';
import Link from 'common/Link/Link';
export interface Props {
className?: string;
openSidebar?: () => void;
}
const TopBar = ({ className, openSidebar }: Props) => {
const classes = useStyles();
const { signOut } = useAuth();
return (
<AppBar className={clsx(className)}>
<Container maxWidth={false}>
<Toolbar disableGutters className={classes.toolbar}>
<div className={classes.leftSideContainer}>
<Hidden lgUp>
<IconButton color="inherit" onClick={openSidebar}>
<MenuIcon />
</IconButton>
</Hidden>
<Typography variant="h4">
<Link color="inherit" to={ROUTE.DASHBOARD_PAGE}>
Zdam Egzamin Zawodowy
</Link>
</Typography>
</div>
<div className={classes.rightSideContainer}>
<Button color="inherit" onClick={signOut}>
Wyloguj się
</Button>
</div>
</Toolbar>
</Container>
</AppBar>
);
};
const useStyles = makeStyles(theme => ({
toolbar: {
justifyContent: 'space-between',
},
leftSideContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
'& > *': {
marginRight: theme.spacing(1),
},
},
rightSideContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
'& > *:not(:last-child)': {
marginRight: theme.spacing(1),
},
},
}));
export default TopBar;

View File

@ -1,5 +1,7 @@
import AppLayout from 'common/AppLayout/AppLayout';
const DashboardPage = () => {
return <div>DashboardPage</div>;
return <AppLayout>DashboardPage</AppLayout>;
};
export default DashboardPage;

View File

@ -1,7 +1,9 @@
import { useForm } from 'react-hook-form';
import { useSnackbar } from 'material-ui-snackbar-provider';
import { useAuth } from 'libs/auth';
import { ApolloError } from '@apollo/client';
import { useAuth } from 'libs/auth';
import { Role } from '../../config/app';
import { makeStyles } from '@material-ui/core/styles';
import {
Button,
@ -13,9 +15,7 @@ import {
FormGroup,
TextField,
Typography,
Box,
} from '@material-ui/core';
import { Role } from '../../config/app';
type FormData = {
email: string;

View File

@ -14,6 +14,11 @@ const createTheme = (): Theme => {
},
},
},
props: {
MuiLink: {
underline: 'none',
},
},
})
);
};