diff --git a/src/components/main/PageWrapper.tsx b/src/components/main/PageWrapper.tsx index f31670844..a969c6181 100644 --- a/src/components/main/PageWrapper.tsx +++ b/src/components/main/PageWrapper.tsx @@ -3,8 +3,9 @@ import { summarize } from '@subsocial/utils/summarize' import clsx from 'clsx' import { CID } from 'ipfs-http-client' import Head from 'next/head' -import React, { ComponentProps, FC } from 'react' +import React, { ComponentProps, FC, useRef } from 'react' import config from 'src/config' +import { useStickyElement } from 'src/hooks/useStickyElement' import { resolveIpfsUrl } from 'src/ipfs' import SideMenu from 'src/layout/SideMenu' import CreatorDashboardSidebar, { @@ -219,16 +220,42 @@ export const PageContent: FC = ({ {rightPanel} {rightPanel === undefined && creatorDashboardSidebarType && ( -
- -
- -
- {/* setShowOnBoardingSidebar(false)} /> */} -
+ )} )} ) } + +function CreatorSidebar({ + creatorDashboardSidebarType, + ...props +}: ComponentProps<'div'> & { creatorDashboardSidebarType: CreatorDashboardSidebarType }) { + const ref = useRef(null) + const { position, top: _top } = useStickyElement({ elRef: ref, top: 76 - BOX_SHADOW_OFFSET }) + + return ( +
+ +
+ +
+ {/* setShowOnBoardingSidebar(false)} /> */} +
+ ) +} diff --git a/src/hooks/useStickyElement.ts b/src/hooks/useStickyElement.ts new file mode 100644 index 000000000..94e878abb --- /dev/null +++ b/src/hooks/useStickyElement.ts @@ -0,0 +1,104 @@ +import React, { RefObject, useLayoutEffect, useState } from 'react' + +export type DimensionProps = { + top?: number +} + +type HookProps = { + elRef: RefObject +} & DimensionProps + +type Position = React.CSSProperties['position'] + +const ceil = (num: number) => Math.ceil(num) + +export const useStickyElement = ({ + elRef, + top: topPositionAtWhichElementBecomesStickyFromTop = 0, +}: HookProps): { + top: number + position: Position +} => { + const [top, setTop] = useState(0) + const [position, setPosition] = useState('relative') + + useLayoutEffect(() => { + const element = elRef.current + let prevScrollTop = window.scrollY + if (!element) return + const offset = ceil(element.offsetTop) + const handleScroll = () => { + const elementBoundingRect = element.getBoundingClientRect() + const elementBoundingRectTop = ceil(elementBoundingRect.top) + const elementBoundingRectBottom = ceil(elementBoundingRect.bottom) + const topDistanceOfElementRelativeToPageTop = ceil(element.offsetTop) + const topDistanceAtWhichElementBecomesStickyFromBottom = elementBoundingRectTop + + const scrollYOffset = window.scrollY + const isScrollingUp = scrollYOffset < prevScrollTop + const isScrollingDown = scrollYOffset > prevScrollTop + const windowHeight = document.documentElement.clientHeight + + const topEndPosition = elementBoundingRectTop - topPositionAtWhichElementBecomesStickyFromTop + const bottomEndPosition = elementBoundingRectBottom + + const isTopEndAboveViewport = topEndPosition < 0 + const isTopEndBelowViewport = topEndPosition > windowHeight + const isBottomEndBelowViewport = bottomEndPosition > windowHeight + const isBottomEndAboveViewport = bottomEndPosition < 0 + + const isTopEndBetweenViewport = !isTopEndAboveViewport && !isTopEndBelowViewport + const isBottomEndBetweenViewport = !isBottomEndAboveViewport && !isBottomEndBelowViewport + + const areBothTopAndBottomEndsOnOppositeEndsOfViewport = + isTopEndAboveViewport && isBottomEndBelowViewport + const areBothTopAndBottomEndsBetweenViewport = + isTopEndBetweenViewport && isBottomEndBetweenViewport + + if (isTopEndBelowViewport || isBottomEndAboveViewport) { + setPosition('relative') + setTop(scrollYOffset) + prevScrollTop = scrollYOffset + return + } + if (areBothTopAndBottomEndsOnOppositeEndsOfViewport) { + setPosition('relative') + setTop(topDistanceOfElementRelativeToPageTop - offset) + prevScrollTop = scrollYOffset + return + } + + if (areBothTopAndBottomEndsBetweenViewport) { + setPosition('sticky') + setTop(topPositionAtWhichElementBecomesStickyFromTop) + prevScrollTop = scrollYOffset + return + } + + if (isScrollingUp) { + if (isTopEndBetweenViewport) { + setPosition('sticky') + setTop(topPositionAtWhichElementBecomesStickyFromTop) + } else if (isBottomEndBetweenViewport) { + setPosition('relative') + setTop(topDistanceOfElementRelativeToPageTop - offset) + } + } else if (isScrollingDown) { + if (isTopEndBetweenViewport) { + setPosition('relative') + setTop(topDistanceOfElementRelativeToPageTop - offset) + } else if (isBottomEndBetweenViewport) { + setPosition('sticky') + setTop(topDistanceAtWhichElementBecomesStickyFromBottom) + } + } + prevScrollTop = scrollYOffset + } + window.addEventListener('scroll', handleScroll, { passive: true }) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + return { top, position } +}