diff --git a/packages/app/e2e/regression/patch-sticky-header.spec.ts b/packages/app/e2e/regression/patch-sticky-header.spec.ts index 1290483ae51..7543d3bd4ae 100644 --- a/packages/app/e2e/regression/patch-sticky-header.spec.ts +++ b/packages/app/e2e/regression/patch-sticky-header.spec.ts @@ -182,7 +182,8 @@ for (const scenario of scenarios) { .poll(() => header.evaluate((element) => { const viewport = element.closest(".scroll-view__viewport")! - const title = viewport.querySelector("[data-session-title]")?.firstElementChild + // Stuck headers sit below the whole title, including its fade. + const title = viewport.querySelector("[data-session-title]") const toolTitle = element .closest('[data-component="edit-tool"]') ?.querySelector('[data-slot="collapsible-trigger"][data-locked]') @@ -194,7 +195,13 @@ for (const scenario of scenarios) { const top = sessionTop + (group?.getBoundingClientRect().height ?? 0) const rect = element.getBoundingClientRect() const trigger = element.querySelector("button")! + // The first stuck header covers the faded strip between it and the title. + const cover = getComputedStyle(group ?? toolTitle ?? element, "::before") + const fade = title + ? title.getBoundingClientRect().height - title.firstElementChild!.getBoundingClientRect().height + : 0 return { + cover: cover.display === "block" && cover.height === `${fade}px`, gap: Math.abs(rect.top - top - (toolTitle?.getBoundingClientRect().height ?? 0)), groupGap: group ? Math.abs(group.getBoundingClientRect().top - sessionTop) : 0, titleGap: toolTitle ? Math.abs(toolTitle.getBoundingClientRect().top - top) : 0, @@ -204,7 +211,7 @@ for (const scenario of scenarios) { } }), ) - .toEqual({ gap: 0, groupGap: 0, titleGap: 0, clickable: true }) + .toEqual({ cover: true, gap: 0, groupGap: 0, titleGap: 0, clickable: true }) await page.screenshot({ path: info.outputPath(`${file}.png`) }) } diff --git a/packages/app/src/session/timeline/virtualizer.tsx b/packages/app/src/session/timeline/virtualizer.tsx index 5675de7c22f..42c17c5d1ee 100644 --- a/packages/app/src/session/timeline/virtualizer.tsx +++ b/packages/app/src/session/timeline/virtualizer.tsx @@ -34,6 +34,8 @@ const pendingMarkdown = '[data-component="markdown"]:not([data-markdown-ready])' // exactly to the end, while a one-pixel nudge upward is a deliberate move away from it. const endEpsilon = 0.5 const upwardKeys = new Set(["up", "page-up", "home"]) +// Stuck headers can clear the session title's 16px fade only where they can detect being stuck and cover it. +const coverTitleFade = CSS.supports("container-type", "scroll-state") const cache = new Map< string, { @@ -683,7 +685,10 @@ export function createTimelineVirtualizer(input: Input) { onScroll={handleListScroll} onClick={input.onSelectionInteraction} class="relative min-w-0 w-full h-full" - style={{ "--sticky-accordion-top": input.showHeader() ? "48px" : "0px" }} + style={{ + "--sticky-accordion-top": !input.showHeader() ? "0px" : coverTitleFade ? "64px" : "48px", + "--sticky-accordion-fade": input.showHeader() && coverTitleFade ? "16px" : "0px", + }} >