mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
add dark theme toggle
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
www/src/assets/icons/DarkTheme.tsx
Normal file
11
www/src/assets/icons/DarkTheme.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user