web: pin Connection Info modal header while body scrolls (#1754) (#1755)

Same fix as #1698, applied to the Connection Info modal, which had the same
pattern: a custom header Group (title + close) inside a Stack in the modal body,
using the default `<Modal>` with no `scrollAreaComponent`. With a long server
`instructions` section (e.g. the everything server) the details overflow the
viewport and the whole modal scrolled, taking the header out of view.

Switch to the compound `Modal.Root`/`Content`/`Header`/`Body` API, move the
header into `Modal.Header` (sticky by design) and set
`scrollAreaComponent={ScrollArea.Autosize}` so only the body scrolls. The
fade-down transition is supplied app-wide by `ThemeModalRoot` (added in #1753),
so no `transitionProps` literal is needed here.

Closes #1754


Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-07-23 22:08:04 -04:00 • committed by GitHub
commit e788bcbaca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 21 deletions

View file

@ -1,4 +1,4 @@
import { CloseButton, Group, Modal, Stack } from "@mantine/core";
import { CloseButton, Group, Modal, ScrollArea } from "@mantine/core";
import type {
ClientCapabilities,
DiscoverResult,
@ -35,30 +35,40 @@ export function ConnectionInfoModal({
onClearOAuth,
}: ConnectionInfoModalProps) {
return (
<Modal
// Compound Modal so the header lives in `Modal.Header` (sticky by design)
// while `scrollAreaComponent` confines overflow to `Modal.Body` — otherwise
// a long connection-info payload grows the whole modal past the viewport and
// scrolls the header out of view (#1754, same fix as the settings modals in
// #1698). The fade-down transition is supplied app-wide by `ThemeModalRoot`.
<Modal.Root
opened={opened}
onClose={onClose}
withCloseButton={false}
size="lg"
centered
scrollAreaComponent={ScrollArea.Autosize}
>
<Stack gap="md">
<Group justify="space-between" wrap="nowrap">
{/* `Modal.Title` (not a bare `Title`) registers the modal's
accessible name — it wires the dialog's `aria-labelledby`. */}
<Modal.Title flex={1}>Connection Info</Modal.Title>
<CloseButton aria-label="Close" onClick={onClose} />
</Group>
<ConnectionInfoContent
initializeResult={initializeResult}
clientCapabilities={clientCapabilities}
transport={transport}
protocolEra={protocolEra}
discoverResult={discoverResult}
oauth={oauth}
onClearOAuth={onClearOAuth}
/>
</Stack>
</Modal>
<Modal.Overlay />
<Modal.Content>
<Modal.Header>
<Group justify="space-between" wrap="nowrap" w="100%">
{/* `Modal.Title` (not a bare `Title`) registers the modal's
accessible name — it wires the dialog's `aria-labelledby`. */}
<Modal.Title flex={1}>Connection Info</Modal.Title>
<CloseButton aria-label="Close" onClick={onClose} />
</Group>
</Modal.Header>
<Modal.Body>
<ConnectionInfoContent
initializeResult={initializeResult}
clientCapabilities={clientCapabilities}
transport={transport}
protocolEra={protocolEra}
discoverResult={discoverResult}
oauth={oauth}
onClearOAuth={onClearOAuth}
/>
</Modal.Body>
</Modal.Content>
</Modal.Root>
);
}

View file

@ -1,3 +1,25 @@
# Connection Info modal: sticky header while body scrolls (#1754) — proof screenshots
Same fix as #1698, applied to the **Connection Info** modal. With a real server
whose `instructions` are long (e.g. the everything server in a typical config),
the connection details overflow the viewport; before the fix the whole modal
scrolled and the header left the view. Captured from the `ConnectionInfoModal`
Storybook story (`WithOAuth`, ~620px viewport) via Playwright.
![Connection Info scrolled to the top — header pinned, Server Implementation visible](ci-1754-header-pinned-top.png)
Top of scroll: the **Connection Info** header (title + close) sits above the
first section.
![Connection Info scrolled down — same header still pinned while the body shows Server Instructions / OAuth Details](ci-1754-header-pinned-scrolled.png)
Scrolled down (now showing Server Instructions → OAuth Details): the **same
header stays pinned**. Measured in-page: the body scroll container is
`scrollHeight` 1172 × `clientHeight` 558 (scrolls internally), header
`position: sticky` flush to the dialog top.
---
# Server/Client Settings modal: sticky header while body scrolls (#1698) — proof screenshots
Expanding enough accordion sections grew the Server Settings modal past the

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB