diff --git a/artifacts/tx-os/src/components/ui/dialog.tsx b/artifacts/tx-os/src/components/ui/dialog.tsx index 105a9a33..fa003580 100644 --- a/artifacts/tx-os/src/components/ui/dialog.tsx +++ b/artifacts/tx-os/src/components/ui/dialog.tsx @@ -41,11 +41,23 @@ const DialogContent = React.forwardRef< const keyboardOpen = keyboardInset > 80 const dynamicStyle: React.CSSProperties = keyboardOpen ? { - // Center the dialog inside the *visible* visual viewport (above - // the keyboard) rather than the layout viewport. offsetTop - // accounts for the page being scrolled or pinch-zoomed. - top: `${offsetTop + height / 2}px`, - maxHeight: `${height - 16}px`, + // Anchor the dialog to the TOP of the visible visual viewport + // (right under the iOS status bar) and let it take the full + // available height above the keyboard. Anchoring (vs centering) + // is critical for tall forms — when the dialog is centered and + // the form has many fields, the footer slides into the middle + // of the visible area and the bottom half of the form ends up + // hidden behind the keyboard. With top-anchor + maxHeight, the + // dialog's own flex column (header / scrollable middle / + // footer) keeps Save/Cancel pinned above the keyboard and the + // rest of the form scrolls inside. + // + // The override of `transform` is necessary because the class + // string below applies `translate-y-[-50%]`; we need to keep + // the horizontal centering but cancel the vertical one. + top: `${offsetTop}px`, + maxHeight: `${height - 8}px`, + transform: "translateX(-50%)", ...style, } : (style ?? {})