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
119 changes: 92 additions & 27 deletions components/mode-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,103 @@
"use client";

import { Moon, Sun } from "lucide-react";
import { Moon, Sun, Monitor } from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";

export function ModeToggle() {
const { setTheme } = useTheme();
const { theme, setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);

// Prevent hydration mismatch
useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return (
<Button
variant="ghost"
size="icon"
className="h-9 w-9"
disabled
>
<Sun className="h-[1.2rem] w-[1.2rem]" />
</Button>
);
}

// Determine the next theme to cycle through
const nextTheme = () => {
if (theme === "light") {
setTheme("dark");
} else if (theme === "dark") {
setTheme("system");
} else {
setTheme("light");
}
};

// Determine which icon to display based on resolved theme
const renderIcon = () => {
switch (resolvedTheme) {
case "dark":
return (
<Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
case "light":
return (
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
default:
return (
<Monitor className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
}
};
Comment on lines +48 to +64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Monitor icon will never display - resolvedTheme is never "system".

The resolvedTheme from next-themes always resolves to the actual applied theme ("light" or "dark"), never "system". When the user selects system mode, resolvedTheme reflects the OS preference. This means the default case never executes and the Monitor icon is unreachable.

To show Monitor when the user has selected system theme, use theme instead:

   // Determine which icon to display based on resolved theme
   const renderIcon = () => {
-    switch (resolvedTheme) {
+    switch (theme) {
+      case "system":
+        return (
+          <Monitor className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
+        );
       case "dark":
         return (
           <Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
         );
-      case "light":
-        return (
-          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
-        );
       default:
         return (
-          <Monitor className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
+          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
         );
     }
   };

Alternatively, if you want the icon to reflect the actual appearance when in system mode (Sun/Moon based on OS) while showing Monitor only as a deliberate design choice when theme === "system", adjust the logic accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Determine which icon to display based on resolved theme
const renderIcon = () => {
switch (resolvedTheme) {
case "dark":
return (
<Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
case "light":
return (
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
default:
return (
<Monitor className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
}
};
// Determine which icon to display based on resolved theme
const renderIcon = () => {
switch (theme) {
case "system":
return (
<Monitor className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
case "dark":
return (
<Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
default:
return (
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all duration-300" />
);
}
};
🤖 Prompt for AI Agents
In components/mode-toggle.tsx around lines 48 to 64, the Monitor icon is
unreachable because resolvedTheme never equals "system"; use theme (the user's
selected preference) to detect when they chose "system". Change the render logic
to first check if theme === "system" and return the Monitor icon, otherwise
switch on resolvedTheme to return Sun or Moon (or simply use theme instead of
resolvedTheme if you want Monitor only when theme is "system"); ensure you
import/receive theme and resolvedTheme from next-themes and preserve the
existing classes and transitions.


// Determine tooltip text
const getTooltipText = () => {
switch (theme) {
case "light":
return "Light mode (click to switch to Dark)";
case "dark":
return "Dark mode (click to switch to System)";
case "system":
return `System theme - ${resolvedTheme === "dark" ? "Dark" : "Light"} (click to switch to Light)`;
default:
return "Toggle theme";
}
};

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-9 w-9">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-9 w-9 relative transition-all duration-200 hover:bg-accent"
onClick={nextTheme}
aria-label={`Current theme: ${theme}. Click to cycle through themes.`}
>
{renderIcon()}
<span className="sr-only">
{theme === "light" ? "Light" : theme === "dark" ? "Dark" : "System"} theme
</span>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" className="text-xs">
{getTooltipText()}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.