mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: nav layout
This commit is contained in:
parent
2c0bf3a8c5
commit
677e4d9ef5
14
package-lock.json
generated
14
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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);
|
||||||
@ -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);
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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 ? (
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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[];
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user