From 45b3313163471bca7535390d4c7f16540b6c1ac9 Mon Sep 17 00:00:00 2001 From: Clement Date: Mon, 11 Apr 2022 11:57:06 +0200 Subject: [PATCH] feat(darkMode): store darkMode in Redux store --- src/app/App.tsx | 24 +++++++-------- src/app/DrawerMenu.tsx | 5 ++-- src/app/hooks.ts | 11 ------- src/components/TrackerList/AddTrackerCard.tsx | 8 +++-- .../RequiredCompletionsForm.tsx | 8 +++-- src/config/CustomTheme.ts | 4 ++- src/models/ThemeMode.ts | 6 ++++ src/pages/Trackers.tsx | 8 +++-- src/store/rootReducer.ts | 4 ++- src/store/store.ts | 2 +- src/store/theme/theme.selectors.ts | 7 +++++ src/store/theme/themeSlice.ts | 29 +++++++++++++++++++ 12 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 src/models/ThemeMode.ts create mode 100644 src/store/theme/theme.selectors.ts create mode 100644 src/store/theme/themeSlice.ts diff --git a/src/app/App.tsx b/src/app/App.tsx index 1dafe718..8d53af35 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,7 +1,7 @@ import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import { LocalizationProvider } from '@mui/lab'; import DateAdapter from '@mui/lab/AdapterDateFns'; -import { Container, IconButton, PaletteMode, Paper, useMediaQuery } from '@mui/material'; +import { Container, IconButton, Paper } from '@mui/material'; import { StyledEngineProvider, ThemeProvider, @@ -11,13 +11,17 @@ import { } from '@mui/material/styles'; import frLocale from 'date-fns/locale/fr'; import { SnackbarKey, SnackbarProvider } from 'notistack'; -import { createRef, useEffect, useMemo, useState } from 'react'; +import { createRef, useMemo, useState } from 'react'; import { DRAWER_MENU_WIDTH } from '../config/Constants'; import { components, getPalette, typography } from '../config/CustomTheme'; +import ThemeMode from '../models/ThemeMode'; +import { selectThemeMode } from '../store/theme/theme.selectors'; +import { toggleThemeMode } from '../store/theme/themeSlice'; import AppBar from './AppBar'; import DrawerMenu from './DrawerMenu'; import Router from './Router'; +import { useAppDispatch, useAppSelector } from './hooks'; const MainContent = styled(Paper)(({ theme }) => ({ padding: theme.spacing(2) @@ -31,15 +35,13 @@ const MainContainer = styled(Container)(({ theme }) => ({ // Main component function App() { // Theme configuration - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); - const [themeMode, setThemeMode] = useState(prefersDarkMode ? 'dark' : 'light'); - useEffect(() => { - setThemeMode(prefersDarkMode ? 'dark' : 'light'); - }, [prefersDarkMode]); + const themeMode = useAppSelector(selectThemeMode); + const dispatch = useAppDispatch(); + let theme = useMemo(() => { return createTheme({ components, - palette: getPalette(themeMode), + palette: getPalette(themeMode === ThemeMode.LIGHT ? 'light' : 'dark'), typography: typography }); }, [themeMode]); @@ -56,10 +58,6 @@ function App() { setIsMenuOpen(!isMenuOpen); }; - const toggleThemeMode = () => { - setThemeMode(themeMode === 'light' ? 'dark' : 'light'); - }; - return ( @@ -79,7 +77,7 @@ function App() { width={DRAWER_MENU_WIDTH} open={isMenuOpen} toggleDrawerMenu={toggleDrawerMenu} - toggleThemeMode={toggleThemeMode} + toggleThemeMode={() => dispatch(toggleThemeMode())} /> diff --git a/src/app/DrawerMenu.tsx b/src/app/DrawerMenu.tsx index 557621d4..5632e59d 100644 --- a/src/app/DrawerMenu.tsx +++ b/src/app/DrawerMenu.tsx @@ -18,7 +18,8 @@ import { import { FC } from 'react'; import { Link } from 'react-router-dom'; -import { useThemeMode } from './hooks'; +import { selectThemeMode } from '../store/theme/theme.selectors'; +import { useAppSelector } from './hooks'; interface MenuItemProps { icon: React.ReactNode; @@ -41,7 +42,7 @@ interface Props { } const DrawerMenu: FC = ({ open, toggleDrawerMenu, toggleThemeMode, width }) => { - const themeMode = useThemeMode(); + const themeMode = useAppSelector(selectThemeMode); return ( useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; - -export const useThemeMode = () => { - const [themeMode, setThemeMode] = useState('light'); - const theme = useTheme(); - useEffect(() => { - setThemeMode(theme.palette.mode); - }, [theme]); - return themeMode; -}; diff --git a/src/components/TrackerList/AddTrackerCard.tsx b/src/components/TrackerList/AddTrackerCard.tsx index a6a72b56..7c7a9727 100644 --- a/src/components/TrackerList/AddTrackerCard.tsx +++ b/src/components/TrackerList/AddTrackerCard.tsx @@ -3,7 +3,9 @@ import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline'; import { Box, Card, CardActionArea, CardContent, CardProps } from '@mui/material'; import { FC, useState } from 'react'; -import { useThemeMode } from '../../app/hooks'; +import { useAppSelector } from '../../app/hooks'; +import ThemeMode from '../../models/ThemeMode'; +import { selectThemeMode } from '../../store/theme/theme.selectors'; import TrackerForm from '../forms/TrackerForm/TrackerForm'; interface Props { @@ -11,9 +13,9 @@ interface Props { } const AddTrackerCard: FC = ({ cardProps }) => { const [displayCreateForm, setDisplayCreateForm] = useState(false); - const themeMode = useThemeMode(); + const themeMode = useAppSelector(selectThemeMode); - const hoverColor = themeMode === 'light' ? 'accent.main' : 'secondary.main'; + const hoverColor = themeMode === ThemeMode.LIGHT ? 'accent.main' : 'secondary.main'; const cardActionSx = { '&:hover': { backgroundColor: displayCreateForm ? '' : hoverColor diff --git a/src/components/forms/RequiredCompletionsForm/RequiredCompletionsForm.tsx b/src/components/forms/RequiredCompletionsForm/RequiredCompletionsForm.tsx index 8b346dc9..2c4d17aa 100644 --- a/src/components/forms/RequiredCompletionsForm/RequiredCompletionsForm.tsx +++ b/src/components/forms/RequiredCompletionsForm/RequiredCompletionsForm.tsx @@ -11,7 +11,9 @@ import { UseFieldArrayRemove } from 'react-hook-form'; -import { useThemeMode } from '../../../app/hooks'; +import { useAppSelector } from '../../../app/hooks'; +import ThemeMode from '../../../models/ThemeMode'; +import { selectThemeMode } from '../../../store/theme/theme.selectors'; import { FormValues } from '../TrackerForm/types'; import CompletionQuantityTextField from '../completions/CompletionQuantityTextField'; import CompletionUnitTextField from '../completions/CompletionUnitTextField'; @@ -37,11 +39,11 @@ interface Props { * @return {*} */ const RequiredCompletionsForm: FC = ({ append, control, fields, gridProps, remove }) => { - const themeMode = useThemeMode(); + const themeMode = useAppSelector(selectThemeMode); const theme = useTheme(); const fieldsetSx = { - bgcolor: themeMode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900], + bgcolor: themeMode === ThemeMode.LIGHT ? theme.palette.grey[100] : theme.palette.grey[900], mb: 1 }; diff --git a/src/config/CustomTheme.ts b/src/config/CustomTheme.ts index fca77df9..7bc45ee6 100644 --- a/src/config/CustomTheme.ts +++ b/src/config/CustomTheme.ts @@ -2,6 +2,8 @@ import { PaletteMode, PaletteOptions } from '@mui/material'; import { grey } from '@mui/material/colors'; +import ThemeMode from '../models/ThemeMode'; + const CHARCOAL = { main: '#2E4057' }; @@ -53,7 +55,7 @@ const commonTheme = { export const getPalette = (mode: PaletteMode): PaletteOptions => ({ mode, - ...(mode === 'light' + ...(mode === ThemeMode.LIGHT ? { // palette values for light mode ...commonTheme diff --git a/src/models/ThemeMode.ts b/src/models/ThemeMode.ts new file mode 100644 index 00000000..32225b36 --- /dev/null +++ b/src/models/ThemeMode.ts @@ -0,0 +1,6 @@ +enum ThemeMode { + DARK = 'dark', + LIGHT = 'light' +} + +export default ThemeMode; diff --git a/src/pages/Trackers.tsx b/src/pages/Trackers.tsx index 3dfd4efb..c28f3a8c 100644 --- a/src/pages/Trackers.tsx +++ b/src/pages/Trackers.tsx @@ -4,11 +4,13 @@ import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import { Alert, Box, Tab, Tabs, Typography, useTheme } from '@mui/material'; import { useState } from 'react'; -import { useAppSelector, useThemeMode } from '../app/hooks'; +import { useAppSelector } from '../app/hooks'; import TabPanel from '../components/TabPanel/TabPanel'; import AddTrackerCard from '../components/TrackerList/AddTrackerCard'; import DateSelector from '../components/TrackerList/DateSelector'; import TrackerList from '../components/TrackerList/TrackerList'; +import ThemeMode from '../models/ThemeMode'; +import { selectThemeMode } from '../store/theme/theme.selectors'; import { selectHiddenTrackers, selectTodoTrackers, @@ -24,11 +26,11 @@ function Trackers() { setSelectedTab(newTab); }; const theme = useTheme(); - const themeMode = useThemeMode(); + const themeMode = useAppSelector(selectThemeMode); const cardSxProp = { mb: 2, - bgcolor: themeMode === 'light' ? 'secondary.main' : theme.palette.grey[900] + bgcolor: themeMode === ThemeMode.LIGHT ? 'secondary.main' : theme.palette.grey[900] }; return ( diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 939c937f..8b0af650 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -1,9 +1,11 @@ import { combineReducers } from '@reduxjs/toolkit'; +import themeReducer from './theme/themeSlice'; import trackersReducer from './trackers/trackersSlice'; const rootReducer = combineReducers({ - trackers: trackersReducer + trackers: trackersReducer, + theme: themeReducer }); export default rootReducer; diff --git a/src/store/store.ts b/src/store/store.ts index c284d091..7fba2eb1 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -9,9 +9,9 @@ import { persistReducer, persistStore } from 'redux-persist'; +// defaults to localStorage for web import storage from 'redux-persist/lib/storage'; -// defaults to localStorage for web import rootReducer from './rootReducer'; const persistConfig = { diff --git a/src/store/theme/theme.selectors.ts b/src/store/theme/theme.selectors.ts new file mode 100644 index 00000000..7e4357f2 --- /dev/null +++ b/src/store/theme/theme.selectors.ts @@ -0,0 +1,7 @@ +import { RootState } from '../store'; + +const selectThemeMode = (state: RootState) => { + return state.theme.themeMode; +}; + +export { selectThemeMode }; diff --git a/src/store/theme/themeSlice.ts b/src/store/theme/themeSlice.ts new file mode 100644 index 00000000..2d65957c --- /dev/null +++ b/src/store/theme/themeSlice.ts @@ -0,0 +1,29 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; + +import ThemeMode from '../../models/ThemeMode'; + +export interface ThemeState { + themeMode: ThemeMode; +} + +const initialState: ThemeState = { + themeMode: ThemeMode.LIGHT +}; + +export const themeSlice = createSlice({ + name: 'theme', + initialState, + reducers: { + setThemeMode: (state, action: PayloadAction) => { + state.themeMode = action.payload; + }, + toggleThemeMode: (state) => { + const { themeMode } = state; + state.themeMode = themeMode === ThemeMode.LIGHT ? ThemeMode.DARK : ThemeMode.LIGHT; + } + } +}); + +export const { setThemeMode, toggleThemeMode } = themeSlice.actions; + +export default themeSlice.reducer;