mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-06 00:07:44 +01:00
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
|
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<HTMLDivElement> {
|
||
|
children: ReactNode;
|
||
|
}
|
||
|
|
||
|
export const Sticky = ({ children, ...props }: IStickyProps) => {
|
||
|
const context = useContext(StickyContext);
|
||
|
const ref = useRef<HTMLDivElement>(null);
|
||
|
const [initialTopOffset, setInitialTopOffset] = useState<number | null>(
|
||
|
null,
|
||
|
);
|
||
|
const [top, setTop] = useState<number>();
|
||
|
|
||
|
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 (
|
||
|
<StyledSticky ref={ref} top={top} {...props}>
|
||
|
{children}
|
||
|
</StyledSticky>
|
||
|
);
|
||
|
};
|