diff --git a/B08_DesignDetail/B08_DesignDetail_UI_Page.ts b/B08_DesignDetail/B08_DesignDetail_UI_Page.ts index dc4ae70a..aea0f7e7 100644 --- a/B08_DesignDetail/B08_DesignDetail_UI_Page.ts +++ b/B08_DesignDetail/B08_DesignDetail_UI_Page.ts @@ -510,9 +510,9 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise { ); drawingListEl = drawingPanel; const confirmActions = document.createElement("div"); - // b08-drawing-actions는 자체 sticky 하단 고정(B08 Style.css) — 공용 ui-sidebar-actions와 - // 같은 동작이라 중복 적용하지 않는다(2026-08-05 확인). - confirmActions.className = "b08-drawing-actions"; + // 하단 고정은 공용 ui-sidebar-actions로 통일 — 사이드 본문이 [스크롤 영역][액션 줄]로 + // 쪼개지고 액션 줄은 스크롤 밖에 남는다(2026-08-18 사용자 지시, B04~B08 공통). + confirmActions.className = "b08-drawing-actions ui-sidebar-actions"; confirmActions.append(confirmButton); drawingPanel.append(infoPanelHost, confirmActions); diff --git a/B08_DesignDetail/B08_DesignDetail_UI_Style.css b/B08_DesignDetail/B08_DesignDetail_UI_Style.css index d5d8d0fa..215a44a9 100644 --- a/B08_DesignDetail/B08_DesignDetail_UI_Style.css +++ b/B08_DesignDetail/B08_DesignDetail_UI_Style.css @@ -113,12 +113,10 @@ color: var(--color-success); } +/* 하단 고정·배경·상단 구분선은 공용 ui-sidebar-actions가 맡는다(2026-08-18 통합). + 여기서는 B08 고유 여백만 남긴다. */ .b08-drawing-actions { - position: sticky; - bottom: 0; - margin-top: auto; padding-top: var(--spacing-12); - background: var(--color-surface-raised); } .b08-drawing-actions > button { diff --git a/ui_template/ui_template_overlay.css b/ui_template/ui_template_overlay.css index 5f01514a..4c783fd6 100644 --- a/ui_template/ui_template_overlay.css +++ b/ui_template/ui_template_overlay.css @@ -232,31 +232,48 @@ } } -/* 사이드 패널 최하단 고정 액션 줄(계산·확정 버튼) — 스크롤에서 제외된다. - B04·B05·B06·B07 공통(2026-08-05 사용자 지시). 스크롤로 지나가는 다른 버튼·내용이 - 틈으로 노출되지 않게 불투명 배경 + 사이드 body의 하단 패딩(16px)까지 덮는다. */ +/* 사이드 패널 최하단 고정 액션 줄(계산·확정 버튼) — B04~B08 공통(2026-08-05 사용자 + 지시). 스크롤 영역(.ui-sidebar-scroll) 바깥의 형제라 자리가 흔들리지 않는다 + (2026-08-18 개편 — sticky로만 붙이던 때는 본문 높이가 패널 높이를 넘나들 때마다 + 붙었다 떨어졌다 했다). 스크롤로 지나가는 내용이 틈으로 비치지 않게 배경은 불투명. */ .ui-sidebar-actions { - position: sticky; - bottom: calc(-1 * var(--spacing-16)); - z-index: 5; + flex: none; display: flex; gap: var(--spacing-8); - /* 내용이 패널보다 짧아도 액션 줄은 패널 최하단에 붙는다 — 위 여백을 이 줄이 - 전부 먹는다(2026-08-18 사용자 지시, B04~B08 공통). 내용이 길면 auto가 0이 돼 - 기존처럼 sticky로 따라온다. */ - margin-top: auto; padding: var(--spacing-8) 0 var(--spacing-16); background: var(--color-surface-raised); border-top: 1px solid var(--color-border); } -/* margin-top:auto가 먹히려면 액션 줄을 담은 사이드 body가 세로 플렉스여야 하고 - 높이가 정해져 있어야 한다(--panel--title의 body는 flex:1로 이미 정해진다). - B04·B05·B06은 페이지 CSS가 이미 세로 플렉스지만, 규칙 없이 블록으로 남은 - 패널(B07)도 같이 걸리게 여기서 공통으로 준다. */ -.ui-workflow-overlay__body:has(> .ui-sidebar-actions) { +/* 액션 줄이 있는 사이드 body는 [스크롤 영역][액션 줄] 두 칸(overlay.ts가 래퍼를 + 끼운다). body 자신은 스크롤하지 않는다 — 스크롤은 안쪽 래퍼 몫. */ +.ui-workflow-overlay__body.ui-sidebar-split { display: flex; flex-direction: column; + overflow: hidden; + /* 액션 줄이 바닥까지 내려온다 — 아래 여백은 액션 줄 자신의 패딩(16px)이 만든다. */ + padding-bottom: 0; +} + +/* 마진·패딩 없는 순수 스크롤 래퍼. 그룹 간격(gap)은 페이지가 body에 준 값을 그대로 + 물려받아 기존 간격이 유지된다. */ +.ui-sidebar-split > .ui-sidebar-scroll { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + gap: inherit; + margin: 0; + padding: 0; + overflow-y: scroll; + scrollbar-gutter: stable; +} + +/* 스크롤은 안쪽 래퍼가 맡으므로 body 자신의 상시 스크롤(위 --panel--title 규칙)은 + 끈다. 오른쪽 패딩 보정(-8px)은 그대로 둔다 — 래퍼가 그 폭 안에서 스크롤바 자리를 + 예약하므로 내용 폭은 개편 전과 같다. */ +.ui-workflow-overlay__panel--title > .ui-workflow-overlay__body.ui-sidebar-split { + overflow-y: hidden; } .ui-sidebar-actions > * { diff --git a/ui_template/ui_template_overlay.ts b/ui_template/ui_template_overlay.ts index 1faed533..a066a22c 100644 --- a/ui_template/ui_template_overlay.ts +++ b/ui_template/ui_template_overlay.ts @@ -81,6 +81,25 @@ function readOpenState(key: string): boolean { return sessionStorage.getItem(key) !== "false"; } +/** + * 사이드 패널 본문을 [스크롤 영역][고정 액션 영역] 두 칸으로 쪼갠다. + * + * 액션 줄(`.ui-sidebar-actions`)을 sticky로만 붙여 두면 본문 높이가 패널 높이를 + * 넘나들 때마다 자리가 흔들린다(2026-08-18 사용자 보고). 액션 줄을 스크롤 대상에서 + * 아예 빼고, 나머지 그룹만 마진·패딩 없는 래퍼(`.ui-sidebar-scroll`)에 담아 그쪽만 + * 스크롤시킨다. 페이지는 지금처럼 본문 마지막에 액션 줄을 붙이기만 하면 된다. + */ +function splitSidebarActions(body: HTMLElement): void { + const actions = body.querySelector(":scope > .ui-sidebar-actions"); + if (!actions) return; + const scroll = document.createElement("div"); + scroll.className = "ui-sidebar-scroll"; + // 액션 줄 앞의 형제만 옮긴다 — 뒤에 붙은 요소(없어야 정상)는 그대로 둔다. + while (body.firstChild && body.firstChild !== actions) scroll.append(body.firstChild); + body.prepend(scroll); + body.classList.add("ui-sidebar-split"); +} + function createPanel( variant: "title" | "progress", titleText: string, @@ -115,6 +134,7 @@ function createPanel( } if (body.childElementCount > 0) { body.classList.add("ui-workflow-overlay__body"); + if (isSidebar) splitSidebarActions(body); root.append(body); }