diff --git a/app/layout.tsx b/app/layout.tsx
index 6d97745..7d26443 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -77,7 +77,7 @@ export default function RootLayout({
diff --git a/components/layout/theme-toggle.tsx b/components/layout/theme-toggle.tsx
new file mode 100644
index 0000000..44832ea
--- /dev/null
+++ b/components/layout/theme-toggle.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import { useTheme } from "next-themes"
+import { AnimatePresence, motion } from "framer-motion"
+import { Moon, Sun, Monitor } from "lucide-react"
+
+const iconSizeClasses = {
+ sm: "h-4 w-4",
+ md: "h-5 w-5",
+}
+
+const themeOptions = [
+ { value: "dark", label: "dark", icon: Moon },
+ { value: "light", label: "light", icon: Sun },
+ { value: "system", label: "system", icon: Monitor },
+] as const
+
+const iconTransition = {
+ duration: 0.15,
+}
+
+const dropdownTransition = {
+ type: "spring" as const,
+ stiffness: 300,
+ damping: 20,
+}
+
+function ThemeIcon({ theme, className }: { theme: string; className: string }) {
+ const option = themeOptions.find((o) => o.value === theme)
+ const Icon = option?.icon ?? Moon
+ return
+}
+
+export function ThemeToggle({ iconSize = "sm" }: { iconSize?: "sm" | "md" }) {
+ const { theme, resolvedTheme, setTheme } = useTheme()
+ const [isOpen, setIsOpen] = React.useState(false)
+ const [mounted, setMounted] = React.useState(false)
+ const containerRef = React.useRef(null)
+
+ React.useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ // Click-outside dismissal
+ React.useEffect(() => {
+ if (!isOpen) return
+
+ const handleMouseDown = (e: MouseEvent) => {
+ if (
+ containerRef.current &&
+ !containerRef.current.contains(e.target as Node)
+ ) {
+ setIsOpen(false)
+ }
+ }
+
+ document.addEventListener("mousedown", handleMouseDown)
+ return () => document.removeEventListener("mousedown", handleMouseDown)
+ }, [isOpen])
+
+ // Keyboard handling
+ React.useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ const target = e.target as HTMLElement
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return
+
+ if (e.key === "t") {
+ setTheme(resolvedTheme === "dark" ? "light" : "dark")
+ setIsOpen(false)
+ }
+
+ if (e.key === "Escape") {
+ setIsOpen(false)
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ }, [resolvedTheme, setTheme])
+
+ if (!mounted) return null
+
+ const sizeClass = iconSizeClasses[iconSize]
+
+ return (
+
+
+
+
+ {isOpen && (
+
+ {themeOptions.map((option) => {
+ const isActive = theme === option.value
+ return (
+
+ )
+ })}
+
+ )}
+
+
+ )
+}
diff --git a/components/layout/top-nav.tsx b/components/layout/top-nav.tsx
index b64bd9a..4109ec9 100644
--- a/components/layout/top-nav.tsx
+++ b/components/layout/top-nav.tsx
@@ -3,14 +3,13 @@
import * as React from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
-import { useTheme } from "next-themes"
import { motion } from "framer-motion"
import { cn } from "@/lib/utils"
-import { Sun, Moon } from "lucide-react"
import { MenuIcon } from "@/components/ui/menu-icon"
import { TooltipProvider } from "@/components/ui/tooltip"
import { ShortcutTooltip } from "@/components/common/shortcut-tooltip"
import { MobileMenu } from "@/components/layout/mobile-menu"
+import { ThemeToggle } from "@/components/layout/theme-toggle"
const navItems = [
{ title: "home", href: "/" },
@@ -23,14 +22,8 @@ const navItems = [
export function TopNav() {
const pathname = usePathname()
const router = useRouter()
- const { setTheme, resolvedTheme } = useTheme()
- const [mounted, setMounted] = React.useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false)
- React.useEffect(() => {
- setMounted(true)
- }, [])
-
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const target = e.target as HTMLElement
@@ -40,14 +33,11 @@ export function TopNav() {
if (num >= 1 && num <= navItems.length) {
router.push(navItems[num - 1].href)
}
- if (e.key === "t") {
- setTheme(resolvedTheme === "dark" ? "light" : "dark")
- }
}
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
- }, [resolvedTheme, setTheme, router])
+ }, [router])
const isActive = (href: string) => {
if (href === "/") return pathname === "/"
@@ -94,21 +84,11 @@ export function TopNav() {
))}
{/* Desktop Theme toggle */}
- {mounted && (
-
-
-
- )}
+
+
+
+
+
@@ -116,19 +96,7 @@ export function TopNav() {
{/* Mobile Theme toggle */}
- {mounted && (
-
- )}
+
{/* Hamburger button */}