nimbleghost 2 лет назад
Родитель
Сommit
4f39c7c155

+ 5 - 1
web/src/components/Account.jsx

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

+ 16 - 3
web/src/components/App.jsx

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

+ 2 - 1
web/src/components/Preferences.jsx

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

+ 3 - 1
web/src/components/PublishDialog.jsx

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

+ 4 - 1
web/src/components/ReserveDialogs.jsx

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

+ 2 - 1
web/src/components/SubscribeDialog.jsx

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

+ 2 - 1
web/src/components/SubscriptionPopup.jsx

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

+ 2 - 1
web/src/components/UpgradeDialog.jsx

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

+ 2 - 3
web/src/components/styles.js

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

+ 31 - 16
web/src/components/theme.js

@@ -1,18 +1,7 @@
-import { red } from "@mui/material/colors";
-import { createTheme } from "@mui/material/styles";
+import { grey, red } from "@mui/material/colors";
 
-const theme = createTheme({
-  palette: {
-    primary: {
-      main: "#338574",
-    },
-    secondary: {
-      main: "#6cead0",
-    },
-    error: {
-      main: red.A400,
-    },
-  },
+/** @type {import("@mui/material").ThemeOptions} */
+const themeOptions = {
   components: {
     MuiListItemIcon: {
       styleOverrides: {
@@ -31,6 +20,32 @@ const theme = createTheme({
       },
     },
   },
-});
+};
+
+/** @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 theme;
+export default themeOptions;