fix(ui): a sticky table header turns the scroller's top fade off itself; DataTable renders one layout
- "A page with a sticky ColumnHead must declare fadeTop: false" lived in prose: a registry flag on three pages plus an App special case for the Channels card layout, and the next table page would have re-acquired the fade-on-the-header defect with every lane green. ColumnHead now registers itself with the enclosing PageScroller (ui/stickyTop) while mounted; the registry flag, the App branch and the prop plumbing are gone. Measured on localdev: the top mask is 0px under Channels' and Audit's headers, 20px on Home and the Feed. - ui/DataTable mounted its table AND its card list at every width, one hidden by CSS: double DOM and render cost, duplicate ids, two keyboard copies of every row. It now mounts the one the `md` media query calls for (lib/useMediaQuery); the keyboard test asserts one layout at each width. - The Plex module's content width went px → rem with the rest of the page.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useLayoutEffect, useRef, type ReactNode } from "react";
|
||||
import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { useScrollFade } from "../../lib/useScrollFade";
|
||||
import { StickyTopProvider } from "./stickyTop";
|
||||
import { ensureNavId, peekNavId, subscribeNav } from "../../lib/history";
|
||||
|
||||
// Saved scroll offsets, keyed by `${historyEntryId}:${scrollKey}`. The entry id ties an offset to the
|
||||
@@ -78,14 +79,18 @@ function restoreScroll(el: HTMLElement, top: number): () => void {
|
||||
export default function PageScroller({
|
||||
children,
|
||||
scrollKey,
|
||||
fadeTop = true,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
scrollKey?: string;
|
||||
// Off when the scroller's top is a sticky element (a table's sticky header) — see useScrollFade.
|
||||
fadeTop?: boolean;
|
||||
}) {
|
||||
const fade = useScrollFade(20, fadeTop);
|
||||
// How many sticky elements sit at the scroller's top right now (see ./stickyTop): the top fade is
|
||||
// off while any is mounted — a table layout — and back on the moment none is (a card layout).
|
||||
const [stickyTops, setStickyTops] = useState(0);
|
||||
const registerStickyTop = useCallback(() => {
|
||||
setStickyTops((n) => n + 1);
|
||||
return () => setStickyTops((n) => n - 1);
|
||||
}, []);
|
||||
const fade = useScrollFade(20, stickyTops === 0);
|
||||
const fadeRef = fade.ref;
|
||||
const elRef = useRef<HTMLElement | null>(null);
|
||||
// Stable identity: an inline ref callback changes every render, which makes React detach and
|
||||
@@ -151,7 +156,7 @@ export default function PageScroller({
|
||||
style={fade.style}
|
||||
className="flex-1 min-w-0 overflow-y-auto scrollbar-gutter-stable"
|
||||
>
|
||||
{children}
|
||||
<StickyTopProvider value={registerStickyTop}>{children}</StickyTopProvider>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user