FN-6380: widen Activity Log tablet layout

Widen the Activity Log modal at tablet widths so header controls stay reachable.

- Add component-scoped tablet CSS that widens only the Activity Log modal.
- Allow the Activity Log header and action controls to wrap while keeping close pinned right.
- Cover the tablet-only layout contract with a CSS regression test.

Files changed:
 .../__tests__/activity-log-tablet-layout.test.ts   | 73 ++++++++++++++++++++++
 packages/dashboard/app/components/ScriptsModal.css | 49 +++++++++++++++
 2 files changed, 122 insertions(+)

Fusion-Task-Id: FN-6380
Fusion-Task-Lineage: ae7cf5a6-c69d-4756-a4fa-1e4346b9c077
This commit is contained in:
gsxdsm
2026-06-13 11:03:30 -07:00
parent 1c20a7e82d
commit 0856d3d3fd
2 changed files with 122 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";
import { loadAllAppCss } from "../test/cssFixture";
/**
* Stylesheet regression test for Activity Log tablet layout.
*
* The desktop .modal-lg width is too narrow for the Activity Log header at
* tablet widths, so a component-scoped tablet media block must widen only the
* Activity Log modal and wrap the header controls. If these tablet rules are
* removed, refresh/close can be clipped between 769px and 1024px.
*/
describe("activity-log-tablet-layout.css", () => {
const cssContent = loadAllAppCss();
function extractTabletMediaBlocks(content: string): string {
const blocks: string[] = [];
const regex = /@media[^{}]*\(min-width:\s*769px\)[^{}]*\(max-width:\s*1024px\)[^{}]*\{/g;
let match: RegExpExecArray | null;
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++;
if (content[endIdx] === "}") braceCount--;
endIdx++;
}
if (braceCount === 0) {
blocks.push(content.slice(startIdx, endIdx - 1));
}
}
return blocks.join("\n");
}
const tabletCss = extractTabletMediaBlocks(cssContent);
it("defines tablet Activity Log rules for the broken 769px–1024px range", () => {
expect(tabletCss).toContain(".activity-log-modal");
expect(tabletCss).toContain(".activity-log-header");
});
it("widens only the Activity Log modal beyond the modal-lg base width", () => {
expect(tabletCss).toMatch(/\.activity-log-modal\s*\{[^}]*width:\s*calc\(100vw\s*-\s*var\(--space-2xl\)\)/);
expect(tabletCss).toMatch(/\.activity-log-modal\s*\{[^}]*max-width:\s*calc\(100vw\s*-\s*var\(--space-2xl\)\)/);
});
it("does not redefine the global modal-lg width inside the tablet block", () => {
expect(tabletCss).not.toMatch(/\.modal-lg\s*\{/);
});
it("allows the Activity Log header to wrap on tablet", () => {
expect(tabletCss).toMatch(/\.activity-log-header\s*\{[^}]*flex-wrap:\s*wrap/);
});
it("moves actions to a reachable wrapping row on tablet", () => {
expect(tabletCss).toMatch(/\.activity-log-actions\s*\{[^}]*flex:\s*1\s+1\s+100%/);
expect(tabletCss).toMatch(/\.activity-log-actions\s*\{[^}]*flex-wrap:\s*wrap/);
});
it("keeps the close button pinned to the top-right row on tablet", () => {
expect(tabletCss).toMatch(/\.activity-log-header\s+\.modal-close\s*\{[^}]*order:\s*\d/);
expect(tabletCss).toMatch(/\.activity-log-header\s+\.modal-close\s*\{[^}]*margin-left:\s*auto/);
});
it("keeps filters and refresh/clear controls reachable when optional controls render", () => {
expect(tabletCss).toMatch(/\.activity-log-filter,\s*\n\s*\.activity-log-filter--project\s*\{[^}]*flex:\s*1\s+1\s+0/);
expect(tabletCss).toMatch(/\.activity-log-refresh,\s*\n\s*\.activity-log-clear\s*\{[^}]*flex-shrink:\s*0/);
});
});

View File

@@ -1595,6 +1595,55 @@
border-color: var(--ws-error-dark);
}
/* ── Activity Log — Tablet (769px–1024px) ────────────────────────── */
@media (min-width: 769px) and (max-width: 1024px) {
/* Widen only the Activity Log modal; keep the global .modal-lg width unchanged. */
.activity-log-modal {
width: calc(100vw - var(--space-2xl));
max-width: calc(100vw - var(--space-2xl));
}
/* Header: title on left, close on right of top row, actions wrap below. */
.activity-log-header {
flex-wrap: wrap;
gap: var(--space-sm);
}
.activity-log-title {
flex: 1 1 auto;
order: 0;
}
.activity-log-actions {
flex: 1 1 100%;
flex-wrap: wrap;
gap: var(--space-sm);
order: 2;
}
.activity-log-header .modal-close {
order: 1;
margin-left: auto;
flex: 0 0 auto;
}
.activity-log-filter,
.activity-log-filter--project {
flex: 1 1 0;
min-width: 0;
}
.activity-log-filter-select {
width: 100%;
}
.activity-log-refresh,
.activity-log-clear {
flex-shrink: 0;
}
}
/* ── Activity Log — Mobile (≤ 768px) ─────────────────────────────── */
@media (max-width: 768px) {