Dialogs scroll inside themselves on a phone: nothing squeezed, clear of the status bar, buttons pinned

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-20 12:43:23 -07:00
co-authored by Claude Fable 5.1
parent a446a03b7d
commit a229f2b597
3 changed files with 32 additions and 10 deletions
+7 -5
View File
@@ -93,11 +93,13 @@ export default function StackNew({ mode, onClose, onCreated }: { mode: 'stack' |
<textarea className="mono" rows={2} value={env} onChange={(e) => setEnv(e.target.value)} placeholder={'SECRET=…'} spellCheck={false} />
</label>
</>}
{err && <div className="error" style={{ whiteSpace: 'pre-wrap' }}>{err}</div>}
<div className="svc-actions">
<Btn className="btn on" disabled={!okName || (how === 'form' && !form.image.trim())} onClick={create(true)}>Create and deploy</Btn>
<Btn className="btn" disabled={!okName || (how === 'form' && !form.image.trim())} onClick={create(false)}>Create only</Btn>
<span className="spacer" /><button className="btn" onClick={close}>Cancel</button>
<div className="modal-foot">
{err && <div className="error" style={{ whiteSpace: 'pre-wrap' }}>{err}</div>}
<div className="svc-actions">
<Btn className="btn on" disabled={!okName || (how === 'form' && !form.image.trim())} onClick={create(true)}>Create and deploy</Btn>
<Btn className="btn" disabled={!okName || (how === 'form' && !form.image.trim())} onClick={create(false)}>Create only</Btn>
<span className="spacer" /><button className="btn" onClick={close}>Cancel</button>
</div>
</div>
</>}
{err && !homes && <div className="error">{err}</div>}
+14 -5
View File
@@ -205,8 +205,17 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
/* power + modal */
.power { font-size: 15px; } .power.attn { color: var(--s2); }
.modal-back { position: fixed; inset: 0; z-index: 50; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; padding: 16px; }
.modal { width: min(420px, 100%); background: var(--bg2); border: 1px solid var(--border); border-radius: 10px; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
/* The backdrop keeps clear of the phone's status bar and home indicator; a dialog is never taller
than what is left, and scrolls inside itself. Its parts keep their height: in a column that
may shrink, anything with its own scrollbar (a preview, a list) is squeezed to a sliver
instead, and the dialog never scrolls at all. */
.modal-back { position: fixed; inset: 0; z-index: 50; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left)); }
.modal { --modal-bg: var(--bg2); width: min(420px, 100%); max-height: 100%; overflow-y: auto; overscroll-behavior: contain; background: var(--modal-bg); border: 1px solid var(--border); border-radius: 10px; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.modal > * { flex-shrink: 0; }
/* a long dialog's buttons (and what went wrong) stay in reach while the rest scrolls */
.modal:has(> .modal-foot) { padding-bottom: 0; }
.modal-foot { position: sticky; bottom: 0; z-index: 1; margin: 0 -16px; padding: 12px 16px 16px; background: var(--modal-bg); border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; }
.modal-foot .error { max-height: 30vh; overflow-y: auto; }
.modal h3 { margin: 0; font-size: 15px; }
.modal label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; }
.modal input { padding: 9px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; }
@@ -405,7 +414,7 @@ h2.section { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted);
.w-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; font-size: 13px; align-items: baseline; }
.w-row .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .w-row .meter { grid-column: 1 / 3; margin-top: 0; }
.tile.add-widget { border-style: dashed; color: var(--accent2); min-height: 72px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.modal.picker { width: min(520px, 100%); max-height: 90vh; }
.modal.picker { width: min(520px, 100%); }
.picker-list { overflow-y: auto; max-height: 34vh; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
.picker-row { display: flex; justify-content: space-between; gap: 10px; width: 100%; padding: 7px 10px; text-align: left; font-size: 12px; border-bottom: 1px solid var(--border); }
.picker-row:hover, .picker-row.active { background: var(--bg3); } .picker-row.active { box-shadow: inset 3px 0 0 var(--accent); }
@@ -452,7 +461,7 @@ a.btn { text-decoration: none; display: inline-flex; align-items: center; }
/* HUD: light drawn on glass — hairlines, no fills, thin type */
[data-theme="hud"] .tile, [data-theme="hud"] .card, [data-theme="hud"] .box { background: rgba(53, 214, 255, .03); border-color: rgba(53, 214, 255, .28); border-radius: 2px; box-shadow: 0 0 0 1px rgba(53, 214, 255, .05), inset 0 0 24px rgba(53, 214, 255, .04); }
/* anything that floats over the page is solid: glass on glass is two layers of text */
[data-theme="hud"] .modal { background: #061019; border-color: rgba(53, 214, 255, .45); border-radius: 2px; box-shadow: 0 0 0 1px rgba(53, 214, 255, .08), 0 0 40px rgba(53, 214, 255, .12), inset 0 0 24px rgba(53, 214, 255, .04); }
[data-theme="hud"] .modal { --modal-bg: #061019; border-color: rgba(53, 214, 255, .45); border-radius: 2px; box-shadow: 0 0 0 1px rgba(53, 214, 255, .08), 0 0 40px rgba(53, 214, 255, .12), inset 0 0 24px rgba(53, 214, 255, .04); }
[data-theme="hud"] .modal-back { background: rgba(0, 3, 6, .82); }
[data-theme="hud"] .tile::before, [data-theme="hud"] .card::before { content: ''; position: absolute; top: -1px; left: -1px; width: 12px; height: 12px; border-top: 2px solid var(--accent); border-left: 2px solid var(--accent); }
[data-theme="hud"] .tile, [data-theme="hud"] .card { position: relative; }
@@ -503,7 +512,7 @@ pre.diff .add { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%,
.env-masked { position: relative; display: flex; flex-direction: column; flex: 1; } .env-masked pre { margin: 0; min-height: 30vh; opacity: .8; } .env-masked .btn { position: absolute; top: 10px; right: 10px; }
/* new stack / new container */
.modal.stack-new { width: min(680px, 100%); max-height: 92vh; overflow-y: auto; }
.modal.stack-new { width: min(680px, 100%); }
.stack-new textarea, .stack-new select, .stack-new input:not([type=checkbox]) { padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 14px; width: 100%; box-sizing: border-box; }
.stack-new textarea.mono, .stack-new input.mono { font-family: var(--mono); font-size: 13px; } .stack-new textarea { resize: vertical; }
.stack-new .two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .stack-new .stack-editor { min-height: 32vh; }