Skip to content

Commit bcb1142

Browse files
Merge pull request #704 from ankitkr104/feat/animated-theme-toggle
Feat/animated theme toggle
2 parents 42fc008 + 4510460 commit bcb1142

1 file changed

Lines changed: 55 additions & 5 deletions

File tree

src/components/shared/Header.jsx

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client'
22

3-
import { Fragment, useEffect, useRef } from 'react'
3+
import { Fragment, useEffect, useRef, useState } from 'react'
44
import Image from 'next/image'
55
import Link from 'next/link'
66
import { usePathname } from 'next/navigation'
77
import { Popover, Transition } from '@headlessui/react'
88
import clsx from 'clsx'
9-
import { motion } from 'framer-motion'
9+
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
1010

1111
import { Container } from '@/components/shared/Container'
1212

@@ -183,6 +183,29 @@ function DesktopNavigation(props) {
183183
}
184184

185185
function ModeToggle() {
186+
const [mounted, setMounted] = useState(false)
187+
const [isDarkMode, setIsDarkMode] = useState(false)
188+
const prefersReducedMotion = useReducedMotion()
189+
const iconTransition = prefersReducedMotion
190+
? { duration: 0 }
191+
: { duration: 0.5, ease: 'easeInOut' }
192+
193+
useEffect(() => {
194+
setMounted(true)
195+
setIsDarkMode(document.documentElement.classList.contains('dark'))
196+
197+
const observer = new MutationObserver(() => {
198+
setIsDarkMode(document.documentElement.classList.contains('dark'))
199+
})
200+
201+
observer.observe(document.documentElement, {
202+
attributes: true,
203+
attributeFilter: ['class'],
204+
})
205+
206+
return () => observer.disconnect()
207+
}, [])
208+
186209
function disableTransitionsTemporarily() {
187210
document.documentElement.classList.add('[&_*]:!transition-none')
188211
window.setTimeout(() => {
@@ -204,15 +227,42 @@ function ModeToggle() {
204227
}
205228
}
206229

230+
if (!mounted) {
231+
return (
232+
<div className="h-10 w-10 rounded-full bg-white/90 ring-1 ring-black/10 dark:bg-zinc-800/90 dark:ring-white/10" />
233+
)
234+
}
235+
207236
return (
208237
<button
209238
type="button"
210239
aria-label="Toggle dark mode"
211-
className="group rounded-full bg-white/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-black/10 hover:ring-black/20 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
240+
className="group relative flex h-10 w-10 items-center justify-center rounded-full bg-white/90 shadow-lg shadow-zinc-800/5 ring-1 ring-black/10 hover:ring-black/20 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
212241
onClick={toggleMode}
213242
>
214-
<SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 dark:hidden [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-yellow-400 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-yellow-500" />
215-
<MoonIcon className="hidden h-6 w-6 fill-zinc-700 stroke-zinc-500 transition dark:block [@media(prefers-color-scheme:dark)]:group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" />
243+
<AnimatePresence mode="wait" initial={false}>
244+
{isDarkMode ? (
245+
<motion.div
246+
key="moon"
247+
initial={{ opacity: 0, rotate: -90, scale: 0.5 }}
248+
animate={{ opacity: 1, rotate: 0, scale: 1 }}
249+
exit={{ opacity: 0, rotate: 90, scale: 0.5 }}
250+
transition={iconTransition}
251+
>
252+
<MoonIcon className="h-6 w-6 fill-zinc-700 stroke-zinc-500 transition group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" />
253+
</motion.div>
254+
) : (
255+
<motion.div
256+
key="sun"
257+
initial={{ opacity: 0, rotate: 90, scale: 0.5 }}
258+
animate={{ opacity: 1, rotate: 0, scale: 1 }}
259+
exit={{ opacity: 0, rotate: -90, scale: 0.5 }}
260+
transition={iconTransition}
261+
>
262+
<SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-yellow-400 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-yellow-500" />
263+
</motion.div>
264+
)}
265+
</AnimatePresence>
216266
</button>
217267
)
218268
}

0 commit comments

Comments
 (0)