fix: cap the re-auth banner width and move its flat CSS into the Paper theme (#2218)

`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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYcBw3Sftq5Yx4WpdzXttb
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-05 23:11:23 -04:00
commit 0a4e618cd3
3 changed files with 44 additions and 5 deletions

View file

@ -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(
<ReAuthBannerBar data-testid="bar">contents</ReAuthBannerBar>,
);
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(
<ReAuthBannerBar data-testid="bar">contents</ReAuthBannerBar>,
);
expect(screen.getByTestId("bar")).toHaveAttribute("data-variant", "reauth");
});
});

View file

@ -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 } },
});

View file

@ -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: {