fix(ui): the page's end scrolls out from under Back to top
The button floats over the bottom-right corner, and at the end of a long table it covered the closing pager's '100' and 'All'. While it is up, PageScroller ends the page a button's height later (only then, so a full-height layout gets no scrollbar). Its shown state is a shared store read through a use… wrapper: a store's .use() called straight from a component is memoized by the React Compiler and never re-renders (contract: store.contract.test.ts).
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Pressable } from "./Pressable";
|
||||
import { SURFACE } from "./surfaces";
|
||||
import { backToTopShown, useBackToTopShown } from "./backToTopState";
|
||||
|
||||
// A floating "back to top" button that fades in once the current page's scroll container is
|
||||
// scrolled past a threshold, and smooth-scrolls it back to the top. Every page scrolls inside a
|
||||
@@ -12,7 +13,8 @@ import { SURFACE } from "./surfaces";
|
||||
// viewport-relative regardless of transformed ancestors.
|
||||
export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; threshold?: number }) {
|
||||
const { t } = useTranslation();
|
||||
const [show, setShow] = useState(false);
|
||||
// Shared, not local: PageScroller keeps the page's end clear of the button while it is up.
|
||||
const show = useBackToTopShown();
|
||||
const scrollerRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
// Listen on the document in the CAPTURE phase and read the scroller off the event, rather than
|
||||
@@ -26,7 +28,7 @@ export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; thr
|
||||
const el = e.target as HTMLElement | null;
|
||||
if (!el || el.tagName !== "MAIN") return;
|
||||
scrollerRef.current = el;
|
||||
setShow(el.scrollTop > threshold);
|
||||
backToTopShown.set(el.scrollTop > threshold);
|
||||
};
|
||||
document.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
||||
return () => document.removeEventListener("scroll", onScroll, { capture: true });
|
||||
@@ -38,8 +40,7 @@ export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; thr
|
||||
useEffect(() => {
|
||||
const el = document.querySelector("main");
|
||||
scrollerRef.current = el;
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- reading the DOM is the point — <main> is swapped by the page switch above us, and only after that commit can its scrollTop be read at all. Nothing derivable in render knows where the new page is scrolled to.
|
||||
setShow(!!el && el.scrollTop > threshold);
|
||||
backToTopShown.set(!!el && el.scrollTop > threshold);
|
||||
}, [dep, threshold]);
|
||||
|
||||
// A keyboard press hides the button it was on: the focus goes to the page it scrolled (its <main>
|
||||
|
||||
Reference in New Issue
Block a user