Merged in refactor/LOY-495-hook-for-sidepeek-scrolling (pull request #3297)

refactor(LOY-495): create useSidePeekScrollToTop hook

* refactor(LOY-495): create useSidePeekScrollToTop hook

* fix(LOY-495):  Read ref fresh each time


Approved-by: Emma Zettervall
This commit is contained in:
Chuma Mcphoy (We Ahead)
2025-12-05 13:51:10 +00:00
parent 001000a56d
commit 9d8399b7c7
3 changed files with 62 additions and 24 deletions

View File

@@ -29,11 +29,12 @@ export function useScrollToTop({
const scrollTarget = refScrollable && targetElement ? targetElement : window
function handleScroll() {
const currentElement = element ?? elementRef?.current
let position = window.scrollY
if (targetElement) {
if (currentElement) {
position = refScrollable
? targetElement.scrollTop
: targetElement.getBoundingClientRect().top * -1
? currentElement.scrollTop
: currentElement.getBoundingClientRect().top * -1
}
setShowBackToTop(position > threshold)
}
@@ -47,8 +48,9 @@ export function useScrollToTop({
if (targetElement) {
if (refScrollable) {
targetElement.scrollTo({ top: 0, behavior: "smooth" })
} else {
window.scrollTo({ top: targetElement.offsetTop, behavior: "smooth" })
}
window.scrollTo({ top: targetElement.offsetTop, behavior: "smooth" })
} else {
window.scrollTo({ top: 0, behavior: "smooth" })
}

View File

@@ -0,0 +1,53 @@
import { useState } from "react"
import { useScrollToTop } from "./useScrollToTop"
interface UseSidePeekScrollToTopProps {
threshold?: number
}
/**
* Hook for managing scroll-to-top functionality within a SidePeekSelfControlled component.
* Automatically finds the scrollable dialog container from the SidePeek DOM structure.
*
* @example
* ```tsx
* const { scrollContainerRef, showBackToTop, scrollToTop } = useSidePeekScrollToTop()
*
* return (
* <SidePeekSelfControlled>
* <div ref={scrollContainerRef}>
* {content}
* {showBackToTop && <BackToTopButton onPress={scrollToTop} />}
* </div>
* </SidePeekSelfControlled>
* )
* ```
*/
export function useSidePeekScrollToTop({
threshold = 200,
}: UseSidePeekScrollToTopProps = {}) {
const [scrollContainer, setScrollContainer] = useState<HTMLElement | null>(
null
)
const scrollContainerRef = (node: HTMLDivElement | null) => {
// SidePeekSelfControlled renders children twice: in the modal & in an sr-only SEO wrapper.
// We filter out the SEO wrapper to get the actual scrollable container.
// DOM structure: Dialog (scrollable) -> aside -> sidePeekContent -> our content div
const sidePeekContent = node?.parentElement
const aside = sidePeekContent?.parentElement
const dialog = aside?.parentElement
if (dialog && !sidePeekContent?.classList.contains("sr-only")) {
setScrollContainer(dialog)
}
}
const { showBackToTop, scrollToTop } = useScrollToTop({
threshold,
element: scrollContainer,
refScrollable: true,
})
return { scrollContainerRef, showBackToTop, scrollToTop }
}