67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Moon, Sun, Monitor } from "lucide-react"
|
|
import { useTheme } from "next-themes"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu"
|
|
|
|
const THEME_ICONS = {
|
|
light: Sun,
|
|
dark: Moon,
|
|
system: Monitor,
|
|
} as const
|
|
|
|
type ThemeOption = keyof typeof THEME_ICONS
|
|
|
|
const THEME_OPTIONS: { value: ThemeOption; label: string }[] = [
|
|
{ value: "light", label: "Light theme" },
|
|
{ value: "dark", label: "Dark theme" },
|
|
{ value: "system", label: "System" },
|
|
]
|
|
|
|
export function ThemeToggle() {
|
|
const { setTheme, theme, resolvedTheme } = useTheme()
|
|
const [mounted, setMounted] = React.useState(false)
|
|
|
|
React.useEffect(() => {
|
|
const timeout = setTimeout(() => setMounted(true), 0)
|
|
return () => clearTimeout(timeout)
|
|
}, [])
|
|
|
|
// Once mounted, resolve "system" to its actual rendered theme for the icon
|
|
const activeIcon = !mounted
|
|
? Sun
|
|
: THEME_ICONS[theme === "system" ? (resolvedTheme as ThemeOption) : (theme as ThemeOption)] ?? Sun
|
|
|
|
const Icon = activeIcon
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="w-9 h-9">
|
|
<Icon className="h-[1.2rem] w-[1.2rem]" />
|
|
<span className="sr-only">Toggle theme</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
{THEME_OPTIONS.map(({ value, label }) => {
|
|
const OptionIcon = THEME_ICONS[value]
|
|
return (
|
|
<DropdownMenuItem key={value} onClick={() => setTheme(value)}>
|
|
<OptionIcon className="h-4 w-4" />
|
|
{label}
|
|
</DropdownMenuItem>
|
|
)
|
|
})}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|