feat(M02): 47-1 · 47-2 구조물 조합 카드 [?] 제목 옆 · 도움말 네 절 · 미리보기 파일 불러오기 없앰

- [?] = 카드 제목(도면 정보 · 일위대가 · 면적산출) 바로 옆 · 도움말 = 설명 · 단축키 · 기능 · 사용법
- 도움말 글 한 곳(M02_MasterTemplete_CardHelp.ts) · 일위대가 카드에도 [?]
- 도면 카드 미리보기 📂 없음(noImport) · 도면 상세는 그대로
- 시험 · 화면 시나리오(ui_checks/47-1_카드_도움말.js)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-05 13:38:13 +09:00
co-authored by Claude Opus 5.5
parent c6f84a2c64
commit 605cc090ac
8 changed files with 275 additions and 49 deletions
@@ -4,7 +4,7 @@
* 내용만 넣음. 모양 · 높이 · 스크롤은 `M02_MasterTemplete_Structure.css`.
* 머리 = 제목 줄(h3 · 제목 · 저장 안 한 점 ● · 오른쪽 단추 자리) + 한 줄 부제(번호 · 이름 · 넘치면 말줄임)
* 본문 = 카드 높이를 채움 · 넘치면 카드 안 스크롤 · 상태 = 불러오는 중 · 실패 + [다시 불러오기] · 비었음
* [저장](`saveControl`) = 늘 보임 · 고쳐야 켜짐 · 카드 안 Ctrl+S · 알림 글 한 벌 · ?(`helpButton`) = 단축키 상자
* [저장](`saveControl`) = 늘 보임 · 고쳐야 켜짐 · 카드 안 Ctrl+S · 알림 글 한 벌 · ?(`helpButton`) = 제목 옆 · 도움말 상자
* 접기는 부모의 attachCollapsible(제목 줄 클릭) — 오른쪽 단추를 눌러도 접히지 않음.
* ========================================================================== */
@@ -18,6 +18,11 @@ export const CARD_TEXT = {
failed: "저장 못 함 — {value}",
stale: "그 사이 바뀜 — 다시 읽음",
help: "?",
helpTip: "{value} 도움말",
helpAbout: "설명",
helpKeys: "단축키",
helpFeatures: "기능",
helpSteps: "사용법",
};
export interface CardShell {
@@ -25,6 +30,8 @@ export interface CardShell {
body: HTMLElement;
/** 제목 줄 오른쪽 단추 자리 */
tools: HTMLElement;
/** 제목 글 — 바로 옆에 ?(`helpButton`) */
title: HTMLElement;
/** 저장 안 한 고침 — 제목 옆 점(●) */
setDirty: (dirty: boolean) => void;
/** 0.3초 넘으면 본문에 「불러오는 중」 — 돌려받은 함수 = 끝 */
@@ -53,13 +60,14 @@ export const cardNote = (text: string): HTMLElement =>
export function cardShell(opt: CardShellOptions): CardShell {
const dot = el("span", { className: "m02-struct__dot", text: "●", attrs: { hidden: "" } });
const tools = el("span", { className: "m02-struct__card-tools" });
const title = el("span", { text: opt.title });
tools.addEventListener("click", (event) => event.stopPropagation()); // 단추 누름 ≠ 접기
const root = el("section", {
className: `m02-struct__card ${opt.className ?? ""} ui-collapsible ui-sidebar-section ui-sidebar-card`,
children: [
el("h3", {
className: "ui-collapsible__title",
children: [el("span", { text: opt.title }), dot, tools],
children: [title, dot, tools],
}),
...(opt.sub ? [el("div", { className: "m02-struct__sub", children: opt.sub })] : []),
opt.body,
@@ -76,6 +84,7 @@ export function cardShell(opt: CardShellOptions): CardShell {
root,
body: opt.body,
tools,
title,
setDirty: (dirty) => (dot.hidden = !dirty),
loading: () => showLoading(host),
fail: (reason, retry) => host.replaceChildren(loadingError(reason, retry)),
@@ -137,24 +146,49 @@ export function saveControl(
return { button, dirty: () => dirty, mark, run };
}
/** ? — 단축키 상자(카드 오른쪽 위 · 누르면 보이고 숨김) · 상자는 본문 앞에 끼움(본문 채움 규칙 그대로) */
export function helpButton(shell: CardShell, title: string, keys: [string, string][]): HTMLElement {
const help = createButton({ label: CARD_TEXT.help, variant: "ghost", size: "sm" });
help.title = help.ariaLabel = title;
/** [?] 도움말 네 절(47-1) — 설명 · 단축키 · 기능(단추마다 한 줄) · 사용법(차례) */
export interface CardHelp {
about: string;
keys: [string, string][];
features: [string, string][];
steps: string[];
}
const pairs = (rows: [string, string][]): HTMLElement =>
el("dl", {
children: rows.flatMap(([key, what]) => [el("dt", { text: key }), el("dd", { text: what })]),
});
/** ? — 카드 제목 바로 옆 · 누르면 도움말 상자(카드 오른쪽 위 · 보이고 숨김) · 상자는 본문 앞에 끼움 */
export function helpButton(shell: CardShell, title: string, help: CardHelp): HTMLElement {
const button = createButton({ label: CARD_TEXT.help, variant: "ghost", size: "sm" });
button.title = button.ariaLabel = CARD_TEXT.helpTip.replace("{value}", title);
button.classList.add("m02-struct__help");
const part = (name: string, heading: string, body: HTMLElement): HTMLElement =>
el("section", {
attrs: { "data-help-part": name },
children: [el("h4", { text: heading }), body],
});
const box = el("div", {
className: "m02-struct__keys",
attrs: { hidden: "" },
children: [
el("strong", { text: title }),
el("dl", {
children: keys.flatMap(([key, what]) => [
el("dt", { text: key }),
el("dd", { text: what }),
]),
}),
part("about", CARD_TEXT.helpAbout, el("p", { text: help.about })),
part("keys", CARD_TEXT.helpKeys, pairs(help.keys)),
part("features", CARD_TEXT.helpFeatures, pairs(help.features)),
part(
"steps",
CARD_TEXT.helpSteps,
el("ol", { children: help.steps.map((text) => el("li", { text })) }),
),
],
});
help.addEventListener("click", () => (box.hidden = !box.hidden));
button.addEventListener("click", (event) => {
event.stopPropagation(); // 누름 ≠ 접기
box.hidden = !box.hidden;
});
shell.title.after(button);
shell.root.insertBefore(box, shell.body);
return help;
return button;
}