Skip to content

Commit 8805852

Browse files
feat: Implement dark mode toggle and theme provider in the application (#16)
1 parent 28922de commit 8805852

File tree

6 files changed

+166
-29
lines changed

6 files changed

+166
-29
lines changed

src/App.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import React from "react";
22
import "./App.css";
33
import { GitService } from "./service/gitService";
4-
import { AppBar, Box, Toolbar } from "@mui/material";
4+
import {
5+
AppBar,
6+
Box,
7+
CssBaseline,
8+
ThemeProvider,
9+
Toolbar,
10+
} from "@mui/material";
511
import { SettingsDrawer } from "./SettingsDrawer";
612
import { AuthHeader } from "./components/AuthHeader";
713
import { UnAuthHeader } from "./components/UnAuthHeader";
814
import { Outlet, useNavigate } from "react-router";
915
import { ConfigContext } from "./context/ConfigContext";
16+
import { darkTheme, lightTheme } from "./theme";
1017

1118
function App() {
1219
const [user, setUser] = React.useState<{
@@ -17,6 +24,7 @@ function App() {
1724
const [token, setToken] = React.useState<string>();
1825
const [octokit, setOctokit] = React.useState<GitService | null>(null);
1926
const [openSettings, setOpenSettings] = React.useState<boolean>(false);
27+
const [isDarkMode, setIsDarkMode] = React.useState<boolean>(false);
2028
const navigate = useNavigate();
2129

2230
const onLogin = React.useCallback(() => {
@@ -55,6 +63,8 @@ function App() {
5563
)
5664
);
5765
}
66+
67+
setIsDarkMode(localStorage.getItem("DARK_MODE") === "true");
5868
}, []);
5969

6070
const logOut = React.useCallback(() => {
@@ -65,6 +75,11 @@ function App() {
6575
navigate("/login");
6676
}, [navigate]);
6777

78+
const switchDarkMode = React.useCallback(() => {
79+
setIsDarkMode((prev) => !prev);
80+
localStorage.setItem("DARK_MODE", (!isDarkMode).toString());
81+
}, [isDarkMode]);
82+
6883
const [repositorySettings, setRepositorySettings] = React.useState<
6984
Record<string, boolean>
7085
>({});
@@ -98,7 +113,8 @@ function App() {
98113
}, []);
99114

100115
return (
101-
<>
116+
<ThemeProvider theme={isDarkMode ? darkTheme : lightTheme}>
117+
<CssBaseline />
102118
<ConfigContext.Provider
103119
value={{
104120
octokit,
@@ -121,6 +137,8 @@ function App() {
121137
user={user}
122138
logOut={logOut}
123139
setOpenSettings={setOpenSettings}
140+
onThemeSwitch={switchDarkMode}
141+
darkMode={isDarkMode}
124142
/>
125143
)}
126144
</Toolbar>
@@ -136,7 +154,11 @@ function App() {
136154
paddingTop: "4em",
137155
}}
138156
>
139-
<Box padding={2} width={"calc(100vw - 2em)"}>
157+
<Box
158+
padding={2}
159+
width={"calc(100vw - 2em)"}
160+
justifyContent={"center"}
161+
>
140162
<Outlet />
141163
</Box>
142164
</Box>
@@ -147,7 +169,7 @@ function App() {
147169
/>
148170
)}
149171
</ConfigContext.Provider>
150-
</>
172+
</ThemeProvider>
151173
);
152174
}
153175

src/components/AuthHeader.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useLocation, useNavigate } from "react-router";
77
import { PullRequestIcon } from "./icons/PullRequestIcon";
88
import { RepositoryIcon } from "./icons/RepositoryIcon";
99
import { IssuesIcon } from "./icons/IssuesIcon";
10+
import { ThemeSwitch } from "./ThemeSwitch";
1011

1112
export type AuthHeaderProps = {
1213
user: {
@@ -16,16 +17,27 @@ export type AuthHeaderProps = {
1617
};
1718
logOut: () => void;
1819
setOpenSettings: (value: boolean) => void;
20+
onThemeSwitch: () => void;
21+
darkMode: boolean;
1922
};
2023

2124
export const AuthHeader: React.FC<AuthHeaderProps> = ({
2225
user,
2326
logOut,
2427
setOpenSettings,
28+
onThemeSwitch,
29+
darkMode,
2530
}) => {
2631
const location = useLocation();
2732
const navigate = useNavigate();
2833

34+
const [checked, setChecked] = React.useState(darkMode);
35+
36+
const handleThemeSwitch = () => {
37+
setChecked(!checked);
38+
onThemeSwitch();
39+
};
40+
2941
return (
3042
<Box
3143
sx={{
@@ -43,36 +55,31 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({
4355
<BottomNavigationAction
4456
label="Dashboard"
4557
icon={<Dashboard />}
46-
sx={{ backgroundColor: "#f5f5f5" }}
4758
value="/"
4859
title="Dashboard"
4960
/>
5061
<BottomNavigationAction
5162
label="My PRs"
5263
icon={<PullRequestIcon />}
53-
sx={{ backgroundColor: "#f5f5f5" }}
5464
value="/my-pull-requests"
5565
title="My PRs"
5666
/>
5767

5868
<BottomNavigationAction
5969
label="Repositories"
6070
icon={<RepositoryIcon />}
61-
sx={{ backgroundColor: "#f5f5f5" }}
6271
value="/repositories"
6372
title="Repositories"
6473
/>
6574
<BottomNavigationAction
6675
label="Issues"
6776
icon={<IssuesIcon />}
68-
sx={{ backgroundColor: "#f5f5f5" }}
6977
value="/issues"
7078
title="Issues"
7179
/>
7280
<BottomNavigationAction
7381
label="Coverage"
7482
icon={<Biotech />}
75-
sx={{ backgroundColor: "#f5f5f5" }}
7683
value="/coverage"
7784
title="Coverage"
7885
/>
@@ -86,6 +93,7 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({
8693
justifyContent: "end",
8794
}}
8895
>
96+
<ThemeSwitch checked={checked} onChange={handleThemeSwitch} />
8997
<Avatar alt={user?.login} src={user?.avatar_url} sx={{ mr: 2 }} />
9098
<Chip
9199
label={user?.login}

src/components/ThemeSwitch.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { styled } from "@mui/material/styles";
2+
import Switch from "@mui/material/Switch";
3+
4+
/* eslint-disable max-len */
5+
export const ThemeSwitch = styled(Switch)(({ theme }) => ({
6+
width: 62,
7+
height: 34,
8+
padding: 7,
9+
"& .MuiSwitch-switchBase": {
10+
margin: 1,
11+
padding: 0,
12+
transform: "translateX(6px)",
13+
"&.Mui-checked": {
14+
color: "#fff",
15+
transform: "translateX(22px)",
16+
"& .MuiSwitch-thumb:before": {
17+
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(
18+
"#fff"
19+
)}" 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>')`,
20+
},
21+
"& + .MuiSwitch-track": {
22+
opacity: 1,
23+
backgroundColor: "#aab4be",
24+
...theme.applyStyles("dark", {
25+
backgroundColor: "#8796A5",
26+
}),
27+
},
28+
},
29+
},
30+
"& .MuiSwitch-thumb": {
31+
backgroundColor: "#001e3c",
32+
width: 32,
33+
height: 32,
34+
"&::before": {
35+
content: "''",
36+
position: "absolute",
37+
width: "100%",
38+
height: "100%",
39+
left: 0,
40+
top: 0,
41+
backgroundRepeat: "no-repeat",
42+
backgroundPosition: "center",
43+
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(
44+
"#fff"
45+
)}" 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>')`,
46+
},
47+
...theme.applyStyles("dark", {
48+
backgroundColor: "#003892",
49+
}),
50+
},
51+
"& .MuiSwitch-track": {
52+
opacity: 1,
53+
backgroundColor: "#aab4be",
54+
borderRadius: 20 / 2,
55+
...theme.applyStyles("dark", {
56+
backgroundColor: "#8796A5",
57+
}),
58+
},
59+
}));

src/index.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import App from "./App";
55
import reportWebVitals from "./reportWebVitals";
66
import { HashRouter, Route, Routes } from "react-router";
77
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8-
import { ScopedCssBaseline } from "@mui/material";
98
import Coverage from "./pages/Coverage";
109
import LandingPage from "./pages/LandingPage";
1110
import Dashboard from "./pages/Dashboard";
@@ -22,23 +21,21 @@ const root = ReactDOM.createRoot(
2221
root.render(
2322
<React.StrictMode>
2423
<QueryClientProvider client={queryClient}>
25-
<ScopedCssBaseline>
26-
<HashRouter>
27-
<Routes>
28-
<Route path="/" element={<App />}>
29-
<Route index element={<Dashboard />} />
30-
<Route path="/login" element={<LandingPage />} />
31-
<Route path="/coverage" element={<Coverage />} />
32-
<Route path="/my-pull-requests" element={<MyPullRequests />} />
33-
<Route path="/repositories">
34-
<Route index element={<RepositoriesPage />} />
35-
<Route path=":owner/:repo" element={<RepositoryItem />} />
36-
</Route>
37-
<Route path="/issues" element={<IssuesPage />} />
24+
<HashRouter>
25+
<Routes>
26+
<Route path="/" element={<App />}>
27+
<Route index element={<Dashboard />} />
28+
<Route path="/login" element={<LandingPage />} />
29+
<Route path="/coverage" element={<Coverage />} />
30+
<Route path="/my-pull-requests" element={<MyPullRequests />} />
31+
<Route path="/repositories">
32+
<Route index element={<RepositoriesPage />} />
33+
<Route path=":owner/:repo" element={<RepositoryItem />} />
3834
</Route>
39-
</Routes>
40-
</HashRouter>
41-
</ScopedCssBaseline>
35+
<Route path="/issues" element={<IssuesPage />} />
36+
</Route>
37+
</Routes>
38+
</HashRouter>
4239
</QueryClientProvider>
4340
</React.StrictMode>
4441
);

src/pages/PRLoadingPage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import React from "react";
2-
import { CircularProgress, Typography, Box } from "@mui/material";
2+
import { CircularProgress, Typography, Box, Stack } from "@mui/material";
33

44
export const PRLoadingPage: React.FC = () => {
55
return (
6-
<>
6+
<Stack
7+
sx={{
8+
justifyContent: "center",
9+
alignItems: "center",
10+
height: "calc(100vh - 4em - 32px)",
11+
}}
12+
>
713
<Box
814
component={"img"}
915
src="loading.webp"
@@ -19,7 +25,7 @@ export const PRLoadingPage: React.FC = () => {
1925
Hang tight! We're fetching your pull requests faster than you can say
2026
"Merge Conflict"!
2127
</Typography>
22-
</>
28+
</Stack>
2329
);
2430
};
2531

src/theme/index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createTheme } from "@mui/material/styles";
2+
3+
export const darkTheme = createTheme({
4+
palette: {
5+
mode: "dark",
6+
primary: {
7+
main: "#90caf9",
8+
},
9+
secondary: {
10+
main: "#f48fb1",
11+
},
12+
},
13+
// override BottomNavigationAction
14+
components: {
15+
MuiBottomNavigationAction: {
16+
styleOverrides: {
17+
root: {
18+
backgroundColor: "#272727",
19+
},
20+
},
21+
},
22+
},
23+
});
24+
25+
export const lightTheme = createTheme({
26+
palette: {
27+
mode: "light",
28+
primary: {
29+
main: "#1976d2",
30+
},
31+
secondary: {
32+
main: "#f48fb1",
33+
},
34+
},
35+
// override BottomNavigationAction
36+
components: {
37+
MuiBottomNavigationAction: {
38+
styleOverrides: {
39+
root: {
40+
backgroundColor: "#f5f5f5",
41+
},
42+
},
43+
},
44+
},
45+
});

0 commit comments

Comments
 (0)