History

Fit the empty Toolkits grid to the viewport (#1897)

yingchao committed 2 months ago

Commit f1930dd1 parent c7af9bb3 files · +63 −9Browse files

Unified · Split

3 files
  1. .changeset/toolkits-empty-grid.md +5 −0
  2. e2e/selfhost/toolkits-empty-grid.test.ts +56 −0
  3. packages/plugins/toolkits/src/page.tsx +2 −9
.changeset/toolkits-empty-grid.md+5−0
@@ -0,0 +1,5 @@
1---
2"executor": patch
3---
4
5Prevent the empty Toolkits page from scrolling past its visible add cards.
e2e/selfhost/toolkits-empty-grid.test.ts+56−0
@@ -0,0 +1,56 @@
1// An empty Toolkits grid must fit the viewport: with no toolkits in either
2// scope, both the Workspace and Personal add cards sit above the fold and the
3// grid does not scroll. Regression: each shelf reserved a fixed ~3-row
4// min-height, so two empty shelves stacked past the viewport and the page
5// scrolled with nothing to see.
6import { expect } from "@effect/vitest";
7import { Effect } from "effect";
8
9import { scenario } from "../src/scenario";
10import { Browser, Target } from "../src/services";
11import { visit } from "../src/surfaces/browser";
12
13scenario(
14 "Toolkits · empty grid fits the viewport without scrolling",
15 { timeout: 120_000 },
16 Effect.gen(function* () {
17 const target = yield* Target;
18 const browser = yield* Browser;
19 const identity = yield* target.newIdentity();
20
21 yield* browser.session(identity, async ({ page, step }) => {
22 await step("Open the Toolkits page with no toolkits in either scope", async () => {
23 await visit(page, "/default/toolkits/");
24 await page.getByRole("heading", { name: "Toolkits", level: 1 }).waitFor();
25 await page.getByRole("heading", { name: "Workspace" }).waitFor();
26 await page.getByRole("heading", { name: "Personal" }).waitFor();
27 await page.getByRole("button", { name: "Add workspace toolkit" }).waitFor();
28 await page.getByRole("button", { name: "Add personal toolkit" }).waitFor();
29 await page.locator('main [data-slot="skeleton"]').first().waitFor({ state: "detached" });
30 });
31
32 await step("The empty grid does not overflow its scroll container", async () => {
33 // Walk up from the Personal add card to its nearest scrollable
34 // ancestor; on an empty grid that ancestor must have nothing to
35 // scroll. This is the user-visible contract — both add cards are
36 // reachable without scrolling — measured at the scroll boundary.
37 const overflow = await page.evaluate(() => {
38 const addCard = [...document.querySelectorAll("button")].find(
39 (button) => button.getAttribute("aria-label") === "Add personal toolkit",
40 );
41 let node: HTMLElement | null = addCard ?? null;
42 while (node && node.scrollHeight <= node.clientHeight + 1) {
43 node = node.parentElement;
44 }
45 if (!node) return null;
46 return {
47 tag: node.tagName,
48 scrollHeight: node.scrollHeight,
49 clientHeight: node.clientHeight,
50 };
51 });
52 expect(overflow, "no scrollable ancestor overflows for an empty grid").toBeNull();
53 });
54 });
55 }),
56);
packages/plugins/toolkits/src/page.tsx+2−9
@@ -181,7 +181,6 @@ const toolkitByRouteSlug = ( · 180 unmodified lines
181};181};
182 182
183const toolkitCardStyle = { minHeight: "9rem" };183const toolkitCardStyle = { minHeight: "9rem" };
184const toolkitShelfStyle = { minHeight: "28.5rem" };
185const toolkitGridContainerStyle = { maxWidth: "80rem" };184const toolkitGridContainerStyle = { maxWidth: "80rem" };
186const toolkitToolTreeStyle = { width: "24rem" };185const toolkitToolTreeStyle = { width: "24rem" };
187 186
@@ -636,10 +635,7 @@ function ToolkitSection(props: { · 448 unmodified lines
636 </div>635 </div>
637 ) : null}636 ) : null}
638 637
639 <div638 <div className="grid content-start grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3">
640 className="grid content-start grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3"
641 style={toolkitShelfStyle}
642 >
643 {rows.map((toolkit) => (639 {rows.map((toolkit) => (
644 <ToolkitTile key={toolkit.id} showOwnerLabels={props.showOwnerLabels} toolkit={toolkit} />640 <ToolkitTile key={toolkit.id} showOwnerLabels={props.showOwnerLabels} toolkit={toolkit} />
645 ))}641 ))}
@@ -1204,10 +1200,7 @@ function ToolkitSectionSkeleton(props: { title?: string }) { · 558 unmodified lines
1204 </div>1200 </div>
1205 ) : null}1201 ) : null}
1206 1202
1207 <div1203 <div className="grid content-start grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3">
1208 className="grid content-start grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3"
1209 style={toolkitShelfStyle}
1210 >
1211 <ToolkitTileSkeleton />1204 <ToolkitTileSkeleton />
1212 </div>1205 </div>
1213 </section>1206 </section>