#485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
column ran wider than the pink panel, the title column pushed it
off-screen, and the meeting-# header wrapped to two lines while
the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
right-most input against the dialog frame in RTL.
Both issues were pure layout — the dialog was simply too narrow for
its content.
Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
`sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
md:grid-cols-3` now has room to actually render three inputs at md
without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
`table-fixed` with an explicit `<colgroup>` (w-12 number / flex
title / w-40 times). Title cell keeps its `truncate` + `title=`
tooltip but no longer needs `max-w-[180px]`. Added `whitespace-nowrap`
to # and times headers so the latter never wraps. Wrapper got
`overflow-x-hidden` to belt-and-suspenders the no-horizontal-scroll
guarantee. Action button row already used `flex-wrap` — preserved.
Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a regression test that opens the dialog at 1280×800 in
Arabic, switches to the Reschedule tab, and asserts the bounding
boxes of `reschedule-date` / `reschedule-start` / `reschedule-end`
all sit inside the dialog's bounding box (1 px tolerance).
- Re-ran the full upcoming-alert spec to confirm no behavior
regressions in postpone-by-minutes, cascade prompts, cancel,
reschedule-to-tomorrow, dismiss, or the alert position-clamp test.
Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.
The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
This commit is contained in:
@@ -948,20 +948,29 @@ function CascadePromptBlock({
|
||||
{/* #481: 3-column table (Meeting #, Title, From → To) so the user
|
||||
can scan affected meetings by their meeting-of-the-day number
|
||||
alongside the title and the projected time shift. */}
|
||||
<div className="max-h-32 overflow-y-auto rounded bg-white/60 px-2 py-1 dark:bg-black/30">
|
||||
<div className="max-h-32 overflow-y-auto overflow-x-hidden rounded bg-white/60 px-2 py-1 dark:bg-black/30">
|
||||
<table
|
||||
className="w-full border-collapse text-xs"
|
||||
className="w-full table-fixed border-collapse text-xs"
|
||||
data-testid="cascade-followers-list"
|
||||
>
|
||||
{/* #485: explicit colgroup so the meeting-# header doesn't
|
||||
wrap to two lines, the title column truncates with an
|
||||
ellipsis instead of pushing the times off-screen, and
|
||||
the From → To column always fits on one line. */}
|
||||
<colgroup>
|
||||
<col className="w-12" />
|
||||
<col />
|
||||
<col className="w-40" />
|
||||
</colgroup>
|
||||
<thead className="text-muted-foreground">
|
||||
<tr>
|
||||
<th scope="col" className="w-16 px-1 py-1 text-start font-medium tabular-nums">
|
||||
<th scope="col" className="px-1 py-1 text-start font-medium tabular-nums whitespace-nowrap">
|
||||
{t("executiveMeetings.alert.cascadeColMeetingNumber")}
|
||||
</th>
|
||||
<th scope="col" className="px-1 py-1 text-start font-medium">
|
||||
{t("executiveMeetings.alert.cascadeColTitle")}
|
||||
</th>
|
||||
<th scope="col" className="px-1 py-1 text-end font-medium tabular-nums">
|
||||
<th scope="col" className="px-1 py-1 text-end font-medium tabular-nums whitespace-nowrap">
|
||||
{t("executiveMeetings.alert.cascadeColTimes")}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -979,7 +988,7 @@ function CascadePromptBlock({
|
||||
<td className="px-1 py-0.5 tabular-nums">
|
||||
{dn != null ? dn : "—"}
|
||||
</td>
|
||||
<td className="px-1 py-0.5 truncate max-w-[180px]" title={title}>
|
||||
<td className="px-1 py-0.5 truncate" title={title}>
|
||||
{title}
|
||||
</td>
|
||||
<td className="px-1 py-0.5 text-end tabular-nums whitespace-nowrap">
|
||||
@@ -1441,7 +1450,7 @@ function PostponeDialog({
|
||||
<DialogContent
|
||||
dir={isRtl ? "rtl" : "ltr"}
|
||||
data-testid="postpone-dialog"
|
||||
className="sm:max-w-md"
|
||||
className="sm:max-w-2xl"
|
||||
>
|
||||
<DialogHeader className="space-y-1">
|
||||
<DialogTitle className="text-base">
|
||||
|
||||
@@ -1290,3 +1290,43 @@ test("Upcoming-meeting alert: details panel renders attendees as numbered tables
|
||||
await expect(secondGroupRows.first()).toContainText("1.");
|
||||
await expect(secondGroupRows.first()).toContainText("Internal One");
|
||||
});
|
||||
|
||||
test("Postpone dialog: reschedule date / start / end inputs sit fully inside the dialog (no clipping at desktop width, RTL)", async ({
|
||||
page,
|
||||
}) => {
|
||||
// #485: regression for the layout overlap reported in Arabic. The
|
||||
// postpone dialog used to be sm:max-w-md (~448px) which clipped the
|
||||
// 3-column native date/time row against the dialog border in RTL.
|
||||
// After widening the dialog and tightening the column widths, every
|
||||
// input's bounding box must lie inside the dialog's bounding box.
|
||||
await setLang(page, "ar");
|
||||
await insertImminentMeeting({
|
||||
titleAr: `${TEST_TAG} layout AR`,
|
||||
titleEn: `${TEST_TAG} layout EN`,
|
||||
deltaMins: 3,
|
||||
});
|
||||
await page.setViewportSize({ width: 1280, height: 800 });
|
||||
await loginViaUi(page, "admin", "admin123");
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("upcoming-meeting-alert")).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await page.getByTestId("alert-postpone").click();
|
||||
const dialog = page.getByTestId("postpone-dialog");
|
||||
await expect(dialog).toBeVisible();
|
||||
await page.getByTestId("postpone-tab-reschedule").click();
|
||||
await expect(page.getByTestId("postpone-reschedule-panel")).toBeVisible();
|
||||
|
||||
const dialogBox = await dialog.boundingBox();
|
||||
expect(dialogBox).not.toBeNull();
|
||||
for (const tid of ["reschedule-date", "reschedule-start", "reschedule-end"]) {
|
||||
const inputBox = await page.getByTestId(tid).boundingBox();
|
||||
expect(inputBox, `${tid} should have a bounding box`).not.toBeNull();
|
||||
// Inputs must sit fully within the dialog (small 1px tolerance for
|
||||
// sub-pixel rounding from native controls).
|
||||
expect(inputBox.x + 1).toBeGreaterThanOrEqual(dialogBox.x);
|
||||
expect(inputBox.x + inputBox.width).toBeLessThanOrEqual(
|
||||
dialogBox.x + dialogBox.width + 1,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user