Skip to content
Open
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
13 changes: 8 additions & 5 deletions www/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Outfit, Space_Grotesk } from "next/font/google";
import "./globals.css";
import { QueryClientProvider } from "@/providers/CustomQueryClientProvider";
import { ThemeProvider } from "@/providers/ThemeProvider";
import Script from "next/script";
import { Banner } from "@/components/banner";

Expand Down Expand Up @@ -30,7 +31,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<head>
<script type="text/javascript"></script>
<script
Expand Down Expand Up @@ -71,10 +72,12 @@ export default function RootLayout({
<body
className={`${outfit.variable} ${spaceGrotesk.variable} font-outfit`}
>
<QueryClientProvider>
<Banner />
{children}
</QueryClientProvider>
<ThemeProvider>
<QueryClientProvider>
<Banner />
{children}
</QueryClientProvider>
</ThemeProvider>
</body>
</html>
);
Expand Down
40 changes: 40 additions & 0 deletions www/components/ThemeToggle.tsx
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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The theme variable is destructured from useTheme() but is never used in the component. It's good practice to remove unused variables to keep the code clean and maintainable.

Suggested change
const { theme, setTheme, resolvedTheme } = useTheme();
const { 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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The placeholder button rendered before the component mounts is interactive (e.g., shows hover effects) but has no onClick handler. This can be confusing for users and is not ideal for accessibility. It's better to disable the button in this state by adding the disabled attribute.

Suggested change
aria-label="Toggle theme"
aria-label="Toggle theme" disabled

>
<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>
);
}
27 changes: 16 additions & 11 deletions www/components/animated-nav/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Adding a dark background for the navigation bar is great for dark mode. However, this will likely cause issues with the site logo (/images/logo-full.png), which seems designed for a light background and may become invisible.

To fix this, you should use a different logo for dark mode. Here's a suggested approach:

  1. Import useTheme from next-themes at the top of the file.
  2. Get the theme in the component: const { resolvedTheme } = useTheme();
  3. Create a dark mode version of your logo (e.g., /images/logo-full-dark.png).
  4. Conditionally set the logo src in the Image component (lines 41-47) based on the theme:
<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"}
`}
Expand All @@ -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>
Expand All @@ -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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The embedded GitHub star button iframe will not automatically adapt to the theme change, leading to a light-themed button on a dark background.

To ensure UI consistency, you can dynamically set the color_scheme for the button. The GitHub buttons service supports this via a URL parameter.

Here's how you could implement this:

  1. Import useTheme from next-themes and get the resolvedTheme.
  2. Construct the iframe source URL dynamically before the return statement:
const ghButtonSrc = `https://ghbtns.com/github-btn.html?user=sunithvs&repo=devb.io&type=star&count=true&color_scheme=${resolvedTheme === 'dark' ? 'dark' : 'light'}`;
  1. Use this ghButtonSrc variable for the src attribute of both the desktop (lines 61-69) and mobile (lines 114-121) iframes. This will make the button's theme match the site's theme.

>
<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 */}
Expand All @@ -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>
Expand All @@ -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>
Expand Down
21 changes: 21 additions & 0 deletions www/providers/ThemeProvider.tsx
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>
);
}
1 change: 1 addition & 0 deletions www/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Config } from "tailwindcss";

const config: Config = {
darkMode: "class",
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
Expand Down