Fix menu list on mobile

* add ahooks library
* add useAppDispatch and useAppSelector hooks
* move out router event handle from useEffect
* fix router events off
This commit is contained in:
DefectingCat
2021-11-25 15:54:35 +08:00
parent b17c365063
commit 4db3db2a24
8 changed files with 144 additions and 70 deletions

6
app/hooks.ts Normal file
View File

@ -0,0 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@ -1,4 +1,4 @@
import { FC, MouseEvent, useEffect } from 'react'; import { FC, MouseEvent, useRef } from 'react';
import { import {
Box, Box,
Image, Image,
@ -13,9 +13,8 @@ import {
Button, Button,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import UseAnimations from 'react-useanimations'; import UseAnimations from 'react-useanimations';
import menu2 from 'react-useanimations/lib/menu2'; import menu3 from 'react-useanimations/lib/menu3';
import { FiHome, FiArchive, FiUser, FiSun, FiMoon } from 'react-icons/fi'; import { FiHome, FiArchive, FiUser, FiSun, FiMoon } from 'react-icons/fi';
import Link from 'next/link';
import useGetColors from '../lib/hooks/useGetColors'; import useGetColors from '../lib/hooks/useGetColors';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@ -40,28 +39,68 @@ const menu = [
}, },
]; ];
interface MenuListProps {
boxBg: string;
handleMenuClick: (
// eslint-disable-next-line no-unused-vars
e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
// eslint-disable-next-line no-unused-vars
path: string
) => void;
}
const MenuList: FC<MenuListProps> = ({ 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((item) => {
return (
<Flex
onClick={(e) => handleMenuClick(e, item.path)}
href={item.path}
key={item.id}
as="a"
alignItems="center"
justifyContent={['unset', null, 'space-between']}
fontSize={['18', null, '22']}
my={['0.5rem', null, '0.5rem']}
cursor="pointer"
>
<Icon as={item.icon} mr={['2.5rem', null, 'unset']} />
<Text>{item.name}</Text>
</Flex>
);
})}
</Flex>
);
};
const NavBar: FC = () => { const NavBar: FC = () => {
const router = useRouter(); const router = useRouter();
const [isLargerThan768] = useMediaQuery('(min-width: 768px)'); const [isLargerThan768] = useMediaQuery('(min-width: 768px)');
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
const { colorMode, toggleColorMode } = useColorMode(); const { colorMode, toggleColorMode } = useColorMode();
const { boxBg, bioColor, textColor, headingColor } = useGetColors(); const { boxBg, bioColor, textColor, headingColor } = useGetColors();
useEffect(() => { const iconRef = useRef<HTMLDivElement>(null);
if ((isOpen && !isLargerThan768) || (!isOpen && isLargerThan768)) // Switch pages
onToggle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLargerThan768]);
const handleMenuClick = ( const handleMenuClick = (
e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
path: string path: string
) => { ) => {
e.preventDefault(); e.preventDefault();
!isLargerThan768 && onToggle(); !isLargerThan768 &&
// Click the animate icon.
(iconRef.current?.children[0] as HTMLDivElement).click();
router.push(path); router.push(path);
}; };
@ -124,36 +163,19 @@ const NavBar: FC = () => {
{/* Menu */} {/* Menu */}
<Box mx={['-2rem', '-4rem', 'unset']}> <Box mx={['-2rem', '-4rem', 'unset']}>
<Collapse in={isOpen} animateOpacity> {/* Mobile menu */}
<Flex <Box
as="nav" as={Collapse}
mt={['1.5rem', null]} in={isOpen}
flexFlow={['column']} animateOpacity
py={['1rem', null, 'unset']} display={[null, null, 'none']}
px={['2rem', '4rem', 'unset']} >
bg={[boxBg, null, 'unset']} <MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} />
shadow={['card', null, 'unset']} </Box>
> {/* Desktop menu */}
{menu.map((item) => { <Box display={['none', 'none', 'block']}>
return ( <MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} />
<Flex </Box>
onClick={(e) => handleMenuClick(e, item.path)}
href={item.path}
key={item.id}
as="a"
alignItems="center"
justifyContent={['unset', null, 'space-between']}
fontSize={['18', null, '22']}
my={['0.5rem', null, '0.5rem']}
cursor="pointer"
>
<Icon as={item.icon} mr={['2.5rem', null, 'unset']} />
<Text>{item.name}</Text>
</Flex>
);
})}
</Flex>
</Collapse>
</Box> </Box>
<Box <Box
@ -161,13 +183,14 @@ const NavBar: FC = () => {
position={'absolute'} position={'absolute'}
top={'1.5rem'} top={'1.5rem'}
right={'1rem'} right={'1rem'}
onClick={onToggle} ref={iconRef}
> >
<UseAnimations <UseAnimations
reverse={isOpen} reverse={isOpen}
size={40} size={40}
animation={menu2} animation={menu3}
speed={1.5} speed={2}
onClick={onToggle}
/> />
</Box> </Box>
</Box> </Box>

View File

@ -4,7 +4,7 @@ import { Box, Flex, Heading, Text, Link } from '@chakra-ui/react';
import { AllPostsData } from '../lib/posts'; import { AllPostsData } from '../lib/posts';
import { Icon, Image } from '@chakra-ui/react'; import { Icon, Image } from '@chakra-ui/react';
import { FiCalendar, FiTag } from 'react-icons/fi'; import { FiCalendar, FiTag } from 'react-icons/fi';
import { useDispatch } from 'react-redux'; import { useAppDispatch } from '../app/hooks';
import { setFromPath } from '../features/router/routerSlice'; import { setFromPath } from '../features/router/routerSlice';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import useGetColors from '../lib/hooks/useGetColors'; import useGetColors from '../lib/hooks/useGetColors';
@ -14,7 +14,7 @@ interface Props {
} }
const PostCard: FC<Props> = ({ post }) => { const PostCard: FC<Props> = ({ post }) => {
const dispatch = useDispatch(); const dispatch = useAppDispatch();
const router = useRouter(); const router = useRouter();
const goToPost: MouseEventHandler<HTMLAnchorElement> = (e) => { const goToPost: MouseEventHandler<HTMLAnchorElement> = (e) => {

View File

@ -5,6 +5,7 @@ export interface RouterState {
} }
const initialState: RouterState = { const initialState: RouterState = {
// Record the from path when into the post page.
fromPath: '', fromPath: '',
}; };

View File

@ -20,6 +20,7 @@
"@emotion/styled": "^11", "@emotion/styled": "^11",
"@giscus/react": "^1.0.1", "@giscus/react": "^1.0.1",
"@reduxjs/toolkit": "^1.6.2", "@reduxjs/toolkit": "^1.6.2",
"ahooks": "^2.10.12",
"autoprefixer": "^10.3.7", "autoprefixer": "^10.3.7",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
"framer-motion": "^5.0.2", "framer-motion": "^5.0.2",

View File

@ -26,24 +26,23 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const router = useRouter(); const router = useRouter();
const handleRouteChange = (
url: string,
{ shallow }: { shallow: 'with' | 'without' }
) => {
console.log(
`App is changing to ${url} ${
shallow ? 'with' : 'without'
} shallow routing`
);
};
const handleStart = () => {
NProgress.start();
};
const handleStop = () => {
NProgress.done();
};
useEffect(() => { useEffect(() => {
const handleRouteChange = (
url: string,
{ shallow }: { shallow: 'with' | 'without' }
) => {
console.log(
`App is changing to ${url} ${
shallow ? 'with' : 'without'
} shallow routing`
);
};
const handleStart = () => {
NProgress.start();
};
const handleStop = () => {
NProgress.done();
};
router.events.on('routeChangeStart', handleRouteChange); router.events.on('routeChangeStart', handleRouteChange);
router.events.on('routeChangeStart', handleStart); router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeStart', handleStop); router.events.on('routeChangeStart', handleStop);
@ -52,8 +51,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// from the event with the `off` method: // from the event with the `off` method:
return () => { return () => {
router.events.off('routeChangeStart', handleRouteChange); router.events.off('routeChangeStart', handleRouteChange);
router.events.on('routeChangeStart', handleStart); router.events.off('routeChangeStart', handleStart);
router.events.on('routeChangeStart', handleStop); router.events.off('routeChangeStart', handleStop);
}; };
}, [router.events]); }, [router.events]);

View File

@ -33,11 +33,11 @@ import { useRouter } from 'next/router';
import Footer from '../../components/Footer'; import Footer from '../../components/Footer';
import { Giscus } from '@giscus/react'; import { Giscus } from '@giscus/react';
import { RootState } from '../../app/store'; import { RootState } from '../../app/store';
import { useSelector, useDispatch } from 'react-redux';
import { cleanFromPath } from '../../features/router/routerSlice'; import { cleanFromPath } from '../../features/router/routerSlice';
import CopyButton from '../../components/post/CopyButton'; import CopyButton from '../../components/post/CopyButton';
import useGetColors from '../../lib/hooks/useGetColors'; import useGetColors from '../../lib/hooks/useGetColors';
import PostImage from '../../components/post/PostImage'; import PostImage from '../../components/post/PostImage';
import { useAppSelector, useAppDispatch } from '../../app/hooks';
export async function getStaticPaths() { export async function getStaticPaths() {
const paths = await getAllPostSlugs(); const paths = await getAllPostSlugs();
@ -57,8 +57,8 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
}; };
const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => { const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => {
const fromPath = useSelector((state: RootState) => state.router.fromPath); const fromPath = useAppSelector((state: RootState) => state.router.fromPath);
const dispatch = useDispatch(); const dispatch = useAppDispatch();
const router = useRouter(); const router = useRouter();
const goBack = () => { const goBack = () => {

View File

@ -2,6 +2,14 @@
# yarn lockfile v1 # yarn lockfile v1
"@ahooksjs/use-request@^2.8.13":
version "2.8.13"
resolved "https://registry.npmjs.org/@ahooksjs/use-request/-/use-request-2.8.13.tgz#5ace53859feb6b4fe9ebcbf2e72982bb9b7db383"
integrity sha512-zGSOwGNy6fTudvBcLJ8Ly7DwacEGzQS06fGe1b1mEPWTsK/R6y8ItIbtE2RXTeMoCpbxagamMWo5ZDJkJlmZ6Q==
dependencies:
lodash.debounce "^4.0.8"
lodash.throttle "^4.1.1"
"@babel/code-frame@7.12.11": "@babel/code-frame@7.12.11":
version "7.12.11" version "7.12.11"
resolved "https://registry.npmmirror.com/@babel/code-frame/download/@babel/code-frame-7.12.11.tgz?cache=0&sync_timestamp=1633554562995&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" resolved "https://registry.npmmirror.com/@babel/code-frame/download/@babel/code-frame-7.12.11.tgz?cache=0&sync_timestamp=1633554562995&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@ -1290,6 +1298,22 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0" clean-stack "^2.0.0"
indent-string "^4.0.0" indent-string "^4.0.0"
ahooks@^2.10.12:
version "2.10.12"
resolved "https://registry.npmjs.org/ahooks/-/ahooks-2.10.12.tgz#e8cab653039434279e69569a8342c602b3545dab"
integrity sha512-X3nZwr6+CSi1XjGyd/tQU1vF11mNaP4Bo0pdEdUL2ksBURQl0QZvDeUMrKBYHAStq7852P20ycVgu405XxDlFg==
dependencies:
"@ahooksjs/use-request" "^2.8.13"
"@types/js-cookie" "^2.2.6"
dayjs "^1.9.1"
intersection-observer "^0.7.0"
js-cookie "^2.2.1"
lodash.debounce "^4.0.8"
lodash.isequal "^4.5.0"
lodash.throttle "^4.1.1"
resize-observer-polyfill "^1.5.1"
screenfull "^5.0.0"
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.nlark.com/ajv/download/ajv-6.12.6.tgz?cache=0&sync_timestamp=1631470912358&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fajv%2Fdownload%2Fajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.nlark.com/ajv/download/ajv-6.12.6.tgz?cache=0&sync_timestamp=1631470912358&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fajv%2Fdownload%2Fajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -2187,7 +2211,7 @@ date-fns@^2.25.0:
resolved "https://registry.npmmirror.com/date-fns/download/date-fns-2.25.0.tgz?cache=0&sync_timestamp=1633421929609&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdate-fns%2Fdownload%2Fdate-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" resolved "https://registry.npmmirror.com/date-fns/download/date-fns-2.25.0.tgz?cache=0&sync_timestamp=1633421929609&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdate-fns%2Fdownload%2Fdate-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680"
integrity sha1-jFyPHZWL44CamgP0t0LrqJT8VoA= integrity sha1-jFyPHZWL44CamgP0t0LrqJT8VoA=
dayjs@^1.10.4: dayjs@^1.10.4, dayjs@^1.9.1:
version "1.10.7" version "1.10.7"
resolved "https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz?cache=0&sync_timestamp=1631266651008&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdayjs%2Fdownload%2Fdayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" resolved "https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz?cache=0&sync_timestamp=1631266651008&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdayjs%2Fdownload%2Fdayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg= integrity sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg=
@ -3532,6 +3556,11 @@ internal-slot@^1.0.3:
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.4" side-channel "^1.0.4"
intersection-observer@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.7.0.tgz#ee16bee978db53516ead2f0a8154b09b400bbdc9"
integrity sha512-Id0Fij0HsB/vKWGeBe9PxeY45ttRiBmhFyyt/geBdDHBYNctMRTE3dC1U3ujzz3lap+hVXlEcVaB56kZP/eEUg==
invariant@^2.2.4: invariant@^2.2.4:
version "2.2.4" version "2.2.4"
resolved "https://registry.nlark.com/invariant/download/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" resolved "https://registry.nlark.com/invariant/download/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -3965,6 +3994,16 @@ locate-path@^5.0.0:
dependencies: dependencies:
p-locate "^4.1.0" p-locate "^4.1.0"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.merge@^4.6.2: lodash.merge@^4.6.2:
version "4.6.2" version "4.6.2"
resolved "https://registry.npm.taobao.org/lodash.merge/download/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.npm.taobao.org/lodash.merge/download/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@ -3985,6 +4024,11 @@ lodash.sortby@^4.7.0:
resolved "https://registry.npm.taobao.org/lodash.sortby/download/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" resolved "https://registry.npm.taobao.org/lodash.sortby/download/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.truncate@^4.4.2: lodash.truncate@^4.4.2:
version "4.4.2" version "4.4.2"
resolved "https://registry.nlark.com/lodash.truncate/download/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.nlark.com/lodash.truncate/download/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@ -5682,7 +5726,7 @@ scheduler@^0.20.2:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
screenfull@^5.1.0: screenfull@^5.0.0, screenfull@^5.1.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.npmmirror.com/screenfull/download/screenfull-5.2.0.tgz?cache=0&sync_timestamp=1635923453416&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fscreenfull%2Fdownload%2Fscreenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" resolved "https://registry.npmmirror.com/screenfull/download/screenfull-5.2.0.tgz?cache=0&sync_timestamp=1635923453416&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fscreenfull%2Fdownload%2Fscreenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
integrity sha1-ZTPVJNMGIfwSg7lpIUbz8TqT0bo= integrity sha1-ZTPVJNMGIfwSg7lpIUbz8TqT0bo=