FN-8474: remove tablet task popup gutter
Remove desktop resize clearance from tablet task-detail popups. - Remove the tablet-only right body gutter and suppress resize handles. - Preserve desktop resize clearance and mobile sheet behavior. - Cover empty and populated tablet task popup contracts. Files changed: .changeset/fn-8474-tablet-task-popup-gutter.md | 7 +++ packages/dashboard/app/components/FloatingWindow.css | 14 ++++++ packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx | 57 +++++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-8474 Fusion-Task-Lineage: 975478fa-c0da-4490-8b47-b09e02c6ff5e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8474-tablet-task-popup-gutter.md
Normal file
7
.changeset/fn-8474-tablet-task-popup-gutter.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Remove excess right padding from task popups on tablets.
|
||||||
|
category: fix
|
||||||
|
dev: Tablet task popups no longer reserve desktop resize-handle scrollbar clearance.
|
||||||
@@ -106,6 +106,20 @@ Headerless task pop-outs still need a visible drag affordance. The embedded task
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:FloatingWindow 2026-07-21-00:00:
|
||||||
|
Tablet task-detail popups prioritize symmetric content insets over desktop resize affordances. Their body removes the desktop scrollbar-clearance gutter only while all resize handles are suppressed; desktop retains the clearance and resize-safe handles, and the delegated task header remains draggable.
|
||||||
|
*/
|
||||||
|
@media (min-width: 769px) and (max-width: 1024px) {
|
||||||
|
.floating-window--task-detail .floating-window__body {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-window--task-detail .floating-window__resize-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:ChatModal 2026-06-22-14:49:
|
FNXC:ChatModal 2026-06-22-14:49:
|
||||||
On mobile/narrow app viewports, opening Quick Chat should present the full Chat modal as a full-screen sheet instead of a small draggable desktop window. Scope this to the chat FloatingWindow and override the inline desktop geometry only at the mobile breakpoint; desktop pop-out behavior remains movable/resizable.
|
On mobile/narrow app viewports, opening Quick Chat should present the full Chat modal as a full-screen sheet instead of a small draggable desktop window. Scope this to the chat FloatingWindow and override the inline desktop geometry only at the mobile breakpoint; desktop pop-out behavior remains movable/resizable.
|
||||||
|
|||||||
@@ -70,6 +70,20 @@ function stripAtMediaBlocks(css: string): string {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mediaBlockFor(css: string, query: string): string {
|
||||||
|
const start = css.indexOf(`@media ${query}`);
|
||||||
|
if (start === -1) return "";
|
||||||
|
const open = css.indexOf("{", start);
|
||||||
|
let depth = 1;
|
||||||
|
let index = open + 1;
|
||||||
|
while (index < css.length && depth > 0) {
|
||||||
|
if (css[index] === "{") depth++;
|
||||||
|
else if (css[index] === "}") depth--;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return css.slice(open + 1, index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
function setSheetViewport(isSheetWidth: boolean): void {
|
function setSheetViewport(isSheetWidth: boolean): void {
|
||||||
vi.stubGlobal("matchMedia", vi.fn((query: string) => ({
|
vi.stubGlobal("matchMedia", vi.fn((query: string) => ({
|
||||||
matches: query === "(max-width: 768px)" ? isSheetWidth : query === "(max-height: 480px)",
|
matches: query === "(max-width: 768px)" ? isSheetWidth : query === "(max-height: 480px)",
|
||||||
@@ -182,6 +196,47 @@ describe("FloatingWindow", () => {
|
|||||||
expect(cssRuleFor(floatingWindowCss, ".floating-window--chat.floating-window--headerless .floating-window__body")).toContain("overflow: hidden;");
|
expect(cssRuleFor(floatingWindowCss, ".floating-window--chat.floating-window--headerless .floating-window__body")).toContain("overflow: hidden;");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("removes only tablet task-detail resize clearance and handles for empty and populated popups", () => {
|
||||||
|
const tabletBlock = mediaBlockFor(floatingWindowCss, "(min-width: 769px) and (max-width: 1024px)");
|
||||||
|
const mobileBlock = mediaBlockFor(floatingWindowCss, "(max-width: 768px)");
|
||||||
|
const desktopCss = stripAtMediaBlocks(floatingWindowCss);
|
||||||
|
|
||||||
|
expect(cssRuleFor(tabletBlock, ".floating-window--task-detail .floating-window__body")).toContain("margin-inline-end: 0;");
|
||||||
|
expect(cssRuleFor(tabletBlock, ".floating-window--task-detail .floating-window__resize-handle")).toContain("display: none;");
|
||||||
|
expect(desktopCss.match(/(?:^|\n)\.floating-window__body\s*\{[^}]*\}/)?.[0]).toContain("margin-inline-end: var(--space-lg);");
|
||||||
|
expect(cssRulesForClass(desktopCss, "floating-window--task-detail").some((rule) => /margin-inline-end|display:\s*none/.test(rule))).toBe(false);
|
||||||
|
|
||||||
|
// Mobile remains a full-screen sheet with its independent no-gutter/no-handle contract.
|
||||||
|
expect(cssRuleFor(mobileBlock, ".floating-window--task-detail")).toContain("width: 100vw !important;");
|
||||||
|
expect(cssRuleFor(mobileBlock, ".floating-window--task-detail")).toContain("border-radius: 0;");
|
||||||
|
expect(cssRuleFor(mobileBlock, ".floating-window--task-detail .floating-window__body")).toContain("margin-inline-end: 0;");
|
||||||
|
expect(cssRuleFor(mobileBlock, ".floating-window--task-detail .floating-window__resize-handle")).toContain("display: none;");
|
||||||
|
expect(mobileBlock).toContain(".floating-window--task-detail .task-detail-content--embedded > .modal-header");
|
||||||
|
expect(mobileBlock).toContain("cursor: default;");
|
||||||
|
expect(mobileBlock).toContain("touch-action: auto;");
|
||||||
|
|
||||||
|
Object.defineProperty(window, "innerWidth", { configurable: true, value: 834 });
|
||||||
|
Object.defineProperty(window, "innerHeight", { configurable: true, value: 1112 });
|
||||||
|
setSheetViewport(false);
|
||||||
|
render(
|
||||||
|
<>
|
||||||
|
<FloatingWindow windowKey="tablet-empty" title="Empty task" onClose={() => {}} className="floating-window--task-detail">
|
||||||
|
<div aria-label="empty task detail" />
|
||||||
|
</FloatingWindow>
|
||||||
|
<FloatingWindow windowKey="tablet-populated" title="Populated task" onClose={() => {}} className="floating-window--task-detail">
|
||||||
|
<div aria-label="populated task detail">{Array.from({ length: 40 }, (_, index) => <p key={index}>Scrollable detail {index}</p>)}</div>
|
||||||
|
</FloatingWindow>
|
||||||
|
</>,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const key of ["tablet-empty", "tablet-populated"]) {
|
||||||
|
const panel = screen.getByTestId(`floating-window-${key}`);
|
||||||
|
expect(panel).toHaveClass("floating-window--task-detail");
|
||||||
|
expect(Number.parseFloat(panel.style.width)).toBeLessThanOrEqual(834);
|
||||||
|
expect(screen.getByTestId(`floating-window-body-${key}`)).toHaveClass("floating-window__body");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps task-detail long content clear of right handles while preserving short-content right-edge resize", () => {
|
it("keeps task-detail long content clear of right handles while preserving short-content right-edge resize", () => {
|
||||||
const longContent = Array.from({ length: 40 }, (_, index) => <p key={index}>Scrollable task detail {index}</p>);
|
const longContent = Array.from({ length: 40 }, (_, index) => <p key={index}>Scrollable task detail {index}</p>);
|
||||||
const { unmount } = render(
|
const { unmount } = render(
|
||||||
@@ -950,6 +1005,6 @@ describe("FloatingWindow", () => {
|
|||||||
expect(mobileBlock).toContain("width: 100vw !important;");
|
expect(mobileBlock).toContain("width: 100vw !important;");
|
||||||
expect(mobileBlock).toContain("height: 100dvh !important;");
|
expect(mobileBlock).toContain("height: 100dvh !important;");
|
||||||
expect(mobileBlock).toContain(".floating-window--chat .floating-window__resize-handle");
|
expect(mobileBlock).toContain(".floating-window--chat .floating-window__resize-handle");
|
||||||
expect(floatingWindowCss).not.toMatch(/@media\s*\(min-width:\s*769px\)[\s\S]*\.floating-window--chat[\s\S]*100dvh/);
|
expect(mediaBlockFor(floatingWindowCss, "(min-width: 769px) and (max-width: 1024px)")).not.toContain(".floating-window--chat");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user