feat(ui): 연결창 단추 넷 머리 · 큰 창 · 글 묻는 창 [확인] 머리 · [취소] 지움(147-2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
This commit is contained in:
2026-10-09 19:02:07 +09:00
co-authored by Claude Opus 5.5
parent 0da6daab72
commit 0f522269ad
6 changed files with 48 additions and 44 deletions
@@ -1,6 +1,7 @@
"""공용 모달 머리 단추 · 큰 창 · M02 수량산출 창(PLAN 147) — 소스 검사 · 화면 판정은 사용자.
· 147-1 곁값 `actions`(머리 [닫기] 바로 왼쪽 · 넘긴 창만) · `wide`(96vw × 94vh · 몸만 스크롤)
· 147-2 연결창 단추 넷 → 머리 · 큰 창 · 글 묻는 창 [확인] 머리 · [취소] 없음
"""
from __future__ import annotations
@@ -42,3 +43,27 @@ def test_147_1_머리_자리_큰_창_css():
assert rule in wide, rule
body = _block(css, ".ui-modal--wide > .ui-modal__body")
assert "overflow: auto;" in body and "min-height: 0;" in body
def test_147_2_연결창_머리_단추_큰_창():
ts = _text("ui_template/ui_template_item_picker.ts")
assert "children: [copyButton, manageButton, clearButton, applyButton]," in ts
assert "actions: [headButtons]," in ts and "wide: true," in ts
# 편집 화면 = 머리 단추도 숨김(편집 중 [적용] 막음) · 돌아오면 다시
assert "headButtons.hidden = true;" in ts and "headButtons.hidden = false;" in ts
# 안내 글(구조물 [저장]) 은 아래 남김
assert "ui-item-picker__saveline" in ts
css = _text("ui_template/ui_template_item_picker.css")
assert ".ui-item-picker .ui-modal__actions[hidden]" in css
assert "width: min(1320px, 96vw);" not in css
def test_147_2_글_묻는_창_확인만_머리():
ask = _text("ui_template/ui_template_item_picker_ask.ts")
assert "head.append(createButton({ label: opts.okLabel" in ask
assert "cancelLabel" not in ask
for rel in (
"ui_template/ui_template_item_picker.ts",
"ui_template/ui_template_item_picker_detail.ts",
):
assert "cancelLabel" not in _text(rel), rel
@@ -30,9 +30,9 @@ def test_공용_모달_위_고정():
assert "max-height: var(--modal-max, 80vh);" in modal
assert "margin-top: calc((100vh - var(--modal-max, 80vh)) / 2);" in modal
# 큰 창은 max-height 대신 --modal-max(위 거리가 함께 맞음)
assert "--modal-max: 94vh;" in _block(
_text("ui_template/ui_template_item_picker.css"), ".ui-modal.ui-item-picker"
)
# 고르기 창 = 공용 큰 창 곁값(147-2)
assert "--modal-max: 94vh;" in _block(css, ".ui-modal.ui-modal--wide")
assert "wide: true," in _text("ui_template/ui_template_item_picker.ts")
assert "--modal-max: 92vh;" in _block(
_text("B05_Profile/B05_Profile_UI_Style_RouteRecommend.css"), ".ui-modal.b05-routerec"
)
+3 -10
View File
@@ -1,13 +1,5 @@
/* 항목 고르기 모달 — 왼쪽 나무 · 오른쪽 폴더 모양 · 아래 단추 */
/* 공용 모달(.ui-modal)의 폭 · 높이보다 앞서게 — 불러오는 차례에 따라 뒤집히지 않도록 두 클래스 */
.ui-modal.ui-item-picker {
width: min(1320px, 96vw);
--modal-max: 94vh;
}
.ui-item-picker .ui-modal__body {
height: min(80vh, 800px);
}
/* 항목 고르기 모달 — 왼쪽 나무 · 오른쪽 폴더 모양 · 머리 단추(147-2) */
/* 크기 = 공용 큰 창(곁값 wide · 96vw × 94vh · 147-2) */
.ui-item-picker__cols {
display: grid;
@@ -51,6 +43,7 @@
.ui-item-picker__cols[hidden],
.ui-item-picker__actions[hidden],
.ui-item-picker .ui-modal__actions[hidden],
.ui-item-picker__edit-host[hidden] {
display: none;
}
+13 -12
View File
@@ -103,7 +103,6 @@ const NEW_TEXT = {
name: "이름",
unit: "단위",
ok: "만들기",
cancel: "취소",
empty: "{label} 을(를) 넣을 것",
made: "새 항목을 만들어 지정했음 — 구조물 [저장]을 눌러야 이 구조물 복사본이 남음",
failed: "새 항목을 만들지 못함 — {value}",
@@ -289,7 +288,6 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
{ key: "unit", label: NEW_TEXT.unit, placeholder: "m · ㎥ · 개소", required: true },
],
okLabel: NEW_TEXT.ok,
cancelLabel: NEW_TEXT.cancel,
closeLabel: t("M02_Close"),
emptyText: NEW_TEXT.empty,
});
@@ -308,17 +306,16 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
}
}
// 단추 넷 = 머리 [닫기] 왼쪽(147-2) · 안내 글은 아래 남김
const headButtons = el("div", {
className: "ui-modal__actions",
children: [copyButton, manageButton, clearButton, applyButton],
});
const actions = el("div", {
className: "ui-item-picker__actions",
children: [
...(opts.projectId
? [el("span", { className: "ui-item-picker__saveline", text: NEW_TEXT.saveLine })]
: []),
copyButton,
manageButton,
clearButton,
applyButton,
],
children: opts.projectId
? [el("span", { className: "ui-item-picker__saveline", text: NEW_TEXT.saveLine })]
: [],
});
/** 카드 [수정] — 모달 안에서 편집 화면으로 바뀜(나무 · 카드 · 단추는 숨김) */
@@ -328,6 +325,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
editHost.replaceChildren(view.root);
cols.hidden = true;
actions.hidden = true;
headButtons.hidden = true;
editHost.hidden = false;
view.start();
}
@@ -338,6 +336,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
editHost.hidden = true;
cols.hidden = false;
actions.hidden = false;
headButtons.hidden = false;
if (saved) detailView.markEdited();
detailView.reload();
}
@@ -346,6 +345,8 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
title: t("M02_PickerTitle"),
closeLabel: t("M02_Close"),
dialogClass: "ui-item-picker",
wide: true,
actions: [headButtons],
// 닫기 단추 · Esc · 바깥 누르기 = 취소 — 편집 중 저장 안 한 고침이 있으면 물음
beforeClose: async () => {
if (editView && !(await editView.leave())) return false;
@@ -354,7 +355,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
return true;
},
mount: (body) => {
body.append(cols, editHost, actions);
body.append(cols, editHost, ...(opts.projectId ? [actions] : []));
paintDetail();
void load(null);
},
+4 -17
View File
@@ -1,7 +1,8 @@
/* =============================================================================
* ui_template_item_picker_ask.ts
* 고르기 모달에서 글을 물을 때 쓰는 작은 입력 창(계획서 26-3b) — [개인에 저장](경로 · 이름) ·
* 나무 오른쪽 클릭 「새 항목」(이름 · 단위). 확인 = 넣은 글들 · 취소 · 닫기 = null.
* 나무 오른쪽 클릭 「새 항목」(이름 · 단위). 확인 = 넣은 글들 · 닫기 = null.
* [확인] = 머리 [닫기] 왼쪽 · [취소] 없음(머리 [닫기] = 취소 · 147-2).
* ========================================================================== */
import { createButton, createInputField, el } from "./ui_template_elements";
@@ -22,7 +23,6 @@ export interface AskOptions {
note?: string;
fields: AskField[];
okLabel: string;
cancelLabel: string;
closeLabel: string;
/** 빈 값 알림 — `{label}` 자리에 칸 이름 */
emptyText: string;
@@ -45,7 +45,7 @@ export function askFields(opts: AskOptions): Promise<Record<string, string> | nu
finish(null);
return true;
},
mount: (body, close) => {
mount: (body, close, head) => {
const inputs = opts.fields.map((field) => ({
field,
input: createInputField({
@@ -76,21 +76,8 @@ export function askFields(opts: AskOptions): Promise<Record<string, string> | nu
body.append(
...(opts.note ? [el("p", { className: "ui-item-picker__where", text: opts.note })] : []),
...inputs.map(({ input }) => input.root),
el("div", {
className: "ui-item-picker__actions",
children: [
createButton({
label: opts.cancelLabel,
variant: "ghost",
onClick: () => {
finish(null);
close();
},
}),
createButton({ label: opts.okLabel, variant: "filled", onClick: submit }),
],
}),
);
head.append(createButton({ label: opts.okLabel, variant: "filled", onClick: submit }));
},
});
});
@@ -98,7 +98,6 @@ const TEXT = {
saved: "개인 층에 저장했음",
saveFailed: "개인에 저장하지 못함 — {value}",
ok: "확인",
cancel: "취소",
close: "닫기",
empty: "{label} 을(를) 넣을 것",
importFailed: "이 구조물로 가져오지 못함 — {value}",
@@ -267,7 +266,6 @@ export function createItemDetail(opts: ItemDetailOptions): ItemDetail {
{ key: "name", label: TEXT.nameLabel, value: itemName(row), required: true },
],
okLabel: TEXT.ok,
cancelLabel: TEXT.cancel,
closeLabel: TEXT.close,
emptyText: TEXT.empty,
});