add AppLayout
This commit is contained in:
parent
ab2296af13
commit
41f2bb891b
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const DRAWER_WIDTH = 250;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,5 +1,7 @@
|
|||
import AppLayout from 'common/AppLayout/AppLayout';
|
||||
|
||||
const DashboardPage = () => {
|
||||
return <div>DashboardPage</div>;
|
||||
return <AppLayout>DashboardPage</AppLayout>;
|
||||
};
|
||||
|
||||
export default DashboardPage;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -14,6 +14,11 @@ const createTheme = (): Theme => {
|
|||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
MuiLink: {
|
||||
underline: 'none',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
Reference in New Issue