admin: only the content region scrolls on wide screens
the shell grid grew past the viewport and scrolled the document, carrying the sidebar with it. the shell is now viewport-height at the wide breakpoint with main as the sole scroll container; the nav list scrolls inside the pinned rail; router scroll restoration targets the inner scroller so navigation resets it and back/forward restores it.
This commit is contained in:
@@ -425,6 +425,8 @@ export function createAppRouter(history?: RouterHistory, queryClient: QueryClien
|
|||||||
context: { queryClient },
|
context: { queryClient },
|
||||||
defaultPreload: "intent",
|
defaultPreload: "intent",
|
||||||
defaultPreloadStaleTime: 0,
|
defaultPreloadStaleTime: 0,
|
||||||
|
scrollRestoration: true,
|
||||||
|
scrollToTopSelectors: ["#main-content"],
|
||||||
defaultPendingComponent: RoutePending,
|
defaultPendingComponent: RoutePending,
|
||||||
defaultErrorComponent: RouteError,
|
defaultErrorComponent: RouteError,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -146,6 +146,15 @@ test("shell renders the overview route with all nav links", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("main carries the ids the router scrolls and restores", async () => {
|
||||||
|
renderShell();
|
||||||
|
await screen.findByRole("heading", { name: "Overview" });
|
||||||
|
|
||||||
|
const main = screen.getByRole("main");
|
||||||
|
expect(main.id).toBe("main-content");
|
||||||
|
expect(main.getAttribute("data-scroll-restoration-id")).toBe("main");
|
||||||
|
});
|
||||||
|
|
||||||
test("the three configuration pages sit under a labelled group, after the rest", async () => {
|
test("the three configuration pages sit under a labelled group, after the rest", async () => {
|
||||||
renderShell();
|
renderShell();
|
||||||
await screen.findByRole("heading", { name: "Overview" });
|
await screen.findByRole("heading", { name: "Overview" });
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ const styles = stylex.create({
|
|||||||
},
|
},
|
||||||
shell: {
|
shell: {
|
||||||
minHeight: "100dvh",
|
minHeight: "100dvh",
|
||||||
|
height: { default: null, [WIDE]: "100dvh" },
|
||||||
|
overflow: { default: null, [WIDE]: "hidden" },
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: colors.surface,
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
display: { default: "block", [WIDE]: "grid" },
|
display: { default: "block", [WIDE]: "grid" },
|
||||||
@@ -120,6 +122,8 @@ const styles = stylex.create({
|
|||||||
sidebar: {
|
sidebar: {
|
||||||
display: { default: "none", [WIDE]: "flex" },
|
display: { default: "none", [WIDE]: "flex" },
|
||||||
flexDirection: { default: null, [WIDE]: "column" },
|
flexDirection: { default: null, [WIDE]: "column" },
|
||||||
|
minHeight: { default: null, [WIDE]: 0 },
|
||||||
|
overflow: { default: null, [WIDE]: "hidden" },
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
borderRightStyle: "solid",
|
borderRightStyle: "solid",
|
||||||
borderRightColor: colors.border,
|
borderRightColor: colors.border,
|
||||||
@@ -133,11 +137,14 @@ const styles = stylex.create({
|
|||||||
},
|
},
|
||||||
sidebarNav: {
|
sidebarNav: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
minHeight: { default: null, [WIDE]: 0 },
|
||||||
|
overflowY: { default: null, [WIDE]: "auto" },
|
||||||
paddingInline: "0.5rem",
|
paddingInline: "0.5rem",
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
minHeight: "100dvh",
|
minHeight: { default: "100dvh", [WIDE]: 0 },
|
||||||
|
minWidth: { default: null, [WIDE]: 0 },
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
@@ -177,6 +184,9 @@ const styles = stylex.create({
|
|||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
minHeight: { default: null, [WIDE]: 0 },
|
||||||
|
minWidth: { default: null, [WIDE]: 0 },
|
||||||
|
overflowY: { default: null, [WIDE]: "auto" },
|
||||||
padding: "1rem",
|
padding: "1rem",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -335,7 +345,7 @@ export default function AppShell() {
|
|||||||
<SidebarFooter />
|
<SidebarFooter />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<main {...stylex.props(styles.main)}>
|
<main id="main-content" data-scroll-restoration-id="main" {...stylex.props(styles.main)}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,3 +60,26 @@ if (globalThis.CSS === undefined) {
|
|||||||
* than per call; a test that genuinely never resolves still fails, only later.
|
* than per call; a test that genuinely never resolves still fails, only later.
|
||||||
*/
|
*/
|
||||||
configure({ asyncUtilTimeout: 5000 });
|
configure({ asyncUtilTimeout: 5000 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jsdom implements no `Element.prototype.scrollTo`, and its `window.scrollTo` is
|
||||||
|
* a stub that logs "Not implemented". The router's scroll restoration calls both
|
||||||
|
* on every navigation, so without these the shell throws into its error boundary
|
||||||
|
* in tests while working in a browser. jsdom has no layout, so a scroll is a
|
||||||
|
* position assignment and nothing more.
|
||||||
|
*/
|
||||||
|
if (typeof Element.prototype.scrollTo !== "function") {
|
||||||
|
Element.prototype.scrollTo = function scrollTo(...args: unknown[]) {
|
||||||
|
const options = (typeof args[0] === "object" ? args[0] : { left: args[0], top: args[1] }) as ScrollToOptions;
|
||||||
|
if (typeof options.top === "number") this.scrollTop = options.top;
|
||||||
|
if (typeof options.left === "number") this.scrollLeft = options.left;
|
||||||
|
} as typeof Element.prototype.scrollTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.scrollTo = function scrollTo(...args: unknown[]) {
|
||||||
|
const options = (typeof args[0] === "object" ? args[0] : { left: args[0], top: args[1] }) as ScrollToOptions;
|
||||||
|
if (typeof options.top === "number")
|
||||||
|
Object.defineProperty(window, "scrollY", { value: options.top, configurable: true });
|
||||||
|
if (typeof options.left === "number")
|
||||||
|
Object.defineProperty(window, "scrollX", { value: options.left, configurable: true });
|
||||||
|
} as typeof window.scrollTo;
|
||||||
|
|||||||
Reference in New Issue
Block a user