Skip to content

Commit

Permalink
Make sidebar sticky like twitter
Browse files Browse the repository at this point in the history
  • Loading branch information
teodorus-nathaniel committed Mar 8, 2024
1 parent e1de9c5 commit 33c472c
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 8 deletions.
43 changes: 35 additions & 8 deletions src/components/main/PageWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down Expand Up @@ -219,16 +220,42 @@ export const PageContent: FC<Props> = ({
</section>
{rightPanel}
{rightPanel === undefined && creatorDashboardSidebarType && (
<div {...sidebarStyles()}>
<CreatorDashboardSidebar dashboardType={creatorDashboardSidebarType} />
<div className='mt-3'>
<TopUsersCard />
</div>
{/* <OnBoardingSidebar hideOnBoardingSidebar={() => setShowOnBoardingSidebar(false)} /> */}
</div>
<CreatorSidebar
{...sidebarStyles()}
creatorDashboardSidebarType={creatorDashboardSidebarType}
/>
)}
</div>
)}
</>
)
}

function CreatorSidebar({
creatorDashboardSidebarType,
...props
}: ComponentProps<'div'> & { creatorDashboardSidebarType: CreatorDashboardSidebarType }) {
const ref = useRef<HTMLDivElement | null>(null)
const { position, top: _top } = useStickyElement({ elRef: ref, top: 76 - BOX_SHADOW_OFFSET })

return (
<div
{...props}
style={{
...props.style,
top: _top,
position,
height: 'fit-content',
maxHeight: 'none',
overflow: 'visible',
}}
ref={ref}
>
<CreatorDashboardSidebar dashboardType={creatorDashboardSidebarType} />
<div className='mt-3'>
<TopUsersCard />
</div>
{/* <OnBoardingSidebar hideOnBoardingSidebar={() => setShowOnBoardingSidebar(false)} /> */}
</div>
)
}
104 changes: 104 additions & 0 deletions src/hooks/useStickyElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { RefObject, useLayoutEffect, useState } from 'react'

export type DimensionProps = {
top?: number
}

type HookProps = {
elRef: RefObject<HTMLElement | null>
} & 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<Position>('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 }
}

0 comments on commit 33c472c

Please sign in to comment.