Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views. - Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts. - Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface. - Added loading and error states in ContactsListView for better user feedback during data fetching. - Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list. - Updated package.json to include @formkit/auto-animate for improved UI animations.
80 lines
2.0 KiB
TypeScript
80 lines
2.0 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useRef, type RefObject } from "react"
|
|
|
|
interface UseDiscoveryScrollLoadOptions {
|
|
sentinelRef: RefObject<HTMLElement | null>
|
|
hasNextPage: boolean
|
|
isFetchingNextPage: boolean
|
|
onLoadMore: () => void
|
|
/** Pages chargées auto sans scroll (évite de tout prefetch si le sentinel est visible) */
|
|
maxAutoLoads?: number
|
|
scrollRootSelector?: string
|
|
}
|
|
|
|
export function useDiscoveryScrollLoad({
|
|
sentinelRef,
|
|
hasNextPage,
|
|
isFetchingNextPage,
|
|
onLoadMore,
|
|
maxAutoLoads = 1,
|
|
scrollRootSelector = "main.overflow-y-auto",
|
|
}: UseDiscoveryScrollLoadOptions) {
|
|
const autoLoadsRef = useRef(0)
|
|
const userScrolledRef = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (hasNextPage) {
|
|
autoLoadsRef.current = 0
|
|
userScrolledRef.current = false
|
|
}
|
|
}, [hasNextPage])
|
|
|
|
useEffect(() => {
|
|
const root =
|
|
document.querySelector(scrollRootSelector) ??
|
|
sentinelRef.current?.closest("main")
|
|
|
|
if (!root) return
|
|
|
|
const onScroll = () => {
|
|
if (root.scrollTop > 40) userScrolledRef.current = true
|
|
}
|
|
root.addEventListener("scroll", onScroll, { passive: true })
|
|
return () => root.removeEventListener("scroll", onScroll)
|
|
}, [scrollRootSelector, sentinelRef])
|
|
|
|
useEffect(() => {
|
|
const el = sentinelRef.current
|
|
if (!el || !hasNextPage) return
|
|
|
|
const root =
|
|
document.querySelector(scrollRootSelector) ??
|
|
el.closest("main")
|
|
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (!entry?.isIntersecting || isFetchingNextPage) return
|
|
if (!userScrolledRef.current && autoLoadsRef.current >= maxAutoLoads) return
|
|
autoLoadsRef.current += 1
|
|
onLoadMore()
|
|
},
|
|
{
|
|
root: root instanceof Element ? root : null,
|
|
rootMargin: "160px",
|
|
threshold: 0,
|
|
},
|
|
)
|
|
|
|
observer.observe(el)
|
|
return () => observer.disconnect()
|
|
}, [
|
|
sentinelRef,
|
|
hasNextPage,
|
|
isFetchingNextPage,
|
|
onLoadMore,
|
|
scrollRootSelector,
|
|
maxAutoLoads,
|
|
])
|
|
}
|