feat: sticky list mode switcher
This commit is contained in:
@@ -1,15 +1,18 @@
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
|
||||||
|
import AccountButton from './AccountButton'
|
||||||
import HomeButton from './HomeButton'
|
import HomeButton from './HomeButton'
|
||||||
import NotificationsButton from './NotificationsButton'
|
import NotificationsButton from './NotificationsButton'
|
||||||
import PostButton from './PostButton'
|
import PostButton from './PostButton'
|
||||||
import AccountButton from './AccountButton'
|
|
||||||
|
|
||||||
export default function BottomNavigationBar({ visible = true }: { visible?: boolean }) {
|
export default function BottomNavigationBar() {
|
||||||
|
const { deepBrowsing } = useDeepBrowsing()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed bottom-0 w-full z-20 bg-background/80 backdrop-blur-xl duration-700 transition-transform flex items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0',
|
'fixed bottom-0 w-full z-20 bg-background/80 backdrop-blur-xl duration-700 transition-transform flex items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
visible ? '' : 'translate-y-full'
|
deepBrowsing ? 'translate-y-full' : ''
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
height: 'calc(3rem + env(safe-area-inset-bottom))',
|
height: 'calc(3rem + env(safe-area-inset-bottom))',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PICTURE_EVENT_KIND } from '@/constants'
|
|||||||
import { isReplyNoteEvent } from '@/lib/event'
|
import { isReplyNoteEvent } from '@/lib/event'
|
||||||
import { checkAlgoRelay } from '@/lib/relay'
|
import { checkAlgoRelay } from '@/lib/relay'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
@@ -236,9 +237,15 @@ function ListModeSwitch({
|
|||||||
setListMode: (listMode: TNoteListMode) => void
|
setListMode: (listMode: TNoteListMode) => void
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { deepBrowsing } = useDeepBrowsing()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
className={cn(
|
||||||
|
'sticky top-12 bg-background z-10 duration-700 transition-transform',
|
||||||
|
deepBrowsing ? '-translate-y-[calc(100%+12rem)]' : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div
|
<div
|
||||||
className={`w-1/3 text-center py-2 font-semibold clickable cursor-pointer rounded-lg ${listMode === 'posts' ? '' : 'text-muted-foreground'}`}
|
className={`w-1/3 text-center py-2 font-semibold clickable cursor-pointer rounded-lg ${listMode === 'posts' ? '' : 'text-muted-foreground'}`}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { ChevronUp } from 'lucide-react'
|
import { ChevronUp } from 'lucide-react'
|
||||||
|
|
||||||
export default function ScrollToTopButton({
|
export default function ScrollToTopButton({
|
||||||
scrollAreaRef,
|
scrollAreaRef,
|
||||||
className,
|
className
|
||||||
visible = true
|
|
||||||
}: {
|
}: {
|
||||||
scrollAreaRef: React.RefObject<HTMLDivElement>
|
scrollAreaRef?: React.RefObject<HTMLDivElement>
|
||||||
className?: string
|
className?: string
|
||||||
visible?: boolean
|
|
||||||
}) {
|
}) {
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
|
const { deepBrowsing, lastScrollTop } = useDeepBrowsing()
|
||||||
|
const visible = !deepBrowsing && lastScrollTop > 800
|
||||||
|
|
||||||
const handleScrollToTop = () => {
|
const handleScrollToTop = () => {
|
||||||
if (isSmallScreen) {
|
if (!scrollAreaRef) {
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
|
||||||
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
|
|
||||||
export function Titlebar({
|
export function Titlebar({
|
||||||
children,
|
children,
|
||||||
className,
|
className
|
||||||
visible = true
|
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
visible?: boolean
|
|
||||||
}) {
|
}) {
|
||||||
|
const { isSmallScreen } = useScreenSize()
|
||||||
|
const { deepBrowsing } = useDeepBrowsing()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed sm:sticky top-0 w-full z-20 bg-background duration-700 transition-transform [&_svg]:size-4 [&_svg]:shrink-0',
|
'sticky top-0 w-full z-20 bg-background duration-700 transition-transform [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
visible ? '' : '-translate-y-full',
|
isSmallScreen && deepBrowsing ? '-translate-y-full' : '',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import ScrollToTopButton from '@/components/ScrollToTopButton'
|
|||||||
import { Titlebar } from '@/components/Titlebar'
|
import { Titlebar } from '@/components/Titlebar'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { TPrimaryPageName, usePrimaryPage } from '@/PageManager'
|
import { TPrimaryPageName, usePrimaryPage } from '@/PageManager'
|
||||||
|
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||||
|
|
||||||
const PrimaryPageLayout = forwardRef(
|
const PrimaryPageLayout = forwardRef(
|
||||||
(
|
(
|
||||||
@@ -22,8 +23,6 @@ const PrimaryPageLayout = forwardRef(
|
|||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||||
const [visible, setVisible] = useState(true)
|
|
||||||
const [lastScrollTop, setLastScrollTop] = useState(0)
|
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { current } = usePrimaryPage()
|
const { current } = usePrimaryPage()
|
||||||
|
|
||||||
@@ -44,77 +43,39 @@ const PrimaryPageLayout = forwardRef(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
window.scrollTo({ top: 0 })
|
window.scrollTo({ top: 0 })
|
||||||
setVisible(true)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}, [current])
|
}, [current])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (current !== pageName) return
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
const atBottom = isSmallScreen
|
|
||||||
? window.innerHeight + window.scrollY >= document.body.offsetHeight - 20
|
|
||||||
: scrollAreaRef.current
|
|
||||||
? scrollAreaRef.current?.clientHeight + scrollAreaRef.current?.scrollTop >=
|
|
||||||
scrollAreaRef.current?.scrollHeight - 20
|
|
||||||
: false
|
|
||||||
if (atBottom) {
|
|
||||||
setVisible(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollTop = (isSmallScreen ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
|
|
||||||
const diff = scrollTop - lastScrollTop
|
|
||||||
if (scrollTop <= 800) {
|
|
||||||
setVisible(true)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff > 20) {
|
|
||||||
setVisible(false)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
} else if (diff < -20) {
|
|
||||||
setVisible(true)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', handleScroll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
|
||||||
return () => {
|
|
||||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
|
||||||
}
|
|
||||||
}, [lastScrollTop, isSmallScreen, current])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea
|
<DeepBrowsingProvider active={current === pageName}>
|
||||||
className="sm:h-screen sm:overflow-auto pt-12 sm:pt-0"
|
<div
|
||||||
scrollBarClassName="sm:z-50"
|
|
||||||
ref={scrollAreaRef}
|
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: isSmallScreen ? 'calc(env(safe-area-inset-bottom) + 3rem)' : ''
|
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{titlebar && (
|
{titlebar && <PrimaryPageTitlebar>{titlebar}</PrimaryPageTitlebar>}
|
||||||
<PrimaryPageTitlebar visible={!isSmallScreen || visible}>{titlebar}</PrimaryPageTitlebar>
|
{children}
|
||||||
)}
|
{displayScrollToTopButton && <ScrollToTopButton />}
|
||||||
<div className="overflow-x-hidden">{children}</div>
|
<BottomNavigationBar />
|
||||||
{displayScrollToTopButton && (
|
</div>
|
||||||
<ScrollToTopButton
|
</DeepBrowsingProvider>
|
||||||
scrollAreaRef={scrollAreaRef}
|
)
|
||||||
visible={visible && lastScrollTop > 800}
|
}
|
||||||
/>
|
|
||||||
)}
|
return (
|
||||||
{isSmallScreen && <BottomNavigationBar visible={visible} />}
|
<DeepBrowsingProvider active={current === pageName} scrollAreaRef={scrollAreaRef}>
|
||||||
|
<ScrollArea
|
||||||
|
className="h-screen overflow-auto"
|
||||||
|
scrollBarClassName="z-50"
|
||||||
|
ref={scrollAreaRef}
|
||||||
|
>
|
||||||
|
{titlebar && <PrimaryPageTitlebar>{titlebar}</PrimaryPageTitlebar>}
|
||||||
|
{children}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
||||||
|
</DeepBrowsingProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -125,16 +86,6 @@ export type TPrimaryPageLayoutRef = {
|
|||||||
scrollToTop: () => void
|
scrollToTop: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrimaryPageTitlebar({
|
function PrimaryPageTitlebar({ children }: { children?: React.ReactNode }) {
|
||||||
children,
|
return <Titlebar className="h-12 p-1">{children}</Titlebar>
|
||||||
visible = true
|
|
||||||
}: {
|
|
||||||
children?: React.ReactNode
|
|
||||||
visible?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Titlebar className="h-12 p-1" visible={visible}>
|
|
||||||
{children}
|
|
||||||
</Titlebar>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import ScrollToTopButton from '@/components/ScrollToTopButton'
|
|||||||
import { Titlebar } from '@/components/Titlebar'
|
import { Titlebar } from '@/components/Titlebar'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
|
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
export default function SecondaryPageLayout({
|
export default function SecondaryPageLayout({
|
||||||
children,
|
children,
|
||||||
@@ -23,119 +24,65 @@ export default function SecondaryPageLayout({
|
|||||||
displayScrollToTopButton?: boolean
|
displayScrollToTopButton?: boolean
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||||
const [visible, setVisible] = useState(true)
|
|
||||||
const [lastScrollTop, setLastScrollTop] = useState(0)
|
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { currentIndex } = useSecondaryPage()
|
const { currentIndex } = useSecondaryPage()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
window.scrollTo({ top: 0 })
|
window.scrollTo({ top: 0 })
|
||||||
setVisible(true)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentIndex !== index) return
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
const atBottom = isSmallScreen
|
|
||||||
? window.innerHeight + window.scrollY >= document.body.offsetHeight - 20
|
|
||||||
: scrollAreaRef.current
|
|
||||||
? scrollAreaRef.current?.clientHeight + scrollAreaRef.current?.scrollTop >=
|
|
||||||
scrollAreaRef.current?.scrollHeight - 20
|
|
||||||
: false
|
|
||||||
if (atBottom) {
|
|
||||||
setVisible(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollTop = (isSmallScreen ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
|
|
||||||
const diff = scrollTop - lastScrollTop
|
|
||||||
if (scrollTop <= 800) {
|
|
||||||
setVisible(true)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff > 20) {
|
|
||||||
setVisible(false)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
} else if (diff < -20) {
|
|
||||||
setVisible(true)
|
|
||||||
setLastScrollTop(scrollTop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', handleScroll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
|
||||||
return () => {
|
|
||||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
|
||||||
}
|
|
||||||
}, [lastScrollTop, isSmallScreen, currentIndex])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea
|
<DeepBrowsingProvider active={currentIndex === index}>
|
||||||
className="sm:h-screen sm:overflow-auto pt-12 sm:pt-0"
|
<div
|
||||||
scrollBarClassName="sm:z-50"
|
|
||||||
ref={scrollAreaRef}
|
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: isSmallScreen ? 'calc(env(safe-area-inset-bottom) + 3rem)' : ''
|
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SecondaryPageTitlebar
|
<SecondaryPageTitlebar
|
||||||
title={title}
|
title={title}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
hideBackButton={hideBackButton}
|
hideBackButton={hideBackButton}
|
||||||
visible={visible}
|
|
||||||
/>
|
/>
|
||||||
<div className="pb-4 mt-2">{children}</div>
|
<div className="pb-4 mt-2">{children}</div>
|
||||||
{displayScrollToTopButton && (
|
{displayScrollToTopButton && <ScrollToTopButton />}
|
||||||
<ScrollToTopButton scrollAreaRef={scrollAreaRef} visible={visible && lastScrollTop > 800} />
|
<BottomNavigationBar />
|
||||||
)}
|
</div>
|
||||||
{isSmallScreen && <BottomNavigationBar visible={visible} />}
|
</DeepBrowsingProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}>
|
||||||
|
<ScrollArea
|
||||||
|
className="h-screen overflow-auto"
|
||||||
|
scrollBarClassName="sm:z-50"
|
||||||
|
ref={scrollAreaRef}
|
||||||
|
>
|
||||||
|
<SecondaryPageTitlebar title={title} controls={controls} hideBackButton={hideBackButton} />
|
||||||
|
<div className="pb-4 mt-2">{children}</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
||||||
|
</DeepBrowsingProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SecondaryPageTitlebar({
|
export function SecondaryPageTitlebar({
|
||||||
title,
|
title,
|
||||||
controls,
|
controls,
|
||||||
hideBackButton = false,
|
hideBackButton = false
|
||||||
visible = true
|
|
||||||
}: {
|
}: {
|
||||||
title?: React.ReactNode
|
title?: React.ReactNode
|
||||||
controls?: React.ReactNode
|
controls?: React.ReactNode
|
||||||
hideBackButton?: boolean
|
hideBackButton?: boolean
|
||||||
visible?: boolean
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { isSmallScreen } = useScreenSize()
|
|
||||||
|
|
||||||
if (isSmallScreen) {
|
|
||||||
return (
|
return (
|
||||||
<Titlebar
|
<Titlebar className="h-12 flex gap-1 p-1 items-center justify-between font-semibold">
|
||||||
className="h-12 flex gap-1 p-1 items-center justify-between font-semibold"
|
|
||||||
visible={visible}
|
|
||||||
>
|
|
||||||
<BackButton hide={hideBackButton}>{title}</BackButton>
|
<BackButton hide={hideBackButton}>{title}</BackButton>
|
||||||
<div className="flex-shrink-0">{controls}</div>
|
<div className="flex-shrink-0">{controls}</div>
|
||||||
</Titlebar>
|
</Titlebar>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Titlebar className="h-12 flex gap-1 p-1 justify-between items-center font-semibold">
|
|
||||||
<div className="flex items-center gap-1 flex-1 w-0">
|
|
||||||
<BackButton hide={hideBackButton}>{title}</BackButton>
|
|
||||||
</div>
|
|
||||||
<div className="flex-shrink-0">{controls}</div>
|
|
||||||
</Titlebar>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
80
src/providers/DeepBrowsingProvider.tsx
Normal file
80
src/providers/DeepBrowsingProvider.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
type TDeepBrowsingContext = {
|
||||||
|
deepBrowsing: boolean
|
||||||
|
lastScrollTop: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeepBrowsingContext = createContext<TDeepBrowsingContext | undefined>(undefined)
|
||||||
|
|
||||||
|
export const useDeepBrowsing = () => {
|
||||||
|
const context = useContext(DeepBrowsingContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useDeepBrowsing must be used within a DeepBrowsingProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeepBrowsingProvider({
|
||||||
|
children,
|
||||||
|
active,
|
||||||
|
scrollAreaRef
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
active: boolean
|
||||||
|
scrollAreaRef?: React.RefObject<HTMLDivElement>
|
||||||
|
}) {
|
||||||
|
const [deepBrowsing, setDeepBrowsing] = useState(false)
|
||||||
|
const [lastScrollTop, setLastScrollTop] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!active) return
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const atBottom = !scrollAreaRef
|
||||||
|
? window.innerHeight + window.scrollY >= document.body.offsetHeight - 20
|
||||||
|
: scrollAreaRef.current
|
||||||
|
? scrollAreaRef.current?.clientHeight + scrollAreaRef.current?.scrollTop >=
|
||||||
|
scrollAreaRef.current?.scrollHeight - 20
|
||||||
|
: false
|
||||||
|
if (atBottom) {
|
||||||
|
setDeepBrowsing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollTop = (!scrollAreaRef ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
|
||||||
|
const diff = scrollTop - lastScrollTop
|
||||||
|
if (scrollTop <= 800) {
|
||||||
|
setDeepBrowsing(false)
|
||||||
|
setLastScrollTop(scrollTop)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff > 20) {
|
||||||
|
setDeepBrowsing(true)
|
||||||
|
setLastScrollTop(scrollTop)
|
||||||
|
} else if (diff < -20) {
|
||||||
|
setDeepBrowsing(false)
|
||||||
|
setLastScrollTop(scrollTop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scrollAreaRef) {
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||||
|
return () => {
|
||||||
|
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
}, [lastScrollTop, active])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeepBrowsingContext.Provider value={{ deepBrowsing, lastScrollTop }}>
|
||||||
|
{children}
|
||||||
|
</DeepBrowsingContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user