-
-
Notifications
You must be signed in to change notification settings - Fork 41
feat: Add dark theme toggle support #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import * as React from "react"; | ||||||
| import { useTheme } from "next-themes"; | ||||||
| import { Moon, Sun } from "lucide-react"; | ||||||
|
|
||||||
| export function ThemeToggle() { | ||||||
| const { theme, setTheme, resolvedTheme } = useTheme(); | ||||||
| const [mounted, setMounted] = React.useState(false); | ||||||
|
|
||||||
| // Avoid hydration mismatch | ||||||
| React.useEffect(() => { | ||||||
| setMounted(true); | ||||||
| }, []); | ||||||
|
|
||||||
| if (!mounted) { | ||||||
| return ( | ||||||
| <button | ||||||
| className="inline-flex items-center justify-center rounded-md p-2.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||||||
| aria-label="Toggle theme" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The placeholder button rendered before the component mounts is interactive (e.g., shows hover effects) but has no
Suggested change
|
||||||
| > | ||||||
| <Sun className="h-5 w-5" /> | ||||||
| </button> | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| return ( | ||||||
| <button | ||||||
| onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")} | ||||||
| className="inline-flex items-center justify-center rounded-md p-2.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||||||
| aria-label="Toggle theme" | ||||||
| > | ||||||
| {resolvedTheme === "dark" ? ( | ||||||
| <Sun className="h-5 w-5 text-yellow-500" /> | ||||||
| ) : ( | ||||||
| <Moon className="h-5 w-5 text-slate-700" /> | ||||||
| )} | ||||||
| </button> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import Image from "next/image"; | |
| import { useEffect, useState } from "react"; | ||
| import { Book, GitFork, Menu, X } from "lucide-react"; | ||
| import { useBannerStore } from "@/hooks/banner-store"; | ||
| import { ThemeToggle } from "@/components/ThemeToggle"; | ||
|
|
||
| export default function AnimatedNavClient() { | ||
| const { data } = useBannerStore(); | ||
|
|
@@ -25,7 +26,7 @@ export default function AnimatedNavClient() { | |
| return ( | ||
| <motion.nav | ||
| className={`fixed top-0 left-0 right-0 z-40 transition-all duration-300 backdrop-blur-md ${ | ||
| isScrolled ? "bg-white/75 shadow-sm" : "bg-transparent" | ||
| isScrolled ? "bg-white/75 dark:bg-gray-900/75 shadow-sm" : "bg-transparent" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a dark background for the navigation bar is great for dark mode. However, this will likely cause issues with the site logo ( To fix this, you should use a different logo for dark mode. Here's a suggested approach:
<Image
src={resolvedTheme === 'dark' ? '/images/logo-full-dark.png' : '/images/logo-full.png'}
alt="DevB Logo"
// ... other props
/>This will ensure your branding is always visible and looks great in both themes. |
||
| } | ||
| ${isBannerVisible ? "top-10" : "top-0"} | ||
| `} | ||
|
|
@@ -51,7 +52,7 @@ export default function AnimatedNavClient() { | |
| <div className="hidden md:flex items-center gap-4"> | ||
| <Link | ||
| href="https://docs.devb.io" | ||
| className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:text-black transition-colors rounded-lg hover:bg-black/5" | ||
| className="flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-200 hover:text-black dark:hover:text-white transition-colors rounded-lg hover:bg-black/5 dark:hover:bg-white/10" | ||
| > | ||
| <Book size={18} /> | ||
| <span>Docs</span> | ||
|
|
@@ -71,20 +72,24 @@ export default function AnimatedNavClient() { | |
| href="https://github.com/sunithvs/devb.io/fork" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="flex items-center gap-2 px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors" | ||
| className="flex items-center gap-2 px-4 py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The embedded GitHub star button To ensure UI consistency, you can dynamically set the Here's how you could implement this:
const ghButtonSrc = `https://ghbtns.com/github-btn.html?user=sunithvs&repo=devb.io&type=star&count=true&color_scheme=${resolvedTheme === 'dark' ? 'dark' : 'light'}`;
|
||
| > | ||
| <GitFork size={18} /> | ||
| <span>Contribute</span> | ||
| </a> | ||
| <ThemeToggle /> | ||
| </div> | ||
|
|
||
| {/* Mobile Menu Button */} | ||
| <button | ||
| className="md:hidden text-gray-600 hover:text-black transition-colors" | ||
| onClick={() => setIsMenuOpen(!isMenuOpen)} | ||
| > | ||
| {isMenuOpen ? <X size={24} /> : <Menu size={24} />} | ||
| </button> | ||
| <div className="md:hidden flex items-center gap-2"> | ||
| <ThemeToggle /> | ||
| <button | ||
| className="text-gray-600 dark:text-gray-300 hover:text-black dark:hover:text-white transition-colors" | ||
| onClick={() => setIsMenuOpen(!isMenuOpen)} | ||
| > | ||
| {isMenuOpen ? <X size={24} /> : <Menu size={24} />} | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Mobile Menu */} | ||
|
|
@@ -100,7 +105,7 @@ export default function AnimatedNavClient() { | |
| <div className="py-4 space-y-2"> | ||
| <Link | ||
| href="/docs" | ||
| className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:text-black transition-colors rounded-lg hover:bg-black/5" | ||
| className="flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-200 hover:text-black dark:hover:text-white transition-colors rounded-lg hover:bg-black/5 dark:hover:bg-white/10" | ||
| > | ||
| <Book size={18} /> | ||
| <span>Documentation</span> | ||
|
|
@@ -119,7 +124,7 @@ export default function AnimatedNavClient() { | |
| href="https://github.com/sunithvs/devb.io/fork" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="flex items-center gap-2 px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors w-fit" | ||
| className="flex items-center gap-2 px-4 py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors w-fit" | ||
| > | ||
| <GitFork size={18} /> | ||
| <span>Contribute</span> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| "use client"; | ||
|
|
||
| import * as React from "react"; | ||
| import { ThemeProvider as NextThemesProvider } from "next-themes"; | ||
|
|
||
| export function ThemeProvider({ | ||
| children, | ||
| ...props | ||
| }: React.ComponentProps<typeof NextThemesProvider>) { | ||
| return ( | ||
| <NextThemesProvider | ||
| attribute="class" | ||
| defaultTheme="system" | ||
| enableSystem | ||
| disableTransitionOnChange | ||
| {...props} | ||
| > | ||
| {children} | ||
| </NextThemesProvider> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
themevariable is destructured fromuseTheme()but is never used in the component. It's good practice to remove unused variables to keep the code clean and maintainable.