From 4242b40f5ca892488e6a7857c86d5dc5974ea537 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 30 Mar 2026 13:01:56 +0800 Subject: [PATCH] feat: declutter mobile bottom sheets --- web/storyforge-web-v4/assets/app.js | 8 ++++++-- web/storyforge-web-v4/assets/styles.css | 6 ++++++ web/storyforge-web-v4/tests/workbench-pages.test.mjs | 9 +++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index e89ed6b..6de75f3 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -850,11 +850,13 @@ function openAuthModal() { const modal = document.querySelector(".auth-modal-backdrop"); if (!modal) return; const session = appState.session; + document.body.classList.add("sheet-open", "auth-sheet-open"); modal.classList.remove("hidden"); setAuthField("backendUrl", session?.backendUrl || DEFAULT_BACKEND_URL); } function closeAuthModal() { + document.body.classList.remove("sheet-open", "auth-sheet-open"); document.querySelector(".auth-modal-backdrop")?.classList.add("hidden"); } @@ -976,6 +978,7 @@ function openActionModal(config) { submit.textContent = config.submitLabel || "执行"; submit.disabled = false; submit.hidden = Boolean(config.hideSubmit); + document.body.classList.add("sheet-open", "action-sheet-open"); modal.classList.remove("hidden"); if (typeof config.onOpen === "function") { config.onOpen({ modal, title, description, fields, message, submit }); @@ -984,6 +987,7 @@ function openActionModal(config) { function closeActionModal() { currentActionConfig = null; + document.body.classList.remove("sheet-open", "action-sheet-open"); document.querySelector(".action-modal-backdrop")?.classList.add("hidden"); } @@ -1475,13 +1479,13 @@ function syncOneLinerRunPolling() { function openOneLinerPanel() { ensureOneLinerUi(); - document.body.classList.add("oneliner-open"); + document.body.classList.add("sheet-open", "oneliner-open"); document.querySelector(".oneliner-backdrop")?.classList.remove("hidden"); syncOneLinerRunPolling(); } function closeOneLinerPanel() { - document.body.classList.remove("oneliner-open"); + document.body.classList.remove("sheet-open", "oneliner-open"); document.querySelector(".oneliner-backdrop")?.classList.add("hidden"); clearOneLinerRunPollTimer(); } diff --git a/web/storyforge-web-v4/assets/styles.css b/web/storyforge-web-v4/assets/styles.css index 87d0c2a..9e1d0c4 100644 --- a/web/storyforge-web-v4/assets/styles.css +++ b/web/storyforge-web-v4/assets/styles.css @@ -1264,6 +1264,12 @@ select { transform: translateY(16px) scale(0.96); } +.sheet-open .mobile-tabbar { + opacity: 0; + pointer-events: none; + transform: translateY(calc(100% + env(safe-area-inset-bottom))); +} + .oneliner-fab-mark { width: 28px; height: 28px; diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 700eeb6..06d03f9 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -97,9 +97,14 @@ test("mobile shell removes duplicated desktop topbar and collapses the main agen test("mobile action sheets and oneliner runtime behave like bottom sheets", () => { assert.match(APP, /class="sheet-handle"/); - assert.match(APP, /document\.body\.classList\.add\("oneliner-open"\)/); - assert.match(APP, /document\.body\.classList\.remove\("oneliner-open"\)/); + assert.match(APP, /document\.body\.classList\.add\("sheet-open", "auth-sheet-open"\)/); + assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "auth-sheet-open"\)/); + assert.match(APP, /document\.body\.classList\.add\("sheet-open", "action-sheet-open"\)/); + assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "action-sheet-open"\)/); + assert.match(APP, /document\.body\.classList\.add\("sheet-open", "oneliner-open"\)/); + assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "oneliner-open"\)/); assert.match(CSS, /\.sheet-handle\s*\{/); + assert.match(CSS, /\.sheet-open \.mobile-tabbar\s*\{[\s\S]*opacity:\s*0/); assert.match(CSS, /\.oneliner-open \.oneliner-fab\s*\{[\s\S]*opacity:\s*0/); assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-modal,\s*[\s\S]*\.action-modal,\s*[\s\S]*\.oneliner-panel\s*\{[\s\S]*border-radius:\s*24px 24px 0 0/); assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-actions\s*\{[\s\S]*position:\s*sticky/);