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;
|
||||
}
|
||||
|
||||
/*
|
||||
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:
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
vi.stubGlobal("matchMedia", vi.fn((query: string) => ({
|
||||
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;");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const longContent = Array.from({ length: 40 }, (_, index) => <p key={index}>Scrollable task detail {index}</p>);
|
||||
const { unmount } = render(
|
||||
@@ -950,6 +1005,6 @@ describe("FloatingWindow", () => {
|
||||
expect(mobileBlock).toContain("width: 100vw !important;");
|
||||
expect(mobileBlock).toContain("height: 100dvh !important;");
|
||||
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