Implement dark mode

Resolves #206
This commit is contained in:
nimbleghost 2023-06-28 16:45:40 +02:00
parent d40b776205
commit 4f39c7c155
10 changed files with 69 additions and 29 deletions

View File

@ -33,6 +33,7 @@ import {
IconButton, IconButton,
MenuItem, MenuItem,
DialogContentText, DialogContentText,
useTheme,
} from "@mui/material"; } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
@ -55,7 +56,6 @@ import DialogFooter from "./DialogFooter";
import { Paragraph } from "./styles"; import { Paragraph } from "./styles";
import { IncorrectPasswordError, UnauthorizedError } from "../app/errors"; import { IncorrectPasswordError, UnauthorizedError } from "../app/errors";
import { ProChip } from "./SubscriptionPopup"; import { ProChip } from "./SubscriptionPopup";
import theme from "./theme";
import session from "../app/Session"; import session from "../app/Session";
const Account = () => { const Account = () => {
@ -147,6 +147,7 @@ const ChangePassword = () => {
}; };
const ChangePasswordDialog = (props) => { const ChangePasswordDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [currentPassword, setCurrentPassword] = useState(""); const [currentPassword, setCurrentPassword] = useState("");
@ -430,6 +431,7 @@ const PhoneNumbers = () => {
}; };
const AddPhoneNumberDialog = (props) => { const AddPhoneNumberDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [phoneNumber, setPhoneNumber] = useState(""); const [phoneNumber, setPhoneNumber] = useState("");
@ -928,6 +930,7 @@ const TokensTable = (props) => {
}; };
const TokenDialog = (props) => { const TokenDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [label, setLabel] = useState(props.token?.label || ""); const [label, setLabel] = useState(props.token?.label || "");
@ -1069,6 +1072,7 @@ const DeleteAccount = () => {
}; };
const DeleteAccountDialog = (props) => { const DeleteAccountDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useContext(AccountContext); const { account } = useContext(AccountContext);
const [error, setError] = useState(""); const [error, setError] = useState("");

View File

@ -1,11 +1,11 @@
import * as React from "react"; import * as React from "react";
import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react"; import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react";
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress } from "@mui/material"; import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider, createTheme } from "@mui/material/styles";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom"; import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
import { AllSubscriptions, SingleSubscription } from "./Notifications"; import { AllSubscriptions, SingleSubscription } from "./Notifications";
import theme from "./theme"; import themeOptions, { darkPalette, lightPalette } from "./theme";
import Navigation from "./Navigation"; import Navigation from "./Navigation";
import ActionBar from "./ActionBar"; import ActionBar from "./ActionBar";
import notifier from "../app/Notifier"; import notifier from "../app/Notifier";
@ -29,6 +29,19 @@ const App = () => {
const [account, setAccount] = useState(null); const [account, setAccount] = useState(null);
const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]); const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]);
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
const theme = React.useMemo(
() =>
createTheme({
...themeOptions,
palette: {
...(prefersDarkMode ? darkPalette : lightPalette),
},
}),
[prefersDarkMode]
);
return ( return (
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<BrowserRouter> <BrowserRouter>

View File

@ -26,6 +26,7 @@ import {
DialogTitle, DialogTitle,
DialogContent, DialogContent,
DialogActions, DialogActions,
useTheme,
} from "@mui/material"; } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
@ -34,7 +35,6 @@ import { useLiveQuery } from "dexie-react-hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Info } from "@mui/icons-material"; import { Info } from "@mui/icons-material";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import theme from "./theme";
import userManager from "../app/UserManager"; import userManager from "../app/UserManager";
import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils"; import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils";
import session from "../app/Session"; import session from "../app/Session";
@ -400,6 +400,7 @@ const UserTable = (props) => {
}; };
const UserDialog = (props) => { const UserDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [baseUrl, setBaseUrl] = useState(""); const [baseUrl, setBaseUrl] = useState("");
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");

View File

@ -19,6 +19,7 @@ import {
IconButton, IconButton,
MenuItem, MenuItem,
Box, Box,
useTheme,
} from "@mui/material"; } from "@mui/material";
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon"; import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
import { Close } from "@mui/icons-material"; import { Close } from "@mui/icons-material";
@ -34,7 +35,6 @@ import DialogFooter from "./DialogFooter";
import api from "../app/Api"; import api from "../app/Api";
import userManager from "../app/UserManager"; import userManager from "../app/UserManager";
import EmojiPicker from "./EmojiPicker"; import EmojiPicker from "./EmojiPicker";
import theme from "./theme";
import session from "../app/Session"; import session from "../app/Session";
import routes from "./routes"; import routes from "./routes";
import accountApi from "../app/AccountApi"; import accountApi from "../app/AccountApi";
@ -42,6 +42,7 @@ import { UnauthorizedError } from "../app/errors";
import { AccountContext } from "./App"; import { AccountContext } from "./App";
const PublishDialog = (props) => { const PublishDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useContext(AccountContext); const { account } = useContext(AccountContext);
const [baseUrl, setBaseUrl] = useState(""); const [baseUrl, setBaseUrl] = useState("");
@ -806,6 +807,7 @@ const AttachmentBox = (props) => {
}; };
const ExpandingTextField = (props) => { const ExpandingTextField = (props) => {
const theme = useTheme();
const invisibleFieldRef = useRef(); const invisibleFieldRef = useRef();
const [textWidth, setTextWidth] = useState(props.minWidth); const [textWidth, setTextWidth] = useState(props.minWidth);
const determineTextWidth = () => { const determineTextWidth = () => {

View File

@ -14,10 +14,10 @@ import {
MenuItem, MenuItem,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
useTheme,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Check, DeleteForever } from "@mui/icons-material"; import { Check, DeleteForever } from "@mui/icons-material";
import theme from "./theme";
import { validTopic } from "../app/utils"; import { validTopic } from "../app/utils";
import DialogFooter from "./DialogFooter"; import DialogFooter from "./DialogFooter";
import session from "../app/Session"; import session from "../app/Session";
@ -27,6 +27,7 @@ import ReserveTopicSelect from "./ReserveTopicSelect";
import { TopicReservedError, UnauthorizedError } from "../app/errors"; import { TopicReservedError, UnauthorizedError } from "../app/errors";
export const ReserveAddDialog = (props) => { export const ReserveAddDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [topic, setTopic] = useState(props.topic || ""); const [topic, setTopic] = useState(props.topic || "");
@ -87,6 +88,7 @@ export const ReserveAddDialog = (props) => {
}; };
export const ReserveEditDialog = (props) => { export const ReserveEditDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [everyone, setEveryone] = useState(props.reservation?.everyone || Permission.DENY_ALL); const [everyone, setEveryone] = useState(props.reservation?.everyone || Permission.DENY_ALL);
@ -124,6 +126,7 @@ export const ReserveEditDialog = (props) => {
}; };
export const ReserveDeleteDialog = (props) => { export const ReserveDeleteDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState(""); const [error, setError] = useState("");
const [deleteMessages, setDeleteMessages] = useState(false); const [deleteMessages, setDeleteMessages] = useState(false);

View File

@ -12,10 +12,10 @@ import {
FormGroup, FormGroup,
useMediaQuery, useMediaQuery,
Switch, Switch,
useTheme,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import theme from "./theme";
import api from "../app/Api"; import api from "../app/Api";
import { randomAlphanumericString, topicUrl, validTopic, validUrl } from "../app/utils"; import { randomAlphanumericString, topicUrl, validTopic, validUrl } from "../app/utils";
import userManager from "../app/UserManager"; import userManager from "../app/UserManager";
@ -49,6 +49,7 @@ export const subscribeTopic = async (baseUrl, topic, opts) => {
}; };
const SubscribeDialog = (props) => { const SubscribeDialog = (props) => {
const theme = useTheme();
const [baseUrl, setBaseUrl] = useState(""); const [baseUrl, setBaseUrl] = useState("");
const [topic, setTopic] = useState(""); const [topic, setTopic] = useState("");
const [showLoginPage, setShowLoginPage] = useState(false); const [showLoginPage, setShowLoginPage] = useState(false);

View File

@ -15,6 +15,7 @@ import {
MenuItem, MenuItem,
IconButton, IconButton,
ListItemIcon, ListItemIcon,
useTheme,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -30,7 +31,6 @@ import {
RemoveCircle, RemoveCircle,
Send, Send,
} from "@mui/icons-material"; } from "@mui/icons-material";
import theme from "./theme";
import subscriptionManager from "../app/SubscriptionManager"; import subscriptionManager from "../app/SubscriptionManager";
import DialogFooter from "./DialogFooter"; import DialogFooter from "./DialogFooter";
import accountApi, { Role } from "../app/AccountApi"; import accountApi, { Role } from "../app/AccountApi";
@ -281,6 +281,7 @@ export const SubscriptionPopup = (props) => {
}; };
const DisplayNameDialog = (props) => { const DisplayNameDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { subscription } = props; const { subscription } = props;
const [error, setError] = useState(""); const [error, setError] = useState("");

View File

@ -21,6 +21,7 @@ import {
Box, Box,
DialogContentText, DialogContentText,
DialogActions, DialogActions,
useTheme,
} from "@mui/material"; } from "@mui/material";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Check, Close } from "@mui/icons-material"; import { Check, Close } from "@mui/icons-material";
@ -31,7 +32,6 @@ import { AccountContext } from "./App";
import routes from "./routes"; import routes from "./routes";
import session from "../app/Session"; import session from "../app/Session";
import accountApi, { SubscriptionInterval } from "../app/AccountApi"; import accountApi, { SubscriptionInterval } from "../app/AccountApi";
import theme from "./theme";
const Feature = (props) => <FeatureItem feature>{props.children}</FeatureItem>; const Feature = (props) => <FeatureItem feature>{props.children}</FeatureItem>;
@ -61,6 +61,7 @@ const Banner = {
}; };
const UpgradeDialog = (props) => { const UpgradeDialog = (props) => {
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useContext(AccountContext); // May be undefined! const { account } = useContext(AccountContext); // May be undefined!
const [error, setError] = useState(""); const [error, setError] = useState("");

View File

@ -1,19 +1,18 @@
import { Typography, Container, Backdrop, styled } from "@mui/material"; import { Typography, Container, Backdrop, styled } from "@mui/material";
import theme from "./theme";
export const Paragraph = styled(Typography)({ export const Paragraph = styled(Typography)({
paddingTop: 8, paddingTop: 8,
paddingBottom: 8, paddingBottom: 8,
}); });
export const VerticallyCenteredContainer = styled(Container)({ export const VerticallyCenteredContainer = styled(Container)(({ theme }) => ({
display: "flex", display: "flex",
flexGrow: 1, flexGrow: 1,
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",
alignContent: "center", alignContent: "center",
color: theme.palette.text.primary, color: theme.palette.text.primary,
}); }));
export const LightboxBackdrop = styled(Backdrop)({ export const LightboxBackdrop = styled(Backdrop)({
backgroundColor: "rgba(0, 0, 0, 0.8)", // was: 0.5 backgroundColor: "rgba(0, 0, 0, 0.8)", // was: 0.5

View File

@ -1,18 +1,7 @@
import { red } from "@mui/material/colors"; import { grey, red } from "@mui/material/colors";
import { createTheme } from "@mui/material/styles";
const theme = createTheme({ /** @type {import("@mui/material").ThemeOptions} */
palette: { const themeOptions = {
primary: {
main: "#338574",
},
secondary: {
main: "#6cead0",
},
error: {
main: red.A400,
},
},
components: { components: {
MuiListItemIcon: { MuiListItemIcon: {
styleOverrides: { styleOverrides: {
@ -31,6 +20,32 @@ const theme = createTheme({
}, },
}, },
}, },
}); };
export default theme; /** @type {import("@mui/material").ThemeOptions['palette']} */
export const lightPalette = {
mode: "light",
primary: {
main: "#338574",
},
secondary: {
main: "#6cead0",
},
error: {
main: red.A400,
},
};
/** @type {import("@mui/material").ThemeOptions['palette']} */
export const darkPalette = {
...lightPalette,
mode: "dark",
background: {
paper: grey["800"],
},
primary: {
main: "#6cead0",
},
};
export default themeOptions;