web: pin Server/Client Settings modal header while body scrolls (#1753)

* web: pin Server/Client Settings modal header while body scrolls (#1698)

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

* docs(pr-screenshots): Server Settings modal sticky-header proof (#1698)

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

* web: move Modal.Root fade-down transition into the theme (#1698 review)

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-07-23 19:57:03 -04:00 • committed by GitHub
commit 092165476e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,6 +13,7 @@ import {
ThemeGroup,
ThemeInput,
ThemeModal,
ThemeModalRoot,
ThemePaper,
ThemeScrollArea,
ThemeScrollAreaAutosize,
@ -194,6 +195,7 @@ export const theme = createTheme({
Group: ThemeGroup,
Input: ThemeInput,
Modal: ThemeModal,
ModalRoot: ThemeModalRoot,
Paper: ThemePaper,
ScrollArea: ThemeScrollArea,
ScrollAreaAutosize: ThemeScrollAreaAutosize,