|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import * as React from 'react' |
4 | | -import { Moon, Sun } from 'lucide-react' |
| 3 | +import { useEffect, useState } from 'react' |
| 4 | +import { Listbox } from '@headlessui/react' |
| 5 | + |
| 6 | +import clsx from 'clsx' |
5 | 7 | import { useTheme } from 'next-themes' |
| 8 | +import { motion, AnimatePresence } from 'framer-motion' |
| 9 | + |
| 10 | +import { SunIcon } from '@heroicons/react/24/outline' |
| 11 | +import { MoonIcon, CheckIcon } from '@heroicons/react/20/solid' |
| 12 | + |
| 13 | +export default function ThemeSwitcher() { |
| 14 | + const [mounted, setMounted] = useState(false) |
| 15 | + const { theme, setTheme, resolvedTheme, themes } = useTheme() |
6 | 16 |
|
7 | | -import { Button } from '@/components/ui/button' |
8 | | -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' |
| 17 | + useEffect(() => { |
| 18 | + setMounted(true) |
| 19 | + }, []) |
9 | 20 |
|
10 | | -export function ModeToggle() { |
11 | | - const { setTheme } = useTheme() |
| 21 | + if (!mounted) { |
| 22 | + return null |
| 23 | + } |
12 | 24 |
|
13 | 25 | return ( |
14 | | - <DropdownMenu> |
15 | | - <DropdownMenuTrigger asChild className={''}> |
16 | | - <Button className={'bg-transparent border-0'} variant="ghost" size="icon"> |
17 | | - <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 text-[#646464] transition-all dark:-rotate-90 dark:scale-0 " /> |
18 | | - <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 text-[#b4b4b4] scale-0 transition-all dark:rotate-0 dark:scale-100" /> |
19 | | - <span className="sr-only ">Toggle theme</span> |
20 | | - </Button> |
21 | | - </DropdownMenuTrigger> |
22 | | - <DropdownMenuContent className={'!z-[999999999]'} align="end"> |
23 | | - <DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem> |
24 | | - <DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem> |
25 | | - <DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem> |
26 | | - </DropdownMenuContent> |
27 | | - </DropdownMenu> |
| 26 | + <> |
| 27 | + <Listbox value={theme} onChange={(value) => setTheme(value)}> |
| 28 | + {({ open }) => { |
| 29 | + const iconClassName = clsx('w-5 h-5 dark:text-[#B4B4B4] text-black hover:!text-primary cursor-pointer transition-colors', open ? 'text-primary' : 'text-secondary') |
| 30 | + return ( |
| 31 | + <div className="relative"> |
| 32 | + <Listbox.Button className={clsx('relative w-8 h-8 cursor-default rounded-full flex items-center justify-center focus:outline-none ')}> |
| 33 | + {resolvedTheme === 'dark' ? <MoonIcon className={iconClassName} /> : <SunIcon className={'w-5 h-5 dark:text-[#B4B4B4] text-black hover:!text-primary cursor-pointer transition-colors'} />} |
| 34 | + </Listbox.Button> |
| 35 | + <AnimatePresence> |
| 36 | + {open && ( |
| 37 | + <Listbox.Options |
| 38 | + as={motion.ul} |
| 39 | + static |
| 40 | + initial={{ opacity: 0, scale: 0.9 }} |
| 41 | + animate={{ opacity: 1, scale: 1 }} |
| 42 | + exit={{ opacity: 0, scale: 0.9 }} |
| 43 | + transition={{ type: 'spring', bounce: 0.3, duration: 0.3 }} |
| 44 | + className="absolute right-0 p-2 mt-2 overflow-auto text-base origin-top-right shadow-lg max-h-60 w-42 rounded-xl dark:bg-[#111111] focus:outline-none sm:text-sm capitalize" |
| 45 | + > |
| 46 | + {themes.map((theme) => ( |
| 47 | + <Listbox.Option key={theme} className={({ active }) => clsx('relative cursor-default select-none py-2 pl-10 pr-4 rounded-md', active ? 'bg-secondary' : '')} value={theme}> |
| 48 | + {({ selected }) => ( |
| 49 | + <> |
| 50 | + <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>{theme == 'system' ? 'Automatic' : theme}</span> |
| 51 | + {selected ? ( |
| 52 | + <span className="absolute inset-y-0 left-0 flex items-center pl-3 dark:text-neutral-50"> |
| 53 | + <CheckIcon className="w-5 h-5" aria-hidden="true" /> |
| 54 | + </span> |
| 55 | + ) : null} |
| 56 | + </> |
| 57 | + )} |
| 58 | + </Listbox.Option> |
| 59 | + ))} |
| 60 | + </Listbox.Options> |
| 61 | + )} |
| 62 | + </AnimatePresence> |
| 63 | + </div> |
| 64 | + ) |
| 65 | + }} |
| 66 | + </Listbox> |
| 67 | + </> |
28 | 68 | ) |
29 | 69 | } |
0 commit comments