1- " use client" ;
1+ ' use client'
22
3- import { cn } from "@/lib/utils" ;
4- import { Monitor , Moon , SunDim } from "lucide- react" ;
5- import { useTheme } from "../context/ThemeContext" ;
6- import { useEffect , useRef , useState } from "react" ;
7- import { flushSync } from "react-dom" ;
3+ import { Monitor , Moon , SunDim } from 'lucide-react'
4+ import { useEffect , useRef , useState } from ' react'
5+ import { flushSync } from 'react-dom'
6+ import { cn } from '@/lib/utils'
7+ import { useTheme } from '../context/ThemeContext'
88
9- type props = {
10- className ?: string ;
11- } ;
9+ interface props {
10+ className ?: string
11+ }
1212
13- export const AnimatedThemeToggler = ( { className } : props ) => {
14- const { theme, setTheme } = useTheme ( ) ;
15- const buttonRef = useRef < HTMLButtonElement | null > ( null ) ;
16- const [ mounted , setMounted ] = useState ( false ) ;
13+ export function AnimatedThemeToggler ( { className } : props ) {
14+ const { theme, setTheme } = useTheme ( )
15+ const buttonRef = useRef < HTMLButtonElement | null > ( null )
16+ const [ mounted , setMounted ] = useState ( false )
1717
1818 // Prevent hydration mismatch by only showing theme-dependent content after mount
1919 useEffect ( ( ) => {
20- setMounted ( true ) ;
21- } , [ ] ) ;
20+ setMounted ( true )
21+ } , [ ] )
2222
2323 // Cycle through: system -> light -> dark -> system
2424 const getNextTheme = ( ) => {
25- if ( theme === "system" ) return "light" ;
26- if ( theme === "light" ) return "dark" ;
27- return "system" ;
28- } ;
25+ if ( theme === 'system' )
26+ return 'light'
27+ if ( theme === 'light' )
28+ return 'dark'
29+ return 'system'
30+ }
2931
3032 const getThemeIcon = ( ) => {
31- if ( theme === "system" ) return < Monitor className = "size-4" /> ;
32- if ( theme === "light" ) return < SunDim className = "size-4" /> ;
33- return < Moon className = "size-4" /> ;
34- } ;
33+ if ( theme === 'system' )
34+ return < Monitor className = "size-4" />
35+ if ( theme === 'light' )
36+ return < SunDim className = "size-4" />
37+ return < Moon className = "size-4" />
38+ }
3539
3640 const getThemeLabel = ( ) => {
37- if ( theme === "system" ) return "Switch to light mode" ;
38- if ( theme === "light" ) return "Switch to dark mode" ;
39- return "Switch to system mode" ;
40- } ;
41+ if ( theme === 'system' )
42+ return 'Switch to light mode'
43+ if ( theme === 'light' )
44+ return 'Switch to dark mode'
45+ return 'Switch to system mode'
46+ }
4147
4248 const changeTheme = async ( ) => {
43- if ( ! buttonRef . current ) return ;
49+ if ( ! buttonRef . current )
50+ return
4451
45- const nextTheme = getNextTheme ( ) ;
52+ const nextTheme = getNextTheme ( )
4653
4754 // Check if view transitions are supported
4855 if ( typeof document !== 'undefined' && 'startViewTransition' in document ) {
4956 await document . startViewTransition ( ( ) => {
5057 flushSync ( ( ) => {
51- setTheme ( nextTheme ) ;
52- } ) ;
53- } ) . ready ;
58+ setTheme ( nextTheme )
59+ } )
60+ } ) . ready
5461
55- const { top, left, width, height } =
56- buttonRef . current . getBoundingClientRect ( ) ;
57- const y = top + height / 2 ;
58- const x = left + width / 2 ;
62+ const { top, left, width, height }
63+ = buttonRef . current . getBoundingClientRect ( )
64+ const y = top + height / 2
65+ const x = left + width / 2
5966
60- const right = window . innerWidth - left ;
61- const bottom = window . innerHeight - top ;
62- const maxRad = Math . hypot ( Math . max ( left , right ) , Math . max ( top , bottom ) ) ;
67+ const right = window . innerWidth - left
68+ const bottom = window . innerHeight - top
69+ const maxRad = Math . hypot ( Math . max ( left , right ) , Math . max ( top , bottom ) )
6370
6471 document . documentElement . animate (
6572 {
@@ -70,15 +77,16 @@ export const AnimatedThemeToggler = ({ className }: props) => {
7077 } ,
7178 {
7279 duration : 700 ,
73- easing : "ease-in-out" ,
74- pseudoElement : "::view-transition-new(root)" ,
75- }
76- ) ;
77- } else {
80+ easing : 'ease-in-out' ,
81+ pseudoElement : '::view-transition-new(root)' ,
82+ } ,
83+ )
84+ }
85+ else {
7886 // Fallback for browsers that don't support view transitions
79- setTheme ( nextTheme ) ;
87+ setTheme ( nextTheme )
8088 }
81- } ;
89+ }
8290
8391 // Show a placeholder during SSR to prevent hydration mismatch
8492 if ( ! mounted ) {
@@ -90,7 +98,7 @@ export const AnimatedThemeToggler = ({ className }: props) => {
9098 >
9199 < Monitor className = "size-4" />
92100 </ button >
93- ) ;
101+ )
94102 }
95103
96104 return (
@@ -99,9 +107,9 @@ export const AnimatedThemeToggler = ({ className }: props) => {
99107 onClick = { changeTheme }
100108 className = { cn ( className ) }
101109 aria-label = { getThemeLabel ( ) }
102- title = { `Current: ${ theme === " system" ? " Auto" : theme === " light" ? " Light" : " Dark" } ` }
110+ title = { `Current: ${ theme === ' system' ? ' Auto' : theme === ' light' ? ' Light' : ' Dark' } ` }
103111 >
104112 { getThemeIcon ( ) }
105113 </ button >
106- ) ;
107- } ;
114+ )
115+ }
0 commit comments