Files
buzz/desktop/src/features/channels/ui/ThreadPanelSurface.tsx
thomaspblock cada3028ce fix(desktop): keep project sheets independent from threads (#6901)
## Summary

Project workspace sheets now slide in from the right as an independent
layer over an open conversation thread instead of replacing the thread
or rendering inside its panel. Closing the sheet restores the unchanged
thread, Escape applies only to the topmost surface, and the covered
thread's resize divider no longer leaks through the overlay.

### Related issue

Related: #6602. No matching issue found.

### Testing

- `pnpm --dir desktop exec biome check
src/features/channels/ui/RightAuxiliaryPane.tsx
tests/e2e/project-commit-detail.spec.ts`
- `pnpm exec playwright test tests/e2e/project-commit-detail.spec.ts
--project=smoke --grep "project workspace sheet stays independent from
an open thread"`
- Pre-push desktop formatting, file-size, typecheck, and unit-test
checks

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-08-26 20:59:47 -04:00

67 lines
1.8 KiB
TypeScript

import * as React from "react";
import { FocusThreadDrawer } from "@/features/channels/ui/FocusThreadDrawer";
import { usePresenceCoverage } from "@/features/channels/ui/useFocusDrawerPresence";
type ThreadPanelSurfaceProps = {
channelName: string;
children: React.ReactNode;
covered: boolean;
hasActiveEdit: boolean;
isFocusDrawer: boolean;
onClose: () => void;
};
/** Keeps a thread mounted while controlling its focus-drawer presentation. */
export const ThreadPanelSurface = React.forwardRef<
HTMLDivElement,
ThreadPanelSurfaceProps
>(function ThreadPanelSurface(
{ channelName, children, covered, hasActiveEdit, isFocusDrawer, onClose },
ref,
) {
return (
<div
aria-hidden={covered ? true : undefined}
className="contents"
data-testid="thread-surface"
inert={covered ? true : undefined}
ref={ref}
>
{isFocusDrawer ? (
<FocusThreadDrawer
channelName={channelName}
escapeEnabled={!covered}
hasActiveEdit={hasActiveEdit}
onClose={onClose}
>
{children}
</FocusThreadDrawer>
) : (
children
)}
</div>
);
});
/** Supplies covered-thread lifecycle and focus ownership for an overlay drawer. */
export function useThreadPanelSurface(
open: boolean,
onExitComplete: () => void,
) {
const ref = React.useRef<HTMLDivElement>(null);
const coverage = usePresenceCoverage(open);
const markExitComplete = React.useCallback(() => {
coverage.markExitComplete();
onExitComplete();
}, [coverage.markExitComplete, onExitComplete]);
const restoreFocusTarget = React.useCallback(
() =>
ref.current?.querySelector<HTMLElement>(
'[data-testid="auxiliary-panel-close"]',
) ?? null,
[],
);
return { ...coverage, markExitComplete, ref, restoreFocusTarget };
}