docs: fix irregular scrolling in code tabs (#5239)

* docs: fix blocking scroll util

* remove unnecessary util methods
This commit is contained in:
Shahed Nasser
2023-09-27 19:17:04 +03:00
committed by GitHub
parent 4e3f3b54cb
commit 11b2f1237b
4 changed files with 22 additions and 78 deletions

View File

@@ -26,7 +26,7 @@ const Providers = ({ children }: ProvidersProps) => {
<BaseSpecsProvider>
<SidebarProvider>
<NavbarProvider>
<ScrollControllerProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SearchProvider>
<MobileProvider>{children}</MobileProvider>
</SearchProvider>

View File

@@ -35,7 +35,10 @@ export default function RootLayout({
>
<Providers>
<Navbar />
<div className="w-full h-[calc(100%-57px)] overflow-y-scroll">
<div
className="w-full h-[calc(100%-57px)] overflow-y-scroll"
id="main"
>
<div className="max-w-xxl grid w-full grid-cols-1 px-6 lg:mx-auto lg:grid-cols-[280px_1fr]">
<Sidebar expandItems={true} />
<div className="relative flex w-full flex-1 items-start justify-center px-4 pb-8 pt-16 md:px-8 lg:px-16 lg:py-[112px]">

View File

@@ -24,7 +24,7 @@ const Providers = ({ children }: ProvidersProps) => {
<SidebarProvider>
<NavbarProvider basePath={process.env.NEXT_PUBLIC_BASE_PATH}>
<SearchProvider>
<ScrollControllerProvider>
<ScrollControllerProvider scrollableSelector="#main">
{children}
</ScrollControllerProvider>
</SearchProvider>

View File

@@ -53,11 +53,19 @@ type ScrollController = {
enableScrollEvents: () => void
/** Disable scroll events in `useScrollPosition`. */
disableScrollEvents: () => void
/** Retrieves the scrollable element. By default, it's window. */
getScrollableElement: () => Element | Window
}
function useScrollControllerContextValue(): ScrollController {
function useScrollControllerContextValue(
scrollableSelector: string
): ScrollController {
const scrollEventsEnabledRef = useRef(true)
const getScrollableElement = useCallback(() => {
return (document.querySelector(scrollableSelector) as Element) || window
}, [scrollableSelector])
return useMemo(
() => ({
scrollEventsEnabledRef,
@@ -67,8 +75,9 @@ function useScrollControllerContextValue(): ScrollController {
disableScrollEvents: () => {
scrollEventsEnabledRef.current = false
},
getScrollableElement,
}),
[]
[getScrollableElement]
)
}
@@ -78,10 +87,12 @@ const ScrollMonitorContext = React.createContext<ScrollController | undefined>(
export function ScrollControllerProvider({
children,
scrollableSelector = "",
}: {
children: ReactNode
scrollableSelector?: string
}): JSX.Element {
const value = useScrollControllerContextValue()
const value = useScrollControllerContextValue(scrollableSelector)
return (
<ScrollMonitorContext.Provider value={value}>
{children}
@@ -165,6 +176,7 @@ type UseScrollPositionSaver = {
}
function useScrollPositionSaver(): UseScrollPositionSaver {
const { getScrollableElement } = useScrollController()
const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
elem: null,
top: 0,
@@ -187,7 +199,7 @@ function useScrollPositionSaver(): UseScrollPositionSaver {
const newTop = elem.getBoundingClientRect().top
const heightDiff = newTop - top
if (heightDiff) {
window.scrollBy({ left: 0, top: heightDiff })
getScrollableElement().scrollBy({ left: 0, top: heightDiff })
}
lastElementRef.current = { elem: null, top: 0 }
@@ -258,74 +270,3 @@ export function useScrollPositionBlocker(): {
blockElementScrollPositionUntilNextRender,
}
}
type CancelScrollTop = () => void
function smoothScrollNative(top: number): CancelScrollTop {
window.scrollTo({ top, behavior: "smooth" })
return () => {
// Nothing to cancel, it's natively cancelled if user tries to scroll down
}
}
function smoothScrollPolyfill(top: number): CancelScrollTop {
let raf: number | null = null
const isUpScroll = document.documentElement.scrollTop > top
function rafRecursion() {
const currentScroll = document.documentElement.scrollTop
if (
(isUpScroll && currentScroll > top) ||
(!isUpScroll && currentScroll < top)
) {
raf = requestAnimationFrame(rafRecursion)
window.scrollTo(0, Math.floor((currentScroll - top) * 0.85) + top)
}
}
rafRecursion()
// Break the recursion. Prevents the user from "fighting" against that
// recursion producing a weird UX
return () => raf && cancelAnimationFrame(raf)
}
/**
* A "smart polyfill" of `window.scrollTo({ top, behavior: "smooth" })`.
* This currently always uses a polyfilled implementation unless
* `scroll-behavior: smooth` has been set in CSS, because native support
* detection for scroll behavior seems unreliable.
*
* This hook does not do anything by itself: it returns a start and a stop
* handle. You can execute either handle at any time.
*/
export function useSmoothScrollTo(): {
/**
* Start the scroll.
*
* @param top The final scroll top position.
*/
startScroll: (top: number) => void
/**
* A cancel function, because the non-native smooth scroll-top
* implementation must be interrupted if user scrolls down. If there's no
* existing animation or the scroll is using native behavior, this is a no-op.
*/
cancelScroll: CancelScrollTop
} {
const cancelRef = useRef<CancelScrollTop | null>(null)
// Not all have support for smooth scrolling (particularly Safari mobile iOS)
// TODO proper detection is currently unreliable!
// see https://github.com/wessberg/scroll-behavior-polyfill/issues/16
// For now, we only use native scroll behavior if smooth is already set,
// because otherwise the polyfill produces a weird UX when both CSS and JS try
// to scroll a page, and they cancel each other.
const supportsNativeSmoothScrolling =
getComputedStyle(document.documentElement).scrollBehavior === "smooth"
return {
startScroll: (top: number) => {
cancelRef.current = supportsNativeSmoothScrolling
? smoothScrollNative(top)
: smoothScrollPolyfill(top)
},
cancelScroll: () => cancelRef.current?.(),
}
}