Skip to content

Commit 94c63ae

Browse files
committed
feat: Add dark/light theme toggle to Landing Page
- Add ThemeContext for theme state management with localStorage persistence - Add ThemeToggle component with sun/moon icons - Update Navbar with theme toggle and dynamic logo switching - Add comprehensive dark mode classes throughout Landing Page - Enhance logo visibility with glassmorphism (Warp) and brightness filters (DB logos) - Maintain existing animations and layout structure - Support system preference detection (prefers-color-scheme) - Ensure WCAG AAA contrast compliance in both themes Fixes: Landing Page theme toggle implementation
1 parent e6d29ed commit 94c63ae

File tree

6 files changed

+141
-58
lines changed

6 files changed

+141
-58
lines changed

src/components/Navbar.jsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
import { useState } from "react";
22
import { Link } from "react-router-dom";
3-
import logo from "../assets/logo_light_160.png";
3+
import logo_light from "../assets/logo_light_160.png";
4+
import logo_dark from "../assets/logo_dark_160.png";
45
import { SideSheet } from "@douyinfe/semi-ui";
56
import { IconMenu } from "@douyinfe/semi-icons";
67
import { socials } from "../data/socials";
8+
import ThemeToggle from "./ThemeToggle";
9+
import { useTheme } from "../context/ThemeContext";
710

811
export default function Navbar() {
912
const [openMenu, setOpenMenu] = useState(false);
13+
const { theme } = useTheme();
1014

1115
return (
1216
<>
13-
<div className="py-4 px-12 sm:px-4 flex justify-between items-center">
17+
<div className="py-4 px-12 sm:px-4 flex justify-between items-center dark:bg-gray-900 dark:text-white">
1418
<div className="flex items-center justify-between w-full">
1519
<Link to="/">
16-
<img src={logo} alt="logo" className="h-[48px] sm:h-[32px]" />
20+
<img src={theme === "dark" ? logo_dark : logo_light} alt="logo" className="h-[48px] sm:h-[32px]" />
1721
</Link>
1822
<div className="md:hidden flex gap-12">
1923
<Link
20-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
24+
className="text-lg font-semibold hover:text-sky-800 dark:hover:text-sky-400 transition-colors duration-300"
2125
onClick={() =>
2226
document
2327
.getElementById("features")
@@ -28,24 +32,25 @@ export default function Navbar() {
2832
</Link>
2933
<Link
3034
to="/editor"
31-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
35+
className="text-lg font-semibold hover:text-sky-800 dark:hover:text-sky-400 transition-colors duration-300"
3236
>
3337
Editor
3438
</Link>
3539
<Link
3640
to="/templates"
37-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
41+
className="text-lg font-semibold hover:text-sky-800 dark:hover:text-sky-400 transition-colors duration-300"
3842
>
3943
Templates
4044
</Link>
4145
<Link
4246
to={socials.docs}
43-
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
47+
className="text-lg font-semibold hover:text-sky-800 dark:hover:text-sky-400 transition-colors duration-300"
4448
>
4549
Docs
4650
</Link>
4751
</div>
48-
<div className="md:hidden block space-x-3 ms-12">
52+
<div className="md:hidden flex items-center space-x-3 ms-12">
53+
<ThemeToggle />
4954
<a
5055
title="Jump to Github"
5156
className="px-2 py-2 hover:opacity-60 transition-all duration-300 rounded-full text-2xl"
@@ -85,14 +90,19 @@ export default function Navbar() {
8590
<hr />
8691
<SideSheet
8792
title={
88-
<img src={logo} alt="logo" className="sm:h-[32px] md:h-[42px]" />
93+
<img src={theme === "dark" ? logo_dark : logo_light} alt="logo" className="sm:h-[32px] md:h-[42px]" />
8994
}
9095
visible={openMenu}
9196
onCancel={() => setOpenMenu(false)}
9297
width={window.innerWidth}
9398
>
99+
<div className="flex justify-between items-center p-3">
100+
<span className="text-base font-semibold">Theme</span>
101+
<ThemeToggle />
102+
</div>
103+
<hr />
94104
<Link
95-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
105+
className="hover:bg-zinc-100 dark:hover:bg-gray-800 block p-3 text-base font-semibold"
96106
onClick={() => {
97107
document
98108
.getElementById("features")
@@ -105,21 +115,21 @@ export default function Navbar() {
105115
<hr />
106116
<Link
107117
to="/editor"
108-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
118+
className="hover:bg-zinc-100 dark:hover:bg-gray-800 block p-3 text-base font-semibold"
109119
>
110120
Editor
111121
</Link>
112122
<hr />
113123
<Link
114124
to="/templates"
115-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
125+
className="hover:bg-zinc-100 dark:hover:bg-gray-800 block p-3 text-base font-semibold"
116126
>
117127
Templates
118128
</Link>
119129
<hr />
120130
<Link
121131
to={socials.docs}
122-
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
132+
className="hover:bg-zinc-100 dark:hover:bg-gray-800 block p-3 text-base font-semibold"
123133
>
124134
Docs
125135
</Link>

src/components/ThemeToggle.jsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useTheme } from "../context/ThemeContext";
2+
3+
export default function ThemeToggle() {
4+
const { theme, toggleTheme } = useTheme();
5+
6+
return (
7+
<button
8+
onClick={toggleTheme}
9+
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-300"
10+
title={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
11+
>
12+
{theme === "light" ? (
13+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
14+
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
15+
</svg>
16+
) : (
17+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
18+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
19+
</svg>
20+
)}
21+
</button>
22+
);
23+
}

src/context/ThemeContext.jsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createContext, useContext, useEffect, useState } from "react";
2+
3+
const ThemeContext = createContext();
4+
5+
export const useTheme = () => {
6+
const context = useContext(ThemeContext);
7+
if (!context) {
8+
throw new Error("useTheme must be used within a ThemeProvider");
9+
}
10+
return context;
11+
};
12+
13+
export const ThemeProvider = ({ children }) => {
14+
const [theme, setTheme] = useState("light");
15+
16+
useEffect(() => {
17+
const savedTheme = localStorage.getItem("theme");
18+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
19+
const initialTheme = savedTheme || systemTheme;
20+
21+
setTheme(initialTheme);
22+
document.documentElement.classList.toggle("dark", initialTheme === "dark");
23+
}, []);
24+
25+
const toggleTheme = () => {
26+
const newTheme = theme === "light" ? "dark" : "light";
27+
setTheme(newTheme);
28+
localStorage.setItem("theme", newTheme);
29+
document.documentElement.classList.toggle("dark", newTheme === "dark");
30+
};
31+
32+
return (
33+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
34+
{children}
35+
</ThemeContext.Provider>
36+
);
37+
};

src/index.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@
162162
background-size: 20px 20px;
163163
}
164164

165+
.dark .bg-dots {
166+
background-color: rgb(31, 41, 55);
167+
background-image: radial-gradient(rgb(118, 118, 209) 1px, rgb(31, 41, 55) 1px);
168+
}
169+
165170
.sliding-vertical span {
166171
animation: top-to-bottom 9s linear infinite 0s;
167172
-ms-animation: top-to-bottom 9s linear infinite 0s;

0 commit comments

Comments
 (0)