Files
DefectingCat.github.io/components/NavBar.tsx
2022-01-11 16:07:40 +08:00

242 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { FC, MouseEvent, useCallback, useRef } from 'react';
import {
Box,
Image,
Text,
Heading,
Flex,
Icon,
useDisclosure,
Collapse,
useMediaQuery,
useColorMode,
Button,
} from '@chakra-ui/react';
import UseAnimations from 'react-useanimations';
import menu3 from 'react-useanimations/lib/menu3';
import {
FiHome,
FiArchive,
FiUser,
FiSun,
FiMoon,
FiSearch,
} from 'react-icons/fi';
import useGetColors from 'lib/hooks/useGetColors';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
import { IconType } from 'react-icons';
import React from 'react';
const Search = dynamic(() => import('./search'));
const ImageSpinner = dynamic(() => import('components/ImageSpinner'));
const NavItem = dynamic(() => import('components/nav/NavItem'));
export type MenuItem = {
id: number;
name: string;
path?: string;
icon: IconType;
};
const menu: MenuItem[] = [
{
id: 0,
name: '首页',
path: '/',
icon: FiHome,
},
{
id: 1,
name: '归档',
path: '/archive',
icon: FiArchive,
},
{
id: 4,
name: '关于',
path: '/about',
icon: FiUser,
},
{
id: 5,
name: '搜索',
icon: FiSearch,
},
];
export type HandleMenuClick = (
// eslint-disable-next-line no-unused-vars
e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
// eslint-disable-next-line no-unused-vars
path?: string
) => void;
interface MenuListProps {
boxBg: string;
handleMenuClick: HandleMenuClick;
}
const MenuList: FC<MenuListProps> = React.memo(function MenuList({
boxBg,
handleMenuClick,
}) {
return (
<Flex
as="nav"
mt={['1.5rem', null]}
flexFlow={['column']}
py={['1rem', null, 'unset']}
px={['2rem', '4rem', 'unset']}
bg={[boxBg, null, 'unset']}
shadow={['card', null, 'unset']}
>
{menu.map((menuItem) => (
<NavItem
key={menuItem.id}
handleMenuClick={handleMenuClick}
menuItem={menuItem}
/>
))}
</Flex>
);
});
const NavBar: FC = () => {
const router = useRouter();
const [isLargerThan640] = useMediaQuery('(min-width: 640px)');
// Menu toggle
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
// Modal toggle
const {
isOpen: isModalOpen,
onOpen: onModalOpen,
onClose: onModalClose,
} = useDisclosure();
const { colorMode, toggleColorMode } = useColorMode();
const { boxBg, bioColor, textColor, headingColor } = useGetColors();
const iconRef = useRef<HTMLDivElement>(null);
// Switch pages
const handleMenuClick = useCallback(
(e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, path?: string) => {
e.preventDefault();
if (path) {
!isLargerThan640 &&
// Click the animate icon.
(iconRef.current?.children[0] as HTMLDivElement).click();
router.push(path);
} else {
onModalOpen();
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<>
<Box
color={textColor}
w={[null, null, '8rem']}
p={['3rem 1rem 1rem 1rem', null, 'unset']}
position={'relative'}
flex={[1, 1, 'unset']}
>
<Box
position={'relative'}
boxSize="8rem"
borderRadius="full"
boxShadow={'card'}
>
{/* avatar */}
<Image
boxSize="8rem"
borderRadius="full"
src="/images/img/avatar.svg"
objectFit={'cover'}
alt="Avatar"
fallback={<ImageSpinner />}
/>
{/* emoji on avatar */}
<Flex
boxShadow="card"
position="absolute"
bottom={0}
right={0}
rounded="full"
bg={boxBg}
h="40px"
w="40px"
justify="center"
alignItems="center"
userSelect="none"
>
</Flex>
</Box>
<Flex
justifyContent="space-between"
alignItems="center"
mt="0.8rem"
mb="0.5rem"
>
<Heading color={headingColor} size={'lg'}>
</Heading>
<Button
background="transparent"
fontSize={['18', null, '22']}
onClick={toggleColorMode}
aria-label="Switch color mode"
>
{colorMode === 'light' ? <Icon as={FiSun} /> : <Icon as={FiMoon} />}
</Button>
</Flex>
{/* Bio or something */}
<Text color={bioColor}>...</Text>
{/* Menu */}
<Box mx={['-2rem', '-4rem', 'unset']}>
{/* Mobile menu (controled by collapse) */}
<Box as={Collapse} in={isOpen} animateOpacity display="none">
<MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} />
</Box>
{/* Desktop menu */}
<Box display={['none', 'none', 'block']}>
<MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} />
</Box>
</Box>
{/* Mobile menu icon */}
<Box
display={[null, null, 'none']}
position={'absolute'}
top={'1.5rem'}
right={'1rem'}
ref={iconRef}
>
<UseAnimations
reverse={isOpen}
size={40}
animation={menu3}
speed={2}
onClick={onToggle}
/>
</Box>
</Box>
{isModalOpen && (
<Search isModalOpen={isModalOpen} onModalClose={onModalClose} />
)}
</>
);
};
export default NavBar;