authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-06 20:36:19-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 20:36:19-07:00
log490eb17d31d3037930d0e6b549393e9dbcebde60
treef022c2da1c082146ab0a5d6269b59860fe6cd116
parentf5fb056d248be557e14358d08525361eca047e01
signature Signed by PGP key B5690EEEBB952194

fix(web): clarify sidebar drag dividers and empty targets (#10464)


2 files changed, 50 insertions(+), 16 deletions(-)

apps/web/src/components/Sidebar.drag.test.ts+25
......@@ -622,6 +622,31 @@ describe("sidebar drag projection", () => {
622622 expect(strategy({ ...smaller, index: 2 })?.y).toBe(-62.5);
623623 });
624624
625 it.each(["active", "settled"] as const)(
626 "reveals the mounted empty %s target when its last row leaves and hides it on return",
627 (section) => {
628 const items = [
629 pinnedHeader,
630 thread("p", "pinned"),
631 divider,
632 marker("active-placeholder"),
633 thread("a", "active"),
634 settledHeader,
635 marker("settled-placeholder"),
636 thread("s", "settled"),
637 ];
638 const active = section === "active" ? "a" : "s";
639 const input = { items, settledOrder: ["s"], settledExpanded: true };
640 const placeholderId = sidebarMarkerId(`${section}-placeholder`);
641 const resting = preview(input, active, active);
642 expect(resting.get(placeholderId)?.scaleY).toBe(0);
643 const leaving = preview(input, active, sidebarMarkerId("pinned-header"));
644 expect(leaving.get(placeholderId)?.scaleY).toBe(1);
645 const returning = preview(input, active, active);
646 expect(returning.get(placeholderId)?.scaleY).toBe(0);
647 },
648 );
649
625650 it("uses shelf height for empty target sizing when card height differs from its default", () => {
626651 const items = [
627652 pinnedHeader,
apps/web/src/components/Sidebar.tsx+25-16
......@@ -545,7 +545,8 @@ function SortableSidebarMarker(props: {
545545 className={cn("list-none", props.className)}
546546 style={{
547547 transform: CSS.Translate.toString(transform),
548 transition,
548 // A newly revealed target must not slide from its hidden position.
549 transition: props.marker.endsWith("-placeholder") ? "none" : transition,
549550 visibility: transform?.scaleY === 0 ? "hidden" : undefined,
550551 }}
551552 >
......@@ -599,13 +600,11 @@ function SidebarDragBoundary(props: {
599600 className="pointer-events-none relative mx-0.5 h-0"
600601 >
601602 {props.visible ? (
602 <div className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-1.5">
603 <div className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-2">
603604 <span
604605 className={cn(
605 "inline-flex h-4 shrink-0 items-center rounded-sm border bg-sidebar px-1.5 text-[10px] leading-none font-medium",
606 props.isDropTarget
607 ? "border-primary/40 text-primary"
608 : "border-sidebar-foreground/25 text-sidebar-foreground/80",
606 "shrink-0 text-xs font-medium",
607 props.isDropTarget ? "text-primary" : "text-sidebar-foreground/80",
609608 )}
610609 >
611610 {props.label}
......@@ -635,10 +634,10 @@ function SidebarSectionHeader(props: {
635634}) {
636635 const snoozed = props.marker === "snoozed-header";
637636 const className = cn(
638 "flex h-full w-full items-center gap-2 rounded-md border border-dashed border-transparent px-2 text-left text-xs font-medium",
637 "flex h-full w-full items-center gap-2 px-2 text-left text-xs font-medium",
639638 snoozed ? "text-blue-600 dark:text-blue-400" : "text-sidebar-muted-foreground/60",
640639 props.dragging && "text-sidebar-foreground/80",
641 props.isDropTarget && "border-primary/40 bg-primary/5 text-primary",
640 props.isDropTarget && "text-primary",
642641 );
643642 const content = (
644643 <>
......@@ -3261,9 +3260,7 @@ export default function Sidebar() {
32613260 items.push(...pinnedRows);
32623261 items.push({ kind: "marker", marker: "pinned-divider" });
32633262 const activeRows = rowsOf(activeThreads, "active");
3264 if (activeRows.length === 0) {
3265 items.push({ kind: "marker", marker: "active-placeholder" });
3266 }
3263 items.push({ kind: "marker", marker: "active-placeholder" });
32673264 items.push(...activeRows);
32683265 if (snoozedThreads.length > 0) {
32693266 items.push({ kind: "marker", marker: "snoozed-header" });
......@@ -3271,9 +3268,7 @@ export default function Sidebar() {
32713268 }
32723269 items.push({ kind: "marker", marker: "settled-header" });
32733270 const settledRows = rowsOf(renderedSettledThreads, "settled");
3274 if (settledRows.length === 0) {
3275 items.push({ kind: "marker", marker: "settled-placeholder" });
3276 }
3271 items.push({ kind: "marker", marker: "settled-placeholder" });
32773272 items.push(...settledRows);
32783273 return items;
32793274 }, [
......@@ -4805,7 +4800,14 @@ export default function Sidebar() {
48054800 key="active-placeholder"
48064801 marker="active-placeholder"
48074802 label="Active"
4808 showHint={from !== null}
4803 showHint={
4804 from !== null &&
4805 (activeThreads.length === 0 ||
4806 (from === "active" &&
4807 activeThreads.length === 1 &&
4808 dragTargetSection !== null &&
4809 dragTargetSection !== "active"))
4810 }
48094811 isDropTarget={dragTargetSection === "active"}
48104812 />,
48114813 );
......@@ -4852,7 +4854,14 @@ export default function Sidebar() {
48524854 key="settled-placeholder"
48534855 marker="settled-placeholder"
48544856 label="Settled"
4855 showHint={from !== null && from !== "settled"}
4857 showHint={
4858 from !== null &&
4859 (renderedSettledThreads.length === 0 ||
4860 (from === "settled" &&
4861 renderedSettledThreads.length === 1 &&
4862 dragTargetSection !== null &&
4863 dragTargetSection !== "settled"))
4864 }
48564865 isDropTarget={dragTargetSection === "settled"}
48574866 />,
48584867 );