add dark theme toggle

This commit is contained in:
Sidney Alcantara
2020-11-01 20:57:16 +11:00
parent f5d38abd65
commit 0871f64a7f
6 changed files with 96 additions and 19 deletions

View File

@@ -10,7 +10,7 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@material-ui/pickers": "^3.2.10",
"@mdi/js": "^4.9.95",
"@mdi/js": "^5.8.55",
"@monaco-editor/react": "^3.5.5",
"@tinymce/tinymce-react": "^3.4.0",
"ace-builds": "^1.4.11",

View File

@@ -1,4 +1,5 @@
import React from "react";
import _clone from "lodash/clone";
import _merge from "lodash/merge";
import {
@@ -306,6 +307,9 @@ export const defaultOverrides = (theme: Theme): ThemeOptions => ({
marginRight: theme.spacing(1.5),
},
},
MuiListItemIcon: {
root: { minWidth: theme.spacing(40 / 8) },
},
},
props: {
MuiTypography: {
@@ -344,22 +348,28 @@ export const defaultOverrides = (theme: Theme): ThemeOptions => ({
export const customizableLightTheme = (customization: ThemeOptions) => {
const customizedLightThemeBase = createMuiTheme(
_merge(themeBase, customization)
_merge({}, themeBase, customization)
);
return createMuiTheme(
customizedLightThemeBase,
_merge(defaultOverrides(customizedLightThemeBase), customization)
_merge({}, defaultOverrides(customizedLightThemeBase), customization)
);
};
export const customizableDarkTheme = (customization: ThemeOptions) => {
const customizedDarkThemeBase = createMuiTheme(
_merge(themeBase, darkThemeBase, customization)
_merge({}, themeBase, darkThemeBase, customization)
);
return createMuiTheme(
customizedDarkThemeBase,
_merge(defaultOverrides(customizedDarkThemeBase), customization)
_merge({}, defaultOverrides(customizedDarkThemeBase), customization)
);
};
const Themes = {
light: customizableLightTheme,
dark: customizableDarkTheme,
};
export default Themes;

View File

@@ -0,0 +1,11 @@
import React from "react";
import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon";
import { mdiCircleHalfFull } from "@mdi/js";
export default function DarkTheme(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d={mdiCircleHalfFull} />
</SvgIcon>
);
}

View File

@@ -10,8 +10,13 @@ import {
Menu,
Typography,
MenuItem,
ListItemSecondaryAction,
Divider,
} from "@material-ui/core";
import AccountCircle from "@material-ui/icons/AccountCircle";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import LaunchIcon from "@material-ui/icons/Launch";
import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank";
import CheckBoxIcon from "@material-ui/icons/CheckBox";
import { useAppContext } from "contexts/appContext";
import routes from "constants/routes";
@@ -30,6 +35,15 @@ const useStyles = makeStyles((theme) =>
userSelect: "none",
color: theme.palette.text.disabled,
},
divider: { margin: theme.spacing(1, 2) },
secondaryIcon: {
display: "block",
pointerEvents: "none",
color: theme.palette.action.active,
},
})
);
@@ -39,12 +53,17 @@ export default function UserMenu(props: IconButtonProps) {
const anchorEl = useRef<HTMLButtonElement>(null);
const [open, setOpen] = useState(false);
const { currentUser, userDoc } = useAppContext();
const { currentUser, userDoc, theme, setTheme } = useAppContext();
if (!currentUser || !userDoc || !userDoc?.state?.doc) return null;
const displayName = userDoc?.state?.doc?.user?.displayName;
const avatarUrl = userDoc?.state?.doc?.user?.photoURL;
const handleToggleTheme = () => {
if (theme === "light") setTheme(() => "dark");
if (theme === "dark") setTheme(() => "light");
};
return (
<>
<IconButton
@@ -59,7 +78,7 @@ export default function UserMenu(props: IconButtonProps) {
{avatarUrl ? (
<Avatar src={avatarUrl} className={classes.avatar} />
) : (
<AccountCircle />
<AccountCircleIcon />
)}
</IconButton>
@@ -83,6 +102,31 @@ export default function UserMenu(props: IconButtonProps) {
{displayName}
</Typography>
)}
<MenuItem
component="a"
href={`https://console.firebase.google.com/project/${process.env.REACT_APP_FIREBASE_PROJECT_ID}/firestore/data~2F_FT_USERS~2F${currentUser.uid}`}
target="_blank"
rel="noopener"
>
User Config
<ListItemSecondaryAction>
<LaunchIcon className={classes.secondaryIcon} />
</ListItemSecondaryAction>
</MenuItem>
<Divider className={classes.divider} />
<MenuItem onClick={handleToggleTheme}>
Dark Theme
<ListItemSecondaryAction>
{theme === "light" ? (
<CheckBoxOutlineBlankIcon className={classes.secondaryIcon} />
) : (
<CheckBoxIcon className={classes.secondaryIcon} />
)}
</ListItemSecondaryAction>
</MenuItem>
<MenuItem component={Link} to={routes.signOut}>
Sign Out
</MenuItem>

View File

@@ -4,24 +4,24 @@ import firebase from "firebase/app";
import useDoc from "hooks/useDoc";
import {
useMediaQuery,
MuiThemeProvider,
ThemeOptions,
Theme,
CssBaseline,
} from "@material-ui/core";
import { customizableLightTheme } from "Themes";
import Themes from "Themes";
interface AppContextInterface {
currentUser: firebase.User | null | undefined;
userDoc: any;
setTheme: React.Dispatch<
React.SetStateAction<(customization: ThemeOptions) => Theme>
>;
theme: keyof typeof Themes;
setTheme: React.Dispatch<React.SetStateAction<keyof typeof Themes>>;
}
export const AppContext = React.createContext<AppContextInterface>({
currentUser: undefined,
userDoc: undefined,
theme: "light",
setTheme: () => {},
});
@@ -47,12 +47,23 @@ export const AppProvider: React.FC = ({ children }) => {
}
}, [currentUser]);
// Infer theme based on system settings
const prefersDarkTheme = useMediaQuery("(prefers-color-scheme: dark)");
// Store theme
const [theme, setTheme] = useState(() => customizableLightTheme);
const [theme, setTheme] = useState<keyof typeof Themes>(
prefersDarkTheme ? "dark" : "light"
);
// Update theme when system settings change
useEffect(() => {
if (prefersDarkTheme && theme !== "dark") setTheme("dark");
if (!prefersDarkTheme && theme !== "light") setTheme("light");
}, [prefersDarkTheme]);
// Store themeCustomization from userDoc
const [themeCustomization, setThemeCustomization] = useState<ThemeOptions>(
{}
);
const generatedTheme = Themes[theme](themeCustomization);
useEffect(() => {
if (userDoc.doc) {
@@ -92,10 +103,11 @@ export const AppProvider: React.FC = ({ children }) => {
value={{
userDoc: { state: userDoc, dispatch: dispatchUserDoc },
currentUser,
theme,
setTheme,
}}
>
<MuiThemeProvider theme={theme(themeCustomization)}>
<MuiThemeProvider theme={generatedTheme}>
<CssBaseline />
{children}
</MuiThemeProvider>

View File

@@ -2208,10 +2208,10 @@
prop-types "^15.7.2"
react-is "^16.8.0"
"@mdi/js@^4.9.95":
version "4.9.95"
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-4.9.95.tgz#8984c2ac04c89913a3ff2bbe4d91f4ab51d8ef4f"
integrity sha512-6zKTCqZUCuDWJThdRcjdFTqp2BXfYwXI1UlYa68A1/kmCcy1ijpbpRbrJcUdZ+9WojencCh1DOGFqhj/x8I/eQ==
"@mdi/js@^5.8.55":
version "5.8.55"
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-5.8.55.tgz#630bc5fafd8b1d2f6e63489a9ab170177559e41b"
integrity sha512-2bvln56SW6V/nSDC/0/NTu1bMF/CgSyZox8mcWbAPWElBN3UYIrukKDUckEER8ifr8X2YJl1RLKQqi7T7qLzmg==
"@monaco-editor/react@^3.5.5":
version "3.5.5"