implement-dark-light-theme-toggle kohteeseen master 3 vuotta sitten
| @@ -0,0 +1,57 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogTitle, | |||
| DialogActions, | |||
| Button, | |||
| useMediaQuery, | |||
| useTheme, | |||
| } from '@mui/material'; | |||
| const DialogComponent = ({ | |||
| title, | |||
| content, | |||
| onClose, | |||
| open, | |||
| maxWidth, | |||
| fullWidth, | |||
| responsive, | |||
| }) => { | |||
| const theme = useTheme(); | |||
| const fullScreen = useMediaQuery(theme.breakpoints.down('md')); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| return ( | |||
| <Dialog | |||
| maxWidth={maxWidth} | |||
| fullWidth={fullWidth} | |||
| fullScreen={responsive && fullScreen} | |||
| onClose={handleClose} | |||
| open={open} | |||
| > | |||
| <DialogTitle>{title}</DialogTitle> | |||
| {content && <DialogContent>{content}</DialogContent>} | |||
| <DialogActions> | |||
| <Button onClick={handleClose}>OK</Button> | |||
| <Button onClick={handleClose}>Cancel</Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| ); | |||
| }; | |||
| DialogComponent.propTypes = { | |||
| title: PropTypes.string, | |||
| open: PropTypes.bool.isRequired, | |||
| content: PropTypes.any, | |||
| onClose: PropTypes.func.isRequired, | |||
| maxWidth: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), | |||
| fullWidth: PropTypes.bool, | |||
| responsive: PropTypes.bool, | |||
| }; | |||
| export default DialogComponent; | |||
| @@ -1,37 +1,28 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { | |||
| Drawer, | |||
| List, | |||
| ListItem, | |||
| ListItemButton, | |||
| ListItemIcon, | |||
| ListItemText, | |||
| } from '@mui/material'; | |||
| import { Drawer } from '@mui/material'; | |||
| const DrawerComponent = ({ open, toggleOpen }) => ( | |||
| <Drawer anchor="right" open={open} onClose={toggleOpen}> | |||
| <List> | |||
| <ListItemButton divider onClick={toggleOpen}> | |||
| <ListItemIcon> | |||
| <ListItemText>Link 1</ListItemText> | |||
| </ListItemIcon> | |||
| </ListItemButton> | |||
| <ListItem divider onClick={toggleOpen}> | |||
| <ListItemIcon> | |||
| <ListItemText>Link 2</ListItemText> | |||
| </ListItemIcon> | |||
| </ListItem> | |||
| <ListItem divider onClick={toggleOpen}> | |||
| <ListItemText>Link 3</ListItemText> | |||
| </ListItem> | |||
| </List> | |||
| const DrawerComponent = ({ open, toggleOpen, content, anchor = 'right' }) => ( | |||
| <Drawer | |||
| sx={{ | |||
| minWidth: 250, | |||
| '& .MuiDrawer-paper': { | |||
| minWidth: 250, | |||
| }, | |||
| }} | |||
| anchor={anchor} | |||
| open={open} | |||
| onClose={toggleOpen} | |||
| > | |||
| {content ? content : null} | |||
| </Drawer> | |||
| ); | |||
| DrawerComponent.propTypes = { | |||
| open: PropTypes.bool, | |||
| toggleOpen: PropTypes.func, | |||
| content: PropTypes.any, | |||
| anchor: PropTypes.oneOf(['top', 'right', 'left', 'bottom']), | |||
| }; | |||
| export default DrawerComponent; | |||
| @@ -0,0 +1,72 @@ | |||
| import React, { useState } from 'react'; | |||
| import { Box, Button, Divider, Paper, Typography } from '@mui/material'; | |||
| import DialogComponent from '../DialogComponent'; | |||
| import DrawerComponent from '../DrawerComponent'; | |||
| import PopoverComponent from '../PopoverComponent'; | |||
| const Modals = () => { | |||
| const [dialogOpen, setDialogOpen] = useState(false); | |||
| const [drawerOpen, setDrawerOpen] = useState(false); | |||
| const [popoverOpen, setPopoverOpen] = useState(false); | |||
| const [anchorEl, setAnchorEl] = useState(null); | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| mt: 4, | |||
| ml: 4, | |||
| display: 'flex', | |||
| flexGrow: 1, | |||
| }} | |||
| > | |||
| <Paper | |||
| sx={{ | |||
| p: 4, | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| <Typography variant="h4" gutterBottom align="center"> | |||
| Modals | |||
| </Typography> | |||
| <Divider /> | |||
| <Button onClick={() => setDialogOpen(true)}>Open Dialog</Button> | |||
| <Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button> | |||
| <Button | |||
| onClick={(e) => { | |||
| setPopoverOpen(true); | |||
| setAnchorEl(e.currentTarget); | |||
| }} | |||
| > | |||
| Open Popover | |||
| </Button> | |||
| </Paper> | |||
| <DialogComponent | |||
| title="Dialog Title" | |||
| content={<Typography>Dialog Content</Typography>} | |||
| open={dialogOpen} | |||
| onClose={() => setDialogOpen(false)} | |||
| maxWidth="md" | |||
| fullWidth | |||
| responsive | |||
| /> | |||
| <DrawerComponent | |||
| anchor="left" | |||
| content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>} | |||
| open={drawerOpen} | |||
| toggleOpen={() => setDrawerOpen(!drawerOpen)} | |||
| /> | |||
| <PopoverComponent | |||
| anchorEl={anchorEl} | |||
| open={popoverOpen} | |||
| onClose={() => { | |||
| setPopoverOpen(false); | |||
| setAnchorEl(null); | |||
| }} | |||
| content={<Typography sx={{ p: 2 }}>Popover Content</Typography>} | |||
| /> | |||
| </Box> | |||
| ); | |||
| }; | |||
| export default Modals; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState } from 'react'; | |||
| import React, { useState, useMemo, useContext } from 'react'; | |||
| import { | |||
| AppBar, | |||
| Badge, | |||
| @@ -6,100 +6,154 @@ import { | |||
| IconButton, | |||
| Toolbar, | |||
| Typography, | |||
| List, | |||
| ListItem, | |||
| ListItemButton, | |||
| ListItemIcon, | |||
| ListItemText, | |||
| useMediaQuery, | |||
| } from '@mui/material'; | |||
| import { useTheme } from '@mui/system'; | |||
| import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined'; | |||
| import ShoppingBasketIcon from '@mui/icons-material/ShoppingBasket'; | |||
| import Brightness4Icon from '@mui/icons-material/Brightness4'; | |||
| import Brightness7Icon from '@mui/icons-material/Brightness7'; | |||
| import MenuList from './MenuList'; | |||
| import Drawer from './DrawerComponent'; | |||
| import { ColorModeContext } from '../../context/ColorModeContext'; | |||
| const Navbar = () => { | |||
| const [openDrawer, setOpenDrawer] = useState(false); | |||
| const theme = useTheme(); | |||
| const matches = useMediaQuery(theme.breakpoints.down('sm')); | |||
| const toggleColorMode = useContext(ColorModeContext); | |||
| const handleToggleDrawer = () => { | |||
| setOpenDrawer(!openDrawer); | |||
| }; | |||
| const drawerContent = useMemo( | |||
| () => ( | |||
| <List> | |||
| <ListItemButton divider onClick={handleToggleDrawer}> | |||
| <ListItemIcon> | |||
| <ListItemText>Link 1</ListItemText> | |||
| </ListItemIcon> | |||
| </ListItemButton> | |||
| <ListItem divider onClick={handleToggleDrawer}> | |||
| <ListItemIcon> | |||
| <ListItemText>Link 2</ListItemText> | |||
| </ListItemIcon> | |||
| </ListItem> | |||
| <ListItem divider onClick={handleToggleDrawer}> | |||
| <ListItemText>Link 3</ListItemText> | |||
| </ListItem> | |||
| <ListItem divider> | |||
| <IconButton onClick={toggleColorMode}> | |||
| <ListItemText>Toggle {theme.palette.mode} mode</ListItemText> | |||
| {theme.palette.mode === 'dark' ? ( | |||
| <Brightness7Icon /> | |||
| ) : ( | |||
| <Brightness4Icon /> | |||
| )} | |||
| </IconButton> | |||
| </ListItem> | |||
| </List> | |||
| ), | |||
| [handleToggleDrawer] | |||
| ); | |||
| return ( | |||
| <> | |||
| <AppBar elevation={2} sx={{ backgroundColor: 'background.default' }}> | |||
| <Toolbar> | |||
| <AppBar | |||
| elevation={2} | |||
| sx={{ backgroundColor: 'background.default', position: 'relative' }} | |||
| > | |||
| <Toolbar> | |||
| <Box | |||
| component="div" | |||
| sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'space-between', | |||
| alignItems: 'center', | |||
| width: '100%', | |||
| }} | |||
| > | |||
| {matches ? ( | |||
| <Drawer | |||
| open={openDrawer} | |||
| toggleOpen={handleToggleDrawer} | |||
| content={drawerContent} | |||
| /> | |||
| ) : ( | |||
| <Box sx={{ display: 'flex' }}> | |||
| <Typography | |||
| variant="h6" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 1 | |||
| </Typography> | |||
| <Typography | |||
| variant="body1" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 2 | |||
| </Typography> | |||
| <Typography | |||
| variant="subtitle1" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 3 | |||
| </Typography> | |||
| </Box> | |||
| )} | |||
| <Box> | |||
| <MenuList /> | |||
| </Box> | |||
| <Box | |||
| component="div" | |||
| sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'space-between', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| width: '100%', | |||
| }} | |||
| > | |||
| {matches ? ( | |||
| <Drawer open={openDrawer} toggleOpen={handleToggleDrawer} /> | |||
| ) : ( | |||
| <Box sx={{ display: 'flex' }}> | |||
| <Typography | |||
| variant="h6" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 1 | |||
| </Typography> | |||
| <Typography | |||
| variant="body1" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 2 | |||
| </Typography> | |||
| <Typography | |||
| variant="subtitle1" | |||
| sx={{ | |||
| marginRight: 3, | |||
| cursor: 'pointer', | |||
| color: 'text.primary', | |||
| }} | |||
| > | |||
| Link 3 | |||
| </Typography> | |||
| <Box> | |||
| <IconButton onClick={handleToggleDrawer}> | |||
| <MenuOutlinedIcon /> | |||
| </IconButton> | |||
| </Box> | |||
| )} | |||
| <Box> | |||
| <MenuList /> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| }} | |||
| > | |||
| {matches ? ( | |||
| <Box> | |||
| <IconButton onClick={handleToggleDrawer}> | |||
| <MenuOutlinedIcon /> | |||
| </IconButton> | |||
| </Box> | |||
| ) : ( | |||
| ) : ( | |||
| <Box> | |||
| <IconButton> | |||
| <Badge badgeContent={3} color="primary"> | |||
| <ShoppingBasketIcon color="action" /> | |||
| </Badge> | |||
| </IconButton> | |||
| )} | |||
| </Box> | |||
| <IconButton sx={{ ml: 1 }} onClick={toggleColorMode}> | |||
| {theme.palette.mode === 'dark' ? ( | |||
| <Brightness7Icon /> | |||
| ) : ( | |||
| <Brightness4Icon /> | |||
| )} | |||
| </IconButton> | |||
| </Box> | |||
| )} | |||
| </Box> | |||
| </Toolbar> | |||
| </AppBar> | |||
| </> | |||
| </Box> | |||
| </Toolbar> | |||
| </AppBar> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,35 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { Box, Popover } from '@mui/material'; | |||
| const PopoverComponent = ({ open, anchorEl, onClose, content }) => { | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| return ( | |||
| <Box component="div"> | |||
| <Popover | |||
| sx={{ p: 5 }} | |||
| open={open} | |||
| anchorEl={anchorEl} | |||
| onClose={handleClose} | |||
| anchorOrigin={{ | |||
| vertical: 'bottom', | |||
| horizontal: 'left', | |||
| }} | |||
| > | |||
| {content} | |||
| </Popover> | |||
| </Box> | |||
| ); | |||
| }; | |||
| PopoverComponent.propTypes = { | |||
| anchorEl: PropTypes.object, | |||
| open: PropTypes.bool.isRequired, | |||
| onClose: PropTypes.func.isRequired, | |||
| content: PropTypes.any, | |||
| }; | |||
| export default PopoverComponent; | |||
| @@ -0,0 +1,21 @@ | |||
| import React, { createContext } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ThemeProvider } from '@mui/material/styles'; | |||
| import useToggleColorMode from '../hooks/useToggleColorMode'; | |||
| export const ColorModeContext = createContext(); | |||
| const ColorModeProvider = ({ children }) => { | |||
| const [toggleColorMode, theme] = useToggleColorMode(); | |||
| return ( | |||
| <ColorModeContext.Provider value={toggleColorMode}> | |||
| <ThemeProvider theme={theme}>{children}</ThemeProvider> | |||
| </ColorModeContext.Provider> | |||
| ); | |||
| }; | |||
| ColorModeProvider.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default ColorModeProvider; | |||
| @@ -0,0 +1,31 @@ | |||
| import { useState, useMemo } from 'react'; | |||
| import { createTheme } from '@mui/material/styles'; | |||
| import { | |||
| authScopeSetHelper, | |||
| authScopeStringGetHelper, | |||
| } from '../util/helpers/authScopeHelpers'; | |||
| const useToggleColorMode = () => { | |||
| const currentColorMode = authScopeStringGetHelper('colorMode') || 'light'; | |||
| const [mode, setMode] = useState(currentColorMode); | |||
| const toggleColorMode = () => { | |||
| const nextMode = mode === 'light' ? 'dark' : 'light'; | |||
| setMode(nextMode); | |||
| authScopeSetHelper('colorMode', nextMode); | |||
| }; | |||
| const theme = useMemo( | |||
| () => | |||
| createTheme({ | |||
| palette: { | |||
| mode, | |||
| }, | |||
| }), | |||
| [mode] | |||
| ); | |||
| return [toggleColorMode, theme]; | |||
| }; | |||
| export default useToggleColorMode; | |||
| @@ -7,7 +7,7 @@ import enTranslations from './resources/en'; | |||
| i18n.use(initReactI18next).init({ | |||
| lng: 'en', | |||
| fallbackLng: 'en', | |||
| debug: true, | |||
| debug: false, | |||
| supportedLngs: ['en'], | |||
| resources: { | |||
| en: { | |||
| @@ -8,14 +8,17 @@ import App from './App'; | |||
| import store from './store'; | |||
| import './i18n'; | |||
| import ColorModeProvider from './context/ColorModeContext'; | |||
| ReactDOM.render( | |||
| <HelmetProvider> | |||
| <React.StrictMode> | |||
| <Provider store={store}> | |||
| <App /> | |||
| <ColorModeProvider> | |||
| <App /> | |||
| </ColorModeProvider> | |||
| </Provider> | |||
| </React.StrictMode> | |||
| </HelmetProvider>, | |||
| document.getElementById('root'), | |||
| ); | |||
| ); | |||
| @@ -1,10 +1,16 @@ | |||
| import React from 'react'; | |||
| import Navbar from '../../components/MUI/Navbar'; | |||
| import Modals from '../../components/MUI/Examples/Modals'; | |||
| const HomePage = () => { | |||
| return ( | |||
| <Navbar /> | |||
| ) | |||
| <> | |||
| <Navbar /> | |||
| <Modals /> | |||
| </> | |||
| ); | |||
| }; | |||
| export default HomePage; | |||
| @@ -30,11 +30,6 @@ import ErrorMessage from '../../components/MUI/CustomErrorMessage'; | |||
| import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors'; | |||
| import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants'; | |||
| const LoginValidationSchema = Yup.object().shape({ | |||
| username: Yup.string().required(i18next.t('login.usernameRequired')), | |||
| password: Yup.string().required(i18next.t('login.passwordRequired')), | |||
| }); | |||
| const LoginPage = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| @@ -60,12 +55,12 @@ const LoginPage = ({ history }) => { | |||
| ); | |||
| const handleApiResponseSuccess = () => { | |||
| // history.push({ | |||
| // pathname: HOME_PAGE, | |||
| // state: { | |||
| // from: history.location.pathname, | |||
| // }, | |||
| // }); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| @@ -85,7 +80,10 @@ const LoginPage = ({ history }) => { | |||
| username: '', | |||
| password: '', | |||
| }, | |||
| validationSchema: LoginValidationSchema, | |||
| validationSchema: Yup.object().shape({ | |||
| username: Yup.string().required(t('login.usernameRequired')), | |||
| password: Yup.string().required(t('login.passwordRequired')), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||