Skip to content

Commit

Permalink
[@mantine/hooks] use-headroom: Fix incorrect pinning logic when scrol…
Browse files Browse the repository at this point in the history
…ling up (#5793)

* [@mantine/hooks]: fix unstable pinning of item by `useHeadroom` on mobile devices

[@mantine/hooks]: refactor the onPin and onRelease logic to prevent excessive calls of onPin

* [@mantine/hooks]: fix header not pinned when scrolling up

added useScrollDirection to handle if user is scrolling up and make sure the scroll is not caused by a resize event like mobile address bar showing
  • Loading branch information
Alimobasheri committed Mar 12, 2024
1 parent 2c05d5b commit 25310bd
Showing 1 changed file with 71 additions and 21 deletions.
92 changes: 71 additions & 21 deletions packages/@mantine/hooks/src/use-headroom/use-headroom.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useIsomorphicEffect } from '../use-isomorphic-effect/use-isomorphic-effect';
import { useWindowScroll } from '../use-window-scroll/use-window-scroll';

Expand All @@ -7,6 +7,64 @@ export const isPinned = (current: number, previous: number) => current <= previo
export const isReleased = (current: number, previous: number, fixedAt: number) =>
!isPinned(current, previous) && !isFixed(current, fixedAt);

export const isPinnedOrReleased = (
current: number,
fixedAt: number,
isCurrentlyPinnedRef: React.MutableRefObject<boolean>,
isScrollingUp: boolean,
onPin?: () => void,
onRelease?: () => void
) => {
const isInFixedPosition = isFixed(current, fixedAt);
if (isInFixedPosition && !isCurrentlyPinnedRef.current) {
isCurrentlyPinnedRef.current = true;
onPin?.();
} else if (!isInFixedPosition && isScrollingUp && !isCurrentlyPinnedRef.current) {
isCurrentlyPinnedRef.current = true;
onPin?.();
} else if (!isInFixedPosition && isCurrentlyPinnedRef.current) {
isCurrentlyPinnedRef.current = false;
onRelease?.();
}
};

export const useScrollDirection = () => {
const [lastScrollTop, setLastScrollTop] = useState(0);
const [isScrollingUp, setIsScrollingUp] = useState(false);
const [isResizing, setIsResizing] = useState(false);

useEffect(() => {
let resizeTimer: NodeJS.Timeout | undefined;

const onResize = () => {
setIsResizing(true);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
setIsResizing(false);
}, 300); // Reset the resizing flag after a timeout
};

const onScroll = () => {
if (isResizing) {
return; // Skip scroll events if resizing is in progress
}
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
setIsScrollingUp(currentScrollTop < lastScrollTop);
setLastScrollTop(currentScrollTop);
};

window.addEventListener('scroll', onScroll);
window.addEventListener('resize', onResize);

return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onResize);
};
}, [lastScrollTop, isResizing]);

return isScrollingUp;
};

interface UseHeadroomInput {
/** Number in px at which element should be fixed */
fixedAt?: number;
Expand All @@ -22,36 +80,28 @@ interface UseHeadroomInput {
}

export function useHeadroom({ fixedAt = 0, onPin, onFix, onRelease }: UseHeadroomInput = {}) {
const scrollRef = useRef(0);
const isCurrentlyPinnedRef = useRef(false);
const isScrollingUp = useScrollDirection();
const [{ y: scrollPosition }] = useWindowScroll();

useIsomorphicEffect(() => {
if (isPinned(scrollPosition, scrollRef.current)) {
onPin?.();
}
}, [scrollPosition, onPin]);
isPinnedOrReleased(
scrollPosition,
fixedAt,
isCurrentlyPinnedRef,
isScrollingUp,
onPin,
onRelease
);
}, [scrollPosition]);

useIsomorphicEffect(() => {
if (isFixed(scrollPosition, fixedAt)) {
onFix?.();
}
}, [scrollPosition, fixedAt, onFix]);

useIsomorphicEffect(() => {
if (isReleased(scrollPosition, scrollRef.current, fixedAt)) {
onRelease?.();
}
}, [scrollPosition, onRelease]);

useIsomorphicEffect(() => {
scrollRef.current = window.scrollY;
}, [scrollPosition]);

if (isPinned(scrollPosition, scrollRef.current)) {
return true;
}

if (isFixed(scrollPosition, fixedAt)) {
if (isFixed(scrollPosition, fixedAt) || isScrollingUp) {
return true;
}

Expand Down

0 comments on commit 25310bd

Please sign in to comment.