import { RefObject , useEffect , useState } from "react" ; import { debounce } from "@design-systems/utils" ; /** Get the nearest scroll container */ function getScrollParent ( element ) { let style = getComputedStyle ( element ) ; const excludeStaticParent = style . position === "absolute" ; const overflowRegex = /(auto|scroll|hidden)/ ; if ( style . position === "fixed" ) { return document . body ; } let parent = element ; while ( ( parent = parent . parentElement ) ) { style = getComputedStyle ( parent ) ; if ( excludeStaticParent && style . position === "static" ) { continue ; } if ( overflowRegex . test ( style . overflow + style . overflowY + style . overflowX ) ) { return parent ; } } return document . body ; } param element example export const useStickyState = < T extends HTMLElement > ( element ) => { const [ isSticky , setIsSticky ] = useState ( false ) ; useEffect ( ( ) => { const { current : stickyTarget } = element ; if ( ! stickyTarget || ! window ) { return ; } const parentElement = getScrollParent ( stickyTarget ) ; if ( ! parentElement ) { return ; } const calculateIsSticky = debounce ( ( ) => { const pTop = parentElement . scrollTop ; const tTop = stickyTarget . getBoundingClientRect ( ) . top ; const difference = Math . round ( Math . abs ( pTop - tTop ) ) ; const triggerDistance = window . getComputedStyle ( stickyTarget ) . getPropertyValue ( "top" ) ; setIsSticky ( difference !== Math . abs ( parseInt ( triggerDistance , 10 ) ) ) ; } , 25 ) ; calculateIsSticky ( ) ; parentElement . addEventListener ( "scroll" , calculateIsSticky ) ; return ( ) => parentElement . removeEventListener ( "scroll" , calculateIsSticky ) ; } , [ element ] ) ; return isSticky ; } ;