From 197d21ef177f57d85d94bf9549596152f3b0fb2a Mon Sep 17 00:00:00 2001 From: Riyadh Date: Fri, 1 May 2026 09:54:18 +0000 Subject: [PATCH] #267: Freeze top bar, schedule heading, and table column header on scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top bar and schedule-heading row already used `position: sticky` with dynamic offsets published as `--em-header-h` / `--em-heading-h` via ResizeObserver. The table column header is the new piece. Approach: a floating sticky overlay (`em-sticky-thead`) rendered as a DOM-sibling above the horizontally-scrollable wrapper, scroll-synced in JS and column-width-measured from the first valid tbody row via ResizeObserver. The original `` is hidden in screen mode but kept (no testids, no Sortable) with `print:table-header-group` so column labels still appear at the top of every printed page. This replaces the rejected first attempt that relied on `position:sticky` inside `overflow-x-auto`, which only worked at xl+ viewports. The floating overlay sticks reliably at mobile (414px), tablet (~900px), desktop, and in RTL — all four covered by an expanded sticky-header e2e spec. Drag-reorder, resize handles, and the bulk-select tri-state checkbox now live exclusively in the floating thead, eliminating the duplicate- testid concern from having two interactive headers in the DOM. Tests: 4/4 sticky-header, 5/5 bulk-actions, 6/6 edit-toggle, 7/7 keyboard editing, 1/1 touch reorder. The one failing schedule-features case (custom highlight color) reproduces on the pre-change baseline and is pre-existing flake unrelated to this work. Files: - artifacts/tx-os/src/pages/executive-meetings.tsx - artifacts/tx-os/tests/executive-meetings-sticky-header.spec.mjs --- artifacts/tx-os/public/opengraph.jpg | Bin 25900 -> 25881 bytes .../tx-os/src/pages/executive-meetings.tsx | 265 +++++++++++++----- .../executive-meetings-sticky-header.spec.mjs | 185 ++++++------ 3 files changed, 283 insertions(+), 167 deletions(-) diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index f776ff3208031ceee54b75ca5c8c3d0ffa4a695d..238f8b8389a97842bbab24aa965109c4dc926741 100644 GIT binary patch literal 25881 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 I`Tx5AA12oauK)l5 literal 25900 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 LEFEM0>gGQHGngaw diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index 92afcddc..53a5dfa3 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -1647,6 +1647,66 @@ function ScheduleSection({ }; }, []); + // #267: Floating sticky thead. The actual table sits inside an + // overflow-x-auto wrapper (so wide tables can scroll horizontally on + // narrow screens), which means a CSS-only `position: sticky` on the + // thead is trapped inside that scrolling container and won't stick + // to the viewport. Workaround: render a separate sticky overlay + // above the wrapper that mirrors the actual table's column widths + + // horizontal scroll. The actual is hidden in screen mode + // (kept for print so paginated output still has column labels). + const tableWrapperRef = useRef(null); + const [floatingScrollLeft, setFloatingScrollLeft] = useState(0); + // Live, measured column widths in current visual order. Filled by + // the ResizeObserver below from the real thead's cells. Falls + // back to the stored `columns[i].width` when measurement isn't + // available yet (first paint, or the table isn't yet in the DOM). + const [floatingColWidths, setFloatingColWidths] = useState([]); + const [floatingTableWidth, setFloatingTableWidth] = useState(0); + + useEffect(() => { + const wrapper = tableWrapperRef.current; + if (!wrapper) return; + const onScroll = () => setFloatingScrollLeft(wrapper.scrollLeft); + wrapper.addEventListener("scroll", onScroll, { passive: true }); + onScroll(); + return () => wrapper.removeEventListener("scroll", onScroll); + }, []); + + useEffect(() => { + const wrapper = tableWrapperRef.current; + if (!wrapper) return; + const measure = () => { + const table = wrapper.querySelector("table"); + if (!table) return; + // The actual is display:none in screen mode (only the + // floating thead is visible), so we measure column widths from + // the first regular tbody row whose td-count matches the + // visible-column count. Subheading / placeholder / colspan + // rows are skipped. + const rows = Array.from(table.querySelectorAll("tbody > tr")); + const sample = rows.find((r) => r.children.length === visibleColumns.length); + if (sample) { + const widths = Array.from(sample.children).map( + (td) => (td as HTMLElement).getBoundingClientRect().width, + ); + setFloatingColWidths(widths); + } + setFloatingTableWidth(table.getBoundingClientRect().width); + }; + measure(); + const ro = new ResizeObserver(measure); + ro.observe(wrapper); + const table = wrapper.querySelector("table"); + if (table) ro.observe(table); + window.addEventListener("resize", measure); + return () => { + ro.disconnect(); + window.removeEventListener("resize", measure); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [orderedMeetings.length, visibleColumns.length]); + return (
)} - {/* #267: drop the horizontal scroll container on ≥xl so the - inner thead can use position: sticky against the viewport. - Below xl the table needs overflow-x-auto for narrow screens - (it'd otherwise overflow the page); the column header - gracefully degrades to non-sticky there. The page header and - schedule heading row remain sticky at every width. */} + {/* #267: Floating sticky thead — overlays the column header at + the top of the viewport when the schedule scrolls, with a + translateX kept in sync with the data table's horizontal + scrollLeft so the columns stay aligned. Hidden in print + mode; the actual below carries the labels for + paginated output. */}
+
+ + + {visibleColumns.map((c, i) => ( + + ))} + + + + + c.id)} + strategy={horizontalListSortingStrategy} + > + {visibleColumns.map((c, idx) => ( + + setColumnWidth(c.id, c.width + delta) + } + selectAllOverlay={ + idx === 0 && + effectiveCanMutate && + orderedMeetings.length > 0 ? ( +
e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} + > + + setAllMeetingsSelected(v === true) + } + aria-label={t( + "executiveMeetings.schedule.bulkSelectAll", + )} + data-testid="em-bulk-select-all" + className="h-4 w-4 bg-white shadow-sm" + /> +
+ ) : undefined + } + /> + ))} +
+ + + +
+
+
+ +
))} - - - - c.id)} - strategy={horizontalListSortingStrategy} - > - {visibleColumns.map((c, idx) => ( - setColumnWidth(c.id, c.width + delta)} - selectAllOverlay={ - // Tri-state "select all" lives in the first - // visible header (typically # but falls back - // gracefully when the user has hidden #) and - // only renders in edit mode with ≥1 row to - // act on. - idx === 0 && - effectiveCanMutate && - orderedMeetings.length > 0 ? ( -
e.stopPropagation()} - onPointerDown={(e) => e.stopPropagation()} - > - - setAllMeetingsSelected(v === true) - } - aria-label={t( - "executiveMeetings.schedule.bulkSelectAll", - )} - data-testid="em-bulk-select-all" - className="h-4 w-4 bg-white shadow-sm" - /> -
- ) : undefined - } - /> - ))} -
- - - + {/* #267: hidden in screen mode (the floating sticky thead + above is the visible/interactive one); kept available + in print mode so paginated output still shows column + labels. Plain + + {visibleColumns.map((c) => { + const align = + c.id === "number" || c.id === "time" + ? "text-center" + : "text-start"; + return ( + + ); + })} + + { await pool.end(); }); -test("Schedule: page header, heading row, and column header all stick to the top of the viewport on scroll", async ({ +// The floating sticky thead renders before the (display:none) actual +// thead in the DOM, so .first() reliably targets the visible one. +function visibleColHeader(page, colId) { + return page.getByTestId(`em-col-header-${colId}`).first(); +} + +async function assertStickyAtTop(page, { allowFloatingY = 4 } = {}) { + const header = page.getByTestId("em-page-header"); + const heading = page.getByTestId("em-schedule-heading-bar"); + const colHeader = visibleColHeader(page, "meeting"); + + await expect(header).toBeVisible(); + await expect(heading).toBeVisible(); + await expect(colHeader).toBeVisible(); + + const headerBox = await header.boundingBox(); + const headingBox = await heading.boundingBox(); + const colBox = await colHeader.boundingBox(); + expect(headerBox).not.toBeNull(); + expect(headingBox).not.toBeNull(); + expect(colBox).not.toBeNull(); + + // Page header is flush with the viewport top. + expect(headerBox.y).toBeGreaterThanOrEqual(-1); + expect(headerBox.y).toBeLessThanOrEqual(1); + + // Heading row sits directly below the page header. + const expectedHeadingY = headerBox.y + headerBox.height; + expect(headingBox.y).toBeGreaterThanOrEqual(expectedHeadingY - 2); + expect(headingBox.y).toBeLessThan(expectedHeadingY + 4); + + // Column header sits directly below the heading row — the + // critical assertion: it actually stuck instead of scrolling + // away with the table. + const expectedColY = headingBox.y + headingBox.height; + expect(colBox.y).toBeGreaterThanOrEqual(expectedColY - 2); + expect(colBox.y).toBeLessThan(expectedColY + allowFloatingY); +} + +test("Schedule [desktop, LTR]: page header, heading row, and column header all stick to the top of the viewport on scroll", async ({ page, }) => { await setLangEn(page); const date = uniqueFutureDate(2); const seededIds = []; - // Seed 30 meetings on the same date so the schedule is comfortably - // taller than any reasonable viewport. for (let i = 1; i <= 30; i++) { const hh = String(7 + Math.floor(i / 4)).padStart(2, "0"); const mm = String((i % 4) * 15).padStart(2, "0"); @@ -112,74 +151,22 @@ test("Schedule: page header, heading row, and column header all stick to the top await page.setViewportSize({ width: 1280, height: 720 }); await page.goto("/executive-meetings"); - // Jump to the seeded date. const dateInput = page.locator('input[type="date"]').first(); await dateInput.fill(date); - // Wait for our rows to render. - const firstRow = page.getByTestId(`em-row-${seededIds[0]}`); - await expect(firstRow).toBeVisible({ timeout: 10_000 }); + await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({ + timeout: 10_000, + }); - const header = page.getByTestId("em-page-header"); - const heading = page.getByTestId("em-schedule-heading-bar"); - // Use the meeting-title column header — guaranteed to exist as long - // as the column hasn't been hidden by the user (we just reset prefs - // by reload, so all default columns are visible). - const colHeader = page.getByTestId("em-col-header-meeting"); - - await expect(header).toBeVisible(); - await expect(heading).toBeVisible(); - await expect(colHeader).toBeVisible(); - - // Baseline geometry BEFORE scroll: header is at y=0 already (it's - // sticky from the start), heading is directly underneath, col - // header is below the heading row. - const headerBox0 = await header.boundingBox(); - const headingBox0 = await heading.boundingBox(); - const colBox0 = await colHeader.boundingBox(); - expect(headerBox0).not.toBeNull(); - expect(headingBox0).not.toBeNull(); - expect(colBox0).not.toBeNull(); - - // Now scroll way down. With 30 rows + spacing the body is well - // over the viewport. await page.evaluate(() => window.scrollTo(0, 1500)); - // Give the browser a frame to settle the sticky positions. - await page.waitForTimeout(150); + await page.waitForTimeout(200); - const headerBox = await header.boundingBox(); - const headingBox = await heading.boundingBox(); - const colBox = await colHeader.boundingBox(); - expect(headerBox).not.toBeNull(); - expect(headingBox).not.toBeNull(); - expect(colBox).not.toBeNull(); - - // Header should still be flush with the viewport top. - expect(headerBox.y).toBeGreaterThanOrEqual(-1); - expect(headerBox.y).toBeLessThanOrEqual(1); - - // Heading row sits directly below the header. - const expectedHeadingY = headerBox.y + headerBox.height; - expect(Math.abs(headingBox.y - expectedHeadingY)).toBeLessThanOrEqual(2); - - // Column header sits directly below the heading row. - const expectedColY = headingBox.y + headingBox.height; - expect(colBox.y).toBeGreaterThanOrEqual(expectedColY - 2); - // And critically, the column header is anchored near the top of - // the viewport — i.e. it actually stuck instead of scrolling away. - // (If sticky failed inside the overflow-x-auto wrapper, colBox.y - // would have been negative or far below the heading row.) - expect(colBox.y).toBeLessThan(300); + await assertStickyAtTop(page); }); -test("Schedule [ar/RTL]: page header and schedule heading still stick to the top after scroll", async ({ +test("Schedule [desktop, RTL]: page header, heading row, and column header all stick after scroll in Arabic", async ({ page, }) => { - // Force Arabic so the page renders RTL — confirms our sticky - // styling doesn't depend on writing direction. We deliberately - // assert only the two elements that stick at every viewport (the - // table column header degrades to non-sticky below xl, which is - // covered by the dedicated narrow-viewport scenario below). await page.addInitScript(() => { try { window.localStorage.setItem("tx-lang", "ar"); @@ -220,27 +207,14 @@ test("Schedule [ar/RTL]: page header and schedule heading still stick to the top expect(dir).toBe("rtl"); await page.evaluate(() => window.scrollTo(0, 1500)); - await page.waitForTimeout(150); + await page.waitForTimeout(200); - const headerBox = await page.getByTestId("em-page-header").boundingBox(); - const headingBox = await page.getByTestId("em-schedule-heading-bar").boundingBox(); - expect(headerBox).not.toBeNull(); - expect(headingBox).not.toBeNull(); - - expect(headerBox.y).toBeGreaterThanOrEqual(-1); - expect(headerBox.y).toBeLessThanOrEqual(1); - // Heading sits flush below the header in RTL too. - expect(headingBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height - 1); - expect(headingBox.y).toBeLessThan(headerBox.y + headerBox.height + 4); + await assertStickyAtTop(page); }); -test("Schedule: at narrow ( { - // Below the xl breakpoint (1280px) the table wrapper retains - // overflow-x-auto so the table can scroll horizontally on narrow - // screens. As a documented trade-off the column header doesn't - // stick there; the page header + schedule heading row still do. await setLangEn(page); const date = uniqueFutureDate(4); @@ -251,7 +225,7 @@ test("Schedule: at narrow ( window.scrollTo(0, 1500)); - await page.waitForTimeout(150); + await page.waitForTimeout(200); - const headerBox = await page.getByTestId("em-page-header").boundingBox(); - const headingBox = await page.getByTestId("em-schedule-heading-bar").boundingBox(); - expect(headerBox).not.toBeNull(); - expect(headingBox).not.toBeNull(); - - // Page header sticks. - expect(headerBox.y).toBeGreaterThanOrEqual(-1); - expect(headerBox.y).toBeLessThanOrEqual(1); - // Heading row sticks just under the page header. - expect(headingBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height - 1); - expect(headingBox.y).toBeLessThan(headerBox.y + headerBox.height + 4); + await assertStickyAtTop(page); +}); + +test("Schedule [mobile, 414px]: column header still sticks on a narrow phone-sized viewport", async ({ + page, +}) => { + await setLangEn(page); + + const date = uniqueFutureDate(5); + const seededIds = []; + for (let i = 1; i <= 20; i++) { + const hh = String(7 + Math.floor(i / 4)).padStart(2, "0"); + const mm = String((i % 4) * 15).padStart(2, "0"); + const id = await insertMeeting({ + meetingDate: date, + dailyNumber: i, + titleEn: `StickyTest Mobile ${i}`, + titleAr: `اجتماع جوال ${i}`, + startTime: `${hh}:${mm}:00`, + endTime: `${hh}:${mm}:00`, + }); + seededIds.push(id); + } + + await loginViaUi(page, "admin", "admin123"); + await page.setViewportSize({ width: 414, height: 800 }); + await page.goto("/executive-meetings"); + + const dateInput = page.locator('input[type="date"]').first(); + await dateInput.fill(date); + + await expect(page.getByTestId(`em-row-${seededIds[0]}`)).toBeVisible({ + timeout: 10_000, + }); + + await page.evaluate(() => window.scrollTo(0, 1500)); + await page.waitForTimeout(200); + + await assertStickyAtTop(page); });
s — no testids, no Sortable, no + checkbox — to avoid duplicating those with the floating + thead (which would break getByTestId in tests). */} +
+ {t(`executiveMeetings.col.${c.id}`)} +