fix(FN-821): fix Activity Log modal layout and close control

- Fix modal sizing to properly fit within viewport on various screen sizes
- Fix close button visibility and accessibility in ActivityLogModal component
- Refactor CSS to simplify modal styles (remove unused/verbose rules)
- Add regression tests for modal layout and close button behavior
- Update Activity Log docs to reflect modal sizing changes
This commit is contained in:
gsxdsm
2026-04-04 01:25:16 -07:00
parent eef4614d3f
commit 9f4e387be5
5 changed files with 64 additions and 31 deletions

View File

@@ -234,7 +234,7 @@ View a centralized timeline of all task lifecycle events. Click the history icon
- **Auto-refresh**: Log updates automatically every 30 seconds when the modal is open
- **Pagination**: "Load More" button fetches older entries (100 entries per request, max 1000)
- **Clear Log**: Maintenance function to clear all activity history (with confirmation)
- **Responsive Layout**: On narrow screens (≤768px), the modal adapts with a stacked header, full-width filter controls, wrapped active-filters bar, reflowed entry text, and vertically stacked confirmation actions — preserving access to filters, task links, and clear-log on mobile devices
- **Responsive Layout**: The modal uses the shared `modal-lg` width (640px) and standard `modal-header` pattern with a dedicated close button, consistent with other dashboard modals. On narrow screens (≤768px), the modal adapts with a stacked header, full-width filter controls, wrapped active-filters bar, reflowed entry text, and vertically stacked confirmation actions — preserving access to filters, task links, and clear-log on mobile devices
**Event Metadata**:
- Task moves show from/to column transitions

View File

@@ -40,6 +40,23 @@ describe("activity-log-mobile-layout.css", () => {
const mobileCss = extractMobileMediaBlocks(cssContent);
// ── Modal sizing ────────────────────────────────────────────────────
it("uses modal-lg base class for consistent wide sizing", () => {
// The activity-log-modal should NOT set its own max-width; modal-lg handles width
const modalBlock = cssContent.match(/\.activity-log-modal\s*\{[^}]*\}/)?.[0];
expect(modalBlock).toBeTruthy();
// Should NOT contain max-width (handled by modal-lg base class)
expect(modalBlock).not.toMatch(/max-width:\s*\d+px/);
});
// ── Close button ────────────────────────────────────────────────────
it("does not define a custom activity-log-close style (uses shared modal-close)", () => {
// The modal should use the shared .modal-close class instead of a custom close button
expect(cssContent).not.toMatch(/\.activity-log-close\s*\{/);
});
// ── Modal header / actions ──────────────────────────────────────────
it("has mobile rule for activity-log-header to wrap on narrow screens", () => {

View File

@@ -182,9 +182,9 @@ export function ActivityLogModal({
}}
data-testid="activity-log-modal-overlay"
>
<div className="modal activity-log-modal" data-testid="activity-log-modal">
{/* Header */}
<div className="activity-log-header">
<div className="modal modal-lg activity-log-modal" data-testid="activity-log-modal">
{/* Header — uses shared modal-header pattern for consistent close control */}
<div className="modal-header activity-log-header">
<div className="activity-log-title">
<History size={18} />
<span>Activity Log</span>
@@ -250,17 +250,17 @@ export function ActivityLogModal({
<Trash2 size={14} />
</button>
)}
{/* Close button */}
<button
className="activity-log-close"
onClick={onClose}
title="Close"
data-testid="activity-close"
>
<X size={18} />
</button>
</div>
{/* Close button — uses shared modal-close for consistent sizing and alignment */}
<button
className="modal-close"
onClick={onClose}
aria-label="Close"
title="Close"
data-testid="activity-close"
>
×
</button>
</div>
{/* Active filters display */}

View File

@@ -492,6 +492,10 @@ describe("ActivityLogModal", () => {
// Verify key structural classes that the mobile CSS targets
const modal = container.querySelector(".activity-log-modal");
expect(modal).toBeTruthy();
// Modal uses shared modal-lg for consistent wide sizing
expect(modal!.classList.contains("modal-lg")).toBe(true);
// Header uses shared modal-header pattern
expect(modal!.querySelector(".modal-header")).toBeTruthy();
expect(modal!.querySelector(".activity-log-header")).toBeTruthy();
expect(modal!.querySelector(".activity-log-title")).toBeTruthy();
expect(modal!.querySelector(".activity-log-actions")).toBeTruthy();
@@ -500,6 +504,31 @@ describe("ActivityLogModal", () => {
expect(modal!.querySelector(".activity-log-entry")).toBeTruthy();
});
it("renders close button with shared modal-close class and accessibility attributes", async () => {
const { container } = render(
<ActivityLogModal
isOpen={true}
onClose={mockOnClose}
tasks={mockTasks}
onOpenTaskDetail={mockOnOpenTaskDetail}
/>
);
const closeButton = await screen.findByTestId("activity-close");
expect(closeButton).toBeTruthy();
// Uses shared modal-close class for consistent styling
expect(closeButton.classList.contains("modal-close")).toBe(true);
// Has accessibility label
expect(closeButton.getAttribute("aria-label")).toBe("Close");
// Close button is a direct child of the header, NOT inside the actions row
const header = container.querySelector(".modal-header");
expect(header).toBeTruthy();
expect(header!.contains(closeButton)).toBe(true);
const actions = container.querySelector(".activity-log-actions");
expect(actions).toBeTruthy();
expect(actions!.contains(closeButton)).toBe(false);
});
it("renders entry header and details within each entry for mobile reflow", async () => {
const { container } = render(
<ActivityLogModal

View File

@@ -11919,20 +11919,14 @@ html .column.drag-over * {
/* ── Activity Log Modal ─────────────────────────────────────────── */
.activity-log-modal {
max-width: 600px;
max-height: 80vh;
display: flex;
flex-direction: column;
}
.activity-log-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-lg) 20px;
border-bottom: 1px solid var(--border);
background: var(--bg-secondary);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
/* Extends shared .modal-header with activity-log-specific overrides */
gap: var(--space-sm);
}
.activity-log-title {
@@ -11971,8 +11965,7 @@ html .column.drag-over * {
}
.activity-log-refresh,
.activity-log-clear,
.activity-log-close {
.activity-log-clear {
display: flex;
align-items: center;
justify-content: center;
@@ -11992,11 +11985,6 @@ html .column.drag-over * {
color: var(--text);
}
.activity-log-close:hover {
background: var(--color-red, rgba(239, 68, 68, 0.1));
color: var(--color-red, #ef4444);
}
.activity-log-content {
flex: 1;
overflow-y: auto;
@@ -12379,8 +12367,7 @@ html .column.drag-over * {
/* Action buttons stay inline but shrink */
.activity-log-refresh,
.activity-log-clear,
.activity-log-close {
.activity-log-clear {
width: 28px;
height: 28px;
flex-shrink: 0;