feat(device): 로그인 뒤 ☰ 상태 위 줄 걷기 · ☰ 는 위 오른쪽 모서리에 떠 있음 · 진행 단계를 ☰ 서랍 묶음으로(같은 요소를 빌려 옴 · 한 벌) · 서랍 차례 로고 ─ 진행 단계 ─ 메뉴 ─ 설정 · 위 줄 높이 = --app-header-h 하나 · data-burger (기기대응 6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
+78
-27
@@ -66,19 +66,7 @@ function buildHeader(): HTMLElement {
|
||||
const header = document.createElement("header");
|
||||
header.className = "app-header";
|
||||
|
||||
// 로고 (클릭 시 홈)
|
||||
const brand = document.createElement("button");
|
||||
brand.className = "app-brand";
|
||||
brand.type = "button";
|
||||
const brandIcon = document.createElement("img");
|
||||
brandIcon.className = "app-brand__icon";
|
||||
brandIcon.src = BRAND_ICON_URL;
|
||||
brandIcon.alt = "";
|
||||
const brandName = document.createElement("span");
|
||||
brandName.className = "app-brand__name";
|
||||
brandName.textContent = L("App_BrandName");
|
||||
brand.append(brandIcon, brandName);
|
||||
brand.addEventListener("click", () => navigateTo(ROUTES.A01_HOME));
|
||||
const brand = buildBrand();
|
||||
|
||||
// 네비게이션 링크
|
||||
const nav = document.createElement("nav");
|
||||
@@ -111,6 +99,31 @@ function buildHeader(): HTMLElement {
|
||||
return header;
|
||||
}
|
||||
|
||||
/** 로고 (클릭 시 홈) — 위 줄 · ☰ 서랍 첫 묶음이 같이 씀 */
|
||||
function buildBrand(): HTMLButtonElement {
|
||||
const brand = document.createElement("button");
|
||||
brand.className = "app-brand";
|
||||
brand.type = "button";
|
||||
const brandIcon = document.createElement("img");
|
||||
brandIcon.className = "app-brand__icon";
|
||||
brandIcon.src = BRAND_ICON_URL;
|
||||
brandIcon.alt = "";
|
||||
const brandName = document.createElement("span");
|
||||
brandName.className = "app-brand__name";
|
||||
brandName.textContent = L("App_BrandName");
|
||||
brand.append(brandIcon, brandName);
|
||||
brand.addEventListener("click", () => navigateTo(ROUTES.A01_HOME));
|
||||
return brand;
|
||||
}
|
||||
|
||||
/* ☰ 서랍 진행 단계 묶음(6-2) — 프로젝트 화면이 진행 판에 그린 단계 목록 **그 요소**를 받아 둠.
|
||||
* 서랍을 열 때 서랍으로 옮기고 닫을 때 제자리(진행 판)로 — 그리기 · 누름 코드는 한 벌. */
|
||||
let drawerSteps: { node: HTMLElement; home: HTMLElement } | null = null;
|
||||
|
||||
export function setDrawerSteps(node: HTMLElement | null): void {
|
||||
drawerSteps = node?.parentElement ? { node, home: node.parentElement } : null;
|
||||
}
|
||||
|
||||
const NAV_ITEMS: [keyof typeof ui_locales, string][] = [
|
||||
["App_Nav_Program", ROUTES.A02_PROG_DETAIL],
|
||||
["App_Nav_Company", ROUTES.A03_COMP_DETAIL],
|
||||
@@ -146,20 +159,31 @@ function buildDrawer(header: HTMLElement): HTMLElement[] {
|
||||
drawer.setAttribute("aria-label", L("App_Menu"));
|
||||
const scrim = document.createElement("div");
|
||||
scrim.className = "app-drawer__scrim";
|
||||
const steps = document.createElement("div");
|
||||
steps.className = "app-drawer__group app-drawer__steps";
|
||||
const setOpen = (open: boolean): void => {
|
||||
header.classList.toggle("is-drawer-open", open);
|
||||
burger.setAttribute("aria-expanded", String(open));
|
||||
if (!drawerSteps) return;
|
||||
if (open) steps.append(drawerSteps.node);
|
||||
else if (steps.contains(drawerSteps.node)) drawerSteps.home.append(drawerSteps.node);
|
||||
};
|
||||
burger.addEventListener("click", () => setOpen(!header.classList.contains("is-drawer-open")));
|
||||
scrim.addEventListener("click", () => setOpen(false));
|
||||
drawer.addEventListener("keydown", (e) => e.key === "Escape" && setOpen(false));
|
||||
// 메뉴 · 로그인 항목을 누르면 닫힘(언어 · 테마 · 스위치는 그대로 둠)
|
||||
// 로고 · 진행 단계 · 메뉴 · 로그인 항목을 누르면 닫힘(언어 · 테마 · 스위치는 그대로 둠)
|
||||
drawer.addEventListener("click", (e) => {
|
||||
if ((e.target as Element).closest(".app-drawer__nav, .app-drawer__auth")) setOpen(false);
|
||||
const hit = (e.target as Element).closest("button");
|
||||
if (hit?.closest(".app-drawer__brand, .app-drawer__steps, .app-drawer__nav, .app-drawer__auth"))
|
||||
setOpen(false);
|
||||
});
|
||||
|
||||
// 묶음 차례 = 로고 · 홈 ─ 진행 단계(프로젝트 안) ─ 메뉴 ─ 언어 · 테마 · 로그인 · 터치 모드
|
||||
const brandRow = document.createElement("div");
|
||||
brandRow.className = "app-drawer__group app-drawer__brand";
|
||||
brandRow.append(buildBrand());
|
||||
const navList = document.createElement("nav");
|
||||
navList.className = "app-drawer__nav";
|
||||
navList.className = "app-drawer__group app-drawer__nav";
|
||||
navList.append(...buildNavButtons());
|
||||
const prefs = document.createElement("div");
|
||||
prefs.className = "app-drawer__row";
|
||||
@@ -178,7 +202,10 @@ function buildDrawer(header: HTMLElement): HTMLElement[] {
|
||||
const auth = document.createElement("div");
|
||||
auth.className = "app-drawer__row app-drawer__auth";
|
||||
void fillAuthActions(auth);
|
||||
drawer.append(navList, prefs, auth, buildTouchModeSwitch());
|
||||
const settings = document.createElement("div");
|
||||
settings.className = "app-drawer__group";
|
||||
settings.append(prefs, auth, buildTouchModeSwitch());
|
||||
drawer.append(brandRow, steps, navList, settings);
|
||||
return [burger, scrim, drawer];
|
||||
}
|
||||
|
||||
@@ -283,6 +310,7 @@ function buildFooter(): HTMLElement {
|
||||
* -------------------------------------------------------------------------- */
|
||||
export function renderShell(appRoot: HTMLElement, showFooter = true): HTMLElement {
|
||||
appRoot.innerHTML = "";
|
||||
drawerSteps = null; // 새 쪽이 진행 판을 세우면 다시 맡김
|
||||
const header = buildHeader();
|
||||
const outlet = document.createElement("main");
|
||||
outlet.className = "app-outlet";
|
||||
@@ -354,10 +382,10 @@ const SHELL_CSS = `
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
.app-outlet {
|
||||
min-height: calc(100dvh - 64px - 56px);
|
||||
min-height: calc(100dvh - var(--app-header-h) - 56px);
|
||||
}
|
||||
.app-outlet--without-footer {
|
||||
min-height: calc(100dvh - 64px);
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
}
|
||||
.app-footer {
|
||||
height: 56px;
|
||||
@@ -400,14 +428,12 @@ const SHELL_CSS = `
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.app-burger { display: inline-flex; }
|
||||
}
|
||||
html[data-touch="on"] .app-burger { display: inline-flex; }
|
||||
html[data-burger="on"] .app-burger { display: inline-flex; }
|
||||
.app-header.is-drawer-open { z-index: var(--z-overlay); }
|
||||
.app-header.is-drawer-open .app-drawer__scrim {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 64px 0 0 0;
|
||||
inset: var(--app-header-h) 0 0 0;
|
||||
background: rgba(38, 17, 74, 0.35);
|
||||
}
|
||||
.app-header.is-drawer-open .app-drawer {
|
||||
@@ -415,7 +441,7 @@ html[data-touch="on"] .app-burger { display: inline-flex; }
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
top: var(--app-header-h);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(320px, 100vw);
|
||||
@@ -436,12 +462,20 @@ html[data-touch="on"] .app-burger { display: inline-flex; }
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
.app-drawer__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
.app-drawer__group + .app-drawer__group {
|
||||
padding-top: var(--spacing-16);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.app-drawer__steps:empty { display: none; }
|
||||
.app-drawer__touch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
padding-top: var(--spacing-16);
|
||||
border-top: 1px solid var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
@@ -453,6 +487,23 @@ html[data-touch="on"] .app-burger { display: inline-flex; }
|
||||
html[data-size="compact"] .app-header { padding: 0 var(--spacing-16); gap: var(--spacing-8); }
|
||||
html[data-size="compact"] .app-actions { display: none; }
|
||||
html[data-size="compact"] .app-burger { margin-left: auto; }
|
||||
/* 로그인 뒤 쪽(B · M) + ☰ — 위 줄을 걷고 ☰ 만 화면 위 오른쪽 모서리에 띄움(6-1) · 본문이 화면 높이를 다 씀 */
|
||||
html.is-bm-route[data-burger="on"] .app-header {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
z-index: calc(var(--z-dropdown) + 1);
|
||||
}
|
||||
html.is-bm-route[data-burger="on"] .app-header > :is(.app-brand, .app-nav, .app-actions) {
|
||||
display: none;
|
||||
}
|
||||
html.is-bm-route[data-burger="on"] .app-burger {
|
||||
position: fixed;
|
||||
top: var(--spacing-8);
|
||||
right: var(--spacing-8);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
html.is-bm-route[data-burger="on"] .app-drawer__brand .app-brand { align-self: flex-start; }
|
||||
`;
|
||||
|
||||
export function injectShellStyles(): void {
|
||||
|
||||
@@ -174,7 +174,7 @@ const SCAFFOLD_CSS = `
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.b-scaffold-wf { height: calc(100dvh - 64px - 56px); }
|
||||
.b-scaffold-wf { height: calc(100dvh - var(--app-header-h) - 56px); }
|
||||
|
||||
.b-pending {
|
||||
display: flex;
|
||||
|
||||
@@ -83,3 +83,12 @@ html:is([data-size="compact"], [data-size="medium"])
|
||||
html:not([data-touch="on"]) .ui-touch-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 위 줄 높이(6-1) — 화면 높이 계산이 모두 이 값을 씀 · ☰ 가 메뉴를 맡는 로그인 뒤 쪽(B · M)은 위 줄을 걷어 0 */
|
||||
:root {
|
||||
--app-header-h: 64px;
|
||||
}
|
||||
|
||||
html.is-bm-route[data-burger="on"] {
|
||||
--app-header-h: 0px;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* - data-input = 마지막 누름의 pointerType(mouse · touch · pen)
|
||||
* - data-touch-mode = 사용자 스위치(auto · on · off · 이 기기 브라우저에만 저장)
|
||||
* - data-touch = on · off — 누를 자리 크기를 키울지(auto 면 주 입력 coarse 일 때)
|
||||
* - data-burger = on · off — ☰ 서랍이 메뉴를 맡는지(폭 900 이하 · 터치) — 로그인 뒤 쪽은 위 줄을 걷음(6-1)
|
||||
* 바뀔 때만 window 에 `app:context` 이벤트(detail = 지금 상태).
|
||||
* 경계 표는 여기 한 벌 — CSS 는 위 표시(data-size)로 가르고, 숫자가 필요하면 --bp-* 변수.
|
||||
* 첫 화면 깜빡임 — 화면은 모두 JS 가 그리므로 main.ts 가 맨 먼저 import 하면 그리기 전에 적힘.
|
||||
@@ -15,6 +16,8 @@
|
||||
import "./device_context.css";
|
||||
|
||||
export const SIZE_BREAKS = { medium: 600, expanded: 840, large: 1200 } as const;
|
||||
/** 위 메뉴가 숨는 폭(app_shell `@media (max-width: 900px)` 과 짝) — 이하면 ☰ 서랍 */
|
||||
export const BURGER_MAX = 900;
|
||||
export type SizeClass = "compact" | "medium" | "expanded" | "large";
|
||||
export type TouchMode = "auto" | "on" | "off";
|
||||
|
||||
@@ -24,6 +27,7 @@ export interface DeviceContext {
|
||||
input: string;
|
||||
touchMode: TouchMode;
|
||||
touch: boolean;
|
||||
burger: boolean;
|
||||
}
|
||||
|
||||
const TOUCH_MODE_KEY = "frd_touch_mode";
|
||||
@@ -54,12 +58,14 @@ let lastInput = "";
|
||||
function read(): DeviceContext {
|
||||
const pointer = coarseQuery.matches ? "coarse" : "fine";
|
||||
const touchMode = readTouchMode();
|
||||
const touch = touchMode === "on" || (touchMode === "auto" && pointer === "coarse");
|
||||
return {
|
||||
size: sizeClassOf(window.innerWidth),
|
||||
pointer,
|
||||
input: lastInput || (pointer === "coarse" ? "touch" : "mouse"),
|
||||
touchMode,
|
||||
touch: touchMode === "on" || (touchMode === "auto" && pointer === "coarse"),
|
||||
touch,
|
||||
burger: touch || window.innerWidth <= BURGER_MAX,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,6 +78,7 @@ function apply(): void {
|
||||
html.dataset.input = next.input;
|
||||
html.dataset.touchMode = next.touchMode;
|
||||
html.dataset.touch = next.touch ? "on" : "off";
|
||||
html.dataset.burger = next.burger ? "on" : "off";
|
||||
const first = !current;
|
||||
current = next;
|
||||
if (!first) window.dispatchEvent(new CustomEvent("app:context", { detail: next }));
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
min-height: calc(100dvh - var(--spacing-64));
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
}
|
||||
|
||||
.b04-surface__workspace > .b04-surface__status {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.b05-route-layout {
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
/* --- 좌측 입력 폼 --- */
|
||||
.b06-profile-layout {
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
/* 워크플로우 레이아웃 높이 (B06 패턴 준수) */
|
||||
.b07-design-layout {
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
padding: var(--spacing-16) var(--spacing-24);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
표 양식일 때는 페이지 쪽 모양 그대로 두려고 도면을 품은 때만 건다. */
|
||||
.m02-main:has(> .m02-main__host > .m02-drawing) {
|
||||
box-sizing: border-box;
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/* 카드가 보이면 상세 칸은 창 높이까지 — 표 + 카드가 넘치면 상세 칸 전체가 스크롤 */
|
||||
.m02-main:has(> .m02-main__extra > .m02-struct:not([hidden])) {
|
||||
box-sizing: border-box;
|
||||
max-height: calc(100dvh - var(--spacing-64));
|
||||
max-height: calc(100dvh - var(--app-header-h));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
/* ? 도움말 상자 — 카드 오른쪽 위(제목 줄 · 부제 아래 · 도면 · 엑셀 카드) */
|
||||
.m02-struct__card > .m02-struct__keys {
|
||||
top: calc(var(--spacing-64) + var(--spacing-8));
|
||||
top: calc(var(--app-header-h) + var(--spacing-8));
|
||||
}
|
||||
.m02-struct__keys {
|
||||
position: absolute;
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
/* 면적산출 · 재료산출 전체 편집 — `.ss` 는 height:100% 로 부모 높이를 받음 · 도면 편집과 같이 메인 칸을 화면 높이로 채움 */
|
||||
.m02-main:has(> .m02-main__host > .ss) {
|
||||
box-sizing: border-box;
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
/* 일위대가 상세(85-5) — 면적 상세와 같은 고정 높이 · 본문 칸만 굴림 · 오른쪽 패널은 칸 높이(따로 스크롤) */
|
||||
.m02-main:has(> .m02-main__host > .ui-side-panels-row) {
|
||||
box-sizing: border-box;
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--app-header-h));
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ AUDIT_JS = r"""
|
||||
}
|
||||
out.pointer = matchMedia('(pointer: coarse)').matches ? 'coarse' : 'fine';
|
||||
out.rendered = !!(outlet && outlet.childElementCount);
|
||||
out.outlet_top = outlet ? Math.round(outlet.getBoundingClientRect().top) : null; // 본문 위 끝(위 줄 걷힘 = 0 · 6-3)
|
||||
return out;
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""☰ 서랍 · 위 줄 걷기(PLAN_기기대응 6 · `app_shell.ts` · `ui_template_workflow_layout.ts`) — 실제 브라우저.
|
||||
|
||||
① 휴대폰 · 로그인 뒤 B 쪽 = 위 줄 걷힘(본문 위 끝 0) · ☰ 는 위 오른쪽 모서리 · 떠 있는 진행 판 없음
|
||||
② ☰ 서랍 = 로고 · 홈 ─ 진행 단계 ─ 메뉴 ─ 언어 · 테마 · 로그인 · 터치 모드 차례 · 진행 단계는 진행 판의 **그 요소**(한 벌)
|
||||
지금 단계 강조 그대로 · 닫으면 제자리(진행 판)로 돌아감 · 단계 누름 = 이동 + 서랍 닫힘
|
||||
③ 휴대폰 · 공개 쪽(A01) = 위 줄 그대로(본문 위 끝 64)
|
||||
④ 데스크탑(1440 · 마우스) = 위 줄 · 진행 판 그대로 · ☰ 없음
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import test_device_audit as audit # noqa: E402
|
||||
|
||||
OUTLET_TOP = "Math.round(document.getElementById('app-outlet').getBoundingClientRect().top)"
|
||||
GROUPS = "[...document.querySelectorAll('.app-drawer > *')].map(e => e.className)"
|
||||
|
||||
|
||||
def _ctx(browser, cred, size):
|
||||
spec = audit.SIZES[size]
|
||||
ctx = browser.new_context(
|
||||
viewport=spec["viewport"], has_touch=spec["touch"], is_mobile=spec["touch"]
|
||||
)
|
||||
audit._login(ctx, cred)
|
||||
pid = audit._project_id(ctx)
|
||||
ctx.add_init_script(f"localStorage.setItem('frd_current_project_id', {json.dumps(pid)})")
|
||||
return ctx
|
||||
|
||||
|
||||
@pytest.mark.skipif(not audit._server_up(), reason="sub1 화면 서버(5174) 꺼짐")
|
||||
def test_drawer_holds_steps_and_header_folds():
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
cred = json.loads((audit.ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(channel="msedge", headless=True)
|
||||
page = _ctx(browser, cred, "phone_390x844_touch").new_page()
|
||||
audit._open(page, "b07-design-detail")
|
||||
|
||||
# ①
|
||||
assert page.evaluate(OUTLET_TOP) == 0
|
||||
burger = page.evaluate(
|
||||
"(() => { const r = document.querySelector('.app-burger').getBoundingClientRect();"
|
||||
" return [Math.round(r.right), Math.round(r.top), Math.round(r.width)]; })()"
|
||||
)
|
||||
assert burger[0] >= 390 - 12 and burger[1] <= 12 and burger[2] >= 44, burger
|
||||
assert page.evaluate(
|
||||
"!document.querySelector('.ui-workflow-overlay__panel--progress').getClientRects().length"
|
||||
)
|
||||
|
||||
# ②
|
||||
page.evaluate(
|
||||
"window.__steps = document.querySelector('.ui-workflow-overlay__panel--progress .ui-workflow-layout__steps')"
|
||||
)
|
||||
page.evaluate("document.querySelector('.app-burger').click()")
|
||||
groups = page.evaluate(GROUPS)
|
||||
order = [
|
||||
next((k for k in ("brand", "steps", "nav", "touch") if k in c), "settings")
|
||||
for c in groups
|
||||
]
|
||||
assert order == ["brand", "steps", "nav", "settings"], groups
|
||||
assert page.evaluate(
|
||||
"document.querySelector('.app-drawer__steps').firstElementChild === window.__steps"
|
||||
)
|
||||
active = page.evaluate(
|
||||
"document.querySelector('.app-drawer__steps .ui-workflow-layout__step.is-active')?.textContent"
|
||||
)
|
||||
assert active and "상세" in active, active
|
||||
page.evaluate("document.querySelector('.app-drawer__scrim').click()") # 닫기 = 제자리
|
||||
assert page.evaluate(
|
||||
"window.__steps.closest('.ui-workflow-overlay__panel--progress') !== null"
|
||||
)
|
||||
page.evaluate("document.querySelector('.app-burger').click()")
|
||||
page.evaluate(
|
||||
"[...document.querySelectorAll('.app-drawer__steps .ui-workflow-layout__step.is-enabled')]"
|
||||
".find(b => b.textContent.includes('수량')).click()"
|
||||
)
|
||||
page.wait_for_function("location.hash.includes('b08-quantity')", timeout=10000)
|
||||
assert not page.evaluate(
|
||||
"document.querySelector('.app-header')?.classList.contains('is-drawer-open')"
|
||||
)
|
||||
|
||||
# ③
|
||||
audit._open(page, "a01-home")
|
||||
assert page.evaluate(OUTLET_TOP) == 64
|
||||
|
||||
# ④
|
||||
desk = _ctx(browser, cred, audit.DESKTOP).new_page()
|
||||
audit._open(desk, "b07-design-detail")
|
||||
assert desk.evaluate(OUTLET_TOP) == 64
|
||||
assert not desk.evaluate("document.querySelector('.app-burger').getClientRects().length")
|
||||
assert desk.evaluate(
|
||||
"document.querySelector('.ui-workflow-overlay__panel--progress').getClientRects().length > 0"
|
||||
)
|
||||
browser.close()
|
||||
@@ -155,6 +155,6 @@ def test_항목_추가_창_카드_격자_표_접기():
|
||||
assert "grid-auto-rows: var(--m02-struct-card-h);" in css
|
||||
assert "--m02-struct-card-h: min(640px, 70vh);" in css # 31-6 화면 높이에 맞춤
|
||||
assert "flex: 1 0 var(--m02-struct-grid-min);" in css # 엑셀 격자가 카드 높이를 채움
|
||||
assert "max-height: calc(100dvh - var(--spacing-64));\n overflow-y: auto;" in css
|
||||
assert "max-height: calc(100dvh - var(--app-header-h));\n overflow-y: auto;" in css
|
||||
main = (m02 / "M02_MasterTemplete_UI_Main.ts").read_text("utf-8")
|
||||
assert "host.hidden = !host.hidden;" in main and 'title.classList.toggle("is-folded"' in main
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.ui-general-layout {
|
||||
min-height: calc(100dvh - var(--spacing-64));
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
padding: var(--spacing-40) var(--spacing-24) var(--spacing-48);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
.ui-workflow-overlay__panel {
|
||||
position: fixed;
|
||||
top: calc(var(--spacing-64) + var(--spacing-16));
|
||||
top: calc(var(--app-header-h) + var(--spacing-16));
|
||||
z-index: var(--z-dropdown);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100dvh - var(--spacing-64) - var(--spacing-32));
|
||||
max-height: calc(100dvh - var(--app-header-h) - var(--spacing-32));
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
/* title 패널: 헤더 아래 전체 높이 도킹 사이드바 (열림: 본문 밀기 / 닫힘: 좌측으로 슬라이드 아웃) */
|
||||
.ui-workflow-overlay__panel--title {
|
||||
top: var(--spacing-64);
|
||||
top: var(--app-header-h);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: min(var(--wf-left-panel-width), calc(100vw - var(--spacing-48)));
|
||||
@@ -303,9 +303,9 @@
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.ui-workflow-overlay__panel--progress {
|
||||
top: calc(var(--spacing-64) + var(--spacing-8));
|
||||
top: calc(var(--app-header-h) + var(--spacing-8));
|
||||
right: var(--spacing-8);
|
||||
max-height: calc(100dvh - var(--spacing-64) - var(--spacing-16));
|
||||
max-height: calc(100dvh - var(--app-header-h) - var(--spacing-16));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +525,7 @@
|
||||
}
|
||||
|
||||
/* 기기 대응(PLAN_기기대응 2-3) — 폭 840 미만(data-size compact · medium)만.
|
||||
좌측 패널 = 서랍(본문 위에 겹침 · 덮개를 누르면 닫힘) · 진행단계 판 = 아래에서 올라오는 판(뒤 화면 계속 조작). */
|
||||
좌측 패널 = 서랍(본문 위에 겹침 · 덮개를 누르면 닫힘). */
|
||||
.ui-workflow-overlay__scrim {
|
||||
display: none;
|
||||
}
|
||||
@@ -535,21 +535,15 @@ html:is([data-size="compact"], [data-size="medium"])
|
||||
> .ui-workflow-overlay__scrim {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: var(--spacing-64) 0 0 0;
|
||||
inset: var(--app-header-h) 0 0 0;
|
||||
z-index: calc(var(--z-dropdown) - 1);
|
||||
background: rgba(38, 17, 74, 0.35);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
html:is([data-size="compact"], [data-size="medium"]) .ui-workflow-overlay__panel--progress {
|
||||
top: auto !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
width: auto !important;
|
||||
max-height: 50dvh;
|
||||
flex-direction: column;
|
||||
border-radius: var(--radius-cards) var(--radius-cards) 0 0;
|
||||
/* ☰ 가 메뉴를 맡는 때(폭 900 이하 · 터치)는 떠 있는 진행 판 대신 ☰ 서랍 안 진행 단계 묶음(6-2) */
|
||||
html[data-burger="on"] .ui-workflow-overlay__panel--progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 접힌 서랍 — 화면 밖 내용은 안 보이게(손가락 · 읽기 프로그램이 밖의 단추를 잡지 않음) · 손잡이만 남김 */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.ui-workflow-layout {
|
||||
position: relative;
|
||||
min-height: calc(100dvh - var(--spacing-64));
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
|
||||
.ui-workflow-layout__body {
|
||||
min-height: calc(100dvh - var(--spacing-64));
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
transition: padding-left var(--transition-base);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
.ui-workflow-layout__main {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: calc(100dvh - var(--spacing-64));
|
||||
min-height: calc(100dvh - var(--app-header-h));
|
||||
overflow: auto;
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ROUTES } from "@config/config_frontend";
|
||||
import { t } from "./ui_template_locale";
|
||||
import { createWorkflowOverlays } from "./ui_template_overlay";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { setDrawerSteps } from "../A00_Common/app_shell";
|
||||
import { fetchDashboardMe } from "../B01_Dashboard/B01_Dashboard_Api_Fetch";
|
||||
|
||||
let dashboardRolePromise: Promise<string> | undefined;
|
||||
@@ -198,6 +199,8 @@ export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLa
|
||||
});
|
||||
|
||||
root.append(body, overlays.root);
|
||||
// ☰ 서랍 진행 단계 묶음(6-2) — 같은 단계 목록을 서랍이 열릴 때 빌려 감(그리기 · 누름 한 벌)
|
||||
setDrawerSteps(progressContent);
|
||||
const steps = [
|
||||
...progressContent.querySelectorAll<HTMLElement>(
|
||||
".ui-workflow-layout__step:not(.ui-workflow-layout__step--home)",
|
||||
|
||||
Reference in New Issue
Block a user