fix(projects): stabilize workspace sheet transitions

Collapse the context rail before a workspace drawer enters so the sheet keeps a fixed width, and hide the sidebar-style resize guide without removing resizing.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
Thomas Petersen
2026-08-23 23:29:34 -04:00
committed by Wrench
parent 6961e4c9d0
commit b5c927e90c
6 changed files with 67 additions and 8 deletions

View File

@@ -828,8 +828,8 @@ export const ChannelPane = React.memo(function ChannelPane({
</section>
) : null}
{/* Keep focus drawers mounted until their exit animation finishes. */}
<AnimatePresence onExitComplete={markExitComplete}>
{/* Serialize replacements so focus drawers keep one travel direction. */}
<AnimatePresence mode="wait" onExitComplete={markExitComplete}>
{channelManagementOpen && activeChannel ? (
<ChannelManagementAuxiliaryPanel
activeChannel={activeChannel}

View File

@@ -11,6 +11,7 @@ type RightAuxiliaryPaneProps = {
detached?: boolean;
onResetWidth: () => void;
onResizeStart: (event: React.PointerEvent<HTMLButtonElement>) => void;
showResizeIndicator?: boolean;
testId?: string;
widthPx: number;
};
@@ -23,6 +24,7 @@ export function RightAuxiliaryPane({
detached = false,
onResetWidth,
onResizeStart,
showResizeIndicator = true,
testId,
widthPx,
}: RightAuxiliaryPaneProps) {
@@ -56,7 +58,12 @@ export function RightAuxiliaryPane({
}
type="button"
>
<span className="absolute bottom-0 left-1/2 top-0 w-px -translate-x-1/2 bg-transparent group-hover/right-pane-resize:bg-border/80 group-focus-visible/right-pane-resize:bg-border/80" />
{showResizeIndicator ? (
<span
className="absolute bottom-0 left-1/2 top-0 w-px -translate-x-1/2 bg-transparent group-hover/right-pane-resize:bg-border/80 group-focus-visible/right-pane-resize:bg-border/80"
data-testid="right-auxiliary-pane-resize-indicator"
/>
) : null}
</button>
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col">
{children}

View File

@@ -134,6 +134,12 @@ export function ProjectChannelHome({
null;
const workspaceSheetOpen =
workspaceSheetTab != null && workspaceRepository != null;
const previousWorkspaceSheetOpenRef = React.useRef(workspaceSheetOpen);
const workspaceSheetVisibilityChanged =
previousWorkspaceSheetOpenRef.current !== workspaceSheetOpen;
React.useEffect(() => {
previousWorkspaceSheetOpenRef.current = workspaceSheetOpen;
}, [workspaceSheetOpen]);
const summaryVisible = summaryOpen && !workspaceSheetOpen;
const openWorkspaceSheet = React.useCallback(
@@ -390,6 +396,7 @@ export function ProjectChannelHome({
projects={projects}
/>
<ProjectContextRail
animateWidth={!workspaceSheetVisibilityChanged}
open={summaryVisible}
panelWidthPx={summaryWidth.widthPx}
resizing={summaryWidth.isResizing}

View File

@@ -5,6 +5,7 @@ import { cn } from "@/shared/lib/cn";
const CONTEXT_RAIL_GUTTER_PX = 8;
export function ProjectContextRail({
animateWidth = true,
children,
open,
panelWidthPx,
@@ -12,6 +13,7 @@ export function ProjectContextRail({
rounded = true,
testId = "project-context-rail",
}: {
animateWidth?: boolean;
children: React.ReactNode;
open: boolean;
panelWidthPx: number;
@@ -24,7 +26,7 @@ export function ProjectContextRail({
aria-hidden={!open}
className={cn(
"relative z-30 h-full shrink-0 overflow-hidden motion-reduce:transition-none",
resizing
resizing || !animateWidth
? "transition-none"
: "transition-[width] duration-200 ease-linear",
)}

View File

@@ -29,6 +29,7 @@ export function ProjectHomeColumn({
detached
onResetWidth={onResetWidth}
onResizeStart={onResizeStart}
showResizeIndicator={false}
testId={testId}
widthPx={widthPx}
>

View File

@@ -774,6 +774,49 @@ test("latest files commit opens its detail without a divider", async ({
await expect(page.getByTestId("project-commit-detail")).toBeVisible();
});
test("project workspace sheet enters at its settled width", async ({
page,
}) => {
await enableProjectsFeature(page);
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-projects-view").click();
await openCreateProjectDialog(page);
await page.getByTestId("create-project-name").fill("sheet-motion-demo");
await page.getByTestId("create-project-submit").click();
await expect(page.getByTestId("project-channel-home")).toBeVisible();
const summaryColumn = page.getByTestId("project-home-summary-column");
const resizeHandle = summaryColumn.getByTestId(
"right-auxiliary-pane-resize-handle",
);
await expect(resizeHandle).toBeVisible();
await resizeHandle.hover();
await expect(
resizeHandle.getByTestId("right-auxiliary-pane-resize-indicator"),
).toHaveCount(0);
const summaryRail = page.getByTestId("project-home-summary-rail");
const openRailWidth = await summaryRail.evaluate(
(element) => element.getBoundingClientRect().width,
);
expect(openRailWidth).toBeGreaterThan(0);
await page.getByTestId("project-home-context-tasks").click();
await expect(page.getByTestId("project-home-workspace-sheet")).toBeVisible();
const collapsedRailWidth = await summaryRail.evaluate(
(element) => element.getBoundingClientRect().width,
);
expect(collapsedRailWidth).toBeLessThanOrEqual(1);
const focusDrawer = page.getByTestId("focus-thread-drawer");
const enteringDrawerWidth = (await focusDrawer.boundingBox())?.width ?? 0;
expect(enteringDrawerWidth).toBeGreaterThan(0);
await waitForAnimations(page);
const settledDrawerWidth = (await focusDrawer.boundingBox())?.width ?? 0;
expect(
Math.abs(settledDrawerWidth - enteringDrawerWidth),
).toBeLessThanOrEqual(1);
});
test("commit detail opens from the commits feed with a diff", async ({
page,
}) => {
@@ -806,10 +849,9 @@ test("commit detail opens from the commits feed with a diff", async ({
await expect(page.getByTestId("focus-thread-drawer")).toBeVisible();
await page.getByTestId("project-home-context-tasks").click();
await expect(page.getByTestId("project-home-workspace-sheet")).toBeVisible();
await expect(page.getByTestId("focus-thread-drawer")).toHaveCSS(
"outline-style",
"none",
);
const focusDrawer = page.getByTestId("focus-thread-drawer");
await expect(focusDrawer).toHaveCount(1);
await expect(focusDrawer).toHaveCSS("outline-style", "none");
const workspaceSheet = page.getByTestId("project-home-workspace-sheet");
await expect(workspaceSheet).toHaveAttribute("data-tab", "issues");
await expect(