fix: prevent trainer label flash on carousel drag release

This commit is contained in:
2026-03-27 14:39:44 +03:00
parent 035f68776a
commit d5541a8bc9
+18 -18
View File
@@ -94,30 +94,22 @@ export function TeamCarousel({ members, activeIndex, onActiveChange }: TeamCarou
[] []
); );
// Deferred index update — avoids calling parent setState during render
// (onLostPointerCapture can fire during React reconciliation)
const pendingIndexRef = useRef<number | null>(null);
useEffect(() => {
if (pendingIndexRef.current !== null) {
onActiveChange(pendingIndexRef.current);
pendingIndexRef.current = null;
}
});
const onPointerUp = useCallback(() => { const onPointerUp = useCallback(() => {
if (!dragStartRef.current) return; if (!dragStartRef.current) return;
const startIdx = dragStartRef.current.startIndex; const startIdx = dragStartRef.current.startIndex;
const currentOffset = dragOffset; const currentOffset = dragOffset;
const wasDrag = Math.abs(currentOffset) > 10; const wasDrag = Math.abs(currentOffset) > 10;
const steps = wasDrag ? Math.round(currentOffset / CARD_SPACING) : 0; const steps = wasDrag ? Math.round(currentOffset / CARD_SPACING) : 0;
setDragOffset(0);
if (steps !== 0) {
pendingIndexRef.current = wrapIndex(startIdx - steps, total);
}
dragStartRef.current = null; dragStartRef.current = null;
isDraggingRef.current = false; isDraggingRef.current = false;
pausedUntilRef.current = Date.now() + PAUSE_MS; pausedUntilRef.current = Date.now() + PAUSE_MS;
}, [total, dragOffset]); if (steps !== 0) {
// Update index and reset offset in the same batch so the old card
// never becomes center for a frame (prevents label flash)
onActiveChange(wrapIndex(startIdx - steps, total));
}
setDragOffset(0);
}, [total, dragOffset, onActiveChange]);
// Compute interpolated style for each card // Compute interpolated style for each card
const baseIndex = dragStartRef.current ? dragStartRef.current.startIndex : activeIndex; const baseIndex = dragStartRef.current ? dragStartRef.current.startIndex : activeIndex;
@@ -165,6 +157,8 @@ export function TeamCarousel({ members, activeIndex, onActiveChange }: TeamCarou
? "none" ? "none"
: "all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)", : "all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
isCenter: absDiff < 0.5, isCenter: absDiff < 0.5,
/** 1 at center, fades to 0 by absDiff=0.6 */
centerOpacity: clamp(1 - absDiff / 0.6, 0, 1),
}; };
} }
@@ -228,10 +222,16 @@ export function TeamCarousel({ members, activeIndex, onActiveChange }: TeamCarou
draggable={false} draggable={false}
/> />
{style.isCenter && ( {style.centerOpacity > 0 && (
<> <>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/10 to-transparent" /> <div
<div className="absolute bottom-0 left-0 right-0 p-5"> className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/10 to-transparent"
style={{ opacity: style.centerOpacity }}
/>
<div
className="absolute bottom-0 left-0 right-0 p-5"
style={{ opacity: style.centerOpacity }}
>
<h3 className="text-lg font-bold text-white sm:text-xl drop-shadow-lg"> <h3 className="text-lg font-bold text-white sm:text-xl drop-shadow-lg">
{m.name} {m.name}
</h3> </h3>