diff --git a/apps/web/src/components/Sidebar.drag.test.ts b/apps/web/src/components/Sidebar.drag.test.ts index 1058dfcd88595c943f0669b6e974f44b4f2f8d8d..c757eca87aa4addb79b1f618475983c211333a05 100644 --- a/apps/web/src/components/Sidebar.drag.test.ts +++ b/apps/web/src/components/Sidebar.drag.test.ts @@ -253,6 +253,37 @@ describe("sidebar collision detection", () => { }); describe("sidebar drag projection", () => { + it.each([ + ["a1", "a2"], + ["a1", sidebarMarkerId("settled-header")], + ["z", "a2"], + ])("keeps sparse shelves at the bottom when dragging %s over %s", (active, over) => { + const items = [ + pinnedHeader, + divider, + thread("a1", "active"), + thread("a2", "active"), + marker("snoozed-header"), + thread("z", "snoozed"), + settledHeader, + thread("s", "settled"), + ]; + const strategy = createSidebarSortingStrategy({ + items, + settledOrder: over === sidebarMarkerId("settled-header") ? ["a1", "s"] : ["s"], + settledExpanded: true, + boundaryLabelHeight: 24, + snoozedThreadCount: 1, + }); + const args = layout(items, active, over); + for (const rect of args.rects.slice(4)) { + rect.top += 400; + rect.bottom += 400; + } + const lastIndex = items.length - 1; + expect(strategy({ ...args, index: lastIndex })).toEqual(stationary); + }); + const pinned = [ pinnedHeader, thread("p1", "pinned"), diff --git a/apps/web/src/components/Sidebar.drag.ts b/apps/web/src/components/Sidebar.drag.ts index c64de1c27b756365350e26aeb74041226767dbb1..0b5710c671fcfa90c3b44aca301aeb91b320c3ff 100644 --- a/apps/web/src/components/Sidebar.drag.ts +++ b/apps/web/src/components/Sidebar.drag.ts @@ -194,27 +194,58 @@ export function createSidebarSortingStrategy(input: { } marker("settled-header"); section("settled"); - const result = items.map(() => hidden); - let top = rects[0].top; - for (const item of projected) { + const heights = projected.map((item) => { const index = indices.get(sidebarListItemId(item)); const rect = index === undefined ? undefined : rects[index]; - if (index !== undefined && rect) result[index] = { ...stationary, y: top - rect.top }; const fallback = item.kind === "thread" && (item.section === "pinned" || item.section === "active") ? cardHeight : slimHeight; const moved = item.kind === "thread" && item.key === active.key; - const height = - item.kind === "marker" && + return item.kind === "marker" && (item.marker === "pinned-header" || item.marker === "pinned-divider") - ? labelHeight - : item.kind === "marker" && item.marker.endsWith("placeholder") - ? slimHeight - : moved - ? fallback - : (rect?.height ?? fallback); - top += height + 1; + ? labelHeight + : item.kind === "marker" && item.marker.endsWith("placeholder") + ? slimHeight + : moved + ? fallback + : (rect?.height ?? fallback); + }); + const firstShelf = items.findIndex( + (item) => + item.kind === "marker" && + (item.marker === "snoozed-header" || item.marker === "settled-header"), + ); + const shelfRect = rects[firstShelf]; + const beforeShelf = rects[firstShelf - 1]; + const lastRect = rects.at(-1); + // Consume the shelf's auto margin as drag labels and resized rows need + // room, keeping the combined shelves at their measured bottom. + let shelfSpace = + !input.compact && + shelfRect && + beforeShelf && + lastRect && + shelfRect.top > beforeShelf.bottom + 1 + ? Math.max( + 0, + lastRect.bottom - rects[0].top - heights.reduce((sum, height) => sum + height + 1, -1), + ) + : 0; + const result = items.map(() => hidden); + let top = rects[0].top; + for (const [projectedIndex, item] of projected.entries()) { + if ( + item.kind === "marker" && + (item.marker === "snoozed-header" || item.marker === "settled-header") + ) { + top += shelfSpace; + shelfSpace = 0; + } + const index = indices.get(sidebarListItemId(item)); + const rect = index === undefined ? undefined : rects[index]; + if (index !== undefined && rect) result[index] = { ...stationary, y: top - rect.top }; + top += heights[projectedIndex]! + 1; } result[activeIndex] = stationary; return result; diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 1b850f1875ba9fa12f401a31c8908fdcec492fa3..a86bb9a3868e1d38ebae4676950af51bd346bc22 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -661,6 +661,7 @@ function SidebarDragBoundary(props: { function SidebarSectionHeader(props: { marker: "snoozed-header" | "settled-header"; label: string; + className?: string; // While dragging, the settled header reads at full strength and takes the // accent while the lifted row is over it. dragging?: boolean; @@ -712,7 +713,7 @@ function SidebarSectionHeader(props: { 0 ? (
    } > - + {isSearchingThreads ? ( threadSearchResults.length > 0 ? ( 0 && "flex-1", + )} > {(() => { const renderThreadRowInner = ( @@ -5021,6 +5033,7 @@ export default function Sidebar() {