refactor: nav layout

This commit is contained in:
Sergey Yarkov 2021-12-06 22:25:01 +03:00
parent 2c0bf3a8c5
commit 677e4d9ef5
10 changed files with 79 additions and 89 deletions

14
package-lock.json generated
View File

@ -1337,11 +1337,6 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
}, },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001267", "version": "1.0.30001267",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz",
@ -2155,12 +2150,9 @@
"integrity": "sha512-7XhbCr7d9fDC1TgcK/BUbt7D3q0VJMu7jPErfsa0JrxVjv/nni41xWdJcy0Zb7R+Np8OsCkQ2lMyloAtE3DLiQ==" "integrity": "sha512-7XhbCr7d9fDC1TgcK/BUbt7D3q0VJMu7jPErfsa0JrxVjv/nni41xWdJcy0Zb7R+Np8OsCkQ2lMyloAtE3DLiQ=="
}, },
"react-icons": { "react-icons": {
"version": "3.11.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.11.0.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
"integrity": "sha512-JRgiI/vdF6uyBgyZhVyYJUZAop95Sy4XDe/jmT3R/bKliFWpO/uZBwvSjWEdxwzec7SYbEPNPck0Kff2tUGM2Q==", "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
"requires": {
"camelcase": "^5.0.0"
}
}, },
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",

View File

@ -27,7 +27,7 @@
"react-error-boundary": "^3.1.3", "react-error-boundary": "^3.1.3",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-hook-form": "^7.12.2", "react-hook-form": "^7.12.2",
"react-icons": "^3.11.0", "react-icons": "^4.3.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-spinners": "^0.11.0", "react-spinners": "^0.11.0",
"react-virtuoso": "^2.2.8", "react-virtuoso": "^2.2.8",

View File

@ -1,25 +1,24 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Flex, Box, Heading, useColorMode } from '@chakra-ui/react'; import { Flex, Box } from '@chakra-ui/react';
import { MdSchool } from 'react-icons/md';
import config from '@educt/config';
/** /**
* Components * Components
*/ */
import Logo from '@educt/components/Logo';
import NavMobile from '@educt/components/Nav/Mobile'; import NavMobile from '@educt/components/Nav/Mobile';
import ColorModeSwitcher from '@educt/components/ColorModeSwitcher'; import ColorModeSwitcher from '@educt/components/ColorModeSwitcher';
import UserInfo from './UserInfo'; import ProfileMenu from './ProfileMenu';
import UserNotifications from './UserNotifications'; import NotificationsMenu from './NotificationsMenu';
/** /**
* Hooks * Hooks
*/ */
import { useMediaQuery } from '@chakra-ui/media-query'; import { useMediaQuery } from '@chakra-ui/media-query';
import { useColorModeValue } from '@chakra-ui/color-mode';
const Header: React.FC = () => { const Header: React.FC = () => {
const [isDesktop] = useMediaQuery('(min-width: 992px)'); const [isDesktop] = useMediaQuery('(min-width: 992px)');
const { colorMode } = useColorMode();
return ( return (
<Box <Box
@ -28,10 +27,8 @@ const Header: React.FC = () => {
top={0} top={0}
left={0} left={0}
right={0} right={0}
// borderBottomWidth={1} borderBottomWidth='1px'
// h='43px' backgroundColor={useColorModeValue('white', 'gray.800')}
boxShadow='md'
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
zIndex={4} zIndex={4}
> >
<Flex <Flex
@ -41,25 +38,15 @@ const Header: React.FC = () => {
alignItems='center' alignItems='center'
padding='5px 1.5rem 5px 1.5rem' padding='5px 1.5rem 5px 1.5rem'
> >
<Flex alignItems='center'> <Logo h='10' />
<Box mr='4'> {isDesktop && (
<Box as={MdSchool} color='blue.500' size='36px' />
</Box>
<Box>
<Heading as='p' fontSize='2xl' lineHeight='1'>
{config.metaData.appName}
</Heading>
</Box>
</Flex>
{isDesktop ? (
<Flex> <Flex>
<ColorModeSwitcher /> <ColorModeSwitcher />
<UserNotifications /> <NotificationsMenu />
<UserInfo /> <ProfileMenu />
</Flex> </Flex>
) : (
<NavMobile />
)} )}
{!isDesktop && <NavMobile />}
</Flex> </Flex>
</Box> </Box>
); );

View File

@ -6,7 +6,7 @@ import { MdNotifications } from 'react-icons/md';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
const UserNotifications: React.FC = () => { const NotificationsMenu: React.FC = () => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
if (userStore.me === null) { if (userStore.me === null) {
@ -39,4 +39,4 @@ const UserNotifications: React.FC = () => {
); );
}; };
export default observer(UserNotifications); export default observer(NotificationsMenu);

View File

@ -24,7 +24,7 @@ import UserBadge from '@educt/components/UserBadge';
*/ */
import { useLogout } from '@educt/hooks/queries'; import { useLogout } from '@educt/hooks/queries';
const UserInfo: React.FC = () => { const ProfileMenu: React.FC = () => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { logout } = useLogout(); const { logout } = useLogout();
@ -64,4 +64,4 @@ const UserInfo: React.FC = () => {
); );
}; };
export default observer(UserInfo); export default observer(ProfileMenu);

View File

@ -37,17 +37,10 @@ const Nav: React.FC = () => {
zIndex='1' zIndex='1'
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'} backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
> >
<Flex <Flex as='nav' flexDirection='column' alignItems='flex-start' textDecoration='none' p='32px 24px'>
as='nav' <Flex flexDir='column' pt='3rem' w='full'>
flexDirection='column' <NavList links={config.links} />
alignItems='flex-start' </Flex>
textDecoration='none'
pt='4rem'
pr='5'
pl='5'
pb='5'
>
<NavList links={config.links} />
</Flex> </Flex>
<Box textAlign='center' mt='auto' mb='5'> <Box textAlign='center' mt='auto' mb='5'>
<Text fontSize='xs' color='gray.500' lineHeight='1.6'> <Text fontSize='xs' color='gray.500' lineHeight='1.6'>
@ -55,7 +48,7 @@ const Nav: React.FC = () => {
</Text> </Text>
<Box textAlign='center' mt='2'> <Box textAlign='center' mt='2'>
<IconButton variant='link' aria-label='GitHub repository'> <IconButton variant='link' aria-label='GitHub repository'>
<Link isExternal href='https://github.com/sergeyyarkov/educt-client'> <Link isExternal href={config.metaData.appGithubLink}>
<Box as={FaGithub} /> <Box as={FaGithub} />
</Link> </Link>
</IconButton> </IconButton>

View File

@ -13,6 +13,7 @@ import config from '@educt/config';
* Components * Components
*/ */
import NavList from '../NavList'; import NavList from '../NavList';
import Logo from '@educt/components/Logo';
/** /**
* Hooks * Hooks
@ -37,7 +38,10 @@ const NavMobile: React.FC = () => {
<DrawerContent backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}> <DrawerContent backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}>
<DrawerBody> <DrawerBody>
<Flex flexDirection='column' height='100%'> <Flex flexDirection='column' height='100%'>
<Flex as='nav' flexDirection='column' alignItems='flex-start' textDecoration='none' mb='20px'> <Box mt='2'>
<Logo h='10' ml='2' />
</Box>
<Flex flexDir='column' mt='3' w='full'>
<NavList links={config.links} onCloseDrawer={onClose} /> <NavList links={config.links} onCloseDrawer={onClose} />
</Flex> </Flex>
{me !== null ? ( {me !== null ? (

View File

@ -1,62 +1,67 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { userContainRoles } from '@educt/helpers'; import * as helpers from '@educt/helpers';
import { Box, useColorMode } from '@chakra-ui/react'; import { Flex, Box, Icon, Text } from '@chakra-ui/react';
/**
* Types
*/
import type { LinkType } from '@educt/types'; import type { LinkType } from '@educt/types';
/** /**
* Hooks * Hooks
*/ */
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useColorMode } from '@chakra-ui/react';
type NavLinkPropsType = { type NavLinkPropsType = {
link: LinkType; link: LinkType;
onClickLink: (link: LinkType) => void; onClickLink: (link: LinkType) => void;
}; };
/**
* Returns the link component for navigation.
*/
const NavLink: React.FC<NavLinkPropsType> = ({ link, onClickLink }) => { const NavLink: React.FC<NavLinkPropsType> = ({ link, onClickLink }) => {
const { uiStore, userStore } = useRootStore(); const { uiStore, userStore } = useRootStore();
const { colorMode } = useColorMode(); const { colorMode } = useColorMode();
const isActive = uiStore.location === link.location;
const onClick = () => onClickLink(link); const onClick = () => onClickLink(link);
if (!link.public && link.roles !== undefined) { if (!link.public && link.roles !== undefined) {
const { me } = userStore; const { me } = userStore;
if (me === null) return null; if (me === null) return null;
if (!userContainRoles(me.roles, link.roles)) return null; if (!helpers.userContainRoles(me.roles, link.roles)) return null;
} }
return ( return (
<Box <Box
as='a' as='a'
onClick={onClick} onClick={onClick}
display='flex' display='block'
margin='3px 0'
width='100%'
border='none'
aria-label={link.title} aria-label={link.title}
role='link' fontWeight='medium'
borderRadius='md'
mt='0.5rem'
pt='2'
pb='2'
paddingInlineStart='3'
paddingInlineEnd='3'
transition='all 0.3s ease 0s'
lineHeight='1.5rem'
textDecoration='none'
cursor='pointer' cursor='pointer'
outline='transparent solid 2px'
outlineOffset='2px'
color={isActive ? 'white' : ''}
backgroundColor={isActive ? `${colorMode === 'dark' ? 'gray.700' : 'blue.500'}` : ''}
_hover={{
bg: `${colorMode === 'dark' ? 'gray.700' : `${!isActive ? 'gray.100' : ''}`}`,
}}
> >
<Box <Flex flexDir='row' alignItems='center'>
color={uiStore.location === link.location ? 'blue.400' : ''} <Icon as={link.icon} boxSize='21px' />
backgroundColor={uiStore.location === link.location ? `${colorMode === 'dark' ? 'gray.700' : 'gray.100'}` : ''} <Text as='span' ml='16px'>
alignItems='center' {link.title}
borderRadius='3xl' </Text>
display='flex' </Flex>
padding='10px 15px'
fontWeight='500'
transition='all .1s'
_hover={{
bg: `${colorMode === 'dark' ? 'gray.700' : 'gray.100'}`,
color: `blue.400`,
}}
>
<Box as={link.icon} boxSize='26px' mr={3} />
{link.title}
</Box>
</Box> </Box>
); );
}; };

View File

@ -1,5 +1,12 @@
import { UserRoleEnum } from '@educt/enums'; import { UserRoleEnum } from '@educt/enums';
import { MdAccountCircle, MdCollectionsBookmark, MdGroup, MdHome, MdMessage, MdList } from 'react-icons/md'; import {
MdOutlineHome,
MdOutlineMessage,
MdOutlineCollectionsBookmark,
MdOutlineVideoLibrary,
MdOutlineGroup,
MdOutlineAccountCircle,
} from 'react-icons/md';
import { IAppConfig } from './interfaces'; import { IAppConfig } from './interfaces';
import packageJson from '../package.json'; import packageJson from '../package.json';
@ -11,28 +18,29 @@ const config: IAppConfig = {
appName: 'Educt', appName: 'Educt',
appDescription: 'Learning management system', appDescription: 'Learning management system',
appVersion: packageJson.version, appVersion: packageJson.version,
appGithubLink: 'https://github.com/sergeyyarkov/educt-client',
}, },
/** /**
* Links that are rendered in the navigation * Links that are rendered in the navigation
*/ */
links: [ links: [
{ location: '/', title: 'Main', public: true, icon: MdHome }, { location: '/', title: 'Main', public: true, icon: MdOutlineHome },
{ location: '/courses', title: 'Courses', public: true, icon: MdCollectionsBookmark }, { location: '/courses', title: 'Courses', public: true, icon: MdOutlineCollectionsBookmark },
{ {
location: '/lessons', location: '/lessons',
title: 'Lessons', title: 'Lessons',
public: false, public: false,
icon: MdList, icon: MdOutlineVideoLibrary,
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
}, },
{ location: '/messages', title: 'Messages', public: true, icon: MdMessage }, { location: '/messages', title: 'Messages', public: true, icon: MdOutlineMessage },
{ location: '/profile', title: 'Profile', public: true, icon: MdAccountCircle }, { location: '/profile', title: 'Profile', public: true, icon: MdOutlineAccountCircle },
{ {
location: '/users', location: '/users',
title: 'Users', title: 'Users',
public: false, public: false,
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
icon: MdGroup, icon: MdOutlineGroup,
}, },
], ],
}; };

View File

@ -7,6 +7,7 @@ export interface IAppConfig {
appName: string; appName: string;
appDescription: string; appDescription: string;
appVersion: string; appVersion: string;
appGithubLink: string;
}; };
links: LinkType[]; links: LinkType[];
} }