Add dark mode state to global state.

* update mobile menu icon dark color
* update post page dark mode
This commit is contained in:
DefectingCat
2022-01-19 17:38:02 +08:00
parent f4ab03e711
commit 7c21f3c607
6 changed files with 58 additions and 19 deletions

View File

@ -5,6 +5,7 @@ import UseAnimations from 'react-useanimations';
import menu3 from 'react-useanimations/lib/menu3'; import menu3 from 'react-useanimations/lib/menu3';
import { IconType } from 'react-icons'; import { IconType } from 'react-icons';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import useDarkMode from 'lib/hooks/useDarkMode';
const NavAvatar = dynamic(() => import('components/nav/NavAvatar')); const NavAvatar = dynamic(() => import('components/nav/NavAvatar'));
const NavMenuItem = dynamic(() => import('components/nav/NavMenuItem')); const NavMenuItem = dynamic(() => import('components/nav/NavMenuItem'));
@ -44,13 +45,15 @@ export const menus: MenuItem[] = [
]; ];
const NavBar: FC = () => { const NavBar: FC = () => {
const { isDark } = useDarkMode();
const [menuIsOpen, setMenuIsOpen] = useState(false); const [menuIsOpen, setMenuIsOpen] = useState(false);
const iconRef = useRef<HTMLDivElement>(null); const iconRef = useRef<HTMLDivElement>(null);
const onToggle = useCallback(() => { const onToggle = useCallback(() => {
setMenuIsOpen((menuIsOpen) => !menuIsOpen); setMenuIsOpen((menuIsOpen) => !menuIsOpen);
}, []); }, []);
// Mobile menu icon must manually clicked to colse when click the menu item. // Mobile menu icon must manually click to colse when click the menu item.
const handleMenuClick = useCallback(() => { const handleMenuClick = useCallback(() => {
(iconRef.current?.children[0] as HTMLDivElement).click(); (iconRef.current?.children[0] as HTMLDivElement).click();
}, []); }, []);
@ -68,6 +71,7 @@ const NavBar: FC = () => {
animation={menu3} animation={menu3}
speed={2} speed={2}
onClick={onToggle} onClick={onToggle}
strokeColor={isDark ? 'white' : 'black'}
/> />
</div> </div>
</div> </div>
@ -78,10 +82,11 @@ const NavBar: FC = () => {
{ 'max-h-[500px]': menuIsOpen }, { 'max-h-[500px]': menuIsOpen },
{ 'py-4': menuIsOpen }, { 'py-4': menuIsOpen },
{ 'max-h-0': !menuIsOpen }, { 'max-h-0': !menuIsOpen },
'bg-white mx-[-1.5rem] px-6 mt-4 overflow-hidden', 'bg-white mx-[-1.5rem] px-6 mt-4 overflow-hidden',
'md:block md:bg-transparent md:mx-0 md:p-0', 'md:block md:bg-transparent md:mx-0 md:p-0',
'transition-all duration-500 h-auto ', 'transition-all duration-500 h-auto ',
'md:max-h-max' 'md:max-h-max',
'dark:bg-rua-gray-800 md:dark:bg-transparent'
)} )}
> >
{menus.map((menu) => ( {menus.map((menu) => (

View File

@ -14,7 +14,12 @@ interface Props {
const PostHeader: FC<Props> = ({ title, tags, date }) => { const PostHeader: FC<Props> = ({ title, tags, date }) => {
return ( return (
<header className={'mb-6'}> <header className={'mb-6'}>
<h1 className={'text-3xl md:text-4xl font-semibold text-gray-700 mb-4'}> <h1
className={cn(
'text-3xl md:text-4xl font-semibold mb-4',
'text-gray-700 dark:text-gray-300'
)}
>
{title} {title}
</h1> </h1>
@ -26,7 +31,7 @@ const PostHeader: FC<Props> = ({ title, tags, date }) => {
key={item} key={item}
className={cn( className={cn(
'rounded-md bg-gray-100 px-2 py-1 inline text-sm', 'rounded-md bg-gray-100 px-2 py-1 inline text-sm',
'text-gray-700 mr-3' 'text-gray-700 mr-3 '
)} )}
> >
{item} {item}
@ -37,7 +42,7 @@ const PostHeader: FC<Props> = ({ title, tags, date }) => {
<div <div
className={cn( className={cn(
'rounded-md bg-gray-100 px-2 py-1 inline text-sm', 'rounded-md bg-gray-100 px-2 py-1 inline text-sm',
'text-gray-700' 'text-gray-700 dark:text-gray-300 dark:bg-gray-500'
)} )}
> >
{tags} {tags}
@ -45,7 +50,9 @@ const PostHeader: FC<Props> = ({ title, tags, date }) => {
)} )}
</div> </div>
<div className={'flex items-center text-gray-600 text-sm'}> <div
className={'flex items-center text-gray-600 dark:text-gray-400 text-sm'}
>
<FiCalendar className={'mr-2'} /> <FiCalendar className={'mr-2'} />
<DateFormater dateString={date} /> <DateFormater dateString={date} />
</div> </div>

View File

@ -1,20 +1,24 @@
import { useEffect, useState } from 'react'; import { ActionKind, useRUAContext } from '../store';
const useDarkMode = () => { const useDarkMode = () => {
const [isDark, setIsDark] = useState(false); const { state, dispatch } = useRUAContext();
useEffect(() => { const { isDark } = state;
setIsDark(document.documentElement.classList.contains('dark'));
}, []);
const toggleDark = () => { const toggleDark = () => {
if (isDark) { if (isDark) {
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark');
localStorage.setItem('rua-theme', 'light'); localStorage.setItem('rua-theme', 'light');
setIsDark(document.documentElement.classList.contains('dark')); dispatch({
type: ActionKind.SETTHEME,
payload: '',
});
} else { } else {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
localStorage.setItem('rua-theme', 'dark'); localStorage.setItem('rua-theme', 'dark');
setIsDark(document.documentElement.classList.contains('dark')); dispatch({
type: ActionKind.SETTHEME,
payload: '',
});
} }
}; };

View File

@ -1,8 +1,9 @@
import React, { FC, useReducer } from 'react'; import React, { FC, useEffect, useReducer } from 'react';
export enum ActionKind { export enum ActionKind {
SETQUERY = 'SETQUERY', SETQUERY = 'SETQUERY',
SETPATH = 'SETPATH', SETPATH = 'SETPATH',
SETTHEME = 'SETTHEME',
} }
type ReducerAction = { type ReducerAction = {
@ -21,11 +22,17 @@ type State = {
* Back path to record where to go back. * Back path to record where to go back.
*/ */
backPath: string; backPath: string;
/**
* Use document.documentElement.classList
*/
isDark: boolean;
}; };
const initialState: State = { const initialState: State = {
searchQuery: '', searchQuery: '',
backPath: '', backPath: '',
isDark: false,
}; };
const reducer = (state: State, action: ReducerAction): State => { const reducer = (state: State, action: ReducerAction): State => {
@ -35,6 +42,11 @@ const reducer = (state: State, action: ReducerAction): State => {
return { ...state, searchQuery: payload }; return { ...state, searchQuery: payload };
case ActionKind.SETPATH: case ActionKind.SETPATH:
return { ...state, backPath: payload }; return { ...state, backPath: payload };
case ActionKind.SETTHEME:
return {
...state,
isDark: document.documentElement.classList.contains('dark'),
};
default: default:
throw new Error(); throw new Error();
} }
@ -51,6 +63,13 @@ const RUAContext = React.createContext<{
const RUAStore: FC = ({ children }) => { const RUAStore: FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: ActionKind.SETTHEME,
payload: '',
});
}, []);
return ( return (
<> <>
<RUAContext.Provider value={{ state, dispatch }}> <RUAContext.Provider value={{ state, dispatch }}>

View File

@ -69,7 +69,7 @@ const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => {
<div <div
className={cn( className={cn(
'grid grid-cols-12 gap-4 p-2 container mx-auto', 'grid grid-cols-12 gap-4 px-2 py-4 container mx-auto',
'md:py-8' 'md:py-8'
)} )}
> >
@ -97,7 +97,11 @@ const Post = ({ postData }: InferGetStaticPropsType<typeof getStaticProps>) => {
</aside> </aside>
<main className={'md:col-span-10 col-span-12 lg:col-span-8'}> <main className={'md:col-span-10 col-span-12 lg:col-span-8'}>
<div className={'bg-white shadow-md rounded-lg overflow-hidden'}> <div
className={
'bg-white dark:bg-rua-gray-800 shadow-md rounded-lg overflow-hidden'
}
>
{index_img && ( {index_img && (
<div className="relative aspect-video"> <div className="relative aspect-video">
<Image <Image

View File

@ -4,7 +4,7 @@
} }
#write { #write {
@apply text-gray-600; @apply text-gray-600 dark:text-gray-300;
} }
#table-of-contents { #table-of-contents {
@ -247,7 +247,7 @@
#write code, #write code,
#write tt { #write tt {
@apply bg-gray-100 rounded-md; @apply bg-gray-100 dark:bg-gray-500 rounded-md;
padding: 0 4px; padding: 0 4px;
font-size: 0.9em; font-size: 0.9em;
} }