import {
HTMLAttributes,
ReactNode,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { StickyContext } from './StickyContext';
import { styled } from '@mui/material';
const StyledSticky = styled('div', {
shouldForwardProp: (prop) => prop !== 'top',
})<{ top?: number }>(({ theme, top }) => ({
position: 'sticky',
zIndex: theme.zIndex.sticky - 100,
...(top !== undefined
? {
'&': {
top,
},
}
: {}),
}));
interface IStickyProps extends HTMLAttributes {
children: ReactNode;
}
export const Sticky = ({ children, ...props }: IStickyProps) => {
const context = useContext(StickyContext);
const ref = useRef(null);
const [initialTopOffset, setInitialTopOffset] = useState(
null,
);
const [top, setTop] = useState();
if (!context) {
throw new Error(
'Sticky component must be used within a StickyProvider',
);
}
const { registerStickyItem, unregisterStickyItem, getTopOffset } = context;
useEffect(() => {
// We should only set the initial top offset once - when the component is mounted
// This value will be set based on the initial top that was set for this component
// After that, the top will be calculated based on the height of the previous sticky items + this initial top offset
if (ref.current && initialTopOffset === null) {
setInitialTopOffset(
parseInt(getComputedStyle(ref.current).getPropertyValue('top')),
);
}
}, []);
useEffect(() => {
// (Re)calculate the top offset based on the sticky items
setTop(getTopOffset(ref) + (initialTopOffset || 0));
}, [getTopOffset, initialTopOffset]);
useEffect(() => {
// We should register the sticky item when it is mounted and unregister it when it is unmounted
if (!ref.current) {
return;
}
registerStickyItem(ref);
return () => {
unregisterStickyItem(ref);
};
}, [ref, registerStickyItem, unregisterStickyItem]);
return (
{children}
);
};