FN-6403: fix GitHub import pane resizing
Restores reliable GitHub import left-pane resizing while keeping it scoped to the two-pane layout. - Unify resize availability with the side-by-side breakpoint so the handle and fixed pane width only render when dragging is supported. - Enlarge the divider hit area without changing the visible divider treatment. - Cover pointer dragging, clamping, responsive hiding, and list/PR tab resize surfaces. Files changed: .../dashboard/app/components/GitHubImportModal.css | 20 ++++- .../dashboard/app/components/GitHubImportModal.tsx | 35 +++++---- .../__tests__/GitHubImportModal.test.tsx | 91 +++++++++++++++++++++- 3 files changed, 127 insertions(+), 19 deletions(-) Fusion-Task-Id: FN-6403 Fusion-Task-Lineage: 974903f5-63dd-4852-9edf-6b0ade7ca912
This commit is contained in:
@@ -146,22 +146,38 @@
|
||||
}
|
||||
|
||||
.github-import-workspace__resize-handle {
|
||||
flex: 0 0 var(--space-xs);
|
||||
position: relative;
|
||||
flex: 0 0 var(--space-sm);
|
||||
align-self: stretch;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.github-import-workspace__resize-handle::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: var(--space-xs);
|
||||
transform: translateX(-50%);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--border) 35%, transparent);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.github-import-workspace__resize-handle:hover {
|
||||
.github-import-workspace__resize-handle:hover::before,
|
||||
.github-import-workspace__resize-handle:active::before {
|
||||
background: color-mix(in srgb, var(--todo) 50%, transparent);
|
||||
}
|
||||
|
||||
.github-import-workspace__resize-handle:focus-visible {
|
||||
outline: var(--focus-ring-strong);
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.github-import-workspace__resize-handle:focus-visible::before {
|
||||
background: color-mix(in srgb, var(--todo) 60%, transparent);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ interface GitHubImportModalProps {
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
// Mobile breakpoint in pixels
|
||||
// Mobile and two-pane breakpoints in pixels
|
||||
const MOBILE_BREAKPOINT = 640;
|
||||
const TWO_PANE_BREAKPOINT = 860;
|
||||
const GITHUB_IMPORT_LIST_PANE_MIN_WIDTH = 240;
|
||||
const GITHUB_IMPORT_LIST_PANE_MAX_WIDTH = 640;
|
||||
const GITHUB_IMPORT_LIST_PANE_DEFAULT_WIDTH = 360;
|
||||
@@ -82,8 +83,9 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
useModalResizePersist(modalRef, isOpen, "fusion:github-modal-size");
|
||||
const overlayDismissProps = useOverlayDismiss(onClose);
|
||||
|
||||
// Mobile view state
|
||||
// Responsive view state
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [canResizePanes, setCanResizePanes] = useState(false);
|
||||
const [mobileView, setMobileView] = useState<"list" | "preview">("list");
|
||||
const [listPaneWidth, setListPaneWidth] = useState(() => {
|
||||
if (typeof window === "undefined") {
|
||||
@@ -288,20 +290,21 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
return () => document.removeEventListener("keydown", handleKey);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// Detect mobile viewport
|
||||
// Detect responsive viewport bands
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const checkMobile = () => {
|
||||
|
||||
const checkViewportBands = () => {
|
||||
setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT);
|
||||
setCanResizePanes(window.innerWidth > TWO_PANE_BREAKPOINT);
|
||||
};
|
||||
|
||||
|
||||
// Check initially
|
||||
checkMobile();
|
||||
|
||||
checkViewportBands();
|
||||
|
||||
// Listen for resize
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
window.addEventListener("resize", checkViewportBands);
|
||||
return () => window.removeEventListener("resize", checkViewportBands);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -316,7 +319,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
}, [listPaneWidth]);
|
||||
|
||||
const handleListPaneResizeStart = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
if (isMobile) {
|
||||
if (!canResizePanes) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,10 +343,10 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
|
||||
document.addEventListener("pointermove", handlePointerMove);
|
||||
document.addEventListener("pointerup", handlePointerUp);
|
||||
}, [isMobile, listPaneWidth]);
|
||||
}, [canResizePanes, listPaneWidth]);
|
||||
|
||||
const handleListPaneResizeKeyDown = useCallback((event: ReactKeyboardEvent<HTMLDivElement>) => {
|
||||
if (isMobile) {
|
||||
if (!canResizePanes) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -371,7 +374,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
event.preventDefault();
|
||||
setListPaneWidth(GITHUB_IMPORT_LIST_PANE_MAX_WIDTH);
|
||||
}
|
||||
}, [isMobile]);
|
||||
}, [canResizePanes]);
|
||||
|
||||
// Handle issue selection - switch to preview view on mobile
|
||||
const handleIssueSelect = useCallback((issueNumber: number) => {
|
||||
@@ -625,7 +628,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
{/* Left pane: Issue/PR list */}
|
||||
<section
|
||||
className={`github-import-list-pane ${isMobile ? 'mobile' : ''} ${mobileView === 'list' ? 'active' : ''}`}
|
||||
style={!isMobile ? { flex: `0 0 ${listPaneWidth}px` } : undefined}
|
||||
style={canResizePanes ? { flex: `0 0 ${listPaneWidth}px` } : undefined}
|
||||
data-testid="github-import-list-pane"
|
||||
aria-labelledby="github-import-results-heading"
|
||||
>
|
||||
@@ -763,7 +766,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{!isMobile && (
|
||||
{canResizePanes && (
|
||||
<div
|
||||
className="github-import-workspace__resize-handle"
|
||||
role="separator"
|
||||
|
||||
@@ -887,6 +887,50 @@ describe("GitHubImportModal", () => {
|
||||
return rendered;
|
||||
};
|
||||
|
||||
const renderWithEmptyIssues = async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(singleRemote);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce([]);
|
||||
|
||||
const rendered = render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No open issues found")).toBeTruthy();
|
||||
});
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
const renderWithPulls = async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(singleRemote);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce([]);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce([
|
||||
{ number: 7, title: "Resize Test Pull", body: "Pull body", html_url: "https://github.com/owner/repo/pull/7", headBranch: "feature", baseBranch: "main" },
|
||||
]);
|
||||
|
||||
const rendered = render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: /Pull Requests/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Resize Test Pull")).toBeTruthy();
|
||||
});
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
const stubPointerCapture = (handle: HTMLElement) => {
|
||||
handle.setPointerCapture = vi.fn();
|
||||
handle.releasePointerCapture = vi.fn();
|
||||
handle.hasPointerCapture = vi.fn(() => true);
|
||||
};
|
||||
|
||||
const dragHandle = (handle: HTMLElement, startX: number, endX: number) => {
|
||||
stubPointerCapture(handle);
|
||||
fireEvent.pointerDown(handle, { pointerId: 1, clientX: startX });
|
||||
fireEvent.pointerMove(document, { pointerId: 1, clientX: endX });
|
||||
fireEvent.pointerUp(document, { pointerId: 1, clientX: endX });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage.removeItem("fusion:github-import-list-pane-width");
|
||||
setViewportWidth(1200);
|
||||
@@ -897,17 +941,62 @@ describe("GitHubImportModal", () => {
|
||||
setViewportWidth(originalInnerWidth);
|
||||
});
|
||||
|
||||
it("renders handle on desktop and hides it on mobile", async () => {
|
||||
it("renders handle only in the side-by-side two-pane band", async () => {
|
||||
await renderWithIssues();
|
||||
expect(screen.getByTestId("github-import-resize-handle")).toBeTruthy();
|
||||
expect(screen.getByTestId("github-import-list-pane").getAttribute("style")).toContain("flex: 0 0 360px");
|
||||
|
||||
setViewportWidth(800);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("github-import-resize-handle")).toBeNull();
|
||||
});
|
||||
expect(screen.getByTestId("github-import-list-pane").getAttribute("style") ?? "").not.toContain("flex: 0 0");
|
||||
|
||||
setViewportWidth(480);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("github-import-resize-handle")).toBeNull();
|
||||
});
|
||||
expect(screen.getByTestId("github-import-list-pane").getAttribute("style") ?? "").not.toContain("flex: 0 0");
|
||||
});
|
||||
|
||||
it("resizes the list pane with pointer drags and clamps to bounds", async () => {
|
||||
await renderWithIssues();
|
||||
const handle = screen.getByTestId("github-import-resize-handle");
|
||||
const listPane = screen.getByTestId("github-import-list-pane");
|
||||
|
||||
dragHandle(handle, 100, 160);
|
||||
expect(handle.getAttribute("aria-valuenow")).toBe("420");
|
||||
expect(listPane.getAttribute("style")).toContain("flex: 0 0 420px");
|
||||
|
||||
dragHandle(handle, 160, 120);
|
||||
expect(handle.getAttribute("aria-valuenow")).toBe("380");
|
||||
expect(listPane.getAttribute("style")).toContain("flex: 0 0 380px");
|
||||
|
||||
dragHandle(handle, 120, -200);
|
||||
expect(handle.getAttribute("aria-valuenow")).toBe("240");
|
||||
expect(listPane.getAttribute("style")).toContain("flex: 0 0 240px");
|
||||
|
||||
dragHandle(handle, -200, 700);
|
||||
expect(handle.getAttribute("aria-valuenow")).toBe("640");
|
||||
expect(listPane.getAttribute("style")).toContain("flex: 0 0 640px");
|
||||
});
|
||||
|
||||
it("renders the desktop handle regardless of list content or active tab", async () => {
|
||||
const mounted = await renderWithEmptyIssues();
|
||||
expect(screen.getByTestId("github-import-resize-handle")).toBeTruthy();
|
||||
expect(screen.getByTestId("github-import-list-pane").getAttribute("style")).toContain("flex: 0 0 360px");
|
||||
mounted.unmount();
|
||||
|
||||
vi.clearAllMocks();
|
||||
window.localStorage.removeItem("fusion:github-import-list-pane-width");
|
||||
setViewportWidth(1200);
|
||||
|
||||
await renderWithPulls();
|
||||
expect(screen.getByTestId("github-import-resize-handle")).toBeTruthy();
|
||||
expect(screen.getByTestId("github-import-list-pane").getAttribute("style")).toContain("flex: 0 0 360px");
|
||||
});
|
||||
it.each([
|
||||
[{ key: "ArrowRight" }, 370],
|
||||
[{ key: "ArrowLeft" }, 350],
|
||||
|
||||
Reference in New Issue
Block a user