From 09134bab84693aefdf7aabcdafa6da73c166236f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 23:44:19 +0900 Subject: [PATCH] =?UTF-8?q?refactor(M02):=20=EB=9D=A0=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=C2=B7=20=EB=B9=88=20=EC=83=81=ED=83=9C=20=C2=B7=20=EB=B3=B4?= =?UTF-8?q?=EC=A1=B0=20=EA=B8=80=20=C2=B7=20=EB=B0=B0=EC=A7=80=20=C2=B7=20?= =?UTF-8?q?=ED=83=AD=20=C2=B7=20=ED=91=9C=20=C2=B7=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A4=84=20=C2=B7=20=EB=8F=84=EA=B5=AC=EC=A4=84=20=C2=B7=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EB=A5=BC=20=EB=B6=80=ED=92=88=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20(PLAN=20553-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DataFlow: 표 넷 = 데이터표 · 함수 판 알림 · 선/알약 경고 · 끊긴 곳 = 띠 알림 · 알약 · 칸 종류 = 배지 · 고르는 줄 = 목록 줄 · 입력 넷 = 입력 · 도구줄 여섯 · 날 색 → 공통 값 - 구조물 옵션 · 구조물 · 도면 · 면적 · 품목 고르기: 판 띠 = 띠 알림 · 한눈/종류별/부품 = 탭 · 표 둘 = 데이터표 · 체크 둘 = 체크 · 카드 · 번호 = 카드 · 배지 · 도면 도구줄 - 일위대가 · 호표 · 실무자료 · 본 화면 · 서고: 알림 = 띠 알림 · 빈 상태 · 보조 글 · 도구줄 여섯 · 옛 보라 별칭 0 - M01 로직 입력칸 = 입력 부품(작은 칸) · 일위대가 상세 줄 수 고정 시험 693 · 지킴 시험 M02 몫 더함 Co-Authored-By: Claude Opus 5.5 --- .../M01_MasterData_UI_LogicLab_Copy.ts | 2 +- .../M01_MasterData_UI_LogicLab_Detail.ts | 9 +- .../M01_MasterData_UI_Logic_Calc.ts | 2 +- .../M01_MasterData_UI_Logic_Edit.ts | 6 +- .../M01_MasterData_UI_Logic_Style.css | 13 +-- .../DataFlow/M02_DF_CellTable.ts | 7 +- .../DataFlow/M02_DF_Diagram.css | 9 -- M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts | 10 +- .../DataFlow/M02_DF_Entry_Detail.ts | 3 +- .../DataFlow/M02_DF_FormulaView.ts | 5 +- .../DataFlow/M02_DF_FuncPanel.css | 103 +++--------------- .../DataFlow/M02_DF_FuncPanel.ts | 62 +++++++---- .../DataFlow/M02_DF_InputPanel.css | 41 ++----- .../DataFlow/M02_DF_InputPanel.ts | 30 +++-- .../DataFlow/M02_DF_InputPanel_Logic.ts | 30 +++-- .../DataFlow/M02_DF_InputPanel_Material.ts | 33 ++++-- .../DataFlow/M02_DF_InputPanel_Rule.ts | 8 +- .../DataFlow/M02_DF_InputPanel_Table.ts | 18 ++- .../DataFlow/M02_DF_InputPanel_Template.ts | 14 ++- .../DataFlow/M02_DF_InputPanel_Vars.ts | 6 +- .../DataFlow/M02_DF_LinkPanel.css | 21 +--- .../DataFlow/M02_DF_LinkPanel.ts | 32 ++++-- .../DataFlow/M02_DF_OutputPanel.css | 21 ---- .../DataFlow/M02_DF_OutputPanel.ts | 13 ++- M02_MasterTemplete/DataFlow/M02_DF_Panel.ts | 3 +- M02_MasterTemplete/DataFlow/M02_DF_Paper.ts | 4 +- .../DataFlow/M02_DF_PillPanel.ts | 32 +++++- .../DataFlow/M02_DF_RoundRow.ts | 3 +- .../DataFlow/M02_DF_Standards.css | 25 +---- .../DataFlow/M02_DF_Standards.ts | 10 +- .../DataFlow/M02_DF_Summary.css | 63 +---------- M02_MasterTemplete/DataFlow/M02_DF_Summary.ts | 10 +- .../DataFlow/M02_DF_UnitPick.css | 21 +--- .../DataFlow/M02_DF_UnitPick.ts | 6 +- M02_MasterTemplete/DataFlow/M02_DF_Verify.css | 20 +--- M02_MasterTemplete/DataFlow/M02_DF_Verify.ts | 40 ++++--- M02_MasterTemplete/M02_Labor_Values.css | 9 +- M02_MasterTemplete/M02_Labor_Values.ts | 4 +- .../M02_MasterTemplete_AreaCellPick.ts | 6 +- .../M02_MasterTemplete_AreaDrawing.ts | 2 +- .../M02_MasterTemplete_AreaLink.ts | 7 +- .../M02_MasterTemplete_AreaPanel.css | 3 - .../M02_MasterTemplete_Drawing.css | 8 +- .../M02_MasterTemplete_Drawing.ts | 3 +- .../M02_MasterTemplete_Hopyo.css | 16 +-- .../M02_MasterTemplete_Hopyo_Check.ts | 7 +- .../M02_MasterTemplete_Hopyo_CheckRows.ts | 4 +- .../M02_MasterTemplete_Hopyo_Choose.ts | 7 +- .../M02_MasterTemplete_Hopyo_Ledger.ts | 5 +- .../M02_MasterTemplete_Hopyo_Sheet.ts | 18 ++- .../M02_MasterTemplete_Hopyo_Side.ts | 3 +- .../M02_MasterTemplete_ItemCards.ts | 4 +- .../M02_MasterTemplete_ItemPick.css | 2 - .../M02_MasterTemplete_ItemPick.ts | 7 +- .../M02_MasterTemplete_Labor.css | 25 ++--- .../M02_MasterTemplete_Labor_Diagram.css | 9 -- .../M02_MasterTemplete_Labor_Diagram.ts | 7 +- .../M02_MasterTemplete_Labor_Input.ts | 6 +- .../M02_MasterTemplete_Labor_LogicMats.ts | 4 +- .../M02_MasterTemplete_Labor_National.ts | 2 +- .../M02_MasterTemplete_Labor_QtyModal.ts | 2 +- .../M02_MasterTemplete_Labor_RefEdit.ts | 2 +- .../M02_MasterTemplete_Labor_Rows.ts | 13 ++- .../M02_MasterTemplete_Labor_View.ts | 4 +- .../M02_MasterTemplete_Library_List.ts | 2 +- .../M02_MasterTemplete_Practice.ts | 20 +++- .../M02_MasterTemplete_Structure.css | 35 ++---- .../M02_MasterTemplete_TreePanel.css | 7 +- .../M02_MasterTemplete_TreePanel.ts | 12 +- .../M02_MasterTemplete_UI_Main.ts | 12 +- .../M02_MasterTemplete_UI_Side.ts | 3 +- .../M02_MasterTemplete_UI_Side_Precombo.ts | 7 +- .../M02_MasterTemplete_UI_Style.css | 24 +--- M02_MasterTemplete/M02_Material_Pick.ts | 18 +-- .../StructOptions/M02_SO_Def.ts | 44 +++++--- .../StructOptions/M02_SO_Def_Options.ts | 10 +- .../StructOptions/M02_SO_Def_Parts.ts | 5 +- .../StructOptions/M02_SO_Def_Rules.ts | 23 ++-- .../StructOptions/M02_SO_Def_Spec.ts | 5 +- .../StructOptions/M02_SO_Def_Table.ts | 11 +- .../StructOptions/M02_SO_Def_Ui.ts | 20 ++-- .../StructOptions/M02_SO_Groups.ts | 11 +- .../StructOptions/M02_SO_Options.ts | 1 + .../StructOptions/M02_SO_Style.css | 64 ++--------- resources/tester/test_553_5_m01_m02.py | 28 +++++ resources/tester/test_m02_hopyo_screen.py | 2 +- 86 files changed, 583 insertions(+), 705 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Copy.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Copy.ts index 6a8a981fa..ec162d843 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Copy.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Copy.ts @@ -199,7 +199,7 @@ function logicAliasControls( function lineBox(row: LogicRow, line: FormulaLine, onChange: () => void): HTMLElement { const f = field(row, line.자리); - const box = el("textarea", { className: "m01-logic__input m01lab__qty" }); + const box = el("textarea", { className: "ui-input ui-input--sm m01-logic__input m01lab__qty" }); box.value = f.get(); box.rows = Math.max(1, Math.ceil(box.value.length / 60)); box.addEventListener("input", () => { diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts index 15b202ee0..dda7aab44 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts @@ -144,7 +144,10 @@ function infoBox(ctx: DetailContext): HTMLElement { const own = isOwnLogic(row); const field = (label: string, key: "이름" | "원문번호" | "결과단위" | "출처"): HTMLElement => { if (!own) return cell(label, row[key] ?? ""); - const box = el("input", { className: "m01-logic__input", attrs: { "data-own": key } }); + const box = el("input", { + className: "ui-input ui-input--sm m01-logic__input", + attrs: { "data-own": key }, + }); box.value = row[key] ?? ""; box.addEventListener("input", () => { row[key] = box.value; @@ -163,7 +166,9 @@ function infoBox(ctx: DetailContext): HTMLElement { el("span", { text: value || "—" }), ], }); - const note = el("textarea", { className: "m01-logic__input m01-logic__note" }); + const note = el("textarea", { + className: "ui-input ui-input--sm m01-logic__input m01-logic__note", + }); note.value = row.비고 ?? ""; note.addEventListener("input", () => { if (note.value.trim()) row.비고 = note.value; diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts index b97e032be..4aa40833c 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts @@ -111,7 +111,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { } } else { const box = el("input", { - className: "m01-logic__input", + className: "ui-input ui-input--sm m01-logic__input", attrs: { type: "text", inputmode: "decimal" }, }); if (spec.범위) box.placeholder = `${spec.범위[0]} ∼ ${spec.범위[1]}`; diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Edit.ts b/M01_MasterData/M01_MasterData_UI_Logic_Edit.ts index ff4335c98..0421855ed 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Edit.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Edit.ts @@ -61,7 +61,7 @@ export function formatNumber(value: unknown): string { function textBox(value: unknown, onInput: (v: string) => void, className = ""): HTMLInputElement { const input = el("input", { - className: `m01-logic__input ${className}`, + className: `ui-input ui-input--sm m01-logic__input ${className}`, attrs: { type: "text" }, }); input.value = value === null || value === undefined ? "" : String(value); @@ -176,7 +176,9 @@ function head(ctx: EditContext, set: (p: () => void) => void, rerender: () => vo : el("span", { className: "m01-logic__file", text: ctx.file }); const unit = textBox(row.결과단위, (v) => set(() => (row.결과단위 = v))); unit.addEventListener("change", rerender); // 돈 로직 ↔ 돈 아닌 로직 칸이 바뀜 - const note = el("textarea", { className: "m01-logic__input m01-logic__note" }); + const note = el("textarea", { + className: "ui-input ui-input--sm m01-logic__input m01-logic__note", + }); note.value = row.비고 ?? ""; note.addEventListener("input", () => set(() => { diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Style.css b/M01_MasterData/M01_MasterData_UI_Logic_Style.css index 009ac1756..75bcf7ec2 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Style.css +++ b/M01_MasterData/M01_MasterData_UI_Logic_Style.css @@ -97,22 +97,11 @@ font-size: var(--text-body-sm); } +/* 겉 = 입력 부품(ui-input--sm) · 여기는 칸 폭만 */ .m01-logic__input { box-sizing: border-box; width: 100%; min-width: 0; - padding: 3px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; - font-size: var(--text-body-sm); -} - -/* 한 줄 칸은 작은 고르기칸과 같은 높이 */ -input.m01-logic__input { - height: var(--control-height-sm); } .m01-logic__note { diff --git a/M02_MasterTemplete/DataFlow/M02_DF_CellTable.ts b/M02_MasterTemplete/DataFlow/M02_DF_CellTable.ts index 1e8eac043..685a11e39 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_CellTable.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_CellTable.ts @@ -9,6 +9,8 @@ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, el } from "@ui/ui_template_elements"; import type { CfBox, CfLogic } from "./M02_DF_Model"; import { portText } from "./M02_DF_Detail"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_table.css"; const TEXT = { Title: ["칸 연결 표", "Cell connections"], @@ -103,7 +105,8 @@ export function renderCellTable( el("td", { children: [ el("span", { - className: "m02-dfcells__kind", + className: `ui-badge ui-badge--pill ui-badge--${r.kind === "in" ? "info" : r.kind === "var" ? "warning" : "success"} m02-dfcells__kind`, + attrs: { "data-size": "20" }, text: tt(r.kind === "in" ? "In" : r.kind === "var" ? "Var" : "Out"), }), ], @@ -128,7 +131,7 @@ export function renderCellTable( host.replaceChildren( el("strong", { className: "m02-dfcells__title", text: tt("Title") }), el("table", { - className: "m02-dfcells__table", + className: "ui-table ui-table--data m02-dfcells__table", children: [el("thead", { children: [head] }), el("tbody", { children: body })], }), ); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Diagram.css b/M02_MasterTemplete/DataFlow/M02_DF_Diagram.css index 2687a588f..a6d6726b4 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Diagram.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Diagram.css @@ -48,15 +48,6 @@ color: var(--color-text-secondary); } -.m02-dfdg__note { - color: var(--color-danger); -} - -.m02-dfdg__hint { - margin: 0; - color: var(--color-text-muted); -} - /* 판 — 화면 위쪽 큰 영역 */ .m02-dfdg__view { position: relative; diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts b/M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts index 0623a007e..453ef5ff0 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts @@ -38,6 +38,8 @@ import { type TEXT, } from "./M02_DF_Diagram_Parts"; import "./M02_DF_Diagram.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_toolbar.css"; export * from "./M02_DF_Diagram_Parts"; @@ -395,7 +397,7 @@ export function renderDiagram( : []; const bar = el("div", { - className: "m02-dfdg__bar", + className: "ui-toolbar m02-dfdg__bar", children: [ el("strong", { className: "m02-dfdg__title", text: tt("Title") }), ...(ro @@ -425,12 +427,12 @@ export function renderDiagram( children: [ bar, el("div", { - className: "m02-dfdg__tools", + className: "ui-toolbar m02-dfdg__tools", children: [ ...tools, - el("span", { className: "m02-dfdg__note", text: st.note }), + el("span", { className: "ui-text ui-text--error", text: st.note }), el("span", { - className: "m02-dfdg__hint", + className: "ui-text ui-text--muted", text: tools.length || st.note ? "" : tt("Hint"), }), ], diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts b/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts index 5d4a95474..667f9a3d1 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts @@ -12,6 +12,7 @@ import type { CfAnswer, CfQuery } from "./M02_DF_Api"; import { fillTemplate } from "./M02_DF_InputPanel_Build"; import type { CfBreak, CfValues } from "./M02_DF_Eval"; import type { CfElementRef, CfLogic, CfPort } from "./M02_DF_Model"; +import "@ui/ui_template_text.css"; const TEXT = { Detail: ["상세 내용", "Details"], @@ -120,7 +121,7 @@ export function drawWarnings(d: DetailParts, pick: DfPick, all: CfBreak[]): void d.warn.replaceChildren( ...(mine.length ? [...new Set(mine.map((k) => k.text))].map((w) => el("li", { text: w })) - : [el("li", { className: "m02-df__muted", text: tt("WarnNone") })]), + : [el("li", { className: "ui-text ui-text--muted", text: tt("WarnNone") })]), ); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_FormulaView.ts b/M02_MasterTemplete/DataFlow/M02_DF_FormulaView.ts index b1d08283b..97b02b364 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_FormulaView.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_FormulaView.ts @@ -12,6 +12,7 @@ import { recalcWorkbook } from "../../A00_Common/spreadsheet/spreadsheet_recalc" import { ifBody, splitArgs } from "../../A00_Common/spreadsheet/spreadsheet_condition_text"; import type { CalcValue } from "../../A00_Common/spreadsheet/spreadsheet_types"; import type { CfBox } from "./M02_DF_Model"; +import { createEmpty } from "@ui/ui_template_empty"; export type CellScalar = number | string | boolean | null; @@ -259,7 +260,7 @@ function fromCalc(v: CalcValue): CellScalar { /** 식 보기 그리기 — 줄마다 이름 식 · 값 식 · 갈래(지금 갈래는 ▶ 와 「지금」 글) */ export function renderFormulaView(host: HTMLElement, lines: FormulaLine[]): void { - host.classList.add("m02-df-formula"); + host.classList.add("ui-card", "m02-df-formula"); host.replaceChildren( el("strong", { className: "m02-df-formula__title", text: tv("Title") }), ...(lines.length @@ -285,6 +286,6 @@ export function renderFormulaView(host: HTMLElement, lines: FormulaLine[]): void ], }), ) - : [el("p", { className: "m02-df-formula__empty", text: tv("Empty") })]), + : [createEmpty({ text: tv("Empty"), small: true })]), ); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.css b/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.css index 47704d85a..70e698edd 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.css @@ -34,28 +34,14 @@ text-align: center; } -.m02-dfpanel-cell { - padding: 2px var(--spacing-12); - border: 1px solid var(--color-border); - border-radius: var(--radius-pills); - background: var(--color-surface-raised); -} - .m02-dfpanel-label { font-weight: var(--font-weight-bold); } -.m02-dfpanel-note { - color: var(--color-text-muted); - font-size: var(--text-body-sm); -} - -.m02-dfpanel-form { +/* 겉 = 카드 부품 · 여기는 아래 맞춤 · 안 여백만 */ +.m02-dfpanel-form.ui-card { align-items: flex-end; padding: var(--spacing-8) var(--spacing-12); - border: 1px solid var(--color-border); - border-radius: var(--radius-cards); - background: var(--color-surface-raised); } .m02-dfpanel-fieldbox { @@ -72,26 +58,18 @@ width: 6em; } -.m02-dfpanel-why, +/* 알림 = 띠 알림 부품 — 숨김은 부품 display 를 이김 */ .m02-dfpanel-msg { margin: 0; - padding: var(--spacing-8) var(--spacing-12); - border-left: 4px solid var(--color-warning); - background: var(--color-surface-raised); - font-weight: var(--font-weight-medium); } +.m02-dfpanel-msg[hidden] { + display: none; +} + +/* 받음 · 변수 · 출력 알약 = 배지 부품(info · warning · success) — 안 놓인 받음은 점선 */ .m02-dfpanel-chip { - display: inline-flex; - align-items: center; - gap: var(--spacing-4); - padding: var(--spacing-4) var(--spacing-12); - border: 2px solid; - border-radius: var(--radius-pills); - background: var(--color-surface); - color: inherit; - font: inherit; - font-weight: var(--font-weight-medium); + font-family: inherit; } button.m02-dfpanel-chip { @@ -102,22 +80,11 @@ button.m02-dfpanel-chip:focus-visible { outline: 2px solid var(--color-focus-ring); } -.m02-dfpanel-chip.is-in { - border-color: #3b82f6; -} - .m02-dfpanel-chip.is-in:not(.is-placed) { + border-color: var(--badge-tone); border-style: dashed; } -.m02-dfpanel-chip.is-out { - border-color: #27ae60; -} - -.m02-dfpanel-chip.is-var { - border-color: #d99a00; -} - /* 판 높이 크게 — 안쪽 세로 스크롤 없이 보이게 */ .m02-dfpanel-sheet { position: relative; @@ -129,15 +96,15 @@ button.m02-dfpanel-chip:focus-visible { /* 칸 위 표시 — 색 + 글자(받음 · 변수 · 출력) 둘 다 */ .m02-dfpanel .aislo-cell[data-cf-kind="in"] { - box-shadow: inset 0 0 0 2px #3b82f6; + box-shadow: inset 0 0 0 2px var(--color-info); } .m02-dfpanel .aislo-cell[data-cf-kind="var"] { - box-shadow: inset 0 0 0 2px #d99a00; + box-shadow: inset 0 0 0 2px var(--color-warning); } .m02-dfpanel .aislo-cell[data-cf-kind="out"] { - box-shadow: inset 0 0 0 2px #27ae60; + box-shadow: inset 0 0 0 2px var(--color-success); } .m02-dfpanel .aislo-cell[data-cf-kind]::after { @@ -196,20 +163,6 @@ button.m02-dfpanel-chip:focus-visible { font-weight: var(--font-weight-bold); } -.m02-dfcells__table { - width: 100%; - border-collapse: collapse; - font-size: var(--text-body-sm); -} - -.m02-dfcells__table th, -.m02-dfcells__table td { - padding: var(--spacing-4); - border-bottom: 1px solid var(--color-border); - text-align: left; - vertical-align: middle; -} - .m02-dfcells__cell { width: 5em; } @@ -219,34 +172,15 @@ button.m02-dfpanel-chip:focus-visible { border-style: dashed; } -.m02-dfcells__kind { - padding: 0 var(--spacing-8); - border: 2px solid; - border-radius: var(--radius-pills); - white-space: nowrap; -} - -.m02-dfcells__row.is-in .m02-dfcells__kind { - border-color: #3b82f6; -} - -.m02-dfcells__row.is-var .m02-dfcells__kind { - border-color: #d99a00; -} - -.m02-dfcells__row.is-out .m02-dfcells__kind { - border-color: #27ae60; -} +/* 칸 종류 = 배지 부품(받음 info · 변수 warning · 출력 success) */ /* 식 보기 */ -.m02-df-formula { +/* 겉 = 카드 부품 · 여기는 쌓기 · 안 여백만 */ +.m02-df-formula.ui-card { display: flex; flex-direction: column; gap: var(--spacing-4); padding: var(--spacing-8) var(--spacing-12); - border: 1px solid var(--color-border); - border-radius: var(--radius-cards); - background: var(--color-surface-raised); } .m02-df-formula__title { @@ -274,8 +208,3 @@ button.m02-dfpanel-chip:focus-visible { .m02-df-formula__branches .is-now { font-weight: var(--font-weight-bold); } - -.m02-df-formula__empty { - margin: 0; - color: var(--color-text-muted); -} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.ts b/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.ts index 4c5814637..74984eed0 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_FuncPanel.ts @@ -27,6 +27,9 @@ import { createRoundRow } from "./M02_DF_RoundRow"; import { createUnitPick, suggestPlaces } from "./M02_DF_UnitPick"; import { ERR_KEY, FRAMES, TEXT, tf } from "./M02_DF_FuncText"; import "./M02_DF_FuncPanel.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_banner.css"; +import "@ui/ui_template_badge.css"; type Handlers = { change(lg: CfLogic): void }; @@ -261,10 +264,14 @@ function drawHead(st: State): void { }); st.parts.head.replaceChildren( el("strong", { className: "m02-dfpanel-title", text: box?.name ?? "" }), - el("span", { className: "m02-dfpanel-cell", text: `${tf("Cell")} ${st.cell}` }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "24" }, + text: `${tf("Cell")} ${st.cell}`, + }), el("label", { className: "m02-dfpanel-unitbox", - children: [el("span", { className: "m02-dfpanel-note", text: tf("Unit") }), unit], + children: [el("span", { className: "ui-text ui-text--muted", text: tf("Unit") }), unit], }), ); } @@ -275,9 +282,9 @@ function drawGot(st: State): void { const chips = items.map(({ port, unit }) => { const at = f?.inputs.find((i) => i.port === port)?.cell; const b = el("button", { - className: `m02-dfpanel-chip is-in${at ? " is-placed" : ""}`, + className: `ui-badge ui-badge--pill ui-badge--info m02-dfpanel-chip is-in${at ? " is-placed" : ""}`, text: `${port} (${unit}) · ${at ? `${tf("Placed")} ${at}` : tf("Free")}`, - attrs: { type: "button", draggable: st.readOnly ? "false" : "true" }, + attrs: { type: "button", "data-size": "24", draggable: st.readOnly ? "false" : "true" }, }); b.addEventListener("click", () => placePort(st, port, st.cell)); b.addEventListener("dragstart", (e) => e.dataTransfer?.setData("text/plain", port)); @@ -285,9 +292,11 @@ function drawGot(st: State): void { }); st.parts.got.replaceChildren( el("span", { className: "m02-dfpanel-label", text: tf("Got") }), - ...(chips.length ? chips : [el("span", { className: "m02-dfpanel-note", text: tf("NoGot") })]), + ...(chips.length + ? chips + : [el("span", { className: "ui-text ui-text--muted", text: tf("NoGot") })]), ...(chips.length && !st.readOnly - ? [el("span", { className: "m02-dfpanel-note", text: tf("GotHint") })] + ? [el("span", { className: "ui-text ui-text--muted", text: tf("GotHint") })] : []), ); } @@ -303,7 +312,7 @@ function formField( return el("label", { className: "m02-dfpanel-fieldbox", children: [ - el("span", { className: "m02-dfpanel-note", text: tf(label) }), + el("span", { className: "ui-text ui-text--muted", text: tf(label) }), createUnitPick({ value: form.unit || "-", ariaLabel: tf(label), @@ -321,7 +330,7 @@ function formField( input.addEventListener("keydown", (e) => e.key === "Enter" && makeVariable(st)); return el("label", { className: "m02-dfpanel-fieldbox", - children: [el("span", { className: "m02-dfpanel-note", text: tf(label) }), input], + children: [el("span", { className: "ui-text ui-text--muted", text: tf(label) }), input], }); } @@ -330,7 +339,8 @@ function drawVars(st: State): void { const row: HTMLElement[] = [el("span", { className: "m02-dfpanel-label", text: tf("Vars") })]; for (const v of f?.variables ?? []) { const chip = el("span", { - className: "m02-dfpanel-chip is-var", + className: "ui-badge ui-badge--pill ui-badge--warning m02-dfpanel-chip is-var", + attrs: { "data-size": "24" }, children: [`${v.name} (${v.unit}) · ${v.cell ?? ""}`], }); if (!st.readOnly) { @@ -351,7 +361,7 @@ function drawVars(st: State): void { row.push(chip); } if (!f?.variables.length) - row.push(el("span", { className: "m02-dfpanel-note", text: tf("NoVars") })); + row.push(el("span", { className: "ui-text ui-text--muted", text: tf("NoVars") })); const parts: HTMLElement[] = [el("div", { className: "m02-dfpanel-row", children: row })]; if (!st.readOnly) { const form = st.form; @@ -400,12 +410,15 @@ function drawVars(st: State): void { }); parts.push( el("div", { - className: "m02-dfpanel-row m02-dfpanel-form", + className: "ui-card m02-dfpanel-row m02-dfpanel-form", children: [ formField(st, "VarName", "name"), el("label", { className: "m02-dfpanel-fieldbox", - children: [el("span", { className: "m02-dfpanel-note", text: tf("VarType") }), type], + children: [ + el("span", { className: "ui-text ui-text--muted", text: tf("VarType") }), + type, + ], }), formField(st, "VarUnit", "unit"), ...(form.type === "number" @@ -413,7 +426,11 @@ function drawVars(st: State): void { : []), ...(form.type === "choice" ? [formField(st, "VarChoices", "choices")] : []), formField(st, "VarExample", "example"), - el("span", { className: "m02-dfpanel-cell", text: `${tf("VarCell")} ${st.cell}` }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "24" }, + text: `${tf("VarCell")} ${st.cell}`, + }), createButton({ label: tf("Make"), variant: "filled", @@ -427,7 +444,7 @@ function drawVars(st: State): void { onClick: () => ((st.form = null), drawVars(st)), }), ...(form.why - ? [el("span", { className: "m02-dfpanel-why", text: `⚠ ${form.why}` })] + ? [el("span", { className: "ui-text ui-text--warning", text: `⚠ ${form.why}` })] : []), ], }), @@ -441,7 +458,7 @@ function drawBar(st: State): void { const f = funcOf(st); if (st.readOnly) { st.parts.bar.replaceChildren( - el("span", { className: "m02-dfpanel-note", text: tf("ReadOnly") }), + el("span", { className: "ui-text ui-text--muted", text: tf("ReadOnly") }), ); return; } @@ -456,8 +473,12 @@ function drawBar(st: State): void { size: "sm", onClick: () => setOutput(st, st.cell), }), - el("span", { className: "m02-dfpanel-chip is-out", text: outNow }), - el("span", { className: "m02-dfpanel-note", text: tf("OutOnly") }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--success m02-dfpanel-chip is-out", + attrs: { "data-size": "24" }, + text: outNow, + }), + el("span", { className: "ui-text ui-text--muted", text: tf("OutOnly") }), ], }), ...(f?.output.cell @@ -482,7 +503,7 @@ function drawBar(st: State): void { onClick: () => putFormula(st, formula), }), ), - el("span", { className: "m02-dfpanel-note", text: tf("FrameHint") }), + el("span", { className: "ui-text ui-text--muted", text: tf("FrameHint") }), ], }), ); @@ -647,7 +668,10 @@ export function renderFuncPanel( got: el("div", { className: "m02-dfpanel-row" }), vars: el("div", { className: "m02-dfpanel-vars" }), bar: el("div", { className: "m02-dfpanel-bar" }), - msg: el("p", { className: "m02-dfpanel-msg", attrs: { role: "status" } }), + msg: el("p", { + className: "ui-banner ui-banner--warning m02-dfpanel-msg", + attrs: { role: "status" }, + }), sheet: el("div", { className: "m02-dfpanel-sheet", text: tf("Loading") }), }; const st: State = { diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.css b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.css index 18a1b25f5..f33d55d7e 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.css @@ -42,31 +42,9 @@ flex-wrap: wrap; gap: var(--spacing-8); } -.m02-dfip__chip, -.m02-dfip__row { +/* 고르는 줄 = 목록 줄 부품(ui-list-row) — 여기는 안 여백 · 두 칸 벌림만 */ +button.m02-dfip__row { padding: var(--spacing-8) var(--spacing-12); - border: 2px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-surface); - color: var(--color-text); - font: inherit; - cursor: pointer; -} -.m02-dfip__chip { - border-radius: var(--radius-pills); - padding: var(--spacing-4) var(--spacing-12); -} -.m02-dfip__chip.is-on { - border-color: var(--color-accent); - outline: 2px solid var(--color-accent); -} -.m02-dfip__chip:hover, -.m02-dfip__chip:focus-visible, -.m02-dfip__row:hover, -.m02-dfip__row:focus-visible { - border-color: var(--color-accent); -} -.m02-dfip__row { display: flex; flex: 1 1 auto; gap: var(--spacing-8); @@ -104,12 +82,9 @@ .m02-dfip__value { color: var(--color-accent); } -.m02-dfip__tag { +/* 태그 = 배지 부품 — 이름 뒤 띄움만 */ +.m02-dfip__name .ui-badge { margin-left: var(--spacing-8); - padding: 0 var(--spacing-8); - border: 1px solid var(--color-border); - border-radius: var(--radius-pills); - font-size: var(--text-caption); } .m02-dfip__new, .m02-dfip__form { @@ -124,19 +99,17 @@ cursor: pointer; font-weight: 600; } +/* 미리 보기 = 표 부품 데이터표 — 여기는 줄바꿈 막기 · 가운데 · 찾은 줄만 */ .m02-dfip__preview table { - border-collapse: collapse; - font-size: var(--text-body-sm); + width: auto; } .m02-dfip__preview th, .m02-dfip__preview td { - padding: 2px 8px; - border: 1px solid var(--color-border); white-space: nowrap; text-align: center; } .m02-dfip__preview tr.is-hit td { - background: rgba(240, 190, 40, 0.35); + background: color-mix(in srgb, var(--color-warning) 35%, transparent); font-weight: 700; } .m02-dfip__preview { diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.ts index 6e03e078e..3125fa62e 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel.ts @@ -38,6 +38,10 @@ import { renderMaterial } from "./M02_DF_InputPanel_Material"; import { renderTablePick } from "./M02_DF_InputPanel_Table"; import { renderVariables } from "./M02_DF_InputPanel_Vars"; import "./M02_DF_InputPanel.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; /** Api 모양 — 서버 Api 가 채워지면(사용자 요소 = Model 모양 · 값 물음 = ref) 그 모양을 그대로 씀 */ interface Answer { @@ -125,7 +129,7 @@ export function renderInputPanel( host.className = "m02-dfip"; const box = lg.boxes.find((b) => b.id === boxId); if (!box || box.kind !== "input") { - host.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("NotInput") })); + host.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("NotInput") })); return; } // 상자 단위가 고른 요소 · 변수 단위와 다르면(견본 등) 맞춰 다시 그림 — 맞으면 바로 지나감 @@ -246,7 +250,15 @@ function drawChosen( el("span", { className: "m02-dfip__name", children: [ - ...(rule ? [el("span", { className: "m02-dfip__tag", text: t("RuleTag") })] : []), + ...(rule + ? [ + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: t("RuleTag"), + }), + ] + : []), el("strong", { text: ` ${e.name}` }), ...(rule && e.rule ? [ @@ -274,7 +286,7 @@ function drawChosen( ], }); }) - : [el("p", { className: "m02-dfip__muted", text: t("NoneChosen") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoneChosen") })]), ); const items = els .filter((e) => e.mode !== "rule") @@ -331,7 +343,7 @@ async function drawFind( let seq = 0; const load = async (): Promise => { const mine = ++seq; - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") })); try { const word = q.input.value.trim(); const page = await fetchRows(file, 1, 40, word, false, sub, detail); @@ -341,11 +353,11 @@ async function drawFind( list.replaceChildren( ...(rows.length ? rows.map((r) => rowOf(r)) - : [el("p", { className: "m02-dfip__muted", text: t("NoRow") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoRow") })]), ); } catch (e) { if (mine === seq) - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) })); } }; const rowOf = (r: M01Row): HTMLElement => { @@ -361,7 +373,7 @@ async function drawFind( const v = valueOf(group, r, field.select.value || "가격"); const unitText = group === "기계" ? "" : String(r["단위"] ?? ""); const b = el("button", { - className: "m02-dfip__row", + className: "m02-dfip__row ui-list-row ui-sidebar-row", attrs: { type: "button" }, children: [ el("span", { @@ -390,7 +402,7 @@ async function drawFind( subBox.root.hidden = !subs.length; bar.push(subBox.root, detailBox.root); } catch (e) { - host.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) })); + host.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) })); return; } let timer = 0; @@ -399,7 +411,7 @@ async function drawFind( () => (window.clearTimeout(timer), (timer = window.setTimeout(load, 250))), ); host.replaceChildren( - el("div", { className: "m02-dfip__bar", children: [...bar, q.root] }), + el("div", { className: "ui-toolbar m02-dfip__bar", children: [...bar, q.root] }), count, list, ); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Logic.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Logic.ts index af44df841..96103e584 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Logic.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Logic.ts @@ -35,6 +35,10 @@ import { wouldCycle, type MasterLogicBrief, } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Help: [ @@ -183,7 +187,7 @@ export function renderLogicInput( ...(shown.length ? shown.map((i) => { const b = el("button", { - className: "m02-dfip__row", + className: "m02-dfip__row ui-list-row ui-sidebar-row", attrs: { type: "button", ...(i.blocked ? { disabled: "true", title: (i.reasons ?? []).join(" · ") } : {}), @@ -200,7 +204,8 @@ export function renderLogicInput( text: ` ${aliasNote(matchLogic(q.input.value, i).alias)}`, }), el("span", { - className: "m02-dfip__tag", + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, text: i.blocked ? t("Blocked") : i.상세구분, }), ], @@ -211,7 +216,7 @@ export function renderLogicInput( b.addEventListener("click", () => void pick(i)); return b; }) - : [el("p", { className: "m02-dfip__muted", text: t("None") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("None") })]), ); }; @@ -253,10 +258,13 @@ export function renderLogicInput( window.clearTimeout(timer); timer = window.setTimeout(() => ((page = 1), draw()), 200); }); - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") })); host.replaceChildren( - el("p", { className: "m02-dfip__muted", text: t("Help") }), - el("div", { className: "m02-dfip__bar", children: [bookBox.root, chapterBox.root, q.root] }), + el("p", { className: "ui-text ui-text--muted", text: t("Help") }), + el("div", { + className: "ui-toolbar m02-dfip__bar", + children: [bookBox.root, chapterBox.root, q.root], + }), info, list, el("div", { className: "m02-dfip__chips", children: [prev, next] }), @@ -268,7 +276,7 @@ export function renderLogicInput( fillChapters(); draw(); }, - (e) => list.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) })), + (e) => list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) })), ); } @@ -282,7 +290,7 @@ function showChosen( ): void { const read = readNotes.get(ref.key); const values = new Map(); - const errors = el("p", { className: "m02-dfip__muted is-bad" }); + const errors = el("p", { className: "ui-text ui-text--error" }); const line = (name: string, unit: string, valueNode?: HTMLElement, extra = ""): HTMLElement => el("div", { className: "m02-dfip__item", @@ -300,7 +308,7 @@ function showChosen( el("h5", { text: `${t("Chosen")} — ${ref.name ?? ref.key}` }), ...(read?.converted ? [ - el("p", { className: "m02-dfip__muted", text: t("Converted") }), + el("p", { className: "ui-text ui-text--muted", text: t("Converted") }), ...(read.notes.length ? [ el("details", { @@ -320,14 +328,14 @@ function showChosen( values.set(o.name, v); return line(o.name, o.unit, v); }) - : [el("p", { className: "m02-dfip__muted", text: t("NoOut") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoOut") })]), errors, el("h5", { text: `${t("Designs")} · ${ref.designs.length}` }), ...(ref.designs.length ? ref.designs.map((d) => line(d.name, d.unit, undefined, String(ref.examples?.[d.name] ?? "")), ) - : [el("p", { className: "m02-dfip__muted", text: t("NoDesign") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoDesign") })]), el("div", { className: "m02-dfip__chips", children: [ diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Material.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Material.ts index 6203674e2..5ad9cbe18 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Material.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Material.ts @@ -30,6 +30,10 @@ import { variablePills, type MatItem, } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Chosen: ["넣은 재료 — 알약마다 이름 · 값 · 단위", "Chosen materials"], @@ -159,7 +163,10 @@ function scopeBoxes( () => undefined, ); return { - root: el("div", { className: "m02-dfip__bar", children: [subBox.root, detailBox.root] }), + root: el("div", { + className: "ui-toolbar m02-dfip__bar", + children: [subBox.root, detailBox.root], + }), get: () => ({ ...state }), }; } @@ -190,7 +197,7 @@ function itemList( }); const load = async (): Promise => { const mine = ++seq; - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") })); try { const r = await searchMaterials({ q: q.input.value.trim(), ...scope(), page, size: 12 }); if (mine !== seq) return; @@ -208,7 +215,7 @@ function itemList( ...(r.items.length ? r.items.map((it) => { const b = el("button", { - className: "m02-dfip__row", + className: "m02-dfip__row ui-list-row ui-sidebar-row", attrs: { type: "button" }, children: [ el("span", { @@ -227,11 +234,11 @@ function itemList( b.addEventListener("click", () => onPick(it)); return b; }) - : [el("p", { className: "m02-dfip__muted", text: t("NoRow") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoRow") })]), ); } catch { if (mine === seq) - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Failed") })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Failed") })); } }; let timer = 0; @@ -398,9 +405,13 @@ export function renderMaterial( el("span", { className: "m02-dfip__name", children: [ - el("span", { className: "m02-dfip__tag", text: kind }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: kind, + }), el("strong", { text: ` ${e.name}` }), - el("div", { className: "m02-dfip__muted", text: summaryOf(e, lg) }), + el("div", { className: "ui-text ui-text--muted", text: summaryOf(e, lg) }), note, ], }), @@ -435,7 +446,7 @@ export function renderMaterial( ); return row; }) - : [el("p", { className: "m02-dfip__muted", text: t("None") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("None") })]), ); }; @@ -455,7 +466,7 @@ export function renderMaterial( const drawBody = (): void => { if (mode === "fixed") { body.replaceChildren( - el("p", { className: "m02-dfip__muted", text: t("FixedHelp") }), + el("p", { className: "ui-text ui-text--muted", text: t("FixedHelp") }), itemList((it) => on.change( addEl(lg, { @@ -520,7 +531,7 @@ export function renderMaterial( onChange: (v) => ((how = v as "plain" | "template"), drawBody()), }); const tpl = templateBox(lg, () => void refresh()); - const defNote = el("p", { className: "m02-dfip__muted" }); + const defNote = el("p", { className: "ui-text ui-text--muted" }); const build = (): CfElementRef => { const sc = scope.get(); if (mode === "rule" && howBox.select.value === "template") { @@ -603,7 +614,7 @@ export function renderMaterial( ? [tpl.root] : pills.length ? [fieldBox.root, opBox.root, pillBox.root] - : [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]), + : [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })]), ] : [ el("h5", { text: t("Default") }), diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Rule.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Rule.ts index 83b527aca..778b4fbd2 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Rule.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Rule.ts @@ -21,6 +21,10 @@ import { variablePills, type GroupRuleField, } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Help: [ @@ -128,11 +132,11 @@ export function renderGroupRule( }, }); host.replaceChildren( - el("p", { className: "m02-dfip__muted", text: t("Help") }), + el("p", { className: "ui-text ui-text--muted", text: t("Help") }), name.root, ...(pills.length ? [field.root, pill.root, note, add] - : [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]), + : [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })]), ); if (pills.length) void refresh(); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Table.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Table.ts index 00a852a7a..00ba908e8 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Table.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Table.ts @@ -15,6 +15,11 @@ import { } from "@ui/ui_template_elements"; import type { CfGroup, CfLogic } from "./M02_DF_Model"; import { setTable } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; +import "@ui/ui_template_table.css"; const TEXT = { Book: ["적용 품셈", "Standard"], @@ -120,6 +125,7 @@ function previewOf(tb: RawTable, row?: number): HTMLElement { className: "m02-dfip__preview", children: [ el("table", { + className: "ui-table ui-table--data", children: [ el("tr", { children: cols.map((c) => el("th", { text: c })) }), ...body.map((r, i) => @@ -131,7 +137,7 @@ function previewOf(tb: RawTable, row?: number): HTMLElement { ], }), ...(tb.줄.length > body.length - ? [el("p", { className: "m02-dfip__muted", text: `… ${tb.줄.length} 줄` })] + ? [el("p", { className: "ui-text ui-text--muted", text: `… ${tb.줄.length} 줄` })] : []), ...(tb.주?.length ? [ @@ -172,7 +178,7 @@ export function renderTablePick( let seq = 0; const load = async (): Promise => { const mine = ++seq; - list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") })); + list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") })); try { const r = await get<{ sections: SectionItem[] }>("/sections", { book, @@ -184,7 +190,7 @@ export function renderTablePick( ...(r.sections.length ? r.sections.map((s) => { const b = el("button", { - className: "m02-dfip__row", + className: "m02-dfip__row ui-list-row ui-sidebar-row", attrs: { type: "button" }, children: [ el("strong", { text: `${s.section} ${s.title}` }), @@ -197,7 +203,7 @@ export function renderTablePick( b.addEventListener("click", () => void pickSection(s)); return b; }) - : [el("p", { className: "m02-dfip__muted", text: t("NoSection") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoSection") })]), ); } catch (e) { if (mine === seq) showToast(e instanceof Error ? e.message : t("Failed"), "error"); @@ -218,7 +224,7 @@ export function renderTablePick( ...(heads.length ? heads.map((h) => { const b = el("button", { - className: "m02-dfip__row", + className: "m02-dfip__row ui-list-row ui-sidebar-row", attrs: { type: "button" }, children: [ el("strong", { text: h.이름 }), @@ -242,7 +248,7 @@ export function renderTablePick( }); return b; }) - : [el("p", { className: "m02-dfip__muted", text: t("NoTable") })]), + : [el("p", { className: "ui-text ui-text--muted", text: t("NoTable") })]), ); } catch (e) { showToast(e instanceof Error ? e.message : t("Failed"), "error"); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Template.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Template.ts index f2cfa13ef..2898a7dbd 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Template.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Template.ts @@ -8,6 +8,10 @@ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createInputField, createSelectField, el } from "@ui/ui_template_elements"; import type { CfLogic, CfPort } from "./M02_DF_Model"; import { templateSlots, variablePills } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Template: ["검색어 틀 — 자리는 {이름} 으로", "Search template — slots as {name}"], @@ -68,8 +72,8 @@ export function templateBox(lg: CfLogic, onChange: () => void): TemplateBox { picks.set(n, sel.select); return sel.root; }) - : [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })] - : [el("p", { className: "m02-dfip__muted", text: t("NoSlot") })]), + : [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })] + : [el("p", { className: "ui-text ui-text--muted", text: t("NoSlot") })]), ); }; tpl.input.addEventListener("input", () => (draw(), onChange())); @@ -77,7 +81,11 @@ export function templateBox(lg: CfLogic, onChange: () => void): TemplateBox { return { root: el("div", { className: "m02-dfip__stack", - children: [el("p", { className: "m02-dfip__muted", text: t("Help") }), tpl.root, slotBox], + children: [ + el("p", { className: "ui-text ui-text--muted", text: t("Help") }), + tpl.root, + slotBox, + ], }), get: () => ({ template: tpl.input.value.trim(), diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Vars.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Vars.ts index 555ad4a9d..d88fe4d02 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Vars.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Vars.ts @@ -9,6 +9,10 @@ import { createButton, createInputField, createSelectField, el } from "@ui/ui_te import type { CfLogic, CfVariable } from "./M02_DF_Model"; import { NO_UNIT, createUnitPick } from "./M02_DF_UnitPick"; import { checkVariable, putVariable, removeVariable } from "./M02_DF_InputPanel_Build"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_badge.css"; +import "@ui/ui_template_list_row.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { List: ["변수 — 알약마다 하나", "Variables — one pill each"], @@ -201,7 +205,7 @@ export function renderVariables( ], }), ) - : [el("p", { className: "m02-dfip__muted", text: t("None") })]; + : [el("p", { className: "ui-text ui-text--muted", text: t("None") })]; host.replaceChildren(el("h5", { text: t("List") }), ...list, form()); }; draw(); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.css b/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.css index 867c22e06..346a5abae 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.css @@ -24,24 +24,13 @@ font-size: var(--text-body-sm); } -.m02-dflink-value { - padding: 2px var(--spacing-12); - border: 1px solid var(--color-border); - border-radius: var(--radius-pills); - background: var(--color-surface-raised); - font-weight: var(--font-weight-medium); -} - .m02-dflink-num { width: 7em; } +/* 겉 = 띠 알림 부품 */ .m02-dflink-warn { margin: 0; - padding: var(--spacing-8) var(--spacing-12); - border-left: 4px solid var(--color-warning); - background: var(--color-surface-raised); - font-weight: var(--font-weight-medium); } /* 알약 판 */ @@ -72,11 +61,3 @@ gap: var(--spacing-8); align-items: center; } - -.m02-dfpill-rule { - padding: 0 var(--spacing-8); - border: 1px dashed var(--color-border); - border-radius: var(--radius-pills); - color: var(--color-text-secondary); - font-size: var(--text-body-sm); -} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.ts b/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.ts index ac81f785a..3c7cc1e3b 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_LinkPanel.ts @@ -36,6 +36,9 @@ import { } from "./M02_DF_Detail"; import { suggestPlaces } from "./M02_DF_UnitPick"; import "./M02_DF_LinkPanel.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_banner.css"; +import "@ui/ui_template_badge.css"; const TEXT = { Title: ["선 규칙", "Line rule"], @@ -248,7 +251,8 @@ function lookupEditor(ctx: Ctx, rule: Extract): HTML select(ctx, colOpts, rule.col, (v) => put({ col: v }), tk("Take")), ), ); - if (!cols.length) out.push(el("p", { className: "m02-dflink-note", text: tk("NoHeader") })); + if (!cols.length) + out.push(el("p", { className: "ui-text ui-text--muted", text: tk("NoHeader") })); return out; } @@ -289,7 +293,7 @@ function gateValue(ctx: Ctx, c: CfGateCond, onPick: (v: number | string) => void if (range && Number.isFinite(n) && (n < range[0] || n > range[1])) { out.push( el("span", { - className: "m02-dflink-warn", + className: "ui-text ui-text--warning", text: `⚠ ${tk("OutRange", `${range[0]} ~ ${range[1]}`)}`, }), ); @@ -302,7 +306,9 @@ function gateEditor(ctx: Ctx, rule: Extract): HTMLElem const put = (conds: CfGateCond[], join = g.join): void => setRule(ctx, makeGate(conds, join)); const edit = (i: number, patch: Partial): void => put(g.conds.map((c, j) => (j === i ? { ...c, ...patch } : c))); - const out: HTMLElement[] = [el("p", { className: "m02-dflink-note", text: tk("GateHint") })]; + const out: HTMLElement[] = [ + el("p", { className: "ui-text ui-text--muted", text: tk("GateHint") }), + ]; g.conds.forEach((c, i) => { out.push( row( @@ -380,7 +386,7 @@ function gateEditor(ctx: Ctx, rule: Extract): HTMLElem function scaleEditor(ctx: Ctx, rule: Extract): HTMLElement[] { return [ - el("p", { className: "m02-dflink-note", text: tk("ScaleHint") }), + el("p", { className: "ui-text ui-text--muted", text: tk("ScaleHint") }), row( select( ctx, @@ -502,10 +508,18 @@ export function renderLinkPanel( parts.push( row( el("span", { className: "m02-dflink-label", text: tk("Before") }), - el("span", { className: "m02-dflink-value", text: pv.before }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: pv.before, + }), "→", el("span", { className: "m02-dflink-label", text: tk("After") }), - el("span", { className: "m02-dflink-value", text: pv.after }), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: pv.after, + }), ), row( el("span", { className: "m02-dflink-label", text: tk("Label") }), @@ -518,6 +532,10 @@ export function renderLinkPanel( if (!rule.conds.length) warns.push(tk("WarnLookup")); if (!rule.col) warns.push(tk("WarnTake")); } - parts.push(...warns.map((w) => el("p", { className: "m02-dflink-warn", text: `⚠ ${w}` }))); + parts.push( + ...warns.map((w) => + el("p", { className: "ui-banner ui-banner--warning m02-dflink-warn", text: `⚠ ${w}` }), + ), + ); host.replaceChildren(...parts); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.css b/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.css index 27bc226b2..fb7f4e1b4 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.css @@ -53,23 +53,8 @@ } .m02-dfout__input { - box-sizing: border-box; width: 160px; max-width: 100%; - height: var(--control-height-sm); - padding: 3px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; - font-size: var(--text-body-sm); -} - -.m02-dfout__warn { - margin: var(--spacing-4) 0 0; - color: var(--color-danger); - font-size: var(--text-body-sm); } .m02-dfout__muted { @@ -78,12 +63,6 @@ font-size: var(--text-caption); } -.m02-dfout__msg { - margin: var(--spacing-4) 0 0; - color: var(--color-danger); - font-size: var(--text-caption); -} - .m02-dfout__round { margin: var(--spacing-8) 0; color: var(--color-text-body); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.ts b/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.ts index e9c050c5b..1e8e42312 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_OutputPanel.ts @@ -13,6 +13,7 @@ import { portsOut, type CfBox, type CfCost, type CfLogic, type CfRound } from ". import { defaultItemRound, roundText } from "./M02_DF_Round"; import { createUnitPick, suggestPlaces } from "./M02_DF_UnitPick"; import "./M02_DF_OutputPanel.css"; +import "@ui/ui_template_text.css"; const TEXT = { Title: ["출력", "Output"], @@ -83,7 +84,7 @@ export function renderOutputPanel( host.replaceChildren(); return; } - const msg = el("p", { className: "m02-dfout__msg" }); + const msg = el("p", { className: "ui-text ui-text--error" }); const emit = (make: () => CfLogic): void => { msg.textContent = ""; try { @@ -133,14 +134,14 @@ export function renderOutputPanel( } else { got.replaceChildren( el("strong", { text: to("Got") }), - el("p", { className: "m02-dfout__warn", text: to("NoLink") }), + el("p", { className: "ui-text ui-text--error", text: to("NoLink") }), ); } /* 이름 · 비용 종류 · 단위 */ const nameBox = el("input", { - className: "m02-dfout__input", - attrs: { type: "text", "aria-label": to("Name") }, + className: "ui-input m02-dfout__input", + attrs: { "data-size": "32", type: "text", "aria-label": to("Name") }, }); nameBox.value = box.name; nameBox.addEventListener("change", () => { @@ -283,7 +284,9 @@ export function renderOutputPanel( ], }), roundBlock, - ...(cost === "값" ? [el("p", { className: "m02-dfout__muted", text: to("PlainHint") })] : []), + ...(cost === "값" + ? [el("p", { className: "ui-text ui-text--muted", text: to("PlainHint") })] + : []), msg, ); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Panel.ts b/M02_MasterTemplete/DataFlow/M02_DF_Panel.ts index ec3901ac3..efddb4fbf 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Panel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Panel.ts @@ -16,6 +16,7 @@ import type { CfLogic } from "./M02_DF_Model"; import { createPaper } from "./M02_DF_Paper"; import { renderStandards } from "./M02_DF_Standards"; import "./M02_DF_Panel.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Big: ["크게 보기", "Full screen"], @@ -156,7 +157,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane attrs: { open: "" }, children: [el("summary", { text: tf("Cost") })], }); - const tools = el("div", { className: "m02-dfp__tools", children: [undo] }); + const tools = el("div", { className: "ui-toolbar m02-dfp__tools", children: [undo] }); const open = el("div", { className: "m02-dfp__open", children: [head, tools, note, diagram, detail, costBox], diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Paper.ts b/M02_MasterTemplete/DataFlow/M02_DF_Paper.ts index 40bd21d79..9f9682304 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Paper.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Paper.ts @@ -32,6 +32,7 @@ import { type DfPick, } from "./M02_DF_Entry_Detail"; import "./M02_DF_Paper.css"; +import "@ui/ui_template_text.css"; const TEXT = { Undo: ["실행 취소", "Undo"], @@ -133,7 +134,8 @@ export function createPaper(opts: PaperOpts): Paper { }); lock.disabled = readOnly; detail.body.append(lock); - if (readOnly) detail.root.prepend(el("p", { className: "m02-df__muted", text: tp("ReadOnly") })); + if (readOnly) + detail.root.prepend(el("p", { className: "ui-text ui-text--muted", text: tp("ReadOnly") })); const evalNow = (): CfEvalResult => { try { diff --git a/M02_MasterTemplete/DataFlow/M02_DF_PillPanel.ts b/M02_MasterTemplete/DataFlow/M02_DF_PillPanel.ts index deff36790..cb96d913f 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_PillPanel.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_PillPanel.ts @@ -22,6 +22,9 @@ import { type DetailOptions, } from "./M02_DF_Detail"; import "./M02_DF_LinkPanel.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_banner.css"; +import "@ui/ui_template_badge.css"; const TEXT = { Name: ["이름", "Name"], @@ -55,7 +58,7 @@ function steps( empty: string, onBox?: (id: string) => void, ): HTMLElement { - if (!list.length) return el("p", { className: "m02-dflink-note", text: empty }); + if (!list.length) return el("p", { className: "ui-text ui-text--muted", text: empty }); const pill = (box: string, name: string): HTMLElement | string => onBox ? createButton({ @@ -75,7 +78,14 @@ function steps( pill(link.from.box, link.from.name), "→", ...(s.rule - ? [el("span", { className: "m02-dfpill-rule", text: `(${labelOf(s.rule)})` }), "→"] + ? [ + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: `(${labelOf(s.rule)})`, + }), + "→", + ] : []), pill(link.to.box, link.to.name), ] @@ -121,7 +131,14 @@ export function renderPillPanel( ], }), line(tp("Name"), port), - line(tp("Value"), el("span", { className: "m02-dflink-value", text: showValue(value) })), + line( + tp("Value"), + el("span", { + className: "ui-badge ui-badge--pill ui-badge--neutral", + attrs: { "data-size": "20" }, + text: showValue(value), + }), + ), line(tp("Unit"), unit || "-"), el("div", { children: [ @@ -139,8 +156,13 @@ export function renderPillPanel( children: [ el("div", { className: "m02-dflink-label", text: tp("Breaks") }), ...(breaks.length - ? breaks.map((b) => el("p", { className: "m02-dflink-warn", text: `⚠ ${b}` })) - : [el("p", { className: "m02-dflink-note", text: tp("NoBreak") })]), + ? breaks.map((b) => + el("p", { + className: "ui-banner ui-banner--warning m02-dflink-warn", + text: `⚠ ${b}`, + }), + ) + : [el("p", { className: "ui-text ui-text--muted", text: tp("NoBreak") })]), ], }), ); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_RoundRow.ts b/M02_MasterTemplete/DataFlow/M02_DF_RoundRow.ts index c1bf3fad8..b7580b380 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_RoundRow.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_RoundRow.ts @@ -7,6 +7,7 @@ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, el } from "@ui/ui_template_elements"; import type { CfRound } from "./M02_DF_Model"; +import "@ui/ui_template_text.css"; const TEXT = { Label: ["끝수", "Rounding"], @@ -65,7 +66,7 @@ export function createRoundRow(opts: RoundRowOptions): HTMLElement { }); if (opts.suggest !== null && !now && !opts.readOnly) { row.append( - el("span", { className: "m02-dfpanel-note", text: tr("Suggest", opts.suggest) }), + el("span", { className: "ui-text ui-text--muted", text: tr("Suggest", opts.suggest) }), createButton({ label: tr("Apply"), variant: "ghost", diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Standards.css b/M02_MasterTemplete/DataFlow/M02_DF_Standards.css index ca3351b9c..74f63e5ba 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Standards.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Standards.css @@ -16,30 +16,13 @@ margin: 0 auto 0 0; } -.m02-df-std__state, -.m02-df-std__hint, .m02-df-std__line { - color: var(--color-text-muted, #6b7280); -} - -.m02-df-std__hint { - margin: 0; -} - -.m02-df-std__scroll { - overflow-x: auto; -} - -.m02-df-std__table { - border-collapse: collapse; - width: 100%; + color: var(--color-text-muted); } +/* 겉 = 표 부품 데이터표(ui-table-wrap) · 여기는 줄바꿈 막기 · 누르는 칸만 */ .m02-df-std__table th, .m02-df-std__table td { - border: 1px solid var(--color-border, #e5e7eb); - padding: 2px 6px; - text-align: left; white-space: nowrap; } @@ -47,10 +30,6 @@ cursor: pointer; } -.m02-df-std__table th { - background: var(--color-surface-muted, rgba(127, 127, 127, 0.08)); -} - .m02-df-std__notes { margin: 0; padding-left: 0; diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Standards.ts b/M02_MasterTemplete/DataFlow/M02_DF_Standards.ts index 969a92a01..6bd1a8a71 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Standards.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Standards.ts @@ -15,6 +15,8 @@ import { cfStandards, cfStandardsReset, cfStandardsSave, type CfStandards } from import unitDefault from "./_standard/단위표준.json"; import moneyDefault from "./_standard/금액단위표준.json"; import "./M02_DF_Standards.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_table.css"; const TEXT = { Title: ["표준 표", "Standards"], @@ -198,7 +200,7 @@ export function renderStandards( ): void { let work = clone(current); let dirty = false; - const state = el("span", { className: "m02-df-std__state" }); + const state = el("span", { className: "ui-text ui-text--muted m02-df-std__state" }); const save = createButton({ label: tx("Save"), size: "sm", onClick: () => void doSave() }); const reset = createButton({ label: tx("Reset"), @@ -250,10 +252,10 @@ export function renderStandards( const table = (heads: string[], rows: HTMLElement[][]): HTMLElement => el("div", { - className: "m02-df-std__scroll", + className: "ui-table-wrap m02-df-std__scroll", children: [ el("table", { - className: "m02-df-std__table", + className: "ui-table ui-table--data m02-df-std__table", children: [ el("thead", { children: [el("tr", { children: heads.map((h) => el("th", { text: h })) })], @@ -350,7 +352,7 @@ export function renderStandards( save, ], }), - el("p", { className: "m02-df-std__hint", text: `${tx("Source")} · ${tx("Hint")}` }), + el("p", { className: "ui-text ui-text--muted", text: `${tx("Source")} · ${tx("Hint")}` }), body, ], }), diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Summary.css b/M02_MasterTemplete/DataFlow/M02_DF_Summary.css index a4f155260..b84b349fb 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Summary.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Summary.css @@ -41,30 +41,12 @@ .m02-dfsum__scroll { max-width: 100%; - overflow-x: auto; -} - -.m02-dfsum__table { - width: 100%; - min-width: 360px; margin-top: var(--spacing-4); - border-collapse: collapse; - font-size: var(--text-body-sm); } -.m02-dfsum__table th, -.m02-dfsum__table td { - padding: var(--spacing-4) var(--spacing-8); - border: 1px solid var(--color-border); - text-align: left; - vertical-align: middle; -} - -.m02-dfsum__table th { - background: var(--color-surface); - color: var(--color-text-secondary); - font-weight: 600; - white-space: nowrap; +/* 겉 = 표 부품 데이터표(ui-table-wrap) · 여기는 최소 폭 · 수 칸 오른쪽만 */ +.m02-dfsum__table { + min-width: 360px; } .m02-dfsum__table td.m02-dfsum__num { @@ -91,42 +73,3 @@ color: var(--color-text-secondary); font-size: var(--text-body-sm); } - -.m02-dfsum__input { - box-sizing: border-box; - width: 140px; - height: var(--control-height-sm); - padding: 3px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; - font-size: var(--text-body-sm); -} - -.m02-dfsum__muted { - margin: 0; - color: var(--color-text-muted); - font-size: var(--text-caption); -} - -.m02-dfsum__msg { - margin: var(--spacing-4) 0 0; - color: var(--color-danger); - font-size: var(--text-caption); -} - -.m02-dfsum__problems { - margin: 0 0 var(--spacing-12); - padding: var(--spacing-8) var(--spacing-12); - border-left: 4px solid var(--color-danger); - background: var(--color-surface); - color: var(--color-text-body); - font-size: var(--text-body-sm); -} - -.m02-dfsum__problems ul { - margin: var(--spacing-4) 0 0; - padding-left: var(--spacing-16); -} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts b/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts index 148e15827..dd6dc9089 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts @@ -22,6 +22,8 @@ import { } from "./M02_DF_Round"; import { renderVerify, type CfSel } from "./M02_DF_Verify"; import "./M02_DF_Summary.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_table.css"; const TEXT = { Per: ["{per} 당", "per {per}"], @@ -115,10 +117,10 @@ export function renderSummary( children: [ el("strong", { text: title }), el("div", { - className: "m02-dfsum__scroll", + className: "ui-table-wrap m02-dfsum__scroll", children: [ el("table", { - className: "m02-dfsum__table", + className: "ui-table ui-table--data m02-dfsum__table", children: [ el("thead", { children: [ @@ -162,14 +164,14 @@ export function renderSummary( children: [ el("strong", { text: ts("Plain") }), ...plainValues.map(([k, v]) => - el("p", { className: "m02-dfsum__muted", text: `${k} · ${showValue(v)}` }), + el("p", { className: "ui-text ui-text--muted", text: `${k} · ${showValue(v)}` }), ), ], }), ] : []), el("p", { - className: "m02-dfsum__muted", + className: "ui-text ui-text--muted", text: `${ts("RoundTotal")}: ${roundText(sumRound)}`, }), verifyHost, diff --git a/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.css b/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.css index cf2f43126..2893ed5b3 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.css @@ -27,30 +27,15 @@ overflow-y: auto; } +/* 단위 줄 = 목록 줄 부품 · 화살표 키로 고른 줄(is-on)만 마우스 꼴 */ .m02-df-unitpick__item { - display: block; width: 100%; - padding: var(--spacing-4) var(--spacing-8); - border: 0; - border-radius: var(--radius-sm); - background: transparent; - color: inherit; - font: inherit; - text-align: left; - cursor: pointer; } -.m02-df-unitpick__item.is-on, -.m02-df-unitpick__item:hover { - background: var(--color-surface-raised); +.m02-df-unitpick__item.is-on { + background: var(--color-hover); } .m02-df-unitpick__item.is-now { font-weight: var(--font-weight-bold); } - -.m02-df-unitpick__empty { - padding: var(--spacing-8); - color: var(--color-text-muted); - font-size: var(--text-body-sm); -} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.ts b/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.ts index ed0cd9dd0..06c69e67d 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_UnitPick.ts @@ -10,6 +10,8 @@ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, el } from "@ui/ui_template_elements"; import { cfStandards } from "./M02_DF_Api"; import "./M02_DF_UnitPick.css"; +import { createEmpty } from "@ui/ui_template_empty"; +import "@ui/ui_template_list_row.css"; const TEXT = { None: ["없음", "none"], @@ -139,14 +141,14 @@ export function createUnitPick(opts: UnitPickOptions): HTMLElement { ...(shown.length ? shown.map((u, i) => { const b = el("button", { - className: `m02-df-unitpick__item${i === at ? " is-on" : ""}${u === (opts.value || NO_UNIT) ? " is-now" : ""}`, + className: `ui-list-row ui-sidebar-row m02-df-unitpick__item${i === at ? " is-on" : ""}${u === (opts.value || NO_UNIT) ? " is-now" : ""}`, text: label(u), attrs: { type: "button", role: "option" }, }); b.addEventListener("click", () => pick(u)); return el("li", { children: [b] }); }) - : [el("li", { className: "m02-df-unitpick__empty", text: tu("Empty") })]), + : [el("li", { children: [createEmpty({ text: tu("Empty"), small: true })] })]), ); }; const close = (): void => { diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Verify.css b/M02_MasterTemplete/DataFlow/M02_DF_Verify.css index 7f5dd2c53..48e29cc68 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Verify.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Verify.css @@ -3,10 +3,9 @@ color: var(--color-text-body); } +/* 끊긴 곳 = 띠 알림 부품(위험) · 여기는 제목 · 목록 쌓기만 */ .m02-dfver__breaks { - padding: var(--spacing-8) var(--spacing-12); - border-left: 4px solid var(--color-danger); - background: var(--color-surface); + flex-direction: column; } .m02-dfver__title { @@ -33,17 +32,8 @@ } .m02-dfver__input { - box-sizing: border-box; width: 160px; max-width: 100%; - height: var(--control-height-sm); - padding: 3px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; - font-size: var(--text-body-sm); } .m02-dfver__item { @@ -111,12 +101,6 @@ font-size: var(--text-caption); } -.m02-dfver__msg { - margin: var(--spacing-4) 0 0; - color: var(--color-danger); - font-size: var(--text-caption); -} - /* 쉬는 갈래 — 문제가 아니라 회색으로 */ .m02-dfver__rest { padding: var(--spacing-8) var(--spacing-12); diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts b/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts index b19f747bb..08d306253 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts @@ -20,6 +20,9 @@ import { type CfVariable, } from "./M02_DF_Model"; import "./M02_DF_Verify.css"; +import "@ui/ui_template_text.css"; +import "@ui/ui_template_banner.css"; +import "@ui/ui_template_toolbar.css"; const TEXT = { Breaks: ["끊긴 곳", "Broken spots"], @@ -133,8 +136,9 @@ function sampleField( }).root; } else { const box = el("input", { - className: "m02-dfver__input", + className: "ui-input m02-dfver__input", attrs: { + "data-size": "32", type: "text", inputmode: v.type === "number" ? "decimal" : "text", "aria-label": v.name, @@ -168,15 +172,20 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML const panel = el("div", { className: "m02-dfver__panel" }); panel.hidden = true; const q = el("input", { - className: "m02-dfver__input", - attrs: { type: "text", placeholder: tv("Search"), "aria-label": tv("Search") }, + className: "ui-input m02-dfver__input", + attrs: { + "data-size": "32", + type: "text", + placeholder: tv("Search"), + "aria-label": tv("Search"), + }, }); q.value = e.scope?.찾는말 ?? ""; const list = el("ul", { className: "m02-dfver__list" }); - const pager = el("div", { className: "m02-dfver__pager" }); + const pager = el("div", { className: "ui-toolbar m02-dfver__pager" }); let page = 1; const load = async (): Promise => { - list.replaceChildren(el("li", { className: "m02-dfver__muted", text: tv("Loading") })); + list.replaceChildren(el("li", { className: "ui-text ui-text--muted", text: tv("Loading") })); const got = await findMaterials({ q: q.value.trim(), sub: e.scope?.구분, @@ -199,7 +208,7 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML ], }), ) - : [el("li", { className: "m02-dfver__muted", text: tv("NoHit") })]), + : [el("li", { className: "ui-text ui-text--muted", text: tv("NoHit") })]), ); pager.replaceChildren( createButton({ @@ -247,7 +256,7 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML }); panel.append( el("div", { - className: "m02-dfver__search", + className: "ui-toolbar m02-dfver__search", children: [ q, createButton({ @@ -326,7 +335,7 @@ export function renderVerify( on: CfVerifyOn, opts: { values?: CfValues; logics?: CfEvalOptions["logics"] } = {}, ): void { - const msg = el("p", { className: "m02-dfver__msg" }); + const msg = el("p", { className: "ui-text ui-text--error" }); const safe = (make: () => CfLogic): void => { msg.textContent = ""; try { @@ -355,7 +364,7 @@ export function renderVerify( }), ), }), - el("p", { className: "m02-dfver__muted", text: tv(hint) }), + el("p", { className: "ui-text ui-text--muted", text: tv(hint) }), ]; const problems = spots.filter((s) => s.level !== "쉼"); const resting = spots.filter((s) => s.level === "쉼"); @@ -363,7 +372,7 @@ export function renderVerify( ...(problems.length ? [ el("div", { - className: "m02-dfver__block m02-dfver__breaks", + className: "ui-banner ui-banner--danger m02-dfver__block m02-dfver__breaks", children: [el("strong", { text: tv("Breaks") }), ...spotList(problems, "BreaksHint")], }), ] @@ -427,8 +436,13 @@ export function renderVerify( const logics = designs.flatMap((d) => { if (d.kind !== "다른 로직") return []; const input = el("input", { - className: "m02-dfver__input", - attrs: { type: "text", placeholder: tv("OwnSample"), "aria-label": d.port }, + className: "ui-input m02-dfver__input", + attrs: { + "data-size": "32", + type: "text", + placeholder: tv("OwnSample"), + "aria-label": d.port, + }, }); input.value = d.example === null || d.example === undefined ? "" : String(d.example); input.addEventListener("change", () => safe(() => putLogicExample(d.box, d.port, input.value))); @@ -479,7 +493,7 @@ export function renderVerify( children: [ el("strong", { className: "m02-dfver__title", text: tv("Design") }), ...design, - el("p", { className: "m02-dfver__muted", text: tv("DesignHint") }), + el("p", { className: "ui-text ui-text--muted", text: tv("DesignHint") }), msg, ], }), diff --git a/M02_MasterTemplete/M02_Labor_Values.css b/M02_MasterTemplete/M02_Labor_Values.css index 02d9361ac..0f1a2bd6e 100644 --- a/M02_MasterTemplete/M02_Labor_Values.css +++ b/M02_MasterTemplete/M02_Labor_Values.css @@ -19,12 +19,6 @@ min-width: 0; } -.m02-inputval__missing { - margin: 0; - color: var(--color-text-secondary); - font-size: var(--text-caption); -} - .m02-inputval__logic { display: flex; flex-direction: column; @@ -87,10 +81,9 @@ font-weight: 600; } +/* 겉 = 보조 글 부품(ui-text--error) · 여기는 한 줄 차지만 */ .m02-inputval__error { flex: 1 0 100%; - color: var(--color-danger); - font-size: var(--text-caption); } .m02-inputval__control[aria-invalid="true"] { diff --git a/M02_MasterTemplete/M02_Labor_Values.ts b/M02_MasterTemplete/M02_Labor_Values.ts index e99da649e..5df45859a 100644 --- a/M02_MasterTemplete/M02_Labor_Values.ts +++ b/M02_MasterTemplete/M02_Labor_Values.ts @@ -81,7 +81,7 @@ export function createValueControl( : "" : String(value); control.classList.toggle("is-auto", empty); - const error = el("span", { className: "m02-inputval__error" }); + const error = el("span", { className: "ui-text ui-text--error m02-inputval__error" }); const out: ValueControl = { control, error, wasBad: false }; control.addEventListener(control instanceof HTMLSelectElement ? "change" : "input", () => { const raw = control.value; @@ -266,7 +266,7 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand const missing = data.list.빠진로직.length ? [ el("p", { - className: "m02-inputval__missing", + className: "ui-text ui-text--muted", text: fill(VALUE_TEXT.missing, { value: data.list.빠진로직.join(", ") }), }), ] diff --git a/M02_MasterTemplete/M02_MasterTemplete_AreaCellPick.ts b/M02_MasterTemplete/M02_MasterTemplete_AreaCellPick.ts index bcc23497c..0dc755827 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_AreaCellPick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_AreaCellPick.ts @@ -52,7 +52,9 @@ export function mountAreaSheet(host: HTMLElement, opts: AreaSheetOptions): AreaS let picked = "A1"; let moved = false; // 누른 사이 고름이 바뀜 — 머리 · 굴림 막대를 눌렀다 뗀 것은 안 고름 let book: Workbook | null = null; - host.replaceChildren(el("p", { className: "m02-cellpick__now", text: TEXT.loading })); + host.replaceChildren( + el("p", { className: "ui-text ui-text--muted m02-cellpick__now", text: TEXT.loading }), + ); const choose = async (): Promise => { const at = picked; // 엔진 조각은 고를 때만(정적 import 금지 — 계약 `spreadsheet_types.ts` 머리) @@ -101,7 +103,7 @@ export function mountAreaSheet(host: HTMLElement, opts: AreaSheetOptions): AreaS if (done) return; host.replaceChildren( el("p", { - className: "m02-cellpick__now", + className: "ui-text ui-text--muted m02-cellpick__now", text: fill(TEXT.failed, error instanceof Error ? error.message : ""), }), ); diff --git a/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts b/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts index b1ae795f5..d4479a14d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts @@ -89,7 +89,7 @@ export function createAreaDrawing( /** 연결별로 마지막에 도면에 비춘 칸 글(없으면 저장된 `값`) */ const pushed = new Map>(); const note = (text: string): void => - stage.replaceChildren(el("p", { className: "m02-apanel__note", text })); + stage.replaceChildren(el("p", { className: "ui-text ui-text--muted m02-apanel__note", text })); const send = createButton({ label: TEXT.send, size: "sm", variant: "ghost" }); send.title = TEXT.sendTip; diff --git a/M02_MasterTemplete/M02_MasterTemplete_AreaLink.ts b/M02_MasterTemplete/M02_MasterTemplete_AreaLink.ts index 244349a77..ffb23acf6 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_AreaLink.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_AreaLink.ts @@ -9,6 +9,7 @@ import { createButton, createInputField, el } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; import type { CalcValue, LinkInfo, SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet"; +import { createText } from "@ui/ui_template_text"; const TEXT = { title: "칸 지정 — {value}", @@ -62,7 +63,7 @@ export function openAreaLink(opts: AreaLinkOptions): void { closeLabel: TEXT.close, dialogClass: "m02-modal m02-area-link", mount: (body) => { - const now = el("p", { className: "m02-area-link__now" }); + const now = el("p", { className: "ui-text ui-text--muted m02-area-link__now" }); const paint = (): void => { const text = linkedText(opts); now.textContent = `${TEXT.now} · ${text ?? TEXT.none}`; @@ -118,9 +119,7 @@ export function openAreaLink(opts: AreaLinkOptions): void { paint(); body.append( now, - ...(opts.readOnly - ? [el("p", { className: "m02-struct__empty", text: TEXT.readOnly })] - : []), + ...(opts.readOnly ? [createText(TEXT.readOnly)] : []), el("div", { className: "m02-area-link__row", children: [pick, unlink] }), el("div", { className: "m02-area-link__row", children: [address.root, link] }), ); diff --git a/M02_MasterTemplete/M02_MasterTemplete_AreaPanel.css b/M02_MasterTemplete/M02_MasterTemplete_AreaPanel.css index 8d8a682f7..313e96b03 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_AreaPanel.css +++ b/M02_MasterTemplete/M02_MasterTemplete_AreaPanel.css @@ -19,13 +19,11 @@ .m02-apanel__note { margin: var(--spacing-12); - color: var(--color-text-secondary); } /* 칸 지정 모달 — 지금 연결 한 줄 · [지금 고른 칸으로] [풀기] · 주소 칸 [잇기] */ .m02-area-link__now { margin: 0 0 var(--spacing-12); - color: var(--color-text-secondary); } .m02-area-link__row { @@ -54,7 +52,6 @@ .m02-cellpick__now { margin: 0 0 var(--spacing-8); - color: var(--color-text-secondary); } .m02-cellpick__sheet { diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.css b/M02_MasterTemplete/M02_MasterTemplete_Drawing.css index 157e61d99..1ef56a5a5 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.css @@ -29,11 +29,9 @@ flex: 1 1 0; } +/* 겉 = 도구줄 부품(ui-toolbar) · 여기는 줄 사이 간격만 */ .m02-drawing__toolbar { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--spacing-8) var(--spacing-16, 16px); + gap: var(--spacing-8) var(--spacing-16); } .m02-drawing__title { @@ -77,7 +75,7 @@ margin-top: 0; width: 23rem; max-width: calc(100vw - 16px); - box-shadow: 0 4px 16px rgb(0 0 0 / 25%); + box-shadow: var(--shadow-lg); } /* 탭 줄(82-1) — 그림 칸에 붙임 */ diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts index dd9de6500..33e31340d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts @@ -155,7 +155,8 @@ export function mountDrawingTemplate( const root = document.createElement("div"); root.className = "m02-drawing"; const toolbar = document.createElement("div"); - toolbar.className = "m02-drawing__toolbar"; + toolbar.className = "ui-toolbar m02-drawing__toolbar"; // 도구줄 부품 겉 + toolbar.setAttribute("role", "toolbar"); // 작도 영역 — 도면 내용이 이 칸 한가운데에 놓인다. 제목 줄에는 값이 다 보이는 단추 하나, // 누르면 칸 넷이 작은 창으로 펼쳐진다(칸은 창을 닫아도 그대로 살아 있음). diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo.css b/M02_MasterTemplete/M02_MasterTemplete_Hopyo.css index 19eff81b0..2e14e8f32 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo.css @@ -19,16 +19,8 @@ gap: var(--spacing-8); } -.m02-hopyo__note { - margin: 0; - color: var(--color-text-secondary); - font-size: var(--text-body-sm); -} - +/* 겉 = 보조 글 부품(ui-text--warning) · 여기는 줄바꿈 살림만 */ .m02-hopyo__warn { - margin: 0; - color: var(--color-warning); - font-size: var(--text-body-sm); white-space: pre-wrap; } @@ -41,7 +33,7 @@ } .m02-hopyo__row:hover { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .m02-hopyo__num { @@ -300,7 +292,7 @@ .m02-hopyo-sheet__item.is-current { border-left-color: var(--color-accent); - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .m02-hopyo-sheet__no { @@ -318,7 +310,7 @@ } /* 목록 숫자 — 다 연결(359-2)이면 녹색 · [수정] 은 번호 바로 오른쪽 · 줄 높이 안 커짐 */ -.m02-hopyo-sheet__item .m02-hopyo__note.is-linked { +.m02-hopyo-sheet__item .ui-text.is-linked { color: var(--color-success); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Check.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Check.ts index 3fbf4520d..c6cc53813 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Check.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Check.ts @@ -194,7 +194,8 @@ const why = (error: unknown): string => (error instanceof Error ? error.message const won = (v: number | null | undefined): string => v === null || v === undefined ? "" : v.toLocaleString("ko-KR"); const fail = (error: unknown): void => showToast(`실패 — ${why(error)}`, "error"); -const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text }); +const note = (text: string): HTMLElement => + el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } }); const td = (text: string, cls = ""): HTMLElement => el("td", { text, className: cls, attrs: { title: text } }); const openSheet = (o: HopyoSheetOptions): void => @@ -503,7 +504,7 @@ export function buildHopyoCheck(opt: CheckOptions): HTMLElement { tools.push(all, go); } const head = el("div", { - className: "m02-hopyo__bar", + className: "ui-toolbar m02-hopyo__bar", children: [ el("strong", { text: `제${h.번}호표 ${h.이름}` }), note(h.규격), @@ -545,7 +546,7 @@ export function buildHopyoCheck(opt: CheckOptions): HTMLElement { }); return [ el("label", { - className: "m02-hopyo__bar m02-hopyo__whole", + className: "ui-toolbar m02-hopyo__bar m02-hopyo__whole", children: [ box, note( diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_CheckRows.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_CheckRows.ts index 54b241eb5..5b3d90d3a 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_CheckRows.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_CheckRows.ts @@ -149,7 +149,7 @@ function nameNotes(line: HopyoLine, parts: HopyoPart[], price: LinePrice | null) ].filter(Boolean) as string[]; return [ ...opts.map((t) => small(t, "m02-labrow__note m02-hopyo__opts")), - ...why.map((t) => small(`셈 못 함: ${t}`, "m02-labrow__empty")), + ...why.map((t) => small(`셈 못 함: ${t}`, "ui-text ui-text--error")), ...(parts.some((p) => p.호표 !== undefined) ? [small("하위 호표 = 만든 뒤 셈")] : []), ]; } @@ -216,7 +216,7 @@ function logicRow(line: HopyoLine, ctx: RowsContext): HTMLElement { el("td", { className: "m02-labrow__name m02-hopyo__parts", children: [ - ...(parts.length ? partLines(line, parts, ctx) : [small("—", "m02-labrow__muted")]), + ...(parts.length ? partLines(line, parts, ctx) : [small("—", "ui-text ui-text--muted")]), ...nameNotes(line, parts, price), ], }), diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Choose.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Choose.ts index 1aa2637f7..4d8f6c690 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Choose.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Choose.ts @@ -30,7 +30,8 @@ const FOLD_TEXT: Record = { "지역 변형": "하나 고르면 그 지역 · 「전국」으로 되돌림은 위", 숫자: "이름 · 출처 안 닮음 · 숫자 지문만", }; -const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text }); +const note = (text: string): HTMLElement => + el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } }); const num = (v: number): string => String(Math.round(v * 10000) / 10000); /** 대상 글 — 「이름 [단위]」 · 자재는 규격도 · 호표 = 「제N호표」 */ @@ -277,7 +278,7 @@ export function openHopyoChoose(opt: ChooseOptions): void { const excel = qtyText(line.맞춤.품?.엑셀); const basis = line.근거; const head = el("div", { - className: "m02-hopyo__bar m02-hopyo__source", + className: "ui-toolbar m02-hopyo__bar m02-hopyo__source", children: [ ...(excel ? [note(`엑셀 품 — ${excel}`)] : []), ...(basis ? [note(`엑셀 근거 — ${sourceText(basis)}`)] : []), @@ -323,7 +324,7 @@ export function openHopyoChoose(opt: ChooseOptions): void { : "가까운 것 없음 — 아래 검색", ), ...sections, - el("div", { className: "m02-hopyo__bar", children: [together, ...more] }), + el("div", { className: "ui-toolbar m02-hopyo__bar", children: [together, ...more] }), ); }, }); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Ledger.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Ledger.ts index b2d752cc0..3c8c4db34 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Ledger.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Ledger.ts @@ -122,7 +122,8 @@ export const filterRows = (rows: LedgerView[], q: string, kind: string): LedgerV const why = (error: unknown): string => (error instanceof Error ? error.message : String(error)); const td = (text: string, cls = ""): HTMLElement => el("td", { text, className: cls, attrs: { title: text } }); -const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text }); +const note = (text: string): HTMLElement => + el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } }); export interface LedgerOptions { scope: HopyoScope; @@ -323,7 +324,7 @@ export function buildHopyoLedger(opt: LedgerOptions): HTMLElement { } root.replaceChildren( el("div", { - className: "m02-hopyo__bar", + className: "ui-toolbar m02-hopyo__bar", children: [ el("strong", { text: LEDGER_TITLE }), note(`${LEDGER_HINT} · 사전조합 생성 때 남음 · 아래 층이 이김`), diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Sheet.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Sheet.ts index 6702b1564..9711005cd 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Sheet.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Sheet.ts @@ -80,7 +80,8 @@ const DANSAN = "단가산출"; const why = (error: unknown): string => (error instanceof Error ? error.message : String(error)); const won = (v: number | null | undefined): string => v === null || v === undefined ? "" : v.toLocaleString("ko-KR"); -const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text }); +const note = (text: string): HTMLElement => + el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } }); /** 목록 숫자 「연결된 줄 / 연결할 줄」(358-3 두 칸 · 없으면 옛 맞춤 [제안, 줄]) — 다 연결(같고 0 넘음)이면 참 · * 0/0 은 색 없음 */ @@ -144,8 +145,14 @@ export function openHopyoSheet(opt: HopyoSheetOptions): void { const items = new Map(); const target = opt.target ?? opt.scope; - const head = el("div", { className: "m02-hopyo__bar", children: [note("엑셀을 읽는 중…")] }); - const warn = el("p", { className: "m02-hopyo__warn" }); + const head = el("div", { + className: "ui-toolbar m02-hopyo__bar", + children: [note("엑셀을 읽는 중…")], + }); + const warn = el("p", { + className: "ui-text ui-text--warning m02-hopyo__warn", + attrs: { "data-text": "14" }, + }); warn.hidden = true; const left = el("div", { className: "m02-hopyo-sheet__grid", @@ -165,7 +172,10 @@ export function openHopyoSheet(opt: HopyoSheetOptions): void { className: "m02-hopyo-sheet__side", children: [ search, - el("div", { className: "m02-hopyo__bar", children: [count, ...(opt.canWrite ? [go] : [])] }), + el("div", { + className: "ui-toolbar m02-hopyo__bar", + children: [count, ...(opt.canWrite ? [go] : [])], + }), list, result, ], diff --git a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Side.ts b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Side.ts index 72d9c4a88..f0d733669 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Side.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Side.ts @@ -10,6 +10,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { listHopyo, pageScope, uploadHopyo, type HopyoFile } from "./M02_MasterTemplete_Hopyo_Api"; import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplete_Hopyo_Ledger"; +import { createEmpty } from "@ui/ui_template_empty"; export const HOPYO_TITLE = "일위대가 실무자료"; @@ -94,7 +95,7 @@ export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection { row.addEventListener("click", () => void showSheet(f)); return row; }) - : [el("p", { className: "m02-side__empty", text: "올린 실무자료 없음" })]), + : [createEmpty({ text: "올린 실무자료 없음", small: true })]), ); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts index 595e930a1..b8d8f49f9 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts @@ -37,6 +37,7 @@ import { import type { ItemPart, ItemSource } from "./M02_MasterTemplete_ItemSource"; import type { LaborDetailHandle } from "./M02_MasterTemplete_Labor_Detail"; import "./M02_MasterTemplete_Structure.css"; +import { createBadge } from "@ui/ui_template_badge"; /** 집계표 열 하나에 딸린 문서 셋 — 번호 칸 · 이름 */ export type EditKind = ItemPart; @@ -189,7 +190,8 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { body: HTMLElement, save: () => Promise, ) => { - const no = el("span", { className: "m02-struct__no" }); + const no = createBadge("", "accent"); // 번호 = 배지 부품 + no.classList.add("m02-struct__no"); const name = el("span", { className: "m02-struct__title" }); const shell = cardShell({ title, body, sub: [no, name] }); const saver = saveControl( diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css index 18587c1db..30bd8c7ca 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css @@ -24,8 +24,6 @@ .m02-itempick__note { margin: 0; - color: var(--color-text-secondary); - font-size: var(--text-body-sm); } .m02-itempick__buttons { diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts index 385de7bbd..b167c706b 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts @@ -220,7 +220,7 @@ export function buildTwoWay(opt: TwoWayOptions): HTMLElement { return el("div", { className: "m02-itempick__two", children: [ - el("p", { className: "m02-itempick__note", text: opt.note }), + el("p", { className: "ui-text ui-text--muted m02-itempick__note", text: opt.note }), el("div", { className: "m02-itempick__buttons", children: [fresh, copy] }), below, ], @@ -256,7 +256,10 @@ export function pickFrom( close(); })(), }); - if (opt.note) body.append(el("p", { className: "m02-itempick__note", text: opt.note })); + if (opt.note) + body.append( + el("p", { className: "ui-text ui-text--muted m02-itempick__note", text: opt.note }), + ); body.append(picker.root); }, }); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor.css b/M02_MasterTemplete/M02_MasterTemplete_Labor.css index ac3fa3209..1c32aadc0 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor.css @@ -13,7 +13,7 @@ } .m02-labrow__row.is-open:hover { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } /* 이름 칸(45-7) — 칸은 표 칸 그대로(flex 면 칸 선이 끊김) · 코드 · 이름 · 설명은 글마다 한 줄 말줄임(전체 = title) */ @@ -36,18 +36,12 @@ } .m02-labrow__key, -.m02-labrow__note, -.m02-labrow__muted { +.m02-labrow__note { color: var(--color-text-secondary); font-size: var(--text-caption); overflow-wrap: anywhere; } -.m02-labrow__empty { - color: var(--color-danger); - font-size: var(--text-caption); -} - /* 로직 자재 줄(103-1) — 금액은 로직 단가 안(합에 안 더함 · 흐리게) · 바꾼 줄 = 소속 글 강조 */ .m02-labrow__logic-mat .m02-labrow__money { color: var(--color-text-secondary); @@ -138,6 +132,7 @@ /* 단추 칸(51-14 · 102-1) — [▲ · ▼ 위아래][옵션|수정][삭제] · 옵션 · 삭제는 두 단추 높이(2행 병합 꼴) */ .m02-labrow__tools { + flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */ display: inline-flex; align-items: stretch; gap: var(--btn-group-gap); @@ -180,13 +175,13 @@ .m02-labrow__optrow.is-open:hover, .m02-labrow__row.is-open:hover + .m02-labrow__optrow, .m02-labrow__row.is-open:has(+ .m02-labrow__optrow:hover) { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } /* 고른 줄(47-5) — 옵션 값 줄까지 한 덩이 · 왼쪽 띠(차례 칸 = 두 줄 병합 · 113-1) */ .m02-labrow .m02-labrow__row.is-selected, .m02-labrow .m02-labrow__optrow.is-selected { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .m02-labrow__row.is-selected > td:first-child { @@ -255,7 +250,7 @@ /* 총합계 줄 전체 합계 칸(44-6) — 테두리 · 색 · 테두리는 안쪽 그림자(칸 선 굵기 안 바꿈 · 45-7) */ .m02-labrow__sum .m02-labrow__total { box-shadow: inset 0 0 0 2px var(--color-primary); - background: var(--color-mist-violet); + background: var(--color-accent-soft); color: var(--color-primary); } @@ -290,12 +285,6 @@ font-size: var(--text-caption); } -.m02-labin__lost { - margin: 0; - color: var(--color-danger); - font-size: var(--text-caption); -} - /* 보기 갈래(면적산출 오른쪽 패널 · 일위대가 카드) */ .m02-labview { display: flex; @@ -472,7 +461,7 @@ .m02-labrow__qtybtn:hover { border-color: var(--color-primary); - background: var(--color-mist-violet); + background: var(--color-accent-soft); } /* 수량산출 모달(43-1) — 위 지금 수량 · 손 입력 · [연결 풀기] / 아래 면적산출 시트(읽기만) */ diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.css b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.css index 2d22d332f..d612c233f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.css @@ -28,10 +28,6 @@ font-weight: var(--font-weight-bold); } -.m02-labordg__hint { - color: var(--color-text-muted); -} - .m02-labordg__viewport { position: relative; height: clamp(320px, 62vh, 640px); @@ -293,11 +289,6 @@ opacity: 1; } -.m02-labordg__none { - margin: var(--spacing-16); - color: var(--color-text-muted); -} - .m02-labor__dg > summary { cursor: pointer; font-weight: var(--font-weight-bold); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts index 7dab24291..1a749344e 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts @@ -11,6 +11,7 @@ import { wheelZoomsIn } from "../A00_Common/touch_bridge"; import { createButton, el } from "@ui/ui_template_elements"; import { formatMoney } from "../M01_MasterData/M01_MasterData_UI_Logic_Money"; import "./M02_MasterTemplete_Labor_Diagram.css"; +import { createEmpty } from "@ui/ui_template_empty"; const TEXT = { Inputs: ["넣을 값", "Inputs"], @@ -270,13 +271,13 @@ function mountShell(host: HTMLElement): State { }; host.replaceChildren( el("div", { - className: "m02-labordg__bar", + className: "ui-toolbar m02-labordg__bar", children: [ icon("−", "Zoom_Out", () => zoomAt(st, 1 / 1.2)), icon("+", "Zoom_In", () => zoomAt(st, 1.2)), icon("⤢", "Fit", () => fit(st, true)), label, - el("span", { className: "m02-labordg__hint", text: td("Hint") }), + el("span", { className: "ui-text ui-text--muted", text: td("Hint") }), ], }), viewport, @@ -369,7 +370,7 @@ export function renderComboDiagram( } const { world } = st; if (!data.logics.length) { - world.replaceChildren(el("p", { className: "m02-labordg__none", text: td("None") })); + world.replaceChildren(createEmpty({ text: td("None"), small: true })); return; } const hasValue = data.logics.some((l) => isValue(l.out)); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Input.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Input.ts index 75f48475d..4b50c22d6 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Input.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Input.ts @@ -123,7 +123,7 @@ export function openLaborInput(opt: LaborInputOptions): void { ...(opt.notice ? [ el("p", { - className: "m02-labin__muted", + className: "ui-text ui-text--muted", text: opt.notice, attrs: { "data-labor-notice": "" }, }), @@ -132,14 +132,14 @@ export function openLaborInput(opt: LaborInputOptions): void { ...(lost.length ? [ el("p", { - className: "m02-labin__lost", + className: "ui-text ui-text--error", text: fill(tc("Labor_Lost"), lost.join(", ")), }), ] : []), ...(rows.length ? rows - : [el("p", { className: "m02-labin__muted", text: tc("In_None") })]), + : [el("p", { className: "ui-text ui-text--muted", text: tc("In_None") })]), ...(rate ? [ el("div", { diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_LogicMats.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_LogicMats.ts index 2fc6db140..4c9548b3e 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_LogicMats.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_LogicMats.ts @@ -126,7 +126,9 @@ function matRow( text: mat.고름 ? `${owner} · ${tc("LogicMat_Picked")}` : owner, attrs: { title: owner }, }), - ...(mat.까닭 ? [el("small", { className: "m02-labrow__empty", text: mat.까닭 })] : []), + ...(mat.까닭 + ? [el("small", { className: "ui-text ui-text--error", text: mat.까닭 })] + : []), ], }), el("td", { diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_National.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_National.ts index 5b72431c6..7d132e664 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_National.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_National.ts @@ -56,7 +56,7 @@ export function nationalRows(origin: LaborDoc["본뜬"]): HTMLElement[] { }), ); return [ - el("p", { className: "m02-labrow__muted", text: say("Title") }), + el("p", { className: "ui-text ui-text--muted", text: say("Title") }), el("div", { className: "ui-general-block__table-wrap", attrs: { "data-labor-national-table": "" }, diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_QtyModal.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_QtyModal.ts index 1aaacd84f..f7ed589cd 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_QtyModal.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_QtyModal.ts @@ -107,7 +107,7 @@ export function openQtyModal(opt: QtyModalOptions): void { }); } else host.replaceChildren( - el("p", { className: "m02-labin__muted", text: tc("Labor_PickWait") }), + el("p", { className: "ui-text ui-text--muted", text: tc("Labor_PickWait") }), ); const ok = (): void => { const raw = hand.value.trim(); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_RefEdit.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_RefEdit.ts index 109fca8ee..bc08770ce 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_RefEdit.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_RefEdit.ts @@ -83,7 +83,7 @@ export async function openRefEdit(opt: RefEditOptions): Promise { }); body.append( el("p", { - className: "m02-labin__muted", + className: "ui-text ui-text--muted", text: notice, attrs: { "data-labor-notice": "" }, }), diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Rows.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Rows.ts index 4b67f4686..d7638b889 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Rows.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Rows.ts @@ -411,7 +411,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement ...(values.empty ? [ el("small", { - className: "m02-labrow__empty", + className: "ui-text ui-text--error", text: tc("Labor_Empty").replace("{n}", String(values.empty)), }), ] @@ -420,7 +420,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement ...(why ? [ el("small", { - className: "m02-labrow__empty", + className: "ui-text ui-text--error", text: `${tc("Labor_Calc_Fail")}: ${why}`, }), ] @@ -438,7 +438,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement className: "m02-labrow__move", children: [ el("div", { - className: "m02-labrow__tools", + className: "ui-toolbar m02-labrow__tools", children: [ ...(move ? [ @@ -529,7 +529,8 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement ...COSTS.map((c) => tc(HEAD[c])), ...(tools ? [tc("Col_Move")] : []), ]; - if (!data.rows.length) return el("p", { className: "m02-labrow__muted", text: tc("NoLogic") }); + if (!data.rows.length) + return el("p", { className: "ui-text ui-text--muted", text: tc("NoLogic") }); const out = table(heads, trs, foot); if (move) attachRowDrag(out, move); return out; @@ -582,7 +583,7 @@ export function matTable(data: MatTableData, on?: Omit): HT children: [ el("strong", { text: mat.이름 ?? "" }), ...(line?.까닭 - ? [el("small", { className: "m02-labrow__empty", text: line.까닭 })] + ? [el("small", { className: "ui-text ui-text--error", text: line.까닭 })] : []), ], }), @@ -637,5 +638,5 @@ export function matTable(data: MatTableData, on?: Omit): HT const lead = data.lead ?? []; return data.mats.length || lead.length ? table(heads, [...lead, ...trs], []) - : el("p", { className: "m02-labrow__muted", text: tc("Labor_NoMat") }); + : el("p", { className: "ui-text ui-text--muted", text: tc("Labor_NoMat") }); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts index 0f7f4e689..0fdbd43a0 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts @@ -111,7 +111,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH calc = null; fail = null; if (!column) return void root.replaceChildren(); - root.replaceChildren(el("p", { className: "m02-labrow__muted", text: tc("Loading") })); + root.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: tc("Loading") })); try { const got = await readTemplate(layer, "labor", column, null); if (mine !== seq) return; @@ -134,7 +134,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH if (!doc) { root.replaceChildren( el("p", { - className: "m02-labrow__empty", + className: "ui-text ui-text--error", text: error instanceof Error ? error.message : tc("Load_Failed"), }), ); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts b/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts index 0a8d76385..9023227ed 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts @@ -274,7 +274,7 @@ export function mountLibraryList( ); if (opt.canEdit) tools.replaceChildren( - el("span", { className: "m02-main__tools", children: [btnNew, btnCopy, btnDel] }), + el("span", { className: "ui-toolbar m02-main__tools", children: [btnNew, btnCopy, btnDel] }), ); void load(); return { getDoc: () => null, destroy: () => undefined }; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Practice.ts b/M02_MasterTemplete/M02_MasterTemplete_Practice.ts index 0649c592c..23ffa1afb 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Practice.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Practice.ts @@ -16,6 +16,7 @@ import { type ComboList, type ComboOne, } from "./M02_MasterTemplete_Labor_Api"; +import { createEmpty } from "@ui/ui_template_empty"; export interface PracticeSectionOptions { /** 저장 안 한 고침을 버릴지 확인 — 취소면 false */ @@ -125,10 +126,11 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti }); view.replaceChildren( el("div", { - className: "m02-practice__bar", + className: "ui-toolbar m02-practice__bar", children: [ el("span", { - className: "m02-practice__note", + className: "ui-text ui-text--muted", + attrs: { "data-text": "14" }, text: L("M02_PracticeCount").replace("{n}", String(list.줄.length)), }), drop, @@ -145,7 +147,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti ], rows, ) - : el("p", { className: "m02-practice__note", text: L("M02_PracticeEmpty") }), + : createEmpty({ text: L("M02_PracticeEmpty"), small: true }), ); } @@ -171,7 +173,13 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti [L("M02_PracticeKey"), c.키], [L("M02_PracticeGroup"), c.구분 ?? ""], [L("M02_PracticeUnit"), c.단위 ?? ""], - ].map(([k, v]) => el("span", { className: "m02-practice__note", text: `${k} ${v}` })); + ].map(([k, v]) => + el("span", { + className: "ui-text ui-text--muted", + attrs: { "data-text": "14" }, + text: `${k} ${v}`, + }), + ); const lines = one.줄.map((x) => el("tr", { children: cells([ @@ -185,7 +193,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti ); view.replaceChildren( el("div", { - className: "m02-practice__bar", + className: "ui-toolbar m02-practice__bar", children: [back, el("strong", { text: c.이름 }), ...info], }), ...(c.비고 ? [el("p", { className: "m02-practice__memo", text: c.비고 })] : []), @@ -200,7 +208,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti ], lines, ) - : el("p", { className: "m02-practice__note", text: L("M02_PracticeNoLogic") }), + : createEmpty({ text: L("M02_PracticeNoLogic"), small: true }), ); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Structure.css b/M02_MasterTemplete/M02_MasterTemplete_Structure.css index 598a10220..1fb029d58 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Structure.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Structure.css @@ -133,14 +133,18 @@ font-size: var(--text-body); } +/* 카드 번호 = 배지 부품(ui-badge) · 배지 아닌 옛 자리(일위대가 값 요약)만 옛 꼴 */ .m02-struct__no { flex: none; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.m02-struct__no:not(.ui-badge) { padding: 0 var(--spacing-8); border-radius: var(--radius-md); background: color-mix(in srgb, var(--color-accent) 14%, transparent); color: var(--color-text); - font-variant-numeric: tabular-nums; - white-space: nowrap; } /* 미리보기 — 웹캐드는 높이를 부모에서 받음(`.m02-drawing` 이 flex: 1 1 0). @@ -241,22 +245,10 @@ margin: var(--spacing-8) 0; } +/* 줄 = 목록 줄 부품(ui-list-row) · 여기는 왼쪽부터 늘어놓기만 */ .m02-pick__item { - display: flex; + justify-content: flex-start; gap: var(--spacing-12); - padding: var(--spacing-8); - border: none; - border-radius: var(--radius-sm, 4px); - background: none; - color: inherit; - font: inherit; - text-align: left; - cursor: pointer; -} - -.m02-pick__item:hover, -.m02-pick__item:focus-visible { - background: var(--color-surface-sunken); } .m02-pick__key { @@ -266,16 +258,7 @@ font-variant-numeric: tabular-nums; } -/* 개인 자재 표(148-3) · 신규 자재 입력 */ -.m02-pick__tag { - flex: none; - padding: 0 var(--spacing-4); - border-radius: var(--radius-sm, 4px); - background: var(--color-surface-sunken); - color: var(--color-text-secondary); - font-size: 0.85em; -} - +/* 신규 자재 입력 */ .m02-pick__new { display: flex; flex-direction: column; diff --git a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css index a5a67be4d..f637de747 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css +++ b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css @@ -39,6 +39,7 @@ /* 층 단추 줄 오른쪽 끝 — 「N개 고름」 · 「+」(PLAN 30-2) */ .m02-tree__tools { + flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */ display: inline-flex; align-items: center; gap: var(--spacing-8); @@ -64,7 +65,7 @@ /* 여러 개 고른 줄 배경 */ .m02-tree__body .ui-tree__row.is-picked { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } /* 남의 회사 「개인」 사람 나무 — 고른 사람 줄 아래 그 사람 라이브러리(PLAN 29-6) */ @@ -72,11 +73,9 @@ padding-left: var(--spacing-16); } +/* 겉 = 보조 글 부품 · 여기는 여백만 */ .m02-tree__hint { - margin: 0; padding: var(--spacing-8); - color: var(--color-text-muted); - font-size: var(--text-body-sm); } .m02-treedlg { diff --git a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts index a3daadd23..259dc409c 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts @@ -118,7 +118,12 @@ export interface TreePanelHandle { } const why = (error: unknown): string => (error instanceof Error ? error.message : ""); -const hint = (text: string): HTMLElement => el("p", { className: "m02-tree__hint", text }); +const hint = (text: string): HTMLElement => + el("p", { + className: "ui-text ui-text--muted m02-tree__hint", + text, + attrs: { "data-text": "14" }, + }); export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { const owner = opt.owner ?? null; @@ -162,7 +167,10 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { multiBtn.classList.add("ui-touch-only"); multiBtn.title = "여럿 고르기 (Ctrl 누르고 누르기와 같음)"; multiBtn.setAttribute("aria-pressed", "false"); - const tools = el("span", { className: "m02-tree__tools", children: [status, multiBtn, add] }); + const tools = el("span", { + className: "ui-toolbar m02-tree__tools", + children: [status, multiBtn, add], + }); /* --- 그리기 --- */ const canCopy = !!api.paste; diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index ed0af9ff2..a0a616c8f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -38,6 +38,7 @@ import { helpParts } from "./M02_MasterTemplete_CardShell"; import { isPrecombo } from "./M02_MasterTemplete_UI_Side_Precombo"; import type { SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet"; import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links"; +import { createEmpty } from "@ui/ui_template_empty"; export interface Selection { layer: Layer; projectId: string | null; @@ -149,7 +150,10 @@ export function createMain( void open({ ...at, kind }); }); const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴 - const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } }); + const notice = el("div", { + className: "ui-banner ui-banner--danger m02-main__notice", + attrs: { hidden: "" }, + }); const host = el("div", { className: "m02-main__host" }); const extra = el("div", { className: "m02-main__extra" }); // 표 밖 형제 자리 — 구조물 설계 컨테이너 const column = el("div", { @@ -244,7 +248,7 @@ export function createMain( notice.hidden = nodes.length === 0; }; const empty = (text: string): void => { - host.replaceChildren(el("p", { className: "m02-main__empty", text })); + host.replaceChildren(createEmpty({ text })); }; /** 고를 것마다 고칠 권한 — 사전조합 층은 고름에 실림(134-3) · 그 밖 페이지 층 */ const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin); @@ -617,7 +621,9 @@ export function createMain( title.textContent = label; host.replaceChildren(node); if (tools) - slot.replaceChildren(el("span", { className: "m02-main__tools", children: [tools] })); + slot.replaceChildren( + el("span", { className: "ui-toolbar m02-main__tools", children: [tools] }), + ); return (d) => (dirty = d); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts index 8fda71b97..a530445ff 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts @@ -40,6 +40,7 @@ import { PRECOMBO_TITLE, type PrecomboOptions, } from "./M02_MasterTemplete_UI_Side_Precombo"; +import { createEmpty } from "@ui/ui_template_empty"; const KINDS: { kind: Kind; label: () => string }[] = [ { kind: "table", label: () => L("M02_KindTable") }, @@ -225,7 +226,7 @@ export function buildSide(opt: SideOptions): SideHandle { b.addEventListener("click", () => void select(info)); return b; }) - : [el("p", { className: "m02-side__empty", text: L("M02_NoTemplates") })]), + : [createEmpty({ text: L("M02_NoTemplates"), small: true })]), ); } } diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts index 9bf42cb15..5eb4936a0 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts @@ -38,6 +38,7 @@ import { attachTreeDrag } from "./M02_MasterTemplete_TreePanel_Drag"; import { folderIdOf, samePath } from "./M02_MasterTemplete_TreePanel_Model"; import type { Target } from "./M02_MasterTemplete_TreePanel_Pick"; import type { Selection } from "./M02_MasterTemplete_UI_Main"; +import { createEmpty } from "@ui/ui_template_empty"; export type PrecomboKind = Extract; @@ -278,9 +279,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { targets.clear(); const tree = buildPrecomboFolders(rows, view ?? { 폴더: [], 자리: {} }); if (!tree.docs.length && !tree.folders.length) - return void treeHost.replaceChildren( - el("p", { className: "m02-side__empty", text: tc("Lib_None") }), - ); + return void treeHost.replaceChildren(createEmpty({ text: tc("Lib_None"), small: true })); const canWrite = opt.scope().canWrite; treeHost.replaceChildren( ...renderTree(kids(tree), (node, caret, depth, onClick) => { @@ -320,7 +319,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { const mine = ++seq; const at = opt.scope(); barHost.replaceChildren(opt.bar()); - treeHost.replaceChildren(el("p", { className: "m02-side__empty", text: tc("Loading") })); + treeHost.replaceChildren(createEmpty({ text: tc("Loading"), small: true })); const [got, tree] = await Promise.all([ // 못 받으면 빈 나무(목록표에서 까닭) · 폴더를 못 받으면 문서는 모두 뿌리 listTemplates(at.layer, null, at.owner).then( diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css index 88bd8585d..e74257d20 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css @@ -29,12 +29,6 @@ margin-left: auto; } -.m02-practice__note { - margin: 0; - color: var(--color-text-secondary); - font-size: var(--text-body-sm); -} - .m02-practice__memo { margin: 0; color: var(--color-text-secondary); @@ -47,7 +41,7 @@ } .m02-practice__row:hover { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .m02-side__row { @@ -119,20 +113,15 @@ html.is-bm-route .m02-main__head { /* 제목 줄 오른쪽 끝 도구(일위대가 「새 조합」 · PLAN 30-5) */ .m02-main__tools { + flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */ display: inline-flex; gap: var(--spacing-8); /* M01 머리 줄(`m01-head`)과 같은 틈 */ margin-left: auto; } +/* 겉 = 띠 알림 부품(ui-banner--danger) · 여기는 가운데 맞춤만 */ .m02-main__notice { - display: flex; align-items: center; - gap: var(--spacing-8); - padding: var(--spacing-8) var(--spacing-12); - border-left: 4px solid var(--color-danger); - background: color-mix(in srgb, var(--color-danger) 10%, var(--color-canvas)); - color: var(--color-danger); - font-size: var(--text-body-sm); } .m02-main__host { @@ -181,13 +170,6 @@ html.is-bm-route .m02-main__head { overflow: auto; } -.m02-main__empty { - margin: 0; - padding: var(--spacing-24); - color: var(--color-text-muted); - text-align: center; -} - .m02-modal__field { display: flex; flex-direction: column; diff --git a/M02_MasterTemplete/M02_Material_Pick.ts b/M02_MasterTemplete/M02_Material_Pick.ts index 54579ce78..cd0507a15 100644 --- a/M02_MasterTemplete/M02_Material_Pick.ts +++ b/M02_MasterTemplete/M02_Material_Pick.ts @@ -12,6 +12,9 @@ import { openModal } from "@ui/ui_template_modal"; import { searchMaterials } from "../M01_MasterData/M01_MasterData_Api_Fetch"; import { call } from "./M02_MasterTemplete_Api_Fetch"; import { createUnitPick, NO_UNIT } from "./DataFlow/M02_DF_UnitPick"; +import { createBadge } from "@ui/ui_template_badge"; +import { createText } from "@ui/ui_template_text"; +import { createEmpty } from "@ui/ui_template_empty"; /** 고른 자재 한 줄 — M01 자재(키 `MT`) · 개인 자재(키 `PM`) */ export interface MaterialItem { @@ -93,13 +96,14 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer) const search = createInputField({ placeholder: TEXT.search, type: "search" }); const list = el("div", { className: "m02-pick__list" }); const item = (m: MaterialItem, tags: string[] = []): HTMLElement => { + // 목록 줄 부품 겉 · 꼬리표 = 배지 부품 const row = el("button", { - className: "m02-pick__item", - attrs: { type: "button", "data-material": m.코드 }, + className: "m02-pick__item ui-list-row ui-sidebar-row", + attrs: { type: "button", "data-material": m.코드, "data-size": "36" }, children: [ el("span", { className: "m02-pick__key", text: m.코드 }), `${m.이름} ${m.규격} ${m.단위}`.replace(/\s+/g, " "), - ...tags.map((t) => el("span", { className: "m02-pick__tag", text: t })), + ...tags.map((t) => createBadge(t, "neutral")), ], }); row.addEventListener("click", () => { @@ -111,7 +115,7 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer) let seq = 0; const paint = async (): Promise => { const ticket = ++seq; - list.replaceChildren(el("p", { className: "m02-struct__empty", text: TEXT.searching })); + list.replaceChildren(createText(TEXT.searching)); try { const q = search.input.value.trim(); const [hits] = await Promise.all([searchMaterials(q), loaded]); @@ -122,9 +126,7 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer) .map((r) => item(toItem(r), r.값 === null ? [TEXT.own, TEXT.noPrice] : [TEXT.own])); const all = [...ownHits, ...hits.map((m) => item(m))]; list.replaceChildren( - ...(all.length - ? all - : [el("p", { className: "m02-struct__empty", text: TEXT.noMatch })]), + ...(all.length ? all : [createEmpty({ text: TEXT.noMatch, small: true })]), ); } catch { if (ticket === seq) showToast(TEXT.searchFailed, "error"); @@ -219,7 +221,7 @@ function newForm( children: [el("label", { className: "ui-field__label", text: TEXT.unit }), unitHost], }), price.root, - el("p", { className: "m02-struct__empty", text: TEXT.key }), + createText(TEXT.key), el("div", { className: "ui-btn-group", children: [createButton({ label: TEXT.back, variant: "ghost", onClick: back }), save], diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def.ts index 204da47bf..07c978eb2 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def.ts @@ -38,6 +38,9 @@ import { checkBox, pickBox, textBox } from "./M02_SO_Def_Ui"; import { addFromTemplate, templates } from "./M02_SO_Def_Add"; import { buildGroupBlock } from "./M02_SO_Groups"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; +import { createEmpty } from "@ui/ui_template_empty"; +import { createTabs } from "@ui/ui_template_tabs"; /** 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점) — 모든 종류에서 숨김(52-12 · 정의 화면 = 이름만) */ const OLD_EDITOR = false; @@ -93,7 +96,11 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { const canEdit = (): boolean => opt.isAdmin && !past; const kindList = el("div", { className: "m02-opts__kinds", attrs: { "data-sod-kinds": "" } }); const detail = el("div", { className: "m02-opts__list", attrs: { "data-sod-detail": "" } }); - const banner = el("div", { className: "m02-sod__banner", attrs: { "data-sod-banner": "" } }); + // 판 띠 = 띠 알림 부품 꼴(정보 · 지난 판 = 경고) + const banner = el("div", { + className: "ui-banner ui-banner--info m02-sod__banner", + attrs: { "data-sod-banner": "" }, + }); const issues = el("div", { className: "m02-sod__issues", attrs: { "data-sod-issues": "" } }); const historyBox = el("div", { className: "m02-sod__history", @@ -161,8 +168,8 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { const off = t.enabled === false; const groups = d.그룹[t.type_id]?.length ?? 0; const b = el("button", { - className: `m02-opts__kind${t.type_id === current ? " is-active" : ""}${off ? " is-off" : ""}`, - attrs: { type: "button", "data-sod-kind": t.type_id }, + className: `m02-opts__kind ui-list-row ui-sidebar-row${t.type_id === current ? " is-active" : ""}${off ? " is-off" : ""}`, + attrs: { type: "button", "data-sod-kind": t.type_id, "data-size": "32" }, children: [ el("span", { text: t.name }), ...(groups ? [createTag(to("GroupTag", String(groups)), "accent")] : []), @@ -204,7 +211,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { const d = shown(); const type = d.types.find((t) => t.type_id === current); if (!type) { - detail.replaceChildren(el("p", { className: "m02-side__empty", text: to("Pick") })); + detail.replaceChildren(createEmpty({ text: to("Pick"), small: true })); return; } const edit = canEdit(); @@ -220,7 +227,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { attr: "data-sod-kind-name", cls: "m02-sod__name", }), - el("small", { className: "m02-opts__key", text: type.type_id }), + el("small", { className: "ui-text ui-text--muted m02-opts__key", text: type.type_id }), // 안 씀 = 목록 B06 만(enabled 는 목록 짝 · 322-2) · 숨김(hidden)은 한눈 표 목록 칸에서 checkBox( to("Unused"), @@ -269,14 +276,17 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { ["kinds", to("TabKinds")], ["parts", to("TabParts")], ]; - tabBar.replaceChildren( - ...pairs.map(([key, label]) => { - const b = createButton({ label, variant: key === tab ? "filled" : "ghost", size: "sm" }); - b.setAttribute("data-sod-tab", key); - b.addEventListener("click", () => ((tab = key), paint())); - return b; - }), - ); + // 탭 부품(보조 탭) — 칸마다 data-sod-tab 그대로 + const tabs = createTabs({ + kind: "pill", + tabs: pairs.map(([key, label]) => ({ key, label })), + value: tab, + onChange: (key) => ((tab = key as Tab), paint()), + }); + tabs.root.querySelectorAll("[data-tab]").forEach((b) => { + b.setAttribute("data-sod-tab", b.dataset.tab ?? ""); + }); + tabBar.replaceChildren(tabs.root); const edit = canEdit(); if (tab === "table") body.replaceChildren(buildTable({ def: shown(), edit, changed, issues: allProblems() })); @@ -297,8 +307,10 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { ]), ); banner.classList.toggle("is-past", !!past); + banner.classList.toggle("ui-banner--warning", !!past); + banner.classList.toggle("ui-banner--info", !past); const list = allProblems(); - issues.replaceChildren(...list.map((t) => el("p", { className: "m02-sod__bad", text: t }))); + issues.replaceChildren(...list.map((t) => createText(t, "error"))); save.disabled = !opt.isAdmin || !!past || !dirty || list.length > 0; }; @@ -366,7 +378,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { }); return b; }) - : [el("p", { className: "m02-side__empty", text: to("PastNone") })]), + : [createEmpty({ text: to("PastNone"), small: true })]), ); }; const history = createButton({ label: to("PastList"), variant: "ghost" }); @@ -387,7 +399,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void { const node = el("div", { className: "m02-opts m02-sod", children: [ - el("p", { className: "m02-side__empty", text: to("DefNote") }), + createText(to("DefNote")), banner, tabBar, body, diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Options.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Options.ts index 10254bba8..d517a3bc6 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Options.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Options.ts @@ -20,6 +20,8 @@ import { import { checkBox, head, moveButtons, pickBox, textBox } from "./M02_SO_Def_Ui"; import { buildPlaceSelect } from "./M02_SO_Groups"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; +import { createEmpty } from "@ui/ui_template_empty"; export interface OptionsBlockOptions { def: StructDef; @@ -113,8 +115,8 @@ function choicesBlock(opt: OptionsBlockOptions, o: OptionDef): HTMLElement { return el("div", { className: "m02-sod__choices", children: [ - el("p", { className: "m02-side__empty", text: to("ChoiceNote") }), - ...(rows.length ? rows : [el("p", { className: "m02-sod__bad", text: to("ChoiceNone") })]), + createText(to("ChoiceNote")), + ...(rows.length ? rows : [createText(to("ChoiceNone"), "error")]), ...(edit ? [add] : []), ], }); @@ -171,7 +173,7 @@ function optionRow(opt: OptionsBlockOptions, o: OptionDef): HTMLElement { attr: "data-sod-name", cls: "m02-sod__name", }), - el("small", { className: "m02-opts__key", text: o.key }), + el("small", { className: "ui-text ui-text--muted m02-opts__key", text: o.key }), ...(o.phase === "detail" ? [createTag(to("Detail"), "accent")] : []), form, textBox(o.unit ?? "", (v) => (o.unit = v.trim() || null), changed, { @@ -224,7 +226,7 @@ export function buildOptionsBlock(opt: OptionsBlockOptions): HTMLElement { ...head(to("OptionHead"), to("OptionNote")), ...(type.options.length ? type.options.map((o) => optionRow(opt, o)) - : [el("p", { className: "m02-side__empty", text: to("NoOptionKind") })]), + : [createEmpty({ text: to("NoOptionKind"), small: true })]), ...(edit ? [el("div", { className: "m02-side__row", children: [add] })] : []), ], }); diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Parts.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Parts.ts index 7bc028558..97cfc12b7 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Parts.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Parts.ts @@ -18,6 +18,7 @@ import { } from "./M02_SO_Def_Reflect"; import { checkBox, head } from "./M02_SO_Def_Ui"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; type Branch = keyof typeof PART_BOX; @@ -40,7 +41,7 @@ export function buildParts(opt: { const blocks = (Object.keys(PART_BOX) as Branch[]).flatMap((branch) => [ ...head(branch), el("table", { - className: "m02-sod__table m02-sod__table--wrap", + className: "ui-table ui-table--data m02-sod__table m02-sod__table--wrap", attrs: { "data-sod-parts": branch }, children: [ el("thead", { @@ -99,6 +100,6 @@ export function buildParts(opt: { ]); return el("div", { className: "m02-sod__tablewrap", - children: [el("p", { className: "m02-side__empty", text: to("PartsNote") }), ...blocks], + children: [createText(to("PartsNote")), ...blocks], }); } diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Rules.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Rules.ts index bca6186e0..72d9786b1 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Rules.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Rules.ts @@ -16,6 +16,8 @@ import { } from "./M02_SO_Def_Model"; import { head, pickBox } from "./M02_SO_Def_Ui"; import { to } from "./M02_SO_Text"; +import { createEmpty } from "@ui/ui_template_empty"; +import { createCheckbox } from "@ui/ui_template_checkbox"; export interface RulesBlockOptions { def: StructDef; @@ -42,15 +44,18 @@ function multiPick( className: "m02-sod__rulechoices", attrs: { [attr]: "" }, children: all.map((c) => { - const box = el("input", { attrs: { type: "checkbox", value: c } }) as HTMLInputElement; - box.checked = on.has(c); - box.disabled = !edit; - box.addEventListener("change", () => { - if (box.checked) on.add(c); - else on.delete(c); - set(all.filter((x) => on.has(x))); + const { root } = createCheckbox(c, { + checked: on.has(c), + disabled: !edit, + value: c, + onChange: (checked) => { + if (checked) on.add(c); + else on.delete(c); + set(all.filter((x) => on.has(x))); + }, }); - return el("label", { className: "m02-sod__check", children: [box, el("span", { text: c })] }); + root.classList.add("m02-sod__check"); + return root; }), }); } @@ -227,7 +232,7 @@ export function buildRulesBlock(opt: RulesBlockOptions): HTMLElement { ...head(to("RuleHead"), to("RuleNote")), ...(mine.length ? mine.map((r) => ruleRow(opt, r, def.규칙.indexOf(r))) - : [el("p", { className: "m02-side__empty", text: to("RuleNone") })]), + : [createEmpty({ text: to("RuleNone"), small: true })]), ...(edit ? [el("div", { className: "m02-side__row", children: [add] })] : []), ], }); diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec.ts index d74155e09..656484957 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec.ts @@ -28,6 +28,7 @@ import { import { hiddenKeys, readKeys, setShown } from "./M02_SO_Def_Reflect"; import { checkBox, head, textBox } from "./M02_SO_Def_Ui"; import { to, type OptionsTextKey } from "./M02_SO_Text"; +import { createEmpty } from "@ui/ui_template_empty"; const TAG: Record, OptionsTextKey> = { required: "SpecRequired", @@ -132,7 +133,7 @@ export function buildSpecBlock(opt: { cls: "m02-sod__name", }) : el("span", { text: to("SpecLoose") }), - el("small", { className: "m02-opts__key", text: row.key }), + el("small", { className: "ui-text ui-text--muted m02-opts__key", text: row.key }), ...(row.tag ? [createTag(to(TAG[row.tag]), "neutral")] : []), ...shownBox( opt, @@ -164,7 +165,7 @@ export function buildSpecBlock(opt: { attrs: { "data-sod-spec-block": "" }, children: [ ...head(to("SpecHead"), to("SpecNote")), - ...(rows.length ? rows : [el("p", { className: "m02-side__empty", text: to("SpecNone") })]), + ...(rows.length ? rows : [createEmpty({ text: to("SpecNone"), small: true })]), ...head(to("SpecPreview")), el("div", { className: "m02-sod__specpreview", diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Table.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Table.ts index 8e3efb5ec..d6145c6e4 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Table.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Table.ts @@ -25,6 +25,7 @@ import { } from "./M02_SO_Def_Reflect"; import { checkBox, pickBox, textBox } from "./M02_SO_Def_Ui"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; type Pairs = ReadonlyArray; const LIST_PAIRS = (): Pairs => [ @@ -89,7 +90,7 @@ function mark(changed: boolean, ...children: HTMLElement[]): HTMLElement { /** 형상칸 — 벽(형상 wall)만: 기본 형태(그림 형태 고르기) · 물빼기. */ function shapeOptsCell(ctx: CellCtx): HTMLElement { const { def, type, row, edit, changed } = ctx; - if (row.shape !== "wall") return el("span", { className: "m02-side__empty", text: "—" }); + if (row.shape !== "wall") return el("span", { className: "ui-text ui-text--muted", text: "—" }); const form = String(row.shapeOpts.form ?? ""); const forms = !form || REVET_FORMS.includes(form) ? REVET_FORMS : [form, ...REVET_FORMS]; const box = pickBox( @@ -278,9 +279,7 @@ export function buildTable(opt: { el("td", { attrs: { "data-sod-table-check": "" }, children: [ - bad.length - ? el("span", { className: "m02-sod__bad", text: bad.join(" · ") }) - : createTag(to("Ok"), "accent"), + bad.length ? createText(bad.join(" · "), "error") : createTag(to("Ok"), "accent"), reset, ], }), @@ -292,9 +291,9 @@ export function buildTable(opt: { return el("div", { className: "m02-sod__tablewrap", children: [ - el("p", { className: "m02-side__empty", text: to("TableNote") }), + createText(to("TableNote")), el("table", { - className: "m02-sod__table", + className: "ui-table ui-table--data m02-sod__table", attrs: { "data-sod-table": "" }, children: [ el("thead", { diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Def_Ui.ts b/M02_MasterTemplete/StructOptions/M02_SO_Def_Ui.ts index 837c344bd..879d6ca7c 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Def_Ui.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Def_Ui.ts @@ -7,6 +7,8 @@ import { createButton, el } from "@ui/ui_template_elements"; import { step } from "./M02_SO_Def_Model"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; +import { createCheckbox } from "@ui/ui_template_checkbox"; /** 글 칸 — 글자마다 `set` · 떠날 때 `done` */ export function textBox( @@ -69,13 +71,15 @@ export function checkBox( set: (v: boolean) => void, opt: { edit: boolean; attr?: string }, ): HTMLLabelElement { - const box = el("input", { - attrs: { type: "checkbox", ...(opt.attr ? { [opt.attr]: "" } : {}) }, - }) as HTMLInputElement; - box.checked = on; - box.disabled = !opt.edit; - box.addEventListener("change", () => set(box.checked)); - return el("label", { className: "m02-sod__check", children: [box, el("span", { text: label })] }); + // 체크 부품 — 속성(data-sod-*)은 칸에 그대로 + const { root, input } = createCheckbox(label, { + checked: on, + disabled: !opt.edit, + onChange: set, + }); + if (opt.attr) input.setAttribute(opt.attr, ""); + root.classList.add("m02-sod__check"); + return root; } /** 고르기 — [값, 글] 짝 */ @@ -117,5 +121,5 @@ export function moveButtons( /** 절 제목 + 안내 한 줄 */ export const head = (title: string, note?: string): HTMLElement[] => [ el("h4", { className: "m02-opts__group", text: title }), - ...(note ? [el("p", { className: "m02-side__empty", text: note })] : []), + ...(note ? [createText(note)] : []), ]; diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Groups.ts b/M02_MasterTemplete/StructOptions/M02_SO_Groups.ts index 410875cf4..8a3083d47 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Groups.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Groups.ts @@ -11,6 +11,8 @@ import { createButton, el, showConfirmDialog } from "@ui/ui_template_elements"; import { SHAPE_KEYS, type GroupDef } from "./M02_SO_Def_Model"; import { checkBox, textBox } from "./M02_SO_Def_Ui"; import { to } from "./M02_SO_Text"; +import { createText } from "@ui/ui_template_text"; +import { createEmpty } from "@ui/ui_template_empty"; const NAME_MAX = 40; @@ -109,7 +111,7 @@ export function buildGroupBlock(opt: GroupBlockOptions): HTMLElement { children: [ name, el("small", { - className: "m02-opts__key", + className: "ui-text ui-text--muted m02-opts__key", text: to("GroupCount", String(group.옵션.length)), }), ...(opt.edit ? [up, down, del] : []), @@ -129,11 +131,12 @@ export function buildGroupBlock(opt: GroupBlockOptions): HTMLElement { opt.changed(); }); return el("div", { - className: "m02-sog", + className: "ui-card m02-sog", + attrs: { "data-pad": "12" }, children: [ el("h4", { className: "m02-opts__group", text: to("GroupHead") }), - el("p", { className: "m02-side__empty", text: to("GroupNote") }), - ...(rows.length ? rows : [el("p", { className: "m02-side__empty", text: to("GroupNone") })]), + createText(to("GroupNote")), + ...(rows.length ? rows : [createEmpty({ text: to("GroupNone"), small: true })]), ...(opt.edit ? [el("div", { className: "m02-side__row", children: [add] })] : []), ], }); diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Options.ts b/M02_MasterTemplete/StructOptions/M02_SO_Options.ts index 7ede2af54..3136b9a76 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Options.ts +++ b/M02_MasterTemplete/StructOptions/M02_SO_Options.ts @@ -7,6 +7,7 @@ import { el } from "@ui/ui_template_elements"; import { openDefScreen } from "./M02_SO_Def"; +import "@ui/ui_template_card.css"; import "./M02_SO_Style.css"; import { to } from "./M02_SO_Text"; diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Style.css b/M02_MasterTemplete/StructOptions/M02_SO_Style.css index d654c0980..32fcae514 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Style.css +++ b/M02_MasterTemplete/StructOptions/M02_SO_Style.css @@ -37,28 +37,11 @@ font-size: var(--text-body-sm); } +/* 종류 줄 = 목록 줄 부품(ui-list-row) · 여기는 글 · 꼬리표 늘어놓기만 */ .m02-opts__kind { - display: flex; flex-wrap: wrap; - align-items: center; + justify-content: flex-start; gap: var(--spacing-4); - padding: var(--spacing-4) var(--spacing-8); - border: 1px solid transparent; - border-radius: var(--radius-sm); - background: none; - color: var(--color-text); - font: inherit; - text-align: left; - cursor: pointer; -} - -.m02-opts__kind:hover { - background: var(--color-mist-violet); -} - -.m02-opts__kind.is-active { - border-color: var(--color-accent); - background: var(--color-mist-violet); } /* 옵션 줄 = 왼쪽 보이기 체크 · 라벨 · 키 · 표시 / 오른쪽 들어갈 자리(본체 · 그룹) */ @@ -81,14 +64,11 @@ } /* 옵션 그룹(PLAN 46-2) — 그룹 줄 = 이름 · 옵션 수 · 위로 · 아래로 · 지우기 */ +/* 겉 = 카드 부품(ui-card · 여백 12) */ .m02-sog { display: flex; flex-direction: column; gap: var(--spacing-4); - padding: var(--spacing-8) var(--spacing-12); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-surface-raised); } .m02-sog__row { @@ -108,9 +88,9 @@ width: 11rem; } +/* 키 글 = 보조 글 부품(ui-text) · 글줄 안에 놓임 */ .m02-opts__key { - color: var(--color-text-muted); - font-size: 11px; + display: inline-flex; } /* 구조물 정의 편집 화면(PLAN 48-2) — 판 띠 · 종류 머리 · 옵션 · 선택지 · 배치 격자 · 규칙 · 지난 판 */ @@ -118,19 +98,9 @@ max-width: 80rem; } +/* 판 띠 = 띠 알림 부품(정보 · 지난 판 = 경고) · 여기는 한 줄 늘어놓기만 */ .m02-sod__banner { - display: flex; align-items: center; - gap: var(--spacing-8); - color: var(--color-text-muted); -} - -.m02-sod__banner.is-past { - padding: var(--spacing-4) var(--spacing-8); - border: 1px solid var(--color-accent); - border-radius: var(--radius-sm); - background: var(--color-mist-violet); - color: var(--color-text); } .m02-sod__family, @@ -267,7 +237,7 @@ } .m02-sod__cell.is-base { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .m02-sod__cell.is-bad, @@ -328,11 +298,6 @@ gap: var(--spacing-4); } -.m02-sod__bad { - margin: 0; - color: var(--color-danger); -} - /* 구조물 명세 이름 고치기(PLAN 52-11) — 컨테이너 › 항목 › 값 들여쓰기 */ .m02-sod__specrow { display: flex; @@ -370,25 +335,20 @@ } /* ── 322-2 탭 셋 · 한눈 표 · 부품 ── */ +/* 탭 = 탭 부품(보조 탭) · 여기는 위아래 간격만 */ .m02-sod__tabs { - display: flex; - gap: var(--spacing-4); margin: var(--spacing-8) 0; } .m02-sod__tablewrap { overflow-x: auto; } -.m02-sod__table { - border-collapse: collapse; - font-size: var(--text-body-sm); +/* 표 = 표 부품 데이터표(ui-table--data) · 여기는 폭 · 줄바꿈만 */ +table.m02-sod__table { + width: auto; min-width: 100%; } .m02-sod__table th, .m02-sod__table td { - border-bottom: 1px solid var(--color-border); - padding: var(--spacing-4); - text-align: left; - vertical-align: middle; white-space: nowrap; } .m02-sod__familyrow td { @@ -434,7 +394,7 @@ } /* 부품 탭 — 긴 글은 줄바꿈해 표가 화면 안에(322-2 다듬기) */ -.m02-sod__table--wrap { +table.m02-sod__table--wrap { table-layout: fixed; width: 100%; } diff --git a/resources/tester/test_553_5_m01_m02.py b/resources/tester/test_553_5_m01_m02.py index 20e94c87c..c24990aa2 100644 --- a/resources/tester/test_553_5_m01_m02.py +++ b/resources/tester/test_553_5_m01_m02.py @@ -67,3 +67,31 @@ def test_m02_borrowed_m01_classes_follow_parts(): assert "m01-logic__muted" not in ts and "m01-master__muted" not in ts.replace("ui-text", "") assert 'className: "m01-master__grid"' not in ts # 빌린 표도 표 부품 꼴 assert "ui-table m01-master__grid" in ts + + +def _m02_src(suffix: str) -> dict[str, str]: + out = {} + for sub in ("", "DataFlow", "StructOptions"): + out.update(_src(f"M02_MasterTemplete/{sub}".rstrip("/"), suffix)) + return out + + +def test_m02_parts_in_place(): + ts = "".join(_m02_src(".ts").values()) + for part in ("createEmpty(", "createText(", "createBadge(", "createTabs(", "createCheckbox("): + assert part in ts, f"M02 이 부품 {part} 를 안 씀" + for cls in ( + "ui-banner ui-banner--", + "ui-toolbar m02-", + "ui-table ui-table--data", + "ui-list-row", + ): + assert cls in ts, f"M02 부품 꼴 {cls} 없음" + # 시험 · 가이드가 잡는 이름은 그대로 + for keep in ("m02-layers__btn", "m02-side__lists", "m02-main__new", "data-sod-tab"): + assert keep in ts, f"잡히는 이름 {keep} 사라짐" + + +def test_m02_css_no_old_alias(): + for name, css in _m02_src(".css").items(): + assert not OLD_ALIAS.search(css), f"{name}: 옛 보라 별칭" diff --git a/resources/tester/test_m02_hopyo_screen.py b/resources/tester/test_m02_hopyo_screen.py index 33ac9f123..f7449a623 100644 --- a/resources/tester/test_m02_hopyo_screen.py +++ b/resources/tester/test_m02_hopyo_screen.py @@ -360,7 +360,7 @@ def test_좌측_판_자리와_권한(): def test_로직_추가_한_곳과_파일_규칙(): detail = _text("M02_MasterTemplete_Labor_Detail.ts") - assert len(detail.splitlines()) == 695 # 줄 수 그대로(361-2 전국 자재 부르는 줄 둘) + assert len(detail.splitlines()) == 693 # 줄 수 그대로(361-2 전국 자재 부르는 줄 둘 · 553-5 보조 글 부품 -2) assert "withHopyo(library)," in detail pick = _text("M02_MasterTemplete_Labor_Pick.ts") assert "more?: () => Promise;" in pick and "...later" in pick