diff --git a/ui/src/App.tsx b/ui/src/App.tsx index e6c4401..8d30cc0 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -131,7 +131,7 @@ const useStyles = (theme: Theme) => function mapStateToProps(state: AppState) { return { snackbar: state.snackbar, - isDarkTheme: state.settings.isDarkTheme, + themePreference: state.settings.themePreference, }; } @@ -146,7 +146,7 @@ function SlideUpTransition(props: TransitionProps) { } function App(props: ConnectedProps) { - const theme = makeTheme(props.isDarkTheme); + const theme = makeTheme(props.themePreference); const classes = useStyles(theme)(); const [open, setOpen] = useState(true); const toggleDrawer = () => setOpen(!open); diff --git a/ui/src/actions/settingsActions.ts b/ui/src/actions/settingsActions.ts index e5b06d0..dc9f1e4 100644 --- a/ui/src/actions/settingsActions.ts +++ b/ui/src/actions/settingsActions.ts @@ -1,6 +1,8 @@ +import { ThemePreference } from "../reducers/settingsReducer"; + // List of settings related action types. -export const POLL_INTERVAL_CHANGE = 'POLL_INTERVAL_CHANGE'; -export const TOGGLE_DARK_THEME = 'TOGGLE_DARK_THEME'; +export const POLL_INTERVAL_CHANGE = "POLL_INTERVAL_CHANGE"; +export const THEME_PREFERENCE_CHANGE = "THEME_PREFERENCE_CHANGE"; interface PollIntervalChangeAction { type: typeof POLL_INTERVAL_CHANGE; @@ -8,11 +10,14 @@ interface PollIntervalChangeAction { } interface ToggleDarkThemeAction { - type: typeof TOGGLE_DARK_THEME; + type: typeof THEME_PREFERENCE_CHANGE; + value: ThemePreference; } // Union of all settings related action types. -export type SettingsActionTypes = PollIntervalChangeAction | ToggleDarkThemeAction; +export type SettingsActionTypes = + | PollIntervalChangeAction + | ToggleDarkThemeAction; export function pollIntervalChange(value: number) { return { @@ -21,9 +26,9 @@ export function pollIntervalChange(value: number) { }; } -export function toggleDarkTheme() { +export function selectTheme(value: ThemePreference) { return { - type: TOGGLE_DARK_THEME - } + type: THEME_PREFERENCE_CHANGE, + value, + }; } - diff --git a/ui/src/reducers/settingsReducer.ts b/ui/src/reducers/settingsReducer.ts index 4e0f50a..50c88aa 100644 --- a/ui/src/reducers/settingsReducer.ts +++ b/ui/src/reducers/settingsReducer.ts @@ -1,17 +1,22 @@ import { POLL_INTERVAL_CHANGE, SettingsActionTypes, - TOGGLE_DARK_THEME, + THEME_PREFERENCE_CHANGE, } from "../actions/settingsActions"; +export enum ThemePreference { + SystemDefault, + Always, + Never, +} interface SettingsState { pollInterval: number; - isDarkTheme: boolean; + themePreference: ThemePreference; } const initialState: SettingsState = { pollInterval: 8, - isDarkTheme: true, + themePreference: ThemePreference.SystemDefault, }; function settingsReducer( @@ -21,10 +26,10 @@ function settingsReducer( switch (action.type) { case POLL_INTERVAL_CHANGE: return { ...state, pollInterval: action.value }; - case TOGGLE_DARK_THEME: + case THEME_PREFERENCE_CHANGE: return { ...state, - isDarkTheme: !state.isDarkTheme, + themePreference: action.value, }; default: return state; diff --git a/ui/src/theme.tsx b/ui/src/theme.tsx index 36b028a..7992700 100644 --- a/ui/src/theme.tsx +++ b/ui/src/theme.tsx @@ -1,6 +1,15 @@ import { createMuiTheme, Theme } from "@material-ui/core/styles"; +import { ThemePreference } from "./reducers/settingsReducer"; +import useMediaQuery from "@material-ui/core/useMediaQuery"; -export function makeTheme(isDarkTheme: boolean): Theme { +export function makeTheme(themePreference: ThemePreference): Theme { + // eslint-disable-next-line react-hooks/rules-of-hooks + let prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + if (themePreference === ThemePreference.Always) { + prefersDarkMode = true; + } else if (themePreference === ThemePreference.Never) { + prefersDarkMode = false; + } return createMuiTheme({ // Got color palette from https://htmlcolors.com/palette/31/stripe palette: { @@ -13,7 +22,7 @@ export function makeTheme(isDarkTheme: boolean): Theme { background: { default: "#f5f7f9", }, - type: isDarkTheme ? "dark" : "light", + type: prefersDarkMode ? "dark" : "light", }, }); } diff --git a/ui/src/views/RedisInfoView.tsx b/ui/src/views/RedisInfoView.tsx index 35c6d78..9fddae7 100644 --- a/ui/src/views/RedisInfoView.tsx +++ b/ui/src/views/RedisInfoView.tsx @@ -1,7 +1,7 @@ import React from "react"; import { connect, ConnectedProps } from "react-redux"; import Container from "@material-ui/core/Container"; -import { makeStyles } from "@material-ui/core/styles"; +import { makeStyles, useTheme, Theme } from "@material-ui/core/styles"; import Grid from "@material-ui/core/Grid"; import Typography from "@material-ui/core/Typography"; import Card from "@material-ui/core/Card"; @@ -31,7 +31,7 @@ function mapStateToProps(state: AppState) { redisAddress: state.redis.address, redisInfoRaw: state.redis.rawData, pollInterval: state.settings.pollInterval, - isDarkTheme: state.settings.isDarkTheme, + themePreference: state.settings.themePreference, }; } @@ -40,14 +40,9 @@ type Props = ConnectedProps; function RedisInfoView(props: Props) { const classes = useStyles(); - const { - pollInterval, - getRedisInfoAsync, - redisInfo, - redisInfoRaw, - isDarkTheme, - } = props; - + const { pollInterval, getRedisInfoAsync, redisInfo, redisInfoRaw } = props; + const theme = useTheme(); + const isDarkTheme = theme.palette.type === "dark"; usePolling(getRedisInfoAsync, pollInterval); // Metrics to show diff --git a/ui/src/views/SettingsView.tsx b/ui/src/views/SettingsView.tsx index 8230b67..70b29c4 100644 --- a/ui/src/views/SettingsView.tsx +++ b/ui/src/views/SettingsView.tsx @@ -5,15 +5,14 @@ import { makeStyles } from "@material-ui/core/styles"; import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; import Typography from "@material-ui/core/Typography"; -import Switch from "@material-ui/core/Switch"; -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import FormGroup from "@material-ui/core/FormGroup"; import Slider from "@material-ui/core/Slider"; -import { - pollIntervalChange, - toggleDarkTheme, -} from "../actions/settingsActions"; +import { pollIntervalChange, selectTheme } from "../actions/settingsActions"; import { AppState } from "../store"; +import FormControl from "@material-ui/core/FormControl/FormControl"; +import InputLabel from "@material-ui/core/InputLabel"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; +import { ThemePreference } from "../reducers/settingsReducer"; const useStyles = makeStyles((theme) => ({ container: { @@ -26,16 +25,23 @@ const useStyles = makeStyles((theme) => ({ overflow: "auto", flexDirection: "column", }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, })); function mapStateToProps(state: AppState) { return { pollInterval: state.settings.pollInterval, - isDarkTheme: state.settings.isDarkTheme, + themePreference: state.settings.themePreference, }; } -const mapDispatchToProps = { pollIntervalChange, toggleDarkTheme }; +const mapDispatchToProps = { pollIntervalChange, selectTheme }; const connector = connect(mapStateToProps, mapDispatchToProps); @@ -53,8 +59,8 @@ function SettingsView(props: PropsFromRedux) { props.pollIntervalChange(val as number); }; - const handleThemeChange = (event: React.ChangeEvent) => { - props.toggleDarkTheme(); + const handleThemeChange = (event: React.ChangeEvent<{ value: unknown }>) => { + props.selectTheme(event.target.value as ThemePreference); }; return ( @@ -87,23 +93,22 @@ function SettingsView(props: PropsFromRedux) { /> - - - theme switch - - - } - label="🌛" - /> - - - + + Dark theme + + );