fix(FN-1307): fix invisible close button on mobile file browser modal

- Add overflow: hidden and text-overflow: ellipsis to mobile modal header to prevent title from overlapping close button
- Add min-width: 0 and flex-shrink: 1 to header title for proper flex truncation
- Add CSS rules for FileBrowserModal breadcrumb and filename truncation on narrow screens
- Add tests for mobile close button visibility and CSS text truncation behavior
This commit is contained in:
gsxdsm
2026-04-09 06:01:07 -07:00
parent f13af44150
commit 4ccaecf8ac
2 changed files with 110 additions and 6 deletions

View File

@@ -172,6 +172,101 @@ describe("FileBrowserModal", () => {
});
});
it("close button is visible on mobile after selecting a file with a long path", async () => {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 375,
});
// Provide a file with a long path name
const longFileName = "packages/dashboard/app/components/SomeVeryLongComponentName.tsx";
mockUseWorkspaceFileBrowser.mockReturnValue({
...defaultBrowserState,
entries: [
{ name: longFileName, type: "file" as const, size: 2048, mtime: "2024-01-01" },
],
});
const { container } = render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
/>,
);
fireEvent(window, new Event("resize"));
// Select the long-named file
fireEvent.click(screen.getByText(longFileName));
// Verify the file path appears in the header
await waitFor(() => {
const pathEl = container.querySelector(".file-browser-header-path");
expect(pathEl).toBeInTheDocument();
expect(pathEl?.textContent).toBe(longFileName);
});
const closeButton = container.querySelector("button.modal-close");
expect(closeButton).toBeInTheDocument();
expect(closeButton).toBeVisible();
// Clicking the close button should trigger onClose
fireEvent.click(closeButton!);
await waitFor(() => {
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});
it("long file path is truncated on mobile", async () => {
// Read CSS file directly to verify the overflow/ellipsis rules
// (JSDOM doesn't apply stylesheets, so computed style checks won't work)
const { readFileSync } = await import("fs");
const { resolve } = await import("path");
const cssPath = resolve(__dirname, "../styles.css");
const cssContent = readFileSync(cssPath, "utf-8");
// Extract mobile media query blocks
function extractMobileMediaBlocks(content: string): string {
const blocks: string[] = [];
const regex = /@media\s*\(\s*max-width:\s*768px\s*\)\s*\{/g;
let match;
while ((match = regex.exec(content)) !== null) {
const startIdx = match.index + match[0].length;
let braceCount = 1;
let endIdx = startIdx;
while (braceCount > 0 && endIdx < content.length) {
if (content[endIdx] === "{") braceCount += 1;
if (content[endIdx] === "}") braceCount -= 1;
endIdx += 1;
}
if (braceCount === 0) {
blocks.push(content.slice(startIdx, endIdx - 1));
}
}
return blocks.join("\n");
}
const mobileBlock = extractMobileMediaBlocks(cssContent);
// Find the file-browser-header-path rule within mobile blocks
const pathMatch = mobileBlock.match(
/\.file-browser-header-path\s*\{([^}]*)\}/,
);
expect(pathMatch).not.toBeNull();
const pathRules = pathMatch![1];
expect(pathRules).toContain("text-overflow: ellipsis");
expect(pathRules).toContain("white-space: nowrap");
expect(pathRules).toContain("overflow: hidden");
expect(pathRules).toContain("max-width: 50vw");
});
it("closes on Escape and saves on Cmd+S", () => {
mockUseWorkspaceFileEditor.mockReturnValue({
...defaultEditorState,

View File

@@ -13759,26 +13759,35 @@ body[data-color-theme="terminal"][data-theme="light"]::before {
}
.file-browser-modal-header {
overflow: hidden;
flex-wrap: wrap;
align-items: flex-start;
gap: var(--space-sm);
}
.file-browser-header-title {
flex: 1 1 100%;
width: 100%;
overflow: hidden;
min-width: 0;
flex: 1;
}
.file-browser-header-path {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 50vw;
}
.file-browser-header-actions {
width: 100%;
flex-shrink: 0;
justify-content: flex-end;
}
.file-browser-modal-header .modal-close {
min-width: 36px;
min-height: 36px;
min-width: 44px;
min-height: 44px;
padding: 10px;
display: inline-flex;
display: flex;
align-items: center;
justify-content: center;
}