diff --git a/B06_Section/B06_Section_UI_Cross_CutSlope.ts b/B06_Section/B06_Section_UI_Cross_CutSlope.ts index bc0e2232b..ac73fb9c8 100644 --- a/B06_Section/B06_Section_UI_Cross_CutSlope.ts +++ b/B06_Section/B06_Section_UI_Cross_CutSlope.ts @@ -105,7 +105,8 @@ export function buildCutSlopeControl(section: CrossSection, control: CutSlopeCon const reset = document.createElement("button"); reset.type = "button"; - reset.className = "b06-design__rockb-btn is-reset"; + reset.className = "ui-icon-btn ui-icon-btn--ghost b06-design__rockb-btn is-reset"; // 부품 아이콘 단추 22 + reset.setAttribute("data-size", "22"); reset.textContent = "↺"; reset.title = `표준값으로 되돌리기 (1:${standard.toFixed(2)} · ${ratioToDegrees(standard).toFixed(1)}°)`; reset.disabled = sameRatio(ratio, standard); diff --git a/B06_Section/B06_Section_UI_Cross_Design.ts b/B06_Section/B06_Section_UI_Cross_Design.ts index aa7638021..c66043b21 100644 --- a/B06_Section/B06_Section_UI_Cross_Design.ts +++ b/B06_Section/B06_Section_UI_Cross_Design.ts @@ -14,7 +14,10 @@ * 병합된다. * ========================================================================== */ +import { createButton, setUiSize } from "@ui/ui_template_button"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; +import { createSegment } from "@ui/ui_template_segment"; +import { createToggle } from "@ui/ui_template_toggle"; import type { CrossSection, DitchSide, @@ -156,25 +159,19 @@ function segment( legendEl.textContent = legend; wrap.append(legendEl); } - const group = document.createElement("div"); - group.className = "b06-design__seg-buttons"; - for (const [value, labelKey] of options) { - const button = document.createElement("button"); - button.type = "button"; - button.className = `b06-design__btn${value === selected ? " b06-design__btn--active" : ""}`; - button.textContent = L(labelKey); - button.setAttribute("aria-pressed", value === selected ? "true" : "false"); - // 선행 조건 미충족 시 비활성(상시 노출하되 클릭 불가·사유 툴팁) — E-6. - button.disabled = disabled; - if (disabled && reason) button.title = reason; - else button.addEventListener("click", () => onPick(value)); - group.append(button); - } + // 부품 분할 고르기 20(553-2) — 선행 조건 미충족 시 비활성(클릭 불가·사유 툴팁) — E-6. + const group = createSegment({ + options: options.map(([value, labelKey]) => ({ value, label: L(labelKey), disabled })), + value: selected ?? "", + size: 20, + onChange: (value) => onPick(value as T), + }).root; + if (disabled && reason) group.title = reason; wrap.append(group); return wrap; } -/** 온/오프 토글 하나(포장·2단계 경사 공용). 세그먼트와 같은 컨테이너/버튼 스타일을 쓴다. */ +/** 온/오프 토글 하나(포장·2단계 경사 공용) — 부품 켜기 칩 20(553-2). */ function toggle( legend: string, on: boolean, @@ -192,18 +189,16 @@ function toggle( legendEl.textContent = legend; wrap.append(legendEl); } - const buttons = document.createElement("div"); - buttons.className = "b06-design__seg-buttons"; - const button = document.createElement("button"); - button.type = "button"; - button.className = `b06-design__btn${on ? " b06-design__btn--active" : ""}`; - button.textContent = on ? onLabel : offLabel; - button.setAttribute("aria-pressed", on ? "true" : "false"); - button.disabled = disabled; + const button = createToggle({ + kind: "chip", + label: on ? onLabel : offLabel, + checked: on, + size: 20, + disabled, + onChange: onToggle, + }).root; if (disabled && reason) button.title = reason; - else button.addEventListener("click", onToggle); - buttons.append(button); - wrap.append(buttons); + wrap.append(button); return { wrap, button }; } @@ -375,18 +370,26 @@ export function buildDesignControls( const more = document.createElement("details"); more.className = "b06-design__more"; const moreSummary = document.createElement("summary"); - moreSummary.className = "b06-design__more-summary"; + // 부품 아이콘 단추(선) 22 꼴 — 펼침 = 눌림(aria-pressed) + moreSummary.className = "ui-icon-btn ui-icon-btn--outline b06-design__more-summary"; + setUiSize(moreSummary, 22); moreSummary.textContent = "⋯"; moreSummary.title = L("B06_Design_More"); + moreSummary.setAttribute("aria-pressed", "false"); + more.addEventListener("toggle", () => + moreSummary.setAttribute("aria-pressed", String(more.open)), + ); const morePanel = document.createElement("div"); morePanel.className = "b06-design__more-panel"; more.append(moreSummary, morePanel); // [상세](138-4) — 맨 오른쪽 · 넘침 정리(moveable) 밖이라 늘 보임 - const detail = document.createElement("button"); - detail.type = "button"; - detail.className = "b06-design__btn b06-design__detail"; - detail.textContent = L("B06_Design_Detail"); - detail.addEventListener("click", () => openCrossDetail(section)); + const detail = createButton({ + label: L("B06_Design_Detail"), + variant: "ghost", + size: 20, + onClick: () => openCrossDetail(section), + }); + detail.classList.add("b06-design__detail"); bar.append(slopeDirSeg, ...moveable, more, detail); refreshCrossDetail(section); diff --git a/B06_Section/B06_Section_UI_Cross_Line_Cell.ts b/B06_Section/B06_Section_UI_Cross_Line_Cell.ts index e4251125d..8e41d25f7 100644 --- a/B06_Section/B06_Section_UI_Cross_Line_Cell.ts +++ b/B06_Section/B06_Section_UI_Cross_Line_Cell.ts @@ -87,7 +87,9 @@ export function buildLineCell(): LineCell { const buttons = SLOTS.map(([slot, text]) => { const button = document.createElement("button"); button.type = "button"; - button.className = `b06-design__rockb-btn ${CELL_BTN}${slot}`; + // 부품 아이콘 단추 22(553-2) — 클래스 · data-size 계약(시험 흉내 DOM 이 @ui 를 안 풂) + button.className = `ui-icon-btn ui-icon-btn--ghost b06-design__rockb-btn ${CELL_BTN}${slot}`; + button.setAttribute("data-size", "22"); if (slot === "reset") button.classList.add("is-reset"); button.textContent = text; // 길게 누름 — 누르면 즉시 1회 · 반복 예약 · 이어지는 click 은 삼킴 diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index 50de72507..286c4cdb3 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -414,29 +414,7 @@ stroke-width: 8; } -/* 암 경계선 상/하/리셋 제어 (B05 측점 선 제어 ▲/▼/↺ 버튼 패턴 재활용) */ -.b06-design__rockb-btn { - width: 22px; - height: 22px; - padding: 0; - font-size: 0.7rem; - line-height: 1; - color: var(--color-text-secondary); - background: var(--color-surface); - border: none; - border-left: 1px solid var(--color-border); - cursor: pointer; -} - -.b06-design__rockb-btn:first-child { - border-left: none; -} - -.b06-design__rockb-btn:hover { - color: var(--color-text); - background: var(--color-surface-raised); -} - +/* 암 경계선 상/하/리셋 제어 — 단추 = 부품 아이콘 단추 22(553-2) · ↺ 만 경고 글색 */ .b06-design__rockb-btn.is-reset { color: var(--color-warning); } diff --git a/B06_Section/B06_Section_UI_Style_Cross_Chart.css b/B06_Section/B06_Section_UI_Style_Cross_Chart.css index 82c054f5f..5d766dca4 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Chart.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Chart.css @@ -206,19 +206,8 @@ margin-left: auto; } +/* ⋯ = 부품 아이콘 단추(선) 22 — 펼침 표시만 지움 */ .b06-design__more-summary { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - border: 1px solid var(--color-border); - border-radius: var(--radius-inputs); - background: var(--color-surface); - color: var(--color-text-secondary); - font-size: 0.9rem; - line-height: 1; - cursor: pointer; list-style: none; } @@ -226,12 +215,6 @@ display: none; } -.b06-design__more[open] .b06-design__more-summary { - color: var(--color-surface); - background: var(--color-royal-amethyst); - border-color: var(--color-royal-amethyst); -} - .b06-design__more-panel { position: absolute; right: 0; @@ -254,7 +237,7 @@ } .b06-design__seg-legend { - font-size: 0.72rem; + font-size: var(--text-caption); color: var(--color-text-secondary); white-space: nowrap; } @@ -271,31 +254,11 @@ overflow: hidden; } -.b06-design__btn { - padding: 3px 8px; - font-size: 0.72rem; - color: var(--color-text-secondary); - background: var(--color-surface); - border: none; - border-left: 1px solid var(--color-border); - cursor: pointer; -} - -.b06-design__btn:first-child { - border-left: none; -} - -.b06-design__btn--active { - color: var(--color-surface); - background: var(--color-royal-amethyst); - font-weight: var(--font-weight-medium); -} - .b06-design__areas { display: inline-flex; gap: var(--spacing-8); margin-left: auto; - font-size: 0.72rem; + font-size: var(--text-caption); font-family: var(--font-mono); } @@ -329,13 +292,11 @@ .b06-cross-card__linecell .b06-design__rockb-btn { flex: 0 0 auto; - border-left: none; } -.b06-cross-card__linecell - .b06-design__rockb-btn:not([hidden]) - ~ .b06-design__rockb-btn:not([hidden]) { - border-left: 1px solid var(--color-border); +/* 부품 아이콘 단추의 display 가 hidden 을 덮음 — 상태에 없는 칸은 숨김 */ +.b06-design__rockb-btn[hidden] { + display: none; } /* 측점별 암 절토 경사(2026-09-07) — 행의 **오른쪽 끝**(암 경계 = 왼쪽). */ @@ -349,7 +310,7 @@ .b06-design__cutslope-input { width: 44px; padding: 0 2px; - font-size: 0.72rem; + font-size: var(--text-caption); font-family: var(--font-mono); text-align: right; color: var(--color-text); @@ -367,7 +328,7 @@ .b06-design__cutslope-unit { padding: 0 2px 0 1px; - font-size: 0.72rem; + font-size: var(--text-caption); color: var(--color-text-secondary); align-self: center; } diff --git a/resources/tester/helper_318_cross_card.cjs b/resources/tester/helper_318_cross_card.cjs index 870aa8fc2..a4d522d9f 100644 --- a/resources/tester/helper_318_cross_card.cjs +++ b/resources/tester/helper_318_cross_card.cjs @@ -445,11 +445,16 @@ const out = {}; // ── 3. 단추 줄(318-1) — 빈자리 누름 = 카드 고름 · 조작 누름 = 안 넘김 ─────────── { + // 단추 줄 = 부품 단추 · 분할 · 켜기(553-2) — 진짜 부품 파일을 흉내 노드로 돌림 + const UI = (name) => path.join(ROOT, "ui_template", `${name}.ts`); + const parts = ["ui_template_button", "ui_template_segment", "ui_template_toggle"]; + const loadUi = makeLoader([...parts, "ui_template_elements_base"].map(UI)); const load = makeLoader([B06("B06_Section_UI_Cross_Design")], { "@ui/ui_template_locale": { currentLanguageIndex: 0, ui_locales: new Proxy({}, { get: (_t, key) => [String(key)] }), }, + ...Object.fromEntries(parts.map((name) => [`@ui/${name}`, loadUi(UI(name))])), }); const { buildDesignControls } = load(B06("B06_Section_UI_Cross_Design")); const { bar } = buildDesignControls( diff --git a/resources/tester/helper_472_open_no_draft.mjs b/resources/tester/helper_472_open_no_draft.mjs index eb0aafa38..cdc034f81 100644 --- a/resources/tester/helper_472_open_no_draft.mjs +++ b/resources/tester/helper_472_open_no_draft.mjs @@ -155,10 +155,13 @@ const walk = (node) => { walk(groundSegment); walk(bar); const press = (pick) => buttons.find(pick)?.listeners.click?.forEach((fn) => fn({})); +// 부품 켜기 칩(553-2) — 글은 안쪽 span 에 · 자식 글을 이어 읽음 +const text = (node) => + typeof node === "string" + ? node + : (node?.textContent ?? "") + (node?.children ?? []).map(text).join(""); out.soilPressed = buttons[0]?.getAttribute("aria-pressed"); -press( - (b) => b.textContent === "측구" || b.textContent === "측구 없음" || /측구/.test(b.textContent), -); +press((b) => /측구/.test(text(b))); out.ditchChange = sent.at(-1) ?? null; // 새 카드 · 토사 단추 — 지반만(측구형식은 이미 일반이라 안 실림) buttons.length = 0;