mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-06 00:07:44 +01:00
347c1cabbc
https://linear.app/unleash/issue/2-1509/discovery-stacked-sticky-elements Adds a new `Sticky` element that will attempt to stack sticky elements in the DOM in a smart way. This needs a wrapping `StickyProvider` that will keep track of sticky elements. This PR adapts a few components to use this new element: - `DemoBanner` - `FeatureOverviewSidePanel` - `DraftBanner` - `MaintenanceBanner` - `MessageBanner` Pre-existing `top` properties are taken into consideration for the top offset, so we can have nice margins like in the feature overview side panel. ### Before - Sticky elements overlap 😞 ![image](https://github.com/Unleash/unleash/assets/14320932/dd6fa188-6774-4afb-86fd-0eefb9aba93e) ### After - Sticky elements stack 😄 ![image](https://github.com/Unleash/unleash/assets/14320932/c73a84ab-7133-448f-9df6-69bd4c5330c2)
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>
|
|
);
|
|
};
|