Skip to content

Commit 2a902dd

Browse files
I'll implement a dark theme for the Home (Landing) Page while keeping the existing structure and design intact
1 parent e6d29ed commit 2a902dd

File tree

6 files changed

+184
-63
lines changed

6 files changed

+184
-63
lines changed

src/App.jsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@ import BugReport from "./pages/BugReport";
55
import Templates from "./pages/Templates";
66
import LandingPage from "./pages/LandingPage";
77
import SettingsContextProvider from "./context/SettingsContext";
8+
import ThemeProvider from "./context/ThemeContext";
89
import NotFound from "./pages/NotFound";
910

1011
export default function App() {
1112
return (
12-
<SettingsContextProvider>
13-
<BrowserRouter>
14-
<RestoreScroll />
15-
<Routes>
16-
<Route path="/" element={<LandingPage />} />
17-
<Route path="/editor" element={<Editor />} />
18-
<Route path="/bug-report" element={<BugReport />} />
19-
<Route path="/templates" element={<Templates />} />
20-
<Route path="*" element={<NotFound />} />
21-
</Routes>
22-
</BrowserRouter>
23-
</SettingsContextProvider>
13+
<ThemeProvider>
14+
<SettingsContextProvider>
15+
<BrowserRouter>
16+
<RestoreScroll />
17+
<Routes>
18+
<Route path="/" element={<LandingPage />} />
19+
<Route path="/editor" element={<Editor />} />
20+
<Route path="/bug-report" element={<BugReport />} />
21+
<Route path="/templates" element={<Templates />} />
22+
<Route path="*" element={<NotFound />} />
23+
</Routes>
24+
</BrowserRouter>
25+
</SettingsContextProvider>
26+
</ThemeProvider>
2427
);
2528
}
2629

src/components/Navbar.jsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ import logo from "../assets/logo_light_160.png";
44
import { SideSheet } from "@douyinfe/semi-ui";
55
import { IconMenu } from "@douyinfe/semi-icons";
66
import { socials } from "../data/socials";
7+
import ThemeToggle from "./ThemeToggle";
8+
import { useTheme } from "../context/ThemeContext";
79

810
export default function Navbar() {
911
const [openMenu, setOpenMenu] = useState(false);
12+
const { theme } = useTheme();
1013

1114
return (
1215
<>
13-
<div className="py-4 px-12 sm:px-4 flex justify-between items-center">
14-
<div className="flex items-center justify-between w-full">
16+
<div className="py-4 px-12 sm:px-4 flex justify-between items-center dark:bg-gray-800">
17+
<div className="flex items-center justify-between w-full relative">
1518
<Link to="/">
1619
<img src={logo} alt="logo" className="h-[48px] sm:h-[32px]" />
1720
</Link>
1821
<div className="md:hidden flex gap-12">
1922
<Link
20-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
23+
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
2124
onClick={() =>
2225
document
2326
.getElementById("features")
@@ -28,32 +31,33 @@ export default function Navbar() {
2831
</Link>
2932
<Link
3033
to="/editor"
31-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
34+
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
3235
>
3336
Editor
3437
</Link>
3538
<Link
3639
to="/templates"
37-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
40+
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
3841
>
3942
Templates
4043
</Link>
4144
<Link
4245
to={socials.docs}
43-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
46+
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
4447
>
4548
Docs
4649
</Link>
4750
</div>
48-
<div className="md:hidden block space-x-3 ms-12">
51+
<div className="md:hidden flex items-center space-x-3 ms-12">
52+
<ThemeToggle className="mr-2" />
4953
<a
5054
title="Jump to Github"
5155
className="px-2 py-2 hover:opacity-60 transition-all duration-300 rounded-full text-2xl"
5256
href={socials.github}
5357
target="_blank"
5458
rel="noreferrer"
5559
>
56-
<i className="opacity-70 bi bi-github" />
60+
<i className="opacity-70 bi bi-github dark:text-gray-200" />
5761
</a>
5862
<a
5963
title="Follow us on X"
@@ -62,7 +66,7 @@ export default function Navbar() {
6266
target="_blank"
6367
rel="noreferrer"
6468
>
65-
<i className="opacity-70 bi bi-twitter-x" />
69+
<i className="opacity-70 bi bi-twitter-x dark:text-gray-200" />
6670
</a>
6771
<a
6872
title="Join the community on Discord"
@@ -71,13 +75,13 @@ export default function Navbar() {
7175
target="_blank"
7276
rel="noreferrer"
7377
>
74-
<i className="opacity-70 bi bi-discord" />
78+
<i className="opacity-70 bi bi-discord dark:text-gray-200" />
7579
</a>
7680
</div>
7781
</div>
7882
<button
7983
onClick={() => setOpenMenu((prev) => !prev)}
80-
className="hidden md:inline-block h-[24px]"
84+
className="hidden md:inline-block h-[24px] dark:text-gray-200"
8185
>
8286
<IconMenu size="extra-large" />
8387
</button>
@@ -92,7 +96,7 @@ export default function Navbar() {
9296
width={window.innerWidth}
9397
>
9498
<Link
95-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
99+
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
96100
onClick={() => {
97101
document
98102
.getElementById("features")
@@ -105,24 +109,29 @@ export default function Navbar() {
105109
<hr />
106110
<Link
107111
to="/editor"
108-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
112+
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
109113
>
110114
Editor
111115
</Link>
112116
<hr />
113117
<Link
114118
to="/templates"
115-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
119+
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
116120
>
117121
Templates
118122
</Link>
119123
<hr />
120124
<Link
121125
to={socials.docs}
122-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
126+
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
123127
>
124128
Docs
125129
</Link>
130+
<hr className="dark:border-gray-700" />
131+
<div className="p-3 flex items-center">
132+
<span className="text-base font-semibold mr-3 dark:text-gray-200">Theme:</span>
133+
<ThemeToggle />
134+
</div>
126135
</SideSheet>
127136
</>
128137
);

src/components/ThemeToggle.jsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useTheme } from "../context/ThemeContext";
2+
3+
export default function ThemeToggle({ className = "" }) {
4+
const { theme, toggleTheme } = useTheme();
5+
6+
return (
7+
<button
8+
onClick={toggleTheme}
9+
className={`p-2 rounded-full transition-colors duration-300 ${
10+
theme === "dark"
11+
? "bg-gray-700 hover:bg-gray-600 text-yellow-300"
12+
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
13+
} ${className}`}
14+
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
15+
title={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
16+
>
17+
{theme === "dark" ? (
18+
<i className="bi bi-sun-fill text-xl"></i>
19+
) : (
20+
<i className="bi bi-moon-fill text-xl"></i>
21+
)}
22+
</button>
23+
);
24+
}

src/context/ThemeContext.jsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { createContext, useContext, useEffect, useState } from "react";
2+
3+
// Create context
4+
export const ThemeContext = createContext();
5+
6+
// Theme provider component
7+
export default function ThemeProvider({ children }) {
8+
// Check for system preference and stored preference
9+
const getInitialTheme = () => {
10+
// Check if we're in the browser
11+
if (typeof window !== "undefined") {
12+
// Check localStorage first
13+
const storedTheme = localStorage.getItem("theme");
14+
if (storedTheme) {
15+
return storedTheme;
16+
}
17+
18+
// Check system preference
19+
const userPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
20+
return userPrefersDark ? "dark" : "light";
21+
}
22+
23+
// Default to light if not in browser
24+
return "light";
25+
};
26+
27+
const [theme, setTheme] = useState(getInitialTheme);
28+
29+
// Toggle theme function
30+
const toggleTheme = () => {
31+
setTheme(prevTheme => prevTheme === "light" ? "dark" : "light");
32+
};
33+
34+
// Update localStorage and apply theme class to html element
35+
useEffect(() => {
36+
// Save to localStorage
37+
localStorage.setItem("theme", theme);
38+
39+
// Apply class to html element
40+
const htmlElement = document.documentElement;
41+
if (theme === "dark") {
42+
htmlElement.classList.add("dark");
43+
} else {
44+
htmlElement.classList.remove("dark");
45+
}
46+
47+
// Also update the theme-mode attribute for semi-ui components
48+
document.body.setAttribute("theme-mode", theme);
49+
}, [theme]);
50+
51+
// Listen for system preference changes
52+
useEffect(() => {
53+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
54+
const handleChange = () => {
55+
// Only update if user hasn't manually set a preference
56+
if (!localStorage.getItem("theme")) {
57+
setTheme(mediaQuery.matches ? "dark" : "light");
58+
}
59+
};
60+
61+
// Add listener
62+
mediaQuery.addEventListener("change", handleChange);
63+
64+
// Clean up
65+
return () => mediaQuery.removeEventListener("change", handleChange);
66+
}, []);
67+
68+
// Provide theme context to children
69+
return (
70+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
71+
{children}
72+
</ThemeContext.Provider>
73+
);
74+
}
75+
76+
// Custom hook for using theme
77+
export const useTheme = () => {
78+
const context = useContext(ThemeContext);
79+
if (context === undefined) {
80+
throw new Error("useTheme must be used within a ThemeProvider");
81+
}
82+
return context;
83+
};

0 commit comments

Comments
 (0)