docs: fix irregular scrolling in code tabs (#5239)
* docs: fix blocking scroll util * remove unnecessary util methods
This commit is contained in:
@@ -26,7 +26,7 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
<BaseSpecsProvider>
|
<BaseSpecsProvider>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<NavbarProvider>
|
<NavbarProvider>
|
||||||
<ScrollControllerProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
<SearchProvider>
|
<SearchProvider>
|
||||||
<MobileProvider>{children}</MobileProvider>
|
<MobileProvider>{children}</MobileProvider>
|
||||||
</SearchProvider>
|
</SearchProvider>
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ export default function RootLayout({
|
|||||||
>
|
>
|
||||||
<Providers>
|
<Providers>
|
||||||
<Navbar />
|
<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]">
|
<div className="max-w-xxl grid w-full grid-cols-1 px-6 lg:mx-auto lg:grid-cols-[280px_1fr]">
|
||||||
<Sidebar expandItems={true} />
|
<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]">
|
<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]">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<NavbarProvider basePath={process.env.NEXT_PUBLIC_BASE_PATH}>
|
<NavbarProvider basePath={process.env.NEXT_PUBLIC_BASE_PATH}>
|
||||||
<SearchProvider>
|
<SearchProvider>
|
||||||
<ScrollControllerProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
{children}
|
{children}
|
||||||
</ScrollControllerProvider>
|
</ScrollControllerProvider>
|
||||||
</SearchProvider>
|
</SearchProvider>
|
||||||
|
|||||||
@@ -53,11 +53,19 @@ type ScrollController = {
|
|||||||
enableScrollEvents: () => void
|
enableScrollEvents: () => void
|
||||||
/** Disable scroll events in `useScrollPosition`. */
|
/** Disable scroll events in `useScrollPosition`. */
|
||||||
disableScrollEvents: () => void
|
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 scrollEventsEnabledRef = useRef(true)
|
||||||
|
|
||||||
|
const getScrollableElement = useCallback(() => {
|
||||||
|
return (document.querySelector(scrollableSelector) as Element) || window
|
||||||
|
}, [scrollableSelector])
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
scrollEventsEnabledRef,
|
scrollEventsEnabledRef,
|
||||||
@@ -67,8 +75,9 @@ function useScrollControllerContextValue(): ScrollController {
|
|||||||
disableScrollEvents: () => {
|
disableScrollEvents: () => {
|
||||||
scrollEventsEnabledRef.current = false
|
scrollEventsEnabledRef.current = false
|
||||||
},
|
},
|
||||||
|
getScrollableElement,
|
||||||
}),
|
}),
|
||||||
[]
|
[getScrollableElement]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +87,12 @@ const ScrollMonitorContext = React.createContext<ScrollController | undefined>(
|
|||||||
|
|
||||||
export function ScrollControllerProvider({
|
export function ScrollControllerProvider({
|
||||||
children,
|
children,
|
||||||
|
scrollableSelector = "",
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
scrollableSelector?: string
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const value = useScrollControllerContextValue()
|
const value = useScrollControllerContextValue(scrollableSelector)
|
||||||
return (
|
return (
|
||||||
<ScrollMonitorContext.Provider value={value}>
|
<ScrollMonitorContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
@@ -165,6 +176,7 @@ type UseScrollPositionSaver = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useScrollPositionSaver(): UseScrollPositionSaver {
|
function useScrollPositionSaver(): UseScrollPositionSaver {
|
||||||
|
const { getScrollableElement } = useScrollController()
|
||||||
const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
|
const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
|
||||||
elem: null,
|
elem: null,
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -187,7 +199,7 @@ function useScrollPositionSaver(): UseScrollPositionSaver {
|
|||||||
const newTop = elem.getBoundingClientRect().top
|
const newTop = elem.getBoundingClientRect().top
|
||||||
const heightDiff = newTop - top
|
const heightDiff = newTop - top
|
||||||
if (heightDiff) {
|
if (heightDiff) {
|
||||||
window.scrollBy({ left: 0, top: heightDiff })
|
getScrollableElement().scrollBy({ left: 0, top: heightDiff })
|
||||||
}
|
}
|
||||||
lastElementRef.current = { elem: null, top: 0 }
|
lastElementRef.current = { elem: null, top: 0 }
|
||||||
|
|
||||||
@@ -258,74 +270,3 @@ export function useScrollPositionBlocker(): {
|
|||||||
blockElementScrollPositionUntilNextRender,
|
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?.(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user