mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
fix(projects): overlay workspace sheets above threads
Give project workspace sheets explicit auxiliary priority so Tasks, Reviews, Commits, Files, and People remain accessible without discarding an open conversation thread. Signed-off-by: Thomas Petersen <thomasp@squareup.com> Co-authored-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
@@ -84,6 +84,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
header,
|
||||
idleAuxiliaryPanel = null,
|
||||
idleAuxiliaryHeaderActions,
|
||||
idleAuxiliaryOverridesThread = false,
|
||||
idleAuxiliaryTitle = "",
|
||||
hasOlderMessages,
|
||||
historyExhausted,
|
||||
@@ -487,11 +488,15 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
hasThreadSurface: Boolean(threadHeadMessage) || shouldShowThreadSkeleton,
|
||||
useSplitAuxiliaryPane,
|
||||
});
|
||||
const priorityIdleAuxiliary =
|
||||
idleAuxiliaryOverridesThread && useFocusIdleDrawer;
|
||||
const { channelIsCovered, markExitComplete } = useFocusDrawerPresence(
|
||||
useFocusThreadDrawer || useFocusIdleDrawer,
|
||||
useFocusThreadDrawer
|
||||
? onCloseThread
|
||||
: (onCloseIdleAuxiliaryPanel ?? onCloseThread),
|
||||
priorityIdleAuxiliary
|
||||
? (onCloseIdleAuxiliaryPanel ?? onCloseThread)
|
||||
: useFocusThreadDrawer
|
||||
? onCloseThread
|
||||
: (onCloseIdleAuxiliaryPanel ?? onCloseThread),
|
||||
);
|
||||
const { changeThreadViewMode, layoutScrollTargetId, resolveScrollTarget } =
|
||||
useThreadViewModeSwitch({
|
||||
@@ -551,6 +556,25 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
) : (
|
||||
wrapAux(panel, "idle-auxiliary-panel")
|
||||
);
|
||||
const idleAuxiliarySurface =
|
||||
idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel
|
||||
? wrapIdlePanel(
|
||||
<IdleAuxiliaryPanel
|
||||
canResetWidth={canResetThreadPanelWidth}
|
||||
headerControls={idleAuxiliaryHeaderActions}
|
||||
isFocusDrawer={useFocusIdleDrawer}
|
||||
isSinglePanelView={isSinglePanelView}
|
||||
onClose={onCloseIdleAuxiliaryPanel}
|
||||
onResetWidth={onResetThreadPanelWidth}
|
||||
onResizeStart={onThreadPanelResizeStart}
|
||||
title={idleAuxiliaryTitle}
|
||||
useSplitAuxiliaryPane={useSplitAuxiliaryPane}
|
||||
widthPx={threadPanelWidthPx}
|
||||
>
|
||||
{idleAuxiliaryPanel}
|
||||
</IdleAuxiliaryPanel>,
|
||||
)
|
||||
: null;
|
||||
const threadHeaderLeading = useSplitAuxiliaryPane ? (
|
||||
<ThreadViewModeToggle onChange={changeThreadViewMode} />
|
||||
) : undefined;
|
||||
@@ -799,14 +823,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/*
|
||||
* `AnimatePresence` keeps the focus thread drawer mounted through its exit
|
||||
* animation — without it the drawer's own existence condition
|
||||
* (`useFocusThreadDrawer`, which is derived from `threadHeadMessage`) goes
|
||||
* false on the same frame as the close, and there is nothing left to
|
||||
* animate. It can hold the real thread through the exit rather than a
|
||||
* frozen snapshot because the panel is fully prop-driven.
|
||||
*/}
|
||||
{/* Keep focus drawers mounted until their exit animation finishes. */}
|
||||
<AnimatePresence onExitComplete={markExitComplete}>
|
||||
{channelManagementOpen && activeChannel ? (
|
||||
<ChannelManagementAuxiliaryPanel
|
||||
@@ -824,6 +841,8 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
useSplitAuxiliaryPane={useSplitAuxiliaryPane}
|
||||
transparentChrome={hasSplitAuxiliaryPane}
|
||||
/>
|
||||
) : priorityIdleAuxiliary && idleAuxiliarySurface ? (
|
||||
idleAuxiliarySurface
|
||||
) : threadHeadMessage ? (
|
||||
(() => {
|
||||
const panel = (
|
||||
@@ -975,24 +994,9 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
);
|
||||
return wrapAux(panel, "user-profile-panel");
|
||||
})()
|
||||
) : idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel ? (
|
||||
wrapIdlePanel(
|
||||
<IdleAuxiliaryPanel
|
||||
canResetWidth={canResetThreadPanelWidth}
|
||||
headerControls={idleAuxiliaryHeaderActions}
|
||||
isFocusDrawer={useFocusIdleDrawer}
|
||||
isSinglePanelView={isSinglePanelView}
|
||||
onClose={onCloseIdleAuxiliaryPanel}
|
||||
onResetWidth={onResetThreadPanelWidth}
|
||||
onResizeStart={onThreadPanelResizeStart}
|
||||
title={idleAuxiliaryTitle}
|
||||
useSplitAuxiliaryPane={useSplitAuxiliaryPane}
|
||||
widthPx={threadPanelWidthPx}
|
||||
>
|
||||
{idleAuxiliaryPanel}
|
||||
</IdleAuxiliaryPanel>,
|
||||
)
|
||||
) : null}
|
||||
) : (
|
||||
idleAuxiliarySurface
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -49,11 +49,13 @@ export type ChannelPaneProps = {
|
||||
header?: React.ReactNode;
|
||||
/**
|
||||
* Idle-state body for the right auxiliary pane (project extras, etc.).
|
||||
* Shown only when no thread, profile, agent session, or channel-management
|
||||
* panel is open — the same slot as those panels.
|
||||
* Uses the same slot as thread, profile, agent-session, and management panels.
|
||||
* By default it yields to those surfaces; callers may opt into thread override.
|
||||
*/
|
||||
idleAuxiliaryPanel?: React.ReactNode;
|
||||
idleAuxiliaryHeaderActions?: IdleAuxiliaryHeaderControls;
|
||||
/** Show the idle auxiliary surface ahead of an already-open thread. */
|
||||
idleAuxiliaryOverridesThread?: boolean;
|
||||
idleAuxiliaryTitle?: string;
|
||||
hasOlderMessages?: boolean;
|
||||
/** True when the loaded window provably starts at the channel's beginning. */
|
||||
|
||||
@@ -91,6 +91,7 @@ export function ChannelScreen({
|
||||
headerEndActions,
|
||||
idleAuxiliaryPanel,
|
||||
idleAuxiliaryHeaderActions,
|
||||
idleAuxiliaryOverridesThread,
|
||||
idleAuxiliaryTitle,
|
||||
onAddFiles,
|
||||
onCloseIdleAuxiliaryPanel,
|
||||
@@ -855,6 +856,7 @@ export function ChannelScreen({
|
||||
header={channelHeader}
|
||||
idleAuxiliaryPanel={idleAuxiliaryPanel}
|
||||
idleAuxiliaryHeaderActions={idleAuxiliaryHeaderActions}
|
||||
idleAuxiliaryOverridesThread={idleAuxiliaryOverridesThread}
|
||||
idleAuxiliaryTitle={idleAuxiliaryTitle}
|
||||
hasOlderMessages={hasOlderMessages}
|
||||
historyExhausted={historyExhausted}
|
||||
|
||||
@@ -21,6 +21,7 @@ export type ChannelScreenProps = {
|
||||
currentProfile?: Profile;
|
||||
idleAuxiliaryPanel?: ReactNode;
|
||||
idleAuxiliaryHeaderActions?: IdleAuxiliaryHeaderControls;
|
||||
idleAuxiliaryOverridesThread?: boolean;
|
||||
idleAuxiliaryTitle?: string;
|
||||
headerEndActions?: ReactNode;
|
||||
onAddFiles?: () => void;
|
||||
|
||||
@@ -352,6 +352,7 @@ export function ProjectChannelHome({
|
||||
backLabel: workspaceDetail?.backLabel,
|
||||
onBack: workspaceDetail?.onBack,
|
||||
}}
|
||||
idleAuxiliaryOverridesThread={workspaceSheetOpen}
|
||||
idleAuxiliaryTitle={
|
||||
workspaceSheetTab
|
||||
? projectHomeWorkspaceSheetTitle(workspaceSheetTab)
|
||||
|
||||
@@ -791,6 +791,14 @@ test("commit detail opens from the commits feed with a diff", async ({
|
||||
.first();
|
||||
await expect(projectEntry).toBeVisible({ timeout: 10_000 });
|
||||
await projectEntry.click();
|
||||
const rootMessage = page
|
||||
.getByTestId("message-timeline")
|
||||
.getByTestId("message-row")
|
||||
.first();
|
||||
await expect(rootMessage).toBeVisible();
|
||||
await rootMessage.hover();
|
||||
await rootMessage.getByRole("button", { name: "Reply" }).click();
|
||||
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();
|
||||
const workspaceSheet = page.getByTestId("project-home-workspace-sheet");
|
||||
|
||||
Reference in New Issue
Block a user