diff --git a/package.json b/package.json index 794478f..a49039d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "private": true, "dependencies": { + "@flare/js-utils": "^1.0.3", "@flare/tuitio-client-react": "^1.0.1", "@material-ui/core": "^4.11.2", "@material-ui/icons": "^4.11.2", diff --git a/src/features/machines/components/MachinesContainer.js b/src/features/machines/components/MachinesContainer.js index 5708861..20cf0d6 100644 --- a/src/features/machines/components/MachinesContainer.js +++ b/src/features/machines/components/MachinesContainer.js @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useCallback } from "react"; import { ApplicationStateContext, ApplicationDispatchContext -} from "../../../state/ApplicationContexts"; +} from "../../../state/contexts"; import useApi from "../../../api"; import MachinesList from "./MachinesList"; import PageTitle from "../../../components/common/PageTitle"; diff --git a/src/features/settings/components/SettingsContainer.js b/src/features/settings/components/SettingsContainer.js index 8eebf7e..bace773 100644 --- a/src/features/settings/components/SettingsContainer.js +++ b/src/features/settings/components/SettingsContainer.js @@ -1,8 +1,14 @@ import React from "react"; import LanguageContainer from "./language/LanguageContainer"; +import ThemeSettings from "./ThemeSettings"; const SettingsContainer = () => { - return ; + return ( + <> + + + + ); }; export default SettingsContainer; diff --git a/src/features/settings/components/ThemeSettings.js b/src/features/settings/components/ThemeSettings.js new file mode 100644 index 0000000..cf522d3 --- /dev/null +++ b/src/features/settings/components/ThemeSettings.js @@ -0,0 +1,23 @@ +import React from "react"; +import { useApplicationTheme } from "../../../providers/ThemeProvider"; +import { Switch } from "@material-ui/core"; + +const ThemeSettings = () => { + const { isDark, onDarkModeChanged } = useApplicationTheme(); + + const handleChange = event => { + const { checked } = event.target; + onDarkModeChanged(checked); + }; + + return ( + + ); +}; + +export default ThemeSettings; diff --git a/src/index.js b/src/index.js index 6fdef0e..2dc491a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,6 @@ import React, { Suspense } from "react"; import ReactDOM from "react-dom"; -import { ThemeProvider } from "@material-ui/styles"; -import Themes from "./themes"; +import ThemeProvider from "./providers/ThemeProvider"; import CssBaseline from "@material-ui/core/CssBaseline"; import App from "./components/App"; import { TuitioProvider } from "@flare/tuitio-client-react"; @@ -12,7 +11,7 @@ import "./utils/i18n"; ReactDOM.render( - + Loading...}> diff --git a/src/providers/ApplicationStateProvider.js b/src/providers/ApplicationStateProvider.js index 9c27ec0..071541f 100644 --- a/src/providers/ApplicationStateProvider.js +++ b/src/providers/ApplicationStateProvider.js @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { ApplicationStateContext, ApplicationDispatchContext -} from "../state/ApplicationContexts"; +} from "../state/contexts"; import { reducer, dispatchActions as reducerDispatchActions diff --git a/src/providers/ThemeProvider.js b/src/providers/ThemeProvider.js new file mode 100644 index 0000000..bcbef66 --- /dev/null +++ b/src/providers/ThemeProvider.js @@ -0,0 +1,82 @@ +import React, { useReducer, useMemo, useContext } from "react"; +import PropTypes from "prop-types"; +import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles"; +import { localStorage } from "@flare/js-utils"; +import { getThemes } from "../themes"; + +const ApplicationThemeContext = React.createContext(); +const LOCAL_STORAGE_COLOR_SCHEME_KEY = "network-resurrector-color-scheme"; + +const COLOR_SCHEME = { + LIGHT: "light", + DARK: "dark" +}; + +const colorScheme = localStorage.getItem(LOCAL_STORAGE_COLOR_SCHEME_KEY); +const prefersDarkMode = window.matchMedia( + "(prefers-color-scheme: dark)" +).matches; + +const initialState = { + scheme: + colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT) +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case "onColorSchemeChanged": { + return { + ...state, + scheme: action.scheme + }; + } + default: { + return state; + } + } +}; + +const dispatchActions = dispatch => ({ + onColorSchemeChanged: scheme => { + dispatch({ type: "onColorSchemeChanged", scheme }); + localStorage.setItem(LOCAL_STORAGE_COLOR_SCHEME_KEY, scheme); + } +}); + +const useApplicationTheme = () => { + const { state, actions } = useContext(ApplicationThemeContext); + const { onColorSchemeChanged } = actions; + + const { scheme } = state; + const onDarkModeChanged = active => + onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT); + + return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged }; +}; + +const ThemeProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + const actions = useMemo(() => dispatchActions(dispatch), [dispatch]); + const themes = useMemo( + () => getThemes(state.scheme === COLOR_SCHEME.DARK), + [state.scheme] + ); + + return ( + + {children} + + ); +}; + +ThemeProvider.propTypes = { + children: PropTypes.node.isRequired +}; + +export { ThemeProvider, ApplicationThemeContext, useApplicationTheme }; +export default ThemeProvider; diff --git a/src/state/ApplicationContexts.js b/src/state/contexts.js similarity index 100% rename from src/state/ApplicationContexts.js rename to src/state/contexts.js diff --git a/src/themes/index.js b/src/themes/index.js index a0830ec..07216c2 100644 --- a/src/themes/index.js +++ b/src/themes/index.js @@ -1,12 +1,7 @@ import defaultTheme from "./default"; import { createTheme } from "@material-ui/core/styles"; -const prefersDarkMode = false; - const overrides = { - palette: { - type: prefersDarkMode ? "dark" : "light" - }, typography: { h1: { fontSize: "3rem" @@ -29,12 +24,16 @@ const overrides = { } }; -const themes = { - default: createTheme({ - ...defaultTheme, - ...overrides, - palette: { ...defaultTheme.palette, ...overrides.palette } - }) +const getThemes = darkMode => { + const type = darkMode ? "dark" : "light"; + + return { + default: createTheme({ + ...defaultTheme, + ...overrides, + palette: { ...defaultTheme.palette, type } + }) + }; }; -export default themes; +export { getThemes };