web: pin Server/Client Settings modal header while body scrolls#1753
Conversation
Expanding enough accordion sections grew the Server Settings modal past the
viewport, so the whole modal scrolled and carried the header (title + close)
out of view. The header was a custom Group inside the modal body, and the modal
used no `scrollAreaComponent`, so overflow was not confined to the body.
Switch both the Server Settings and Client Settings modals from the default
`<Modal>` (which always wraps children in `Modal.Body`) to the compound
`Modal.Root`/`Modal.Content`/`Modal.Header`/`Modal.Body` API, moving the
toggle/title/close row into `Modal.Header` (sticky by design) and setting
`scrollAreaComponent={ScrollArea.Autosize}` so only the body scrolls.
`Modal.Root` keeps the close-on-escape / close-on-click-outside / focus-trap /
scroll-lock / portal defaults; `transitionProps` restores the fade-down
animation the `<Modal>` wrapper otherwise supplies. Client Settings does not yet
have enough content to scroll but is kept consistent per the issue.
Closes #1698
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Two frames from the ServerSettingsModal story (all sections expanded, short viewport): header pinned at the top of the scroll and again scrolled down, showing it stays put while only the body scrolls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
@claude review |
|
Claude finished @cliffhall's task in 2m 0s —— View job Review: sticky Settings-modal headerRead both modal changes against What's good
Minor points (non-blocking)
Verdict: LGTM. The change is correct, minimal, convention-adherent, and the two minor points above are optional polish rather than blockers. |
Addresses the review note on PR #1753: the `transitionProps={{ transition: "fade-down", duration: 200 }}` literal was duplicated in both settings modals to restore the animation `<Modal>` defaults to but `Modal.Root` doesn't inherit. Move it to a `ThemeModalRoot` (`Modal.Root.extend`) registered under the `ModalRoot` theme key, mirroring the existing `ThemeScrollAreaAutosize` precedent. Both call sites drop the literal; any future compound `Modal.Root` now animates consistently with every plain `<Modal>` from one source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks for the thorough review! Responses to the three points:
|
|
@claude review |
|
Claude finished @cliffhall's task in 52s —— View job Re-review:
|
|
Thanks for the re-review — glad the theme approach landed cleanly. No further changes; ready to merge whenever. |
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>
Closes #1698
Problem
Expanding enough accordion sections in the Server Settings modal grew the modal past the viewport, so the whole modal scrolled and carried the header (title + close button) out of view.
Fix
The header was a custom
Groupinside the modal body, and the modal used noscrollAreaComponent, so overflow wasn't confined to the body. Switched both the Server Settings and Client Settings modals from the default<Modal>(which always wraps children inModal.Body) to the compoundModal.Root/Modal.Content/Modal.Header/Modal.BodyAPI:Modal.Header, which Mantine rendersposition: sticky; top: 0by design.scrollAreaComponent={ScrollArea.Autosize}confines overflow toModal.Body.Modal.Rootkeeps the close-on-escape / close-on-click-outside / focus-trap / scroll-lock / portal defaults;transitionPropsrestores thefade-downanimation the<Modal>wrapper otherwise supplies.Only props are used (no CSS classes / inline styles), per project conventions. Client Settings doesn't yet have enough content to scroll but is kept consistent per the issue.
Verification
Storybook
ServerSettingsModal(FullyConfigured, all sections expanded, ~680px viewport), driven via Playwright. Measured in-page: the body scroll container isscrollHeight2253 ×clientHeight612 (scrolls internally), and the header isposition: stickywith its top flush to the dialog top after scrolling.More detail in
pr-screenshots/README.md.Tests
Existing
ServerSettingsModal/ClientSettingsModalunit tests pass unchanged (51 tests); both files stay above the ≥90 coverage gate (ClientSettings 100%, ServerSettings ~98/100/97/98).npm run cipasses (validate → coverage → smoke → Storybook).🤖 Generated with Claude Code
https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5