From 0a4e618cd31f35452e70472a835bce23baa643d9 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 5 Sep 2026 23:11:23 -0400 Subject: [PATCH] fix: cap the re-auth banner width and move its flat CSS into the Paper theme (#2218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ReAuthBannerBar` set an unconditional `w: 420`. Because the banner is centered with `left: 50%` plus a `translate(-50%, -50%)`, a viewport narrower than 420px made it overflow *both* edges equally — clipping the close button on one side and "Authorize again" on the other, which are the only two controls it has. Losing both is a dead end rather than a cosmetic clip, since dismissing is not equivalent to re-authorizing ("Authorize again" also clears the stale OAuth state). It is reachable on a narrow desktop window too, not only a phone: the element is `fixed`-positioned against the viewport. The width is now capped rather than fixed — `maw={420}` with `w="calc(100vw - 2rem)"` — so the banner shrinks with a 1rem gutter on each side and the shadow and radius still read. The same constant also carried `transform` and `zIndex` as flat CSS in component-level `styles`. Mantine exposes neither as a style prop, but that argues for the next tier in the repo's preference order — a theme variant — not for inline `styles`. Both move to a `reauth` variant in `theme/Paper.ts`, alongside the existing `code`, `contained` and `panel` variants. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LYcBw3Sftq5Yx4WpdzXttb Signed-off-by: cliffhall --- .../ReAuthBanner/ReAuthBannerBar.test.tsx | 19 +++++++++++++++++++ .../groups/ReAuthBanner/ReAuthBannerBar.tsx | 19 ++++++++++++++----- clients/web/src/theme/Paper.ts | 11 +++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.test.tsx b/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.test.tsx index ec73fb18d5..a501078d1d 100644 --- a/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.test.tsx +++ b/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.test.tsx @@ -13,4 +13,23 @@ describe("ReAuthBannerBar", () => { expect(bar.style.transform).toBe("translate(-50%, -50%)"); expect(bar.style.zIndex).toBe("200"); }); + + it("caps its width instead of fixing it, so a narrow viewport cannot clip its controls", () => { + renderWithMantine( + contents, + ); + const bar = screen.getByTestId("bar"); + // Centered by `left: 50%` plus a -50% translate, so a width wider than the + // viewport would overflow both edges and clip the close button on one side + // and "Authorize again" on the other (#2218). + expect(bar.style.width).toBe("calc(100vw - 2rem)"); + expect(bar.style.maxWidth).toBe("calc(26.25rem * var(--mantine-scale))"); + }); + + it("takes its centering offset from the Paper theme rather than inline styles", () => { + renderWithMantine( + contents, + ); + expect(screen.getByTestId("bar")).toHaveAttribute("data-variant", "reauth"); + }); }); diff --git a/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.tsx b/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.tsx index be0b51ddc6..f8240e4b31 100644 --- a/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.tsx +++ b/clients/web/src/components/groups/ReAuthBanner/ReAuthBannerBar.tsx @@ -1,7 +1,10 @@ import { Paper } from "@mantine/core"; // The re-auth popup. A `Paper` so every static style is a prop; the stacking -// order goes through `styles.root` since Mantine has no `z` prop. +// order and the centering offset go through the `reauth` variant in +// `theme/Paper.ts`, since Mantine exposes neither `transform` nor `zIndex` as a +// style prop and flat CSS belongs in the theme layer rather than in inline +// `styles` (#2218). // // Floats rather than spanning the top as a sticky full-bleed bar. The bar cost // the whole view a band of vertical space for what is a notification about one @@ -24,15 +27,21 @@ import { Paper } from "@mantine/core"; // "Authorize again" also clears the stale OAuth state, which a plain reconnect // does not do. So it floats above the page and leaves it usable. // -// `transform` goes through `styles.root` for the same reason `zIndex` does: -// Mantine exposes neither as a style prop. +// The width is CAPPED at 420, not fixed at it. Because the banner is centered +// by `left: 50%` plus a -50% translate, a fixed width wider than the viewport +// overflows BOTH edges equally — clipping the close button on one side and +// "Authorize again" on the other, which are the only two controls it has. That +// is reachable on a narrow desktop window, not just a phone, since the element +// is positioned against the viewport rather than a panel. `maw` caps it while +// `w` keeps a 1rem gutter on each side so the shadow and radius still read. export const ReAuthBannerBar = Paper.withProps({ + variant: "reauth", pos: "fixed", top: "50%", left: "50%", - w: 420, + w: "calc(100vw - 2rem)", + maw: 420, bg: "var(--mantine-color-body)", shadow: "xl", radius: "md", - styles: { root: { transform: "translate(-50%, -50%)", zIndex: 200 } }, }); diff --git a/clients/web/src/theme/Paper.ts b/clients/web/src/theme/Paper.ts index 8170703e27..39d455cd05 100644 --- a/clients/web/src/theme/Paper.ts +++ b/clients/web/src/theme/Paper.ts @@ -25,6 +25,17 @@ export const ThemePaper = Paper.extend({ }, }; } + if (props.variant === "reauth") { + return { + root: { + // Centers the fixed-position re-auth banner against the viewport. + // Neither property is available as a Mantine style prop, so the + // theme layer is where they belong (#2218). + transform: "translate(-50%, -50%)", + zIndex: 200, + }, + }; + } if (props.variant === "panel") { return { root: {