1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
"use client"
import { useRef, useCallback } from "react"
import { useTheme } from "@/hooks/use-theme"
interface SpreadTransitionHook {
startTransition: (coords: { x: number; y: number }, nextTheme: string, callback: () => void) => void
toggleTheme: (event: React.MouseEvent) => void
isTransitioning: () => boolean
}
export function useSpreadTransition(): SpreadTransitionHook {
const { theme, setTheme } = useTheme()
const isTransitioningRef = useRef(false)
const startTransition = useCallback((coords: { x: number; y: number }, nextTheme: string, callback: () => void) => {
if (isTransitioningRef.current) return
isTransitioningRef.current = true
const x = coords.x
const y = coords.y
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)
if ('startViewTransition' in document) {
const transition = (document as Document & { startViewTransition: (callback: () => void) => { finished: Promise<void> } }).startViewTransition(() => {
callback()
})
transition.ready.then(() => {
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
];
const animate = document.documentElement.animate(
{
clipPath: nextTheme === "dark" ? clipPath : [...clipPath].reverse(),
},
{
duration: 450,
easing: 'ease-in',
pseudoElement: nextTheme === "dark"
? '::view-transition-new(root)'
: '::view-transition-old(root)',
},
);
animate.onfinish = () => {
transition.skipTransition();
};
});
transition.finished.finally(() => {
isTransitioningRef.current = false
})
} else {
callback()
setTimeout(() => {
isTransitioningRef.current = false
}, 400)
}
}, [])
const toggleTheme = useCallback((event: React.MouseEvent) => {
// Get precise click coordinates - use clientX/clientY directly like tweakcn
const coords = {
x: event.clientX,
y: event.clientY
}
const nextTheme = theme === "dark" ? "light" : "dark"
startTransition(coords, nextTheme, () => {
setTheme(nextTheme)
})
}, [theme, setTheme, startTransition])
const isTransitioning = useCallback(() => {
return isTransitioningRef.current
}, [])
return {
startTransition,
toggleTheme,
isTransitioning
}
}
|