diff --git a/package.json b/package.json index f81c005b1c4a5f641d84b83b67611b2529741433..2ddf6be7c07768145ba11d65141e940875e50a4e 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "axios": "^0.24.0", "react": "^17.0.0", "react-dom": "^17.0.0", - "react-router-dom": "^6.0.2" + "react-router-dom": "^6.0.2", + "recoil": "^0.5.2" }, "devDependencies": { + "@tauri-apps/cli": "^1.0.0-beta.10", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "@vitejs/plugin-react": "^1.1.1", diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 3d5ba1142b651027dbe0995b4e7490a3b999d7c2..71218148733759a42d086e748041ab460b5db194 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -1,7 +1,3 @@ -html { - background-color: #fefefe; -} - body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", diff --git a/src/assets/styles/layout.scss b/src/assets/styles/layout.scss index 5f52266af3a5ad783b56213683c9fbb14e1372f4..1f17d8fe6708b971249d5ad3670fb9bc2e2697c0 100644 --- a/src/assets/styles/layout.scss +++ b/src/assets/styles/layout.scss @@ -6,7 +6,14 @@ &__sidebar { position: relative; height: 100vh; - flex: 1 1 25%; + flex: 1 0 25%; + max-width: 250px; + } + + &__content { + flex: 1 1 75%; + padding: 20px 30px; + box-sizing: border-box; } &__traffic { @@ -19,10 +26,4 @@ margin: 0 auto; } } - - &__content { - flex: 1 1 75%; - padding: 20px 30px; - box-sizing: border-box; - } } diff --git a/src/components/list-item-link.tsx b/src/components/list-item-link.tsx index 95fc5272a5e1bbd037fa86508c2a6f0411652803..bc1bd31b5b154c00f509d02799b3588c3c9a6550 100644 --- a/src/components/list-item-link.tsx +++ b/src/components/list-item-link.tsx @@ -12,16 +12,41 @@ const ListItemLink = (props: LinkProps) => { return ( <ListItem sx={{ py: 0.5, maxWidth: 250, mx: "auto" }}> <ListItemButton - sx={{ - borderRadius: 2, - textAlign: "center", - bgcolor: match ? "rgba(91,92,157,0.15)" : "transparent", - }} + sx={[ + { + color: "primary", + borderRadius: 2, + textAlign: "center", + }, + (theme) => { + if (!match) return {}; + + if (theme.palette.mode === "light") { + return { + bgcolor: "rgba(91,92,157,0.15)", + "&:hover": { bgcolor: "rgba(91,92,157,0.15)" }, + }; + } + + return { + bgcolor: "rgba(91,92,157,0.35)", + "&:hover": { bgcolor: "rgba(91,92,157,0.35)" }, + }; + }, + ]} onClick={() => navigate(to)} > <ListItemText primary={children} - sx={{ color: match ? "primary.main" : "text.primary" }} + sx={{ + color: (theme) => { + if (!match) return "text.secondary"; + + const light = theme.palette.mode === "light"; + if (match && light) return "primary.main"; + return "primary.light"; + }, + }} /> </ListItemButton> </ListItem> diff --git a/src/components/palette-switch.tsx b/src/components/palette-switch.tsx new file mode 100644 index 0000000000000000000000000000000000000000..49b37f7bb4565de9e5d1b0379dce17e25493452d --- /dev/null +++ b/src/components/palette-switch.tsx @@ -0,0 +1,51 @@ +import { styled, Switch } from "@mui/material"; + +// From: https://mui.com/components/switches/ +const PaletteSwitch = styled(Switch)(({ theme }) => ({ + width: 62, + height: 34, + padding: 7, + "& .MuiSwitch-switchBase": { + margin: 1, + padding: 0, + transform: "translateX(6px)", + "&.Mui-checked": { + color: "#fff", + transform: "translateX(22px)", + "& .MuiSwitch-thumb:before": { + backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent( + "#fff" + )}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`, + }, + "& + .MuiSwitch-track": { + opacity: 1, + backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be", + }, + }, + }, + "& .MuiSwitch-thumb": { + backgroundColor: theme.palette.mode === "dark" ? "#003892" : "#001e3c", + width: 32, + height: 32, + "&:before": { + content: "''", + position: "absolute", + width: "100%", + height: "100%", + left: 0, + top: 0, + backgroundRepeat: "no-repeat", + backgroundPosition: "center", + backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent( + "#fff" + )}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`, + }, + }, + "& .MuiSwitch-track": { + opacity: 1, + backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be", + borderRadius: 20 / 2, + }, +})); + +export default PaletteSwitch; diff --git a/src/main.tsx b/src/main.tsx index a9fa3296aa510328ce68606cd85f4f1dfef0ba6b..1e732179c893b9d67608257eaaa751a313ee57b2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,32 +2,17 @@ import "./assets/styles/index.scss"; import React from "react"; import ReactDOM from "react-dom"; +import { RecoilRoot } from "recoil"; import { BrowserRouter } from "react-router-dom"; -import { createTheme, ThemeProvider } from "@mui/material"; import Layout from "./pages/_layout"; -const theme = createTheme({ - palette: { - mode: "light", - primary: { - main: "#5b5c9d", - }, - text: { - primary: "#637381", - secondary: "#909399", - }, - }, -}); - -// console.log(theme); - ReactDOM.render( <React.StrictMode> - <ThemeProvider theme={theme}> + <RecoilRoot> <BrowserRouter> <Layout /> </BrowserRouter> - </ThemeProvider> + </RecoilRoot> </React.StrictMode>, document.getElementById("root") ); diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index ee0907730916da494fc562d8892d13e98f6f5011..04465382ed7939de0e136c6e6309796f0b7c91c1 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -1,5 +1,14 @@ +import { useMemo } from "react"; import { Route, Routes } from "react-router-dom"; -import { List, Paper, Typography } from "@mui/material"; +import { useRecoilValue } from "recoil"; +import { + createTheme, + List, + Paper, + ThemeProvider, + Typography, +} from "@mui/material"; +import { atomPaletteMode } from "../states/setting"; import LogPage from "../pages/log"; import HomePage from "../pages/home"; import ProxyPage from "../pages/proxy"; @@ -10,6 +19,8 @@ import ListItemLink from "../components/list-item-link"; import Traffic from "../components/traffic"; const Layout = () => { + const paletteMode = useRecoilValue(atomPaletteMode); + const routers = [ { label: "代ç†", @@ -33,41 +44,61 @@ const Layout = () => { }, ]; + const theme = useMemo(() => { + const bgcolor = paletteMode === "light" ? "#f5f5f5" : "#000"; + document.documentElement.style.background = bgcolor; + + return createTheme({ + palette: { + mode: paletteMode, + primary: { + main: "#5b5c9d", + }, + text: { + primary: "#637381", + secondary: "#909399", + }, + }, + }); + }, [paletteMode]); + return ( - <Paper square elevation={0} className="layout"> - <div className="layout__sidebar"> - <Typography - variant="h3" - component="h1" - sx={{ my: 2, px: 2, textAlign: "center", userSelect: "none" }} - > - Clash Verge - </Typography> + <ThemeProvider theme={theme}> + <Paper square elevation={0} className="layout"> + <div className="layout__sidebar"> + <Typography + variant="h3" + component="h1" + sx={{ my: 2, px: 2, textAlign: "center", userSelect: "none" }} + > + Clash Verge + </Typography> - <List sx={{ userSelect: "none" }}> - {routers.map((router) => ( - <ListItemLink key={router.label} to={router.link}> - {router.label} - </ListItemLink> - ))} - </List> + <List sx={{ userSelect: "none" }}> + {routers.map((router) => ( + <ListItemLink key={router.label} to={router.link}> + {router.label} + </ListItemLink> + ))} + </List> - <div className="layout__traffic"> - <Traffic /> + <div className="layout__traffic"> + <Traffic /> + </div> </div> - </div> - <div className="layout__content"> - <Routes> - <Route path="/" element={<HomePage />} /> - <Route path="/proxy" element={<ProxyPage />} /> - <Route path="/profiles" element={<ProfilesPage />} /> - <Route path="/log" element={<LogPage />} /> - <Route path="/connections" element={<ConnectionsPage />} /> - <Route path="/setting" element={<SettingPage />} /> - </Routes> - </div> - </Paper> + <div className="layout__content"> + <Routes> + <Route path="/" element={<HomePage />} /> + <Route path="/proxy" element={<ProxyPage />} /> + <Route path="/profiles" element={<ProfilesPage />} /> + <Route path="/log" element={<LogPage />} /> + <Route path="/connections" element={<ConnectionsPage />} /> + <Route path="/setting" element={<SettingPage />} /> + </Routes> + </div> + </Paper> + </ThemeProvider> ); }; diff --git a/src/pages/setting.tsx b/src/pages/setting.tsx index be8d857e4a0eae51063dc0eb235a6dbc16c13950..f367828f9fe848fa8e1fa26e1cbf5095cfee573e 100644 --- a/src/pages/setting.tsx +++ b/src/pages/setting.tsx @@ -1,5 +1,24 @@ +import { Box } from "@mui/system"; +import { useRecoilState } from "recoil"; +import { atomPaletteMode } from "../states/setting"; +import PaletteSwitch from "../components/palette-switch"; + const SettingPage = () => { - return <h1>Setting</h1>; + const [mode, setMode] = useRecoilState(atomPaletteMode); + + return ( + <Box> + <h1>Setting</h1> + + <Box> + <PaletteSwitch + checked={mode !== "light"} + onChange={(_e, c) => setMode(c ? "dark" : "light")} + inputProps={{ "aria-label": "controlled" }} + /> + </Box> + </Box> + ); }; export default SettingPage; diff --git a/src/states/setting.ts b/src/states/setting.ts new file mode 100644 index 0000000000000000000000000000000000000000..56ceffd2c8f02c24fa8b116072ca0082febe2540 --- /dev/null +++ b/src/states/setting.ts @@ -0,0 +1,6 @@ +import { atom } from "recoil"; + +export const atomPaletteMode = atom<"light" | "dark">({ + key: "atomPaletteMode", + default: "light", +});