fix: prevent trainer label flash on carousel drag release
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user