From 007bc1f7b09e4493e29bf5d67519b59e0fddbe71 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Tue, 31 Mar 2026 13:40:47 +0200 Subject: [PATCH] Refine animated ring behavior to cancel on drag and improve pointer handling --- web/components/profile-grid.tsx | 79 ++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/web/components/profile-grid.tsx b/web/components/profile-grid.tsx index 4c0d9fd3..1e5cd34a 100644 --- a/web/components/profile-grid.tsx +++ b/web/components/profile-grid.tsx @@ -164,23 +164,35 @@ function ProfilePreview(props: { const [isLoading, setIsLoading] = useState(false) const [showRing, setShowRing] = useState(false) const ringTimeoutRef = useRef(null) + const pointerStartRef = useRef<{x: number; y: number} | null>(null) - const handlePointerDown = () => { - setIsLoading(true) - setShowRing(true) - ringTimeoutRef.current = setTimeout(() => { - setShowRing(true) - }, 200) + const handlePointerDown = (e: React.PointerEvent) => { + pointerStartRef.current = {x: e.clientX, y: e.clientY} } - // Cleanup on unmount - useEffect(() => { - return () => { - if (ringTimeoutRef.current) { - clearTimeout(ringTimeoutRef.current) + const handlePointerUp = (e: React.PointerEvent) => { + if (pointerStartRef.current) { + const dx = Math.abs(e.clientX - pointerStartRef.current.x) + const dy = Math.abs(e.clientY - pointerStartRef.current.y) + // If moved more than 10px, treat as drag/scroll - cancel loading + if (dx > 10 || dy > 10) { + setIsLoading(false) + setShowRing(false) + if (ringTimeoutRef.current) { + clearTimeout(ringTimeoutRef.current) + ringTimeoutRef.current = null + } + } else { + setIsLoading(true) + ringTimeoutRef.current = setTimeout(() => { + setShowRing(true) + }, 500) } } - }, []) + pointerStartRef.current = null + } + + const handleClick = () => {} // Show the bottom transparent gradient only if the text can't fit the card const textRef = useRef(null) @@ -287,29 +299,11 @@ function ProfilePreview(props: { !isLoading && 'transition-transform duration-[120ms] ease-in', )} > - {/* Phase 2: Animated ring - appears after 200ms */} - {isLoading && showRing && ( - <> -
- {/* Mask to show only the ring strip */} -
- - )} { - // Cancel ring if navigation completes quickly - if (ringTimeoutRef.current) { - clearTimeout(ringTimeoutRef.current) - } - }} + onPointerUp={handlePointerUp} + onClick={handleClick} className={clsx( 'relative z-10 cursor-pointer group block rounded-lg overflow-hidden bg-transparent h-full border border-canvas-300', hover, @@ -488,6 +482,27 @@ function ProfilePreview(props: {
+ + {/* Phase 2: Animated ring - appears after 200ms */} + {isLoading && showRing && ( +
+
+
+ )}
) }