fix(web): restore resize handle + refine column close/animation behavior (#1616)
- Fix the resize handle disappearing: the animation wrapper is a Mantine Group (default align: center), and the full-height handle has no intrinsic height, so it collapsed to 0. Set align: stretch on the wrapper. - Closing the column now leaves the primary on the screen currently selected in the header (commits the active tab first), instead of swapping in the column's screen. - Keep the primary screen's standard ScreenStage transition; only the column (handle + column) slides open/closed from the right with a fade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c5e064ff71
commit
aba77d47f5
2 changed files with 34 additions and 14 deletions
|
|
@ -1400,24 +1400,38 @@ describe("InspectorView", () => {
|
|||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("carries the column's selection to the primary view on close", async () => {
|
||||
it("keeps the current header screen (not the column's) when closed", async () => {
|
||||
monitorWide.value = true;
|
||||
renderWithMantine(<StatefulInspectorViewHost {...connectedHttp()} />);
|
||||
const user = await gotoTab("Logs");
|
||||
await user.click(
|
||||
await screen.findByRole("button", { name: "Pin as column" }),
|
||||
);
|
||||
// Pinning moved the primary to the first non-Servers header tab (Tools).
|
||||
const header = screen.getByRole("banner");
|
||||
expect(
|
||||
within(header).getByRole("radio", { name: "Tools" }),
|
||||
).toBeChecked();
|
||||
|
||||
// Switch the column to History, then close it.
|
||||
await user.click(await screen.findByRole("radio", { name: "History" }));
|
||||
await user.click(
|
||||
await screen.findByRole("button", { name: "Close monitoring column" }),
|
||||
);
|
||||
|
||||
// The primary header now has History selected (not the pre-pin Logs).
|
||||
const header = screen.getByRole("banner");
|
||||
// The primary stays on the header's current screen (Tools), not the
|
||||
// column's History, and the monitor group returns to the header.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Close monitoring column" }),
|
||||
).toBeNull(),
|
||||
);
|
||||
expect(
|
||||
within(header).getByRole("radio", { name: "Tools" }),
|
||||
).toBeChecked();
|
||||
expect(
|
||||
within(header).getByRole("radio", { name: "History" }),
|
||||
).toBeChecked();
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("persists the pin preference and reopens the column when wide", () => {
|
||||
|
|
|
|||
|
|
@ -253,11 +253,13 @@ const MonitoringColumn = Stack.withProps({
|
|||
});
|
||||
|
||||
// Column open/close animation (#1616): the handle + column slide in from the
|
||||
// right edge and fade as they mount, reversing on close, so pinning reads as the
|
||||
// screen moving into a side column rather than snapping in. `AppShell.Main`'s
|
||||
// `overflow: hidden` clips the off-screen portion during the slide.
|
||||
// right edge and fade as they mount, reversing on close, so pinning reads as a
|
||||
// side column sliding open rather than snapping in. The primary screen keeps its
|
||||
// standard `ScreenStage` transition. Mantine plays `out → in` on enter and
|
||||
// `in → out` on exit. `AppShell.Main`'s `overflow: hidden` clips the off-screen
|
||||
// portion during the slide.
|
||||
const COLUMN_ANIM_MS = 300;
|
||||
const slideInFromRight: MantineTransition = {
|
||||
const columnSlide: MantineTransition = {
|
||||
in: { opacity: 1, transform: "translateX(0)" },
|
||||
out: { opacity: 0, transform: "translateX(100%)" },
|
||||
common: { transformOrigin: "right center" },
|
||||
|
|
@ -265,9 +267,12 @@ const slideInFromRight: MantineTransition = {
|
|||
};
|
||||
|
||||
// Flex-row wrapper holding the resize handle + column so the whole unit animates
|
||||
// as one (the Transition interpolation is applied to it via `style`).
|
||||
// as one (the Transition interpolation is applied to it via `style`). `align:
|
||||
// stretch` overrides Group's default `center` so the full-height resize handle
|
||||
// (which has no intrinsic height) fills the column instead of collapsing.
|
||||
const MonitorColumnGroup = Group.withProps({
|
||||
wrap: "nowrap",
|
||||
align: "stretch",
|
||||
gap: 0,
|
||||
h: "100%",
|
||||
flex: "0 0 auto",
|
||||
|
|
@ -773,11 +778,12 @@ export function InspectorView({
|
|||
/* v8 ignore next */
|
||||
if (isMonitorTab(tab)) setMonitorTab(tab);
|
||||
}
|
||||
// Closing the column carries its selection back to the primary area: the
|
||||
// screen the user was watching in the column becomes the active header tab,
|
||||
// rather than snapping back to whatever was selected before they pinned.
|
||||
// Closing the column leaves the primary area on whatever screen is currently
|
||||
// selected in the header — it does not swap in the column's screen. We commit
|
||||
// the current `activeTab` to the lifted state first so unpinning can't resurrect
|
||||
// the (now stale) monitor tab that was selected before pinning.
|
||||
function closeMonitorColumn() {
|
||||
onActiveTabChange(effectiveMonitorTab);
|
||||
onActiveTabChange(activeTab);
|
||||
setMonitorPinned(false);
|
||||
}
|
||||
function commitMonitorWidth(next: number) {
|
||||
|
|
@ -1062,7 +1068,7 @@ export function InspectorView({
|
|||
</ScreenStageContainer>
|
||||
<Transition
|
||||
mounted={effectivePinned}
|
||||
transition={slideInFromRight}
|
||||
transition={columnSlide}
|
||||
duration={COLUMN_ANIM_MS}
|
||||
exitDuration={COLUMN_ANIM_MS}
|
||||
timingFunction="ease"
|
||||
|
|
|
|||
Loading…
Reference in a new issue