Update use-headroom so that it works with ScrollArea #6223
Closed
Toshinaki
started this conversation in
Feature requests
Replies: 1 comment
-
I twisted the code a bit. It works, but I'm not sure if there's any bug. Also, I tried to add import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { useIsomorphicEffect } from '@mantine/hooks';
import _ from 'lodash';
export const isFixed = (current: number, fixedAt: number) => current <= fixedAt;
export const isPinned = (current: number, previous: number) =>
current <= previous;
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 = (ele?: Element | null) => {
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 = _.throttle(() => {
if (isResizing) {
return; // Skip scroll events if resizing is in progress
}
const currentScrollTop =
ele?.scrollTop || window.scrollY || document.documentElement.scrollTop;
setIsScrollingUp(currentScrollTop < lastScrollTop);
setLastScrollTop(currentScrollTop);
}, 250);
(ele || window).addEventListener('scroll', onScroll);
(ele || window).addEventListener('resize', onResize);
return () => {
(ele || window).removeEventListener('scroll', onScroll);
(ele || window).removeEventListener('resize', onResize);
};
}, [lastScrollTop, isResizing, ele]);
return isScrollingUp;
};
interface UseHeadroomInput {
/** Number in px at which element should be fixed */
fixedAt?: number;
/** Called when element is pinned */
onPin?: () => void;
/** Called when element is at fixed position */
onFix?: () => void;
/** Called when element is unpinned */
onRelease?: () => void;
scrollRef?: RefObject<Element>;
}
export const useHeadroom = ({
fixedAt = 0,
onPin,
onFix,
onRelease,
scrollRef,
}: UseHeadroomInput = {}) => {
const isCurrentlyPinnedRef = useRef(false);
const isScrollingUp = useScrollDirection(scrollRef?.current);
const scrollPosition = scrollRef?.current?.scrollTop || 0;
console.log('isScrollingUp:', isScrollingUp);
useIsomorphicEffect(() => {
isPinnedOrReleased(
scrollPosition,
fixedAt,
isCurrentlyPinnedRef,
isScrollingUp,
onPin,
onRelease
);
}, [scrollPosition]);
useIsomorphicEffect(() => {
if (isFixed(scrollPosition, fixedAt)) {
onFix?.();
}
}, [scrollPosition, fixedAt, onFix]);
const pinned = useMemo(() => {
if (isFixed(scrollPosition, fixedAt) || isScrollingUp) {
return true;
}
return false;
}, [fixedAt, isScrollingUp, scrollPosition]);
return pinned;
}; |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Currently
useHeadroom
hook works only when the screen scrolls:useWindowScroll
Can we have the hook updated so that it works with
ScrollArea
?Example usage:
Beta Was this translation helpful? Give feedback.
All reactions