Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import BugReport from "./pages/BugReport";
import Templates from "./pages/Templates";
import LandingPage from "./pages/LandingPage";
import SettingsContextProvider from "./context/SettingsContext";
import ThemeProvider from "./context/ThemeContext";
import NotFound from "./pages/NotFound";

export default function App() {
return (
<SettingsContextProvider>
<BrowserRouter>
<RestoreScroll />
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/editor" element={<Editor />} />
<Route path="/bug-report" element={<BugReport />} />
<Route path="/templates" element={<Templates />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</SettingsContextProvider>
<ThemeProvider>
<SettingsContextProvider>
<BrowserRouter>
<RestoreScroll />
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/editor" element={<Editor />} />
<Route path="/bug-report" element={<BugReport />} />
<Route path="/templates" element={<Templates />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</SettingsContextProvider>
</ThemeProvider>
);
}

Expand Down
39 changes: 24 additions & 15 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@
import { SideSheet } from "@douyinfe/semi-ui";
import { IconMenu } from "@douyinfe/semi-icons";
import { socials } from "../data/socials";
import ThemeToggle from "./ThemeToggle";
import { useTheme } from "../context/ThemeContext";

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

Check failure on line 12 in src/components/Navbar.jsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'theme' is assigned a value but never used

return (
<>
<div className="py-4 px-12 sm:px-4 flex justify-between items-center">
<div className="flex items-center justify-between w-full">
<div className="py-4 px-12 sm:px-4 flex justify-between items-center dark:bg-gray-800">
<div className="flex items-center justify-between w-full relative">
<Link to="/">
<img src={logo} alt="logo" className="h-[48px] sm:h-[32px]" />
</Link>
<div className="md:hidden flex gap-12">
<Link
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
onClick={() =>
document
.getElementById("features")
Expand All @@ -28,32 +31,33 @@
</Link>
<Link
to="/editor"
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
>
Editor
</Link>
<Link
to="/templates"
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
>
Templates
</Link>
<Link
to={socials.docs}
className="text-lg font-semibold hover:text-sky-800 transition-colors duration-300"
className="text-lg font-semibold hover:text-sky-800 dark:text-gray-200 dark:hover:text-sky-400 transition-colors duration-300"
>
Docs
</Link>
</div>
<div className="md:hidden block space-x-3 ms-12">
<div className="md:hidden flex items-center space-x-3 ms-12">
<ThemeToggle className="mr-2" />
<a
title="Jump to Github"
className="px-2 py-2 hover:opacity-60 transition-all duration-300 rounded-full text-2xl"
href={socials.github}
target="_blank"
rel="noreferrer"
>
<i className="opacity-70 bi bi-github" />
<i className="opacity-70 bi bi-github dark:text-gray-200" />
</a>
<a
title="Follow us on X"
Expand All @@ -62,7 +66,7 @@
target="_blank"
rel="noreferrer"
>
<i className="opacity-70 bi bi-twitter-x" />
<i className="opacity-70 bi bi-twitter-x dark:text-gray-200" />
</a>
<a
title="Join the community on Discord"
Expand All @@ -71,13 +75,13 @@
target="_blank"
rel="noreferrer"
>
<i className="opacity-70 bi bi-discord" />
<i className="opacity-70 bi bi-discord dark:text-gray-200" />
</a>
</div>
</div>
<button
onClick={() => setOpenMenu((prev) => !prev)}
className="hidden md:inline-block h-[24px]"
className="hidden md:inline-block h-[24px] dark:text-gray-200"
>
<IconMenu size="extra-large" />
</button>
Expand All @@ -92,7 +96,7 @@
width={window.innerWidth}
>
<Link
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
onClick={() => {
document
.getElementById("features")
Expand All @@ -105,24 +109,29 @@
<hr />
<Link
to="/editor"
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
>
Editor
</Link>
<hr />
<Link
to="/templates"
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
>
Templates
</Link>
<hr />
<Link
to={socials.docs}
className="hover:bg-zinc-100 block p-3 text-base font-semibold"
className="hover:bg-zinc-100 dark:hover:bg-gray-700 block p-3 text-base font-semibold dark:text-gray-200"
>
Docs
</Link>
<hr className="dark:border-gray-700" />
<div className="p-3 flex items-center">
<span className="text-base font-semibold mr-3 dark:text-gray-200">Theme:</span>
<ThemeToggle />
</div>
</SideSheet>
</>
);
Expand Down
24 changes: 24 additions & 0 deletions src/components/ThemeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useTheme } from "../context/ThemeContext";

export default function ThemeToggle({ className = "" }) {
const { theme, toggleTheme } = useTheme();

return (
<button
onClick={toggleTheme}
className={`p-2 rounded-full transition-colors duration-300 ${
theme === "dark"
? "bg-gray-700 hover:bg-gray-600 text-yellow-300"
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
} ${className}`}
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
title={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
>
{theme === "dark" ? (
<i className="bi bi-sun-fill text-xl"></i>
) : (
<i className="bi bi-moon-fill text-xl"></i>
)}
</button>
);
}
83 changes: 83 additions & 0 deletions src/context/ThemeContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { createContext, useContext, useEffect, useState } from "react";

// Create context
export const ThemeContext = createContext();

// Theme provider component
export default function ThemeProvider({ children }) {
// Check for system preference and stored preference
const getInitialTheme = () => {
// Check if we're in the browser
if (typeof window !== "undefined") {
// Check localStorage first
const storedTheme = localStorage.getItem("theme");
if (storedTheme) {
return storedTheme;
}

// Check system preference
const userPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return userPrefersDark ? "dark" : "light";
}

// Default to light if not in browser
return "light";
};

const [theme, setTheme] = useState(getInitialTheme);

// Toggle theme function
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === "light" ? "dark" : "light");
};

// Update localStorage and apply theme class to html element
useEffect(() => {
// Save to localStorage
localStorage.setItem("theme", theme);

// Apply class to html element
const htmlElement = document.documentElement;
if (theme === "dark") {
htmlElement.classList.add("dark");
} else {
htmlElement.classList.remove("dark");
}

// Also update the theme-mode attribute for semi-ui components
document.body.setAttribute("theme-mode", theme);
}, [theme]);

// Listen for system preference changes
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
// Only update if user hasn't manually set a preference
if (!localStorage.getItem("theme")) {
setTheme(mediaQuery.matches ? "dark" : "light");
}
};

// Add listener
mediaQuery.addEventListener("change", handleChange);

// Clean up
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);

// Provide theme context to children
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

// Custom hook for using theme
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
};
Loading
Loading