From b037073b10c1484dccdb67a3edd17efb7e228fb3 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 22:11:14 +0900 Subject: [PATCH 1/3] =?UTF-8?q?style(spreadsheet):=20=EC=85=80=20=EC=84=9C?= =?UTF-8?q?=EC=8B=9D=20=C2=B7=20=EB=A9=94=EB=AA=A8=20=C2=B7=20=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=C2=B7=20=ED=8C=A8=EB=84=90=20=EC=B0=BD=20=C2=B7=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=E2=9A=A0=20=EB=A5=BC=20=EB=B6=80=ED=92=88?= =?UTF-8?q?=20=EB=AA=A8=EB=8B=AC=20=C2=B7=20=ED=83=AD=20=C2=B7=20=EB=96=A0?= =?UTF-8?q?=20=EC=9E=88=EB=8A=94=20=ED=8C=90=20=C2=B7=20=EB=8B=A8=EC=B6=94?= =?UTF-8?q?=EB=A1=9C=20(PLAN=20553-6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 셀 서식 창 = 부품 모달(40rem) · 탭(32) · 칸 = 부품 입력 · 고르기 28 · 자체 판 · 탭 · 칸 CSS 지움 - 메모 편집 = 공용 확인창 판(.ui-confirm) · 글 칸 = 부품 입력 - 찾기 · 바꾸기 = 떠 있는 판(머리 없음) · 칸 24 · 단추 22 · 닫기 = 아이콘 단추 - 데이터 패널 창(조건부 서식 · 유효성 · 이름) = 떠 있는 판(머리 · 닫기 ✕) - 오류 ⚠ = 아이콘 단추(선 18) · 골라 붙여넣기 · 정렬 칸 · 알약 찾기 칸 = 부품 단추 · 고르기 · 입력 - 옛 보라 별칭 · 날 검정 가림막 → 공통 값(hover · accent · scrim) · 창 닫힘 감시가 모달도 기다림 - 날 단추 23 → 15(남음 = 수식 줄 칸 단추 · 격자 안 ▼ · 알약) · 자체 꼴 규칙 95 → 77 · 격자 색 전후 같음 Co-Authored-By: Claude Opus 5.5 --- A00_Common/spreadsheet/spreadsheet.css | 72 +++++-------------- A00_Common/spreadsheet/spreadsheet_actions.ts | 9 ++- .../spreadsheet/spreadsheet_analysis.css | 16 ++--- .../spreadsheet/spreadsheet_data_menu.ts | 21 +++--- .../spreadsheet/spreadsheet_errcheck.ts | 10 ++- A00_Common/spreadsheet/spreadsheet_extras.ts | 8 +-- .../spreadsheet/spreadsheet_find_panel.ts | 43 +++++------ .../spreadsheet/spreadsheet_format_dialog.css | 61 +--------------- .../spreadsheet/spreadsheet_format_dialog.ts | 64 +++++++++-------- A00_Common/spreadsheet/spreadsheet_links.css | 13 +--- A00_Common/spreadsheet/spreadsheet_links.ts | 4 +- A00_Common/spreadsheet/spreadsheet_menu.ts | 8 +-- .../spreadsheet/spreadsheet_sort_dialog.ts | 7 +- .../spreadsheet/spreadsheet_sortfilter.css | 13 ++-- A00_Common/spreadsheet/spreadsheet_stage3.css | 4 +- .../spreadsheet/spreadsheet_toolbar.css | 39 ++-------- 16 files changed, 128 insertions(+), 264 deletions(-) diff --git a/A00_Common/spreadsheet/spreadsheet.css b/A00_Common/spreadsheet/spreadsheet.css index 4577256a9..a245b9474 100644 --- a/A00_Common/spreadsheet/spreadsheet.css +++ b/A00_Common/spreadsheet/spreadsheet.css @@ -109,7 +109,7 @@ bottom: 40px; z-index: 20; padding: 6px 10px; - border-radius: 4px; + border-radius: var(--radius-buttons); background: var(--color-primary); color: var(--color-primary-text); } @@ -126,6 +126,10 @@ cursor: pointer; } +.ss-brush:hover { + background: var(--color-hover); +} + .ss-brush.is-on { background: color-mix(in srgb, var(--color-focus-ring) 20%, var(--color-surface-raised)); box-shadow: inset 0 0 0 2px var(--color-focus-ring); @@ -135,19 +139,17 @@ cursor: copy; } -.ss-find { - position: absolute; +/* 찾기 = 떠 있는 판 부품(머리 없음) · 칸 · 단추 = 부품 — 여기는 자리 · 줄 배치만(553-6) */ +.ss-find.ui-float-panel { top: 4px; right: 16px; z-index: 20; +} + +.ss-find > .ui-float-panel__body { display: flex; flex-direction: column; gap: 4px; - padding: 8px; - border: 1px solid var(--color-border); - border-radius: 6px; - background: var(--color-surface-raised); - box-shadow: var(--shadow-lg); } .ss-find[hidden], @@ -166,32 +168,17 @@ .ss-find__input { width: 180px; - padding: 2px 6px; - border: 1px solid var(--color-border); - background: var(--color-surface-raised); - color: var(--color-text-body); - font: inherit; -} - -.ss-find__btn { - padding: 2px 8px; - border: 1px solid var(--color-border); - border-radius: 4px; - background: var(--color-surface); - color: var(--color-text-body); - font: inherit; - cursor: pointer; } .ss-find__opt { display: flex; align-items: center; gap: 2px; - font-size: 12px; + font-size: var(--text-caption); } .ss-find__status { - font-size: 12px; + font-size: var(--text-caption); color: var(--color-text-secondary); } @@ -223,19 +210,11 @@ gap: 2px; padding: 4px; border: 1px solid var(--color-border); + border-radius: var(--radius-cards); background: var(--color-surface-raised); box-shadow: var(--shadow-lg); } -.ss-pick > button { - padding: 2px 8px; - border: 1px solid var(--color-border); - background: var(--color-surface); - color: var(--color-text-body); - font: inherit; - cursor: pointer; -} - /* 함수 자동 완성 · 인자 도움말 */ .ss-hint { position: absolute; @@ -265,22 +244,17 @@ } .ss-hint__item.is-on { - background: color-mix(in srgb, var(--color-accent) 18%, var(--color-surface-raised)); + background: var(--color-accent-soft); } /* 데이터 메뉴 — 패널 창 · 유효성 목록 ▼ */ -.ss-dialog { - position: absolute; +/* 패널 창 = 떠 있는 판 부품 — 여기는 자리 · 크기만(553-6) */ +.ss-dialog.ui-float-panel { top: 4px; right: 16px; z-index: 21; max-width: min(720px, calc(100% - 32px)); max-height: calc(100% - 8px); - overflow: auto; - border: 1px solid var(--color-border); - border-radius: 6px; - background: var(--color-surface-raised); - box-shadow: var(--shadow-lg); } .ss-dialog[hidden], @@ -288,20 +262,6 @@ display: none; } -.ss-dialog__head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 6px 8px; - border-bottom: 1px solid var(--color-border); - font-weight: bold; -} - -.ss-dialog__body { - padding: 8px; -} - .ss-list-arrow { position: absolute; z-index: 8; diff --git a/A00_Common/spreadsheet/spreadsheet_actions.ts b/A00_Common/spreadsheet/spreadsheet_actions.ts index a29be7fdc..8e1b654d3 100644 --- a/A00_Common/spreadsheet/spreadsheet_actions.ts +++ b/A00_Common/spreadsheet/spreadsheet_actions.ts @@ -118,10 +118,15 @@ export function dialogKeys(overlay: HTMLElement, close: () => void, run?: () => }); } -/** body 에 뜬 창(.ui-confirm)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */ +/** body 에 뜬 창(.ui-confirm · 모달)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */ export function watchClose(back: () => void): void { const w = new MutationObserver(() => { - if (document.querySelector("body > .ui-confirm, body > .ui-confirm__panel")) return; + if ( + document.querySelector( + "body > .ui-confirm, body > .ui-confirm__panel, body > .ui-modal__backdrop", + ) + ) + return; w.disconnect(); back(); }); diff --git a/A00_Common/spreadsheet/spreadsheet_analysis.css b/A00_Common/spreadsheet/spreadsheet_analysis.css index 31a7041dd..c0b6dd1be 100644 --- a/A00_Common/spreadsheet/spreadsheet_analysis.css +++ b/A00_Common/spreadsheet/spreadsheet_analysis.css @@ -63,7 +63,7 @@ display: flex; align-items: center; justify-content: center; - background: rgb(0 0 0 / 18%); + background: var(--color-scrim); } .ss-steps__box { @@ -115,18 +115,10 @@ overflow: visible; } -.ss-errtip__btn { +/* ⚠ = 아이콘 단추 부품(선 · 18) — 여기는 자리 · 경고 색만(553-6) */ +.ss-errtip__btn.ui-icon-btn { position: absolute; - width: 18px; - height: 18px; - padding: 0; - font-size: 11px; - line-height: 16px; - cursor: pointer; color: var(--color-warning); - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); } .ss-errtip__balloon { @@ -183,5 +175,5 @@ } .ss-errtip__item:hover { - background: var(--color-mist-violet); + background: var(--color-hover); } diff --git a/A00_Common/spreadsheet/spreadsheet_data_menu.ts b/A00_Common/spreadsheet/spreadsheet_data_menu.ts index 3441b644e..695728089 100644 --- a/A00_Common/spreadsheet/spreadsheet_data_menu.ts +++ b/A00_Common/spreadsheet/spreadsheet_data_menu.ts @@ -12,6 +12,7 @@ import "./spreadsheet_stage3.css"; // sub4 패널 스타일(잇는 쪽이 붙임) import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu"; import { el, showToast } from "@ui/ui_template_elements"; +import { createFloatPanel } from "@ui/ui_template_float_panel"; import { parseRange, rangeToA1, toA1 } from "./spreadsheet_address"; import { autoSumCells, autoSumRange } from "./spreadsheet_autosum"; import { isError } from "./spreadsheet_eval"; @@ -40,7 +41,6 @@ const TEXT = { validation: "데이터 유효성…", names: "이름 관리…", grid: "눈금선 보이기 · 숨기기", - close: "닫기", list: "값 목록(Alt+↓)", sheets: "시트 숨기기 · 탭 색…", audit: "수식 분석", @@ -121,18 +121,14 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe }; // ── 패널 창(조건부 서식 · 유효성 · 이름) — 한 번에 하나 ───────────────────── - const title = el("span", { className: "ss-dialog__title" }); - const body = el("div", { className: "ss-dialog__body" }); - const shut = el("button", { - className: "ss-find__btn", - text: "×", - attrs: { type: "button", title: TEXT.close }, - }); - const dialog = el("div", { - className: "ss-dialog ui-popup", - attrs: { hidden: "" }, - children: [el("div", { className: "ss-dialog__head", children: [title, shut] }), body], + // 떠 있는 판 부품(머리 · 닫기 ✕ · 몸 · 553-6) — 자리는 `.ss-dialog` + const { root: dialog, body } = createFloatPanel({ + title: " ", + onClose: () => (closePanel(), back()), }); + dialog.classList.add("ss-dialog"); + dialog.setAttribute("hidden", ""); + const title = dialog.querySelector(".ui-float-panel__title") as HTMLElement; ctx.root.append(dialog); let panel: PartHandle | null = null; const closePanel = (): void => { @@ -141,7 +137,6 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe body.replaceChildren(); dialog.setAttribute("hidden", ""); }; - shut.addEventListener("click", () => (closePanel(), back())); dialog.addEventListener("keydown", (e) => { if (e.key !== "Escape") return; e.stopPropagation(); diff --git a/A00_Common/spreadsheet/spreadsheet_errcheck.ts b/A00_Common/spreadsheet/spreadsheet_errcheck.ts index b46422341..103f91131 100644 --- a/A00_Common/spreadsheet/spreadsheet_errcheck.ts +++ b/A00_Common/spreadsheet/spreadsheet_errcheck.ts @@ -11,7 +11,7 @@ * 시트 전체를 훑지 말고 그리는 칸만 물음(값 하나 · 칸 하나 읽는 값싼 부름). * ========================================================================== */ -import { el } from "@ui/ui_template_elements"; +import { createIconButton, el } from "@ui/ui_template_elements"; import { currentLanguageIndex } from "@ui/ui_template_locale"; import { toA1 } from "./spreadsheet_address"; import { isError } from "./spreadsheet_eval"; @@ -137,11 +137,9 @@ export function attachErrorTip(ctx: SpreadsheetContext, trace?: TraceHandle): Er const g = ctx.grid.root.getBoundingClientRect(); layer.style.left = `${box.x - (a.left - g.left) - 20}px`; layer.style.top = `${box.y - (a.top - g.top)}px`; - const btn = el("button", { - className: "ss-errtip__btn", - text: "⚠", - attrs: { type: "button", "aria-label": w.까닭 }, - }); + const btn = createIconButton({ glyph: "⚠", title: w.까닭, variant: "outline", size: 18 }); + btn.classList.add("ss-errtip__btn"); + btn.removeAttribute("title"); // 까닭은 옆 풍선이 보임 const balloon = el("div", { className: "ss-errtip__balloon", text: w.까닭 }); btn.addEventListener("mousedown", (ev) => ev.stopPropagation()); btn.addEventListener("click", () => (menu ? closeMenu() : openMenu(w, r, c))); diff --git a/A00_Common/spreadsheet/spreadsheet_extras.ts b/A00_Common/spreadsheet/spreadsheet_extras.ts index ccf2db0c6..437ce0520 100644 --- a/A00_Common/spreadsheet/spreadsheet_extras.ts +++ b/A00_Common/spreadsheet/spreadsheet_extras.ts @@ -6,7 +6,7 @@ * 빨간 세모는 C 격자 · 우클릭 [메모 …] 는 E 메뉴 몫(브레인 배정). * ========================================================================== */ -import { el } from "@ui/ui_template_elements"; +import { createButton, el } from "@ui/ui_template_elements"; import { watchClose } from "./spreadsheet_actions"; import { parseClipboard } from "./spreadsheet_clipboard"; import { attachComments, getComment, setComment } from "./spreadsheet_comments"; @@ -138,10 +138,8 @@ export function attachExtras( : undefined; picker.replaceChildren( ...(Object.keys(TEXT.modes) as PasteSpecialMode[]).map((mode) => { - const b = el("button", { - text: TEXT.modes[mode], - attrs: { type: "button", "data-mode": mode }, - }); + const b = createButton({ label: TEXT.modes[mode], variant: "ghost", size: 22 }); + b.dataset.mode = mode; b.addEventListener("click", () => { picker.setAttribute("hidden", ""); // 늦게 — 크기 예산 diff --git a/A00_Common/spreadsheet/spreadsheet_find_panel.ts b/A00_Common/spreadsheet/spreadsheet_find_panel.ts index 985ffd1bd..d2894ad96 100644 --- a/A00_Common/spreadsheet/spreadsheet_find_panel.ts +++ b/A00_Common/spreadsheet/spreadsheet_find_panel.ts @@ -4,7 +4,8 @@ * Enter = 다음 · Esc = 닫고 격자로. 바꾸기 = 활성 칸이 맞으면 바꾸고 다음으로(엑셀). * ========================================================================== */ -import { el } from "@ui/ui_template_elements"; +import { createButton, createIconButton, el, setUiSize } from "@ui/ui_template_elements"; +import { createFloatPanel } from "@ui/ui_template_float_panel"; import { findAll, findNext, replaceAll, replaceOne, type FindOptions } from "./spreadsheet_find"; import { selectCell } from "./spreadsheet_selection"; import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types"; @@ -29,12 +30,14 @@ export interface FindPanelHandle extends PartHandle { } export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindPanelHandle { - const query = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.find } }); - const repl = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.replace } }); + const field = (placeholder: string): HTMLInputElement => + setUiSize(el("input", { className: "ui-input ss-find__input", attrs: { placeholder } }), 24); + const query = field(TEXT.find); + const repl = field(TEXT.replace); const status = el("span", { className: "ss-find__status" }); const button = (text: string, onClick: () => void): HTMLButtonElement => { - const b = el("button", { className: "ss-find__btn", text, attrs: { type: "button" } }); - b.addEventListener("click", onClick); + const b = createButton({ label: text, variant: "ghost", size: 22, onClick }); + b.classList.add("ss-find__btn"); return b; }; const check = (text: string): HTMLInputElement => { @@ -42,8 +45,8 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP options.append(el("label", { className: "ss-find__opt", children: [box, text] })); return box; }; - const closeBtn = button("×", close); - closeBtn.title = TEXT.close; + const closeBtn = createIconButton({ glyph: "×", title: TEXT.close, size: 22, onClick: close }); + closeBtn.classList.add("ss-find__btn"); const options = el("div", { className: "ss-find__row" }); const caseBox = check(TEXT.case); const wholeBox = check(TEXT.whole); @@ -52,19 +55,19 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP className: "ss-find__row", children: [repl, button(TEXT.one, one), button(TEXT.all, all)], }); - const root = el("div", { - className: "ss-find", - attrs: { hidden: "" }, - children: [ - el("div", { - className: "ss-find__row", - children: [query, button(TEXT.next, next), closeBtn], - }), - replaceRow, - options, - status, - ], - }); + // 떠 있는 판 부품(머리 없음 · 553-6) — 자리는 `.ss-find` + const { root, body } = createFloatPanel({ popover: true }); + root.classList.add("ss-find"); + root.setAttribute("hidden", ""); + body.append( + el("div", { + className: "ss-find__row", + children: [query, button(TEXT.next, next), closeBtn], + }), + replaceRow, + options, + status, + ); ctx.root.append(root); const opts = (): FindOptions => ({ diff --git a/A00_Common/spreadsheet/spreadsheet_format_dialog.css b/A00_Common/spreadsheet/spreadsheet_format_dialog.css index 64cf66693..8f8730213 100644 --- a/A00_Common/spreadsheet/spreadsheet_format_dialog.css +++ b/A00_Common/spreadsheet/spreadsheet_format_dialog.css @@ -1,50 +1,9 @@ /* ============================================================================= * spreadsheet_format_dialog.css (2단계 · 주인 sub_laptop_1) - * 셀 서식 대화상자 — 뿌리 배경(`.ui-confirm`)은 공용 확인 모달과 같은 것을 그대로 씀. + * 셀 서식 대화상자 — 창 · 탭 · 칸 = 부품 모달(40rem) · 탭(32) · 입력 · 고르기(28) · 여기는 안 배치만(553-6). * 색 · 간격은 테마 토큰만(`ui_template_theme.css`). * ========================================================================== */ -.ss-fmtdlg__panel { - display: flex; - flex-direction: column; - width: min(560px, 92vw); - max-height: 84vh; - padding: var(--spacing-16); - border-radius: var(--radius-cards); - background: var(--color-surface-raised); - box-shadow: var(--shadow-lg); -} - -.ss-fmtdlg__title { - margin: 0 0 var(--spacing-8); - font-size: 1rem; - color: var(--color-text-body); -} - -.ss-fmtdlg__tabs { - display: flex; - flex-wrap: wrap; - gap: 2px; - border-bottom: 1px solid var(--color-border); -} - -.ss-fmtdlg__tabbtn { - padding: 6px 12px; - border: 1px solid var(--color-border); - border-bottom: 0; - border-radius: var(--radius-sm) var(--radius-sm) 0 0; - background: var(--color-paper); - color: var(--color-text-secondary); - font-size: 0.82rem; - cursor: pointer; -} - -.ss-fmtdlg__tabbtn.is-active { - background: var(--color-canvas); - color: var(--color-text-body); - font-weight: 600; -} - .ss-fmtdlg__body { flex: 1; min-height: 300px; @@ -68,21 +27,7 @@ width: 140px; flex-shrink: 0; color: var(--color-text-secondary); - font-size: 0.82rem; -} - -.ss-fmtdlg__row select, -.ss-fmtdlg__row input[type="text"], -.ss-fmtdlg__row input:not([type]), -.ss-fmtdlg__row input[type="number"], -.ss-fmtdlg__code { - height: 28px; - padding: 0 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font-size: 0.82rem; + font-size: var(--text-body-sm); } .ss-fmtdlg__code { @@ -135,7 +80,7 @@ .ss-fmtdlg__note { margin: 0 0 var(--spacing-8); color: var(--color-text-muted); - font-size: 0.8rem; + font-size: var(--text-caption); } .ss-fmtdlg__actions { diff --git a/A00_Common/spreadsheet/spreadsheet_format_dialog.ts b/A00_Common/spreadsheet/spreadsheet_format_dialog.ts index ce97d678d..e4c2d2442 100644 --- a/A00_Common/spreadsheet/spreadsheet_format_dialog.ts +++ b/A00_Common/spreadsheet/spreadsheet_format_dialog.ts @@ -12,7 +12,9 @@ * 우클릭 메뉴 [셀 서식] 도 같은 함수를 부르면 됨(`spreadsheet_menu.ts`). * ========================================================================== */ -import { createButton, el } from "@ui/ui_template_elements"; +import { createButton, el, setUiSize } from "@ui/ui_template_elements"; +import { createTabs } from "@ui/ui_template_tabs"; +import { openModal } from "@ui/ui_template_modal"; import { toFrac } from "@ui/sheet/ui_template_sheet_frac"; import { colName, rangeToA1, toA1 } from "./spreadsheet_address"; import { @@ -39,6 +41,7 @@ const TEXT = { tabProtect: "보호", ok: "확인", cancel: "취소", + close: "닫기", category: "범주", decimals: "소수 자릿수", thousands: "천 단위 구분 기호 사용", @@ -105,17 +108,9 @@ export function openFormatDialog(ctx: SpreadsheetContext): void { const spansMany = range.r0 !== range.r1 || range.c0 !== range.c1; let mergeWanted = (ctx.sheet().병합 ?? []).includes(rangeToA1(range)); - const overlay = el("div", { className: "ui-confirm ss-fmtdlg" }); - const panel = el("div", { className: "ss-fmtdlg__panel" }); const tabsBar = el("div", { className: "ss-fmtdlg__tabs" }); const body = el("div", { className: "ss-fmtdlg__body" }); - overlay.append(panel); - panel.append(el("h2", { className: "ss-fmtdlg__title", text: TEXT.title }), tabsBar, body); - - const close = (): void => overlay.remove(); - overlay.addEventListener("click", (ev) => { - if (ev.target === overlay) close(); - }); + let close = (): void => {}; // ── 표시 형식 ──────────────────────────────────────────────────────── let category: Category = guessCategory(draft.형식); @@ -464,22 +459,25 @@ export function openFormatDialog(ctx: SpreadsheetContext): void { [TEXT.tabProtect, protectTab], ]; - function showTab(index: number): void { - body.replaceChildren(tabs[index][1]()); - tabsBar.querySelectorAll(".ss-fmtdlg__tabbtn").forEach((btn, i) => { - btn.classList.toggle("is-active", i === index); - }); - } - - tabs.forEach(([label], i) => { - const btn = el("button", { - className: "ss-fmtdlg__tabbtn", - text: label, - attrs: { type: "button" }, - }); - btn.addEventListener("click", () => showTab(i)); - tabsBar.append(btn); + const tabBar = createTabs({ + tabs: tabs.map(([label], i) => ({ key: String(i), label })), + value: "0", + size: 32, + onChange: (key) => showTab(Number(key)), }); + tabsBar.append(tabBar.root); + + function showTab(index: number): void { + const tab = tabs[index][1](); + // 칸 = 부품 입력 · 고르기 28(553-6) + const fields = "select, input:not([type]), input[type=text], input[type=number]"; + for (const f of tab.querySelectorAll(fields)) { + f.classList.add(f.tagName === "SELECT" ? "ui-select" : "ui-input"); + setUiSize(f, 28); + } + body.replaceChildren(tab); + tabBar.select(String(index)); + } function apply(): void { ctx.dispatch({ 종류: "서식", 시트: ctx.selection.시트, 범위: ctx.selection.범위, 바꿀: draft }); @@ -497,13 +495,21 @@ export function openFormatDialog(ctx: SpreadsheetContext): void { const actions = el("div", { className: "ss-fmtdlg__actions", children: [ - createButton({ label: TEXT.cancel, variant: "ghost", onClick: close }), + createButton({ label: TEXT.cancel, variant: "ghost", onClick: () => close() }), createButton({ label: TEXT.ok, variant: "filled", onClick: apply }), ], }); - panel.append(actions); showTab(0); - document.body.append(overlay); - dialogKeys(overlay, close, apply); + openModal({ + title: TEXT.title, + closeLabel: TEXT.close, + width: "40rem", + dialogClass: "ss-fmtdlg", + mount: (box, shut) => { + close = shut; + box.append(tabsBar, body, actions); + dialogKeys(box.parentElement ?? box, shut, apply); + }, + }); } diff --git a/A00_Common/spreadsheet/spreadsheet_links.css b/A00_Common/spreadsheet/spreadsheet_links.css index 185a59526..f1087d6a5 100644 --- a/A00_Common/spreadsheet/spreadsheet_links.css +++ b/A00_Common/spreadsheet/spreadsheet_links.css @@ -120,20 +120,11 @@ width: 320px; padding: var(--spacing-8); border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border-radius: var(--radius-cards); background: var(--color-surface-raised); box-shadow: var(--shadow-lg); } -.ss-links__search { - padding: 4px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; -} - .ss-links__list { display: flex; flex-direction: column; @@ -155,7 +146,7 @@ } .ss-links__row:hover { - background: var(--color-mist-violet); + background: var(--color-hover); } .ss-links__note { diff --git a/A00_Common/spreadsheet/spreadsheet_links.ts b/A00_Common/spreadsheet/spreadsheet_links.ts index 6fec08291..17f0229d3 100644 --- a/A00_Common/spreadsheet/spreadsheet_links.ts +++ b/A00_Common/spreadsheet/spreadsheet_links.ts @@ -585,8 +585,8 @@ export function mountLinks( function openPick(anchor: HTMLElement): void { closePick(); const input = el("input", { - className: "ss-links__search", - attrs: { type: "search", placeholder: TEXT.search }, + className: "ui-input ss-links__search", + attrs: { type: "search", placeholder: TEXT.search, "data-size": "28" }, }) as HTMLInputElement; const list = el("div", { className: "ss-links__list" }); const note = (text: string) => el("div", { className: "ss-links__note", text }); diff --git a/A00_Common/spreadsheet/spreadsheet_menu.ts b/A00_Common/spreadsheet/spreadsheet_menu.ts index c47a50612..14ea72675 100644 --- a/A00_Common/spreadsheet/spreadsheet_menu.ts +++ b/A00_Common/spreadsheet/spreadsheet_menu.ts @@ -59,15 +59,15 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle { /** 메모 글 입력 — 작은 오버레이(native prompt 안 씀, 자동화 창이 native 창을 자동 취소하는 * 문제는 `showConfirmDialog` 머리 주석과 같은 까닭). 저장을 누르면 `onSave`. */ function openCommentEditor(initial: string, onSave: (text: string) => void): void { - const overlay = el("div", { className: "ss-comment-editor" }); - const panel = el("div", { className: "ss-comment-editor__panel" }); + const overlay = el("div", { className: "ui-confirm ss-comment-editor" }); + const panel = el("div", { className: "ui-confirm__panel" }); const textarea = el("textarea", { - className: "ss-comment-editor__input", + className: "ui-input ss-comment-editor__input", }) as HTMLTextAreaElement; textarea.value = initial; const done = (): void => overlay.remove(); const actions = el("div", { - className: "ss-comment-editor__actions", + className: "ui-confirm__actions", children: [ createButton({ label: "취소", variant: "ghost", onClick: done }), createButton({ diff --git a/A00_Common/spreadsheet/spreadsheet_sort_dialog.ts b/A00_Common/spreadsheet/spreadsheet_sort_dialog.ts index 92effb8cd..e4fb8185c 100644 --- a/A00_Common/spreadsheet/spreadsheet_sort_dialog.ts +++ b/A00_Common/spreadsheet/spreadsheet_sort_dialog.ts @@ -100,12 +100,17 @@ export function openSortDialog(ctx: SpreadsheetContext): () => void { function drawKeys() { list.replaceChildren( ...keys.map((k, i) => { - const col = el("select", { attrs: { "aria-label": t("Column") } }); + const col = el("select", { + className: "ui-select", + attrs: { "aria-label": t("Column"), "data-size": "28" }, + }); for (let c = rg.c0; c <= rg.c1; c++) col.append(el("option", { text: columnLabel(c), attrs: { value: String(c) } })); col.value = String(k.열); col.addEventListener("change", () => (k.열 = Number(col.value))); const dir = el("select", { + className: "ui-select", + attrs: { "data-size": "28" }, children: [ el("option", { text: t("Asc"), attrs: { value: "asc" } }), el("option", { text: t("Desc"), attrs: { value: "desc" } }), diff --git a/A00_Common/spreadsheet/spreadsheet_sortfilter.css b/A00_Common/spreadsheet/spreadsheet_sortfilter.css index 36244d642..821104eac 100644 --- a/A00_Common/spreadsheet/spreadsheet_sortfilter.css +++ b/A00_Common/spreadsheet/spreadsheet_sortfilter.css @@ -7,7 +7,7 @@ display: flex; align-items: center; justify-content: center; - background: rgb(0 0 0 / 18%); + background: var(--color-scrim); } .ss-sort__box, @@ -48,11 +48,6 @@ .ss-sort__row select { flex: 1; - padding: var(--spacing-4); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: inherit; } .ss-sort__row option { @@ -92,8 +87,8 @@ } .ss-filter__btn.is-on { - color: var(--color-royal-amethyst); - border-color: var(--color-royal-amethyst); + color: var(--color-accent); + border-color: var(--color-accent); } .ss-filter__panel { @@ -130,5 +125,5 @@ } .ss-filter__link:hover { - background: var(--color-mist-violet); + background: var(--color-hover); } diff --git a/A00_Common/spreadsheet/spreadsheet_stage3.css b/A00_Common/spreadsheet/spreadsheet_stage3.css index e50db4efe..2d394bd59 100644 --- a/A00_Common/spreadsheet/spreadsheet_stage3.css +++ b/A00_Common/spreadsheet/spreadsheet_stage3.css @@ -14,7 +14,7 @@ padding: var(--spacing-8); background: var(--color-surface); border: 1px solid var(--color-border); - border-radius: 8px; + border-radius: var(--radius-cards); min-width: 280px; } @@ -89,7 +89,7 @@ } .ss-validation__dropdown-item:hover { - background: var(--color-mist-violet); + background: var(--color-hover); } .ss-statusbar { diff --git a/A00_Common/spreadsheet/spreadsheet_toolbar.css b/A00_Common/spreadsheet/spreadsheet_toolbar.css index 92606abe7..c5c3cb80f 100644 --- a/A00_Common/spreadsheet/spreadsheet_toolbar.css +++ b/A00_Common/spreadsheet/spreadsheet_toolbar.css @@ -215,42 +215,13 @@ /* ── 메모 편집 창(칸 우클릭 [메모 삽입 · 편집]) ──────────────────────────────── */ -.ss-comment-editor { - position: fixed; - inset: 0; - z-index: var(--z-modal); - display: flex; - align-items: center; - justify-content: center; - background: var(--color-scrim); -} - -.ss-comment-editor__panel { - display: flex; - flex-direction: column; - gap: var(--spacing-8); - width: 280px; - padding: var(--spacing-16); - border-radius: var(--radius-cards); - background: var(--color-surface-raised); - box-shadow: var(--shadow-lg); -} - +/* 창 = 공용 확인창(.ui-confirm) · 칸 = 부품 입력(여러 줄) — 여기는 칸 크기만(553-6) */ .ss-comment-editor__input { + display: block; + box-sizing: border-box; + width: 100%; min-height: 80px; - padding: var(--spacing-8); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-canvas); - color: var(--color-text-body); - font: inherit; - resize: vertical; -} - -.ss-comment-editor__actions { - display: flex; - justify-content: flex-end; - gap: var(--spacing-8); + margin-bottom: var(--spacing-16); } /* ⋯ 칸 메뉴(기기대응 3-5) — 터치 모드에서만 */ From a5bde66bd391f11f44fac1d7cf50b893bb962e3e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 22:17:44 +0900 Subject: [PATCH 2/3] =?UTF-8?q?style(cad=5Fhost):=20CAD=20=ED=83=AD=20?= =?UTF-8?q?=C3=97=20=C2=B7=20+=20=EB=A5=BC=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=20=EB=8B=A8=EC=B6=94=20=EA=BC=B4=EB=A1=9C=20=C2=B7=20=EB=8F=84?= =?UTF-8?q?=EA=B0=81=20=ED=8E=B8=EC=A7=91=20=EB=8B=A8=EC=B6=94=2024=20?= =?UTF-8?q?=C2=B7=20=EB=82=A0=20=EA=B7=B8=EB=A6=BC=EC=9E=90=20=C2=B7=20?= =?UTF-8?q?=EB=B3=B4=EB=9D=BC=20=EB=B3=84=EC=B9=AD=20=E2=86=92=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EA=B0=92=20(PLAN=20553-6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 탭 줄 × = 아이콘 단추(18) · + = 아이콘 단추(22) — 문서 탭 꼴은 그대로 · 자체 단추 CSS 지움 - 도각 편집 띠 [완료 · 파일 불러오기 · 기본 도각 복원 · 취소] = 부품 단추 24(자체 여백 · 글 크기 지움) - 불러오는 중 상자 그림자 = --shadow-lg · 오류 색 날 값 지움 · 시트 표(ui-sheet) 켬 색 · 안내 글 = 공통 값 - 시험 test_cad_host_tabs 가 클래스 여럿 붙은 단추를 찾게 고침 · B07 전후 촬영 CAD 안 배치 0 바뀜 · 바탕 색 같음 Co-Authored-By: Claude Opus 5.5 --- resources/tester/test_cad_host_tabs.py | 6 +++--- ui_template/cad_host/cad_host.css | 10 ++++------ ui_template/cad_host/cad_host_frame_edit.ts | 4 ++++ ui_template/cad_host/cad_host_tabs.css | 20 +------------------- ui_template/cad_host/cad_host_tabs.ts | 6 ++++-- ui_template/sheet/ui_template_sheet.css | 4 ++-- 6 files changed, 18 insertions(+), 32 deletions(-) diff --git a/resources/tester/test_cad_host_tabs.py b/resources/tester/test_cad_host_tabs.py index bcffbcd31..5efd4d11c 100644 --- a/resources/tester/test_cad_host_tabs.py +++ b/resources/tester/test_cad_host_tabs.py @@ -81,7 +81,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template out.fixedX = tabNode("a").kids.length; // 고정 탭 = 글만(지우기 없음) // ③ 지우기 — 물음 뒤 · 지금 탭이면 다음 탭을 실음 await tabs.select("c"); - const xc = tabNode("c").kids.find((k) => k.className === "cad-tabs__x"); + const xc = tabNode("c").kids.find((k) => k.className.split(" ").includes("cad-tabs__x")); await xc.on.click({ stopPropagation() {} }); out.afterRemove = { ids: tabs.tabs().map((t) => t.id), active: tabs.active().id, confirms: confirms.length }; // ④ [+] — create 없으면 아무것도 안 함 · 닫기 기능(B07) = 미저장 탭만 물음 @@ -97,7 +97,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template await closer.open({ id: "d1", name: "종단" }); const before = confirms.length; const d2 = closer.element.kids.find((k) => k.dataset.tabId === "d2"); - await d2.kids.find((k) => k.className === "cad-tabs__x").on.click({ stopPropagation() {} }); + await d2.kids.find((k) => k.className.split(" ").includes("cad-tabs__x")).on.click({ stopPropagation() {} }); const last = closer.element.kids.find((k) => k.dataset.tabId === "d1").kids.length; // 마지막 탭 = 닫기 없음 out.b07 = { log: b07, asked: confirms.length - before, ids: closer.tabs().map((t) => t.id), last }; @@ -137,7 +137,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template }); await new Promise((r) => setTimeout(r, 5)); const bar = host.kids[0].kids.find((k) => k.className === "cad-tabs"); - const plus = bar.kids.find((k) => k.className === "cad-tabs__add"); + const plus = bar.kids.find((k) => k.className.split(" ").includes("cad-tabs__add")); await plus.on.click(); await new Promise((r) => setTimeout(r, 5)); inCad = { ...inCad, entities: [...inCad.entities, { type: "Circle" }] }; // 새 장에 하나 그림 diff --git a/ui_template/cad_host/cad_host.css b/ui_template/cad_host/cad_host.css index 282198718..9b53aff16 100644 --- a/ui_template/cad_host/cad_host.css +++ b/ui_template/cad_host/cad_host.css @@ -24,7 +24,7 @@ padding: 10px 18px; border-radius: var(--radius-cards); background-color: var(--color-surface); - box-shadow: 0 2px 10px rgb(0 0 0 / 18%); + box-shadow: var(--shadow-lg); color: var(--color-text); font-size: 13px; } @@ -38,10 +38,10 @@ transform: translateX(-50%); max-width: 70%; padding: 8px 16px; - border: 1px solid var(--color-danger, #d33); + border: 1px solid var(--color-danger); border-radius: var(--radius-cards); background-color: var(--color-surface); - color: var(--color-danger, #d33); + color: var(--color-danger); font-size: 12px; } @@ -86,7 +86,7 @@ } .cad-frame-edit__label { - font-size: 0.78rem; + font-size: var(--text-caption); line-height: 1.4; color: var(--color-text); } @@ -99,8 +99,6 @@ .cad-frame-edit__buttons > button { flex: 1 1 0; min-width: 0; - padding: 4px 8px; - font-size: 0.78rem; } /* 작도 영역 숫자 칸 넷(169-1 · `cad_host_area.ts`) — 두 줄 두 칸 */ diff --git a/ui_template/cad_host/cad_host_frame_edit.ts b/ui_template/cad_host/cad_host_frame_edit.ts index ec2f12eba..4a110fd98 100644 --- a/ui_template/cad_host/cad_host_frame_edit.ts +++ b/ui_template/cad_host/cad_host_frame_edit.ts @@ -86,6 +86,7 @@ export function createFrameTemplateEditor( const finishButton = createButton({ label: "완료", variant: "filled", + size: 24, onClick: () => void finish(), }); /** @@ -95,6 +96,7 @@ export function createFrameTemplateEditor( const resetButton = createButton({ label: "기본 도각 복원", variant: "ghost", + size: 24, onClick: () => void resetToDefault(), }); /** @@ -111,12 +113,14 @@ export function createFrameTemplateEditor( const importButton = createButton({ label: "파일 불러오기", variant: "ghost", + size: 24, onClick: () => fileInput.click(), }); const cancelButton = createButton({ label: "취소", variant: "ghost", + size: 24, onClick: () => leave(), }); const bannerButtons = document.createElement("div"); diff --git a/ui_template/cad_host/cad_host_tabs.css b/ui_template/cad_host/cad_host_tabs.css index abc753155..0efc12b83 100644 --- a/ui_template/cad_host/cad_host_tabs.css +++ b/ui_template/cad_host/cad_host_tabs.css @@ -66,28 +66,10 @@ font: inherit; } -.cad-tabs__x, -.cad-tabs__add { - padding: 0 4px; - border: none; - border-radius: 4px; - background: transparent; - color: var(--color-text-secondary); - font-size: 14px; - line-height: 1; - cursor: pointer; -} - -.cad-tabs__x:hover, -.cad-tabs__add:hover { - background: var(--color-surface-raised); - color: var(--color-text); -} - +/* × · + = 아이콘 단추 부품(18 · 22) — 여기는 자리만(553-6) */ .cad-tabs__add { align-self: center; margin-left: 2px; - font-size: 16px; } .cad-tabs[data-busy="true"] .cad-tabs__tab { diff --git a/ui_template/cad_host/cad_host_tabs.ts b/ui_template/cad_host/cad_host_tabs.ts index 9140d49fc..a40b6c1c4 100644 --- a/ui_template/cad_host/cad_host_tabs.ts +++ b/ui_template/cad_host/cad_host_tabs.ts @@ -233,7 +233,8 @@ export function createCadTabs(options: CadTabsOptions): CadTabs { if ((features.remove || (features.close && list.length > 1)) && !tab.fixed) { const x = document.createElement("button"); x.type = "button"; - x.className = "cad-tabs__x"; + x.className = "ui-icon-btn ui-icon-btn--ghost cad-tabs__x"; // 아이콘 단추 부품 꼴(553-6) + x.dataset.size = "18"; x.textContent = "×"; x.title = x.ariaLabel = features.remove ? text.remove : text.close; x.addEventListener("click", (event) => { @@ -272,7 +273,8 @@ export function createCadTabs(options: CadTabsOptions): CadTabs { if (features.add) { const plus = document.createElement("button"); plus.type = "button"; - plus.className = "cad-tabs__add"; + plus.className = "ui-icon-btn ui-icon-btn--ghost cad-tabs__add"; + plus.dataset.size = "22"; plus.textContent = "+"; plus.title = plus.ariaLabel = text.add; plus.disabled = busy; diff --git a/ui_template/sheet/ui_template_sheet.css b/ui_template/sheet/ui_template_sheet.css index e9163a6f1..e100d1634 100644 --- a/ui_template/sheet/ui_template_sheet.css +++ b/ui_template/sheet/ui_template_sheet.css @@ -18,13 +18,13 @@ } .ui-sheet__toolbar .ui-btn.is-on { - background: var(--color-mist-violet); + background: var(--color-accent-soft); } .ui-sheet__hint { margin: 0; color: var(--color-text-secondary); /* muted 는 작은 글에 대비 3:1 — 모자람 */ - font-size: 0.8rem; + font-size: var(--text-caption); } .ui-sheet__scroll { From b8a19e983f05cbf244802c7970803a7f2a576c79 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 22:33:45 +0900 Subject: [PATCH 3/3] =?UTF-8?q?style(cad):=20CAD=20=EC=95=B1=20=EA=B2=89?= =?UTF-8?q?=EC=9D=84=20=EC=95=B1=20=EA=B3=B5=ED=86=B5=20=EA=B0=92=20=C2=B7?= =?UTF-8?q?=20=ED=81=AC=EA=B8=B0=20=EB=8B=A8=20=C2=B7=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=EC=B0=BD=20=EB=B6=80=ED=92=88=EC=9C=BC=EB=A1=9C=20=E2=80=94=20?= =?UTF-8?q?window.confirm=20=C2=B7=20prompt=20=EA=B1=B7=EC=9D=8C=20(PLAN?= =?UTF-8?q?=20553-6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 블록 이름 묻기 = 부품 글 묻기(askText) · 블록 지우기 = 위험 확인(showConfirmDialog) — CAD 창 안에 앱과 같은 확인창 꼴(injectBaseStyles) - 크기 단 — 리본 특성 칸 19 → 18 · 빠른 실행 24×20 → 20 · 상태막대 단추 21 → 22 · 명령 칸 26 → 28 · 왼쪽 판 탭 38 → 36 · Button 40 → 42 · 블록 · 자리표 단추 22 · 24 · 큰 리본 도구 66 · 리본 탭 줄은 꼴 그대로 - 마우스 바탕 = --color-hover · 켜짐 바탕 = --color-accent-soft(옛 보라 별칭 걷음) · 떠 있는 판 · 메뉴 둥글기 = 공통 값(날 2 · 3 · 5 px 11 곳 → 0) - 안 쓰는 DropdownButton 지움(grep 0) · 그림(캔버스 바탕 · --draw-*) 전후 같음 · B07 전후 촬영 CAD 안 배치 바뀜 = 빠른 실행 칸만 Co-Authored-By: Claude Opus 5.5 --- B07_DesignDetail/openwebcad/src/App.css | 25 ++++--- .../openwebcad/src/App_Layers.css | 37 +++++----- .../src/commands/commands.insert.ts | 16 +++-- .../src/components/BlockLibraryPanel.tsx | 8 ++- .../openwebcad/src/components/Button.tsx | 2 +- .../src/components/DropdownButton.tsx | 69 ------------------- .../src/inputController/snap-menu.css | 8 +-- B07_DesignDetail/openwebcad/src/main.tsx | 2 + B07_DesignDetail/openwebcad/tsconfig.app.json | 11 ++- 9 files changed, 66 insertions(+), 112 deletions(-) delete mode 100644 B07_DesignDetail/openwebcad/src/components/DropdownButton.tsx diff --git a/B07_DesignDetail/openwebcad/src/App.css b/B07_DesignDetail/openwebcad/src/App.css index 9c4c7197a..60d087d89 100644 --- a/B07_DesignDetail/openwebcad/src/App.css +++ b/B07_DesignDetail/openwebcad/src/App.css @@ -27,7 +27,9 @@ --cad-text-dim: var(--color-text-secondary); --cad-text-muted: var(--color-text-muted); --cad-accent: var(--color-accent); - --cad-hover: var(--color-mist-violet); + --cad-hover: var(--color-hover); + /* 켜짐 바탕 = 앱 켜기 꼴(553-6) */ + --cad-on: var(--color-accent-soft); --cad-canvas: var(--draw-canvas); } @@ -197,7 +199,7 @@ body > canvas[data-id="canvas"] { overflow-y: auto; background: var(--cad-chrome-raised); border: 1px solid var(--cad-line); - border-radius: 4px; + border-radius: var(--radius-cards); box-shadow: var(--shadow-lg); } .cad-ribbon-overflow button { @@ -206,7 +208,7 @@ body > canvas[data-id="canvas"] { align-items: center; padding: 5px 8px; border: 0; - border-radius: 3px; + border-radius: var(--radius-sm); background: transparent; font-size: 11px; text-align: left; @@ -220,7 +222,7 @@ body > canvas[data-id="canvas"] { align-self: center; } .cad-ribbon-tabs .cad-quick-access button { - width: 24px; + width: 20px; height: 20px; padding: 0; border: 1px solid transparent; @@ -254,13 +256,13 @@ body > canvas[data-id="canvas"] { } .cad-prop select, .cad-prop input[type="number"] { - height: 19px; + height: 18px; min-width: 78px; padding: 0 3px; color: var(--cad-text); background: var(--cad-chrome-sunken); border: 1px solid var(--cad-line); - border-radius: 3px; + border-radius: 4px; font-size: 11px; } .cad-prop input[type="number"] { @@ -268,13 +270,13 @@ body > canvas[data-id="canvas"] { width: 52px; } .cad-prop input[type="color"] { - height: 19px; + height: 18px; width: 44px; padding: 1px; background: var(--cad-chrome-sunken); /* 흰색을 골라도 스와치 자리가 보이게 테두리를 진하게 준다 (2026-09-01) */ border: 1px solid var(--cad-text-muted); - border-radius: 3px; + border-radius: 4px; cursor: pointer; } /* 고른 것들 색이 다름(488) — 빈 견본(투명 · 점선 테두리) */ @@ -283,6 +285,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans .cad-ribbon-props .cad-tool[data-size="small"] { height: 22px; min-width: 0; + border-radius: 4px; } .cad-tool { display: flex; @@ -293,7 +296,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans min-width: 48px; padding: 3px 6px; border: 1px solid transparent; - border-radius: 3px; + border-radius: var(--radius-buttons); background: transparent; font-size: 10px; white-space: nowrap; @@ -328,7 +331,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans } .cad-tool[data-active="true"] { border-color: var(--cad-accent); - background: var(--cad-hover); + background: var(--cad-on); box-shadow: inset 0 -2px var(--cad-accent); } .cad-tool:disabled { @@ -376,7 +379,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans .cad-inspector-tabs { display: grid; grid-template-columns: 1fr 1fr; - height: 38px; + height: 36px; border-bottom: 1px solid var(--cad-line); } .cad-inspector-tabs button { diff --git a/B07_DesignDetail/openwebcad/src/App_Layers.css b/B07_DesignDetail/openwebcad/src/App_Layers.css index c46a6d7d1..f45c4a0e5 100644 --- a/B07_DesignDetail/openwebcad/src/App_Layers.css +++ b/B07_DesignDetail/openwebcad/src/App_Layers.css @@ -41,7 +41,7 @@ align-items: center; overflow: hidden; border: 1px solid var(--cad-line); - border-radius: 4px; + border-radius: var(--radius-buttons); background: var(--cad-chrome-raised); box-shadow: var(--shadow-lg); } @@ -144,13 +144,13 @@ body.cad-file-dragging::after { padding: 4px; background: var(--cad-chrome-raised); border: 1px solid var(--cad-line); - border-radius: 4px; + border-radius: var(--radius-cards); box-shadow: var(--shadow-lg); } .cad-command-suggestions button { padding: 4px 8px; border: 0; - border-radius: 3px; + border-radius: var(--radius-sm); background: transparent; font: 11px Consolas, monospace; text-align: left; @@ -182,10 +182,10 @@ body.cad-file-dragging::after { } .cad-command-area input { flex: 1; - height: 26px; + height: 28px; padding: 0 8px; border: 1px solid var(--cad-line); - border-radius: 2px; + border-radius: var(--radius-inputs); outline: none; background: var(--cad-chrome-sunken); color: var(--cad-text); @@ -211,10 +211,10 @@ body.cad-file-dragging::after { border-top: 1px solid var(--cad-line); } .cad-statusbar button { - height: 21px; + height: 22px; padding: 0 8px; border: 1px solid transparent; - border-radius: 2px; + border-radius: 4px; background: transparent; color: var(--cad-text-dim); font-size: 10px; @@ -267,7 +267,7 @@ body.cad-file-dragging::after { min-width: 0; padding: 0 4px; border: 1px solid var(--cad-line); - border-radius: 3px; + border-radius: 6px; background: var(--cad-chrome-sunken); color: var(--cad-text); font-size: 11px; @@ -308,7 +308,7 @@ body.cad-file-dragging::after { padding: 8px 10px 10px; background: var(--cad-chrome-raised); border: 1px solid var(--cad-line); - border-radius: 5px; + border-radius: var(--radius-cards); box-shadow: var(--shadow-lg); } .cad-quick-properties__header { @@ -336,7 +336,7 @@ body.cad-file-dragging::after { padding: 8px 10px 10px; background: var(--cad-chrome-raised); border: 1px solid var(--cad-line); - border-radius: 5px; + border-radius: var(--radius-cards); box-shadow: var(--shadow-lg); } .cad-block-library__empty { @@ -381,9 +381,10 @@ body.cad-file-dragging::after { } .cad-block-library__actions button { flex: 1; - padding: 4px 0; + height: 24px; + padding: 0; border: 1px solid var(--cad-line); - border-radius: 3px; + border-radius: 6px; background: transparent; color: var(--cad-text-dim); font-size: 11px; @@ -405,7 +406,7 @@ body.cad-file-dragging::after { width: 236px; padding: 8px; border: 1px solid var(--cad-line); - border-radius: 6px; + border-radius: var(--radius-cards); background: var(--cad-chrome-raised); box-shadow: var(--shadow-lg); color: var(--cad-text); @@ -444,7 +445,8 @@ body.cad-file-dragging::after { } .cad-frame-tokens__button { - padding: 3px 7px; + height: 22px; + padding: 0 8px; border: 1px solid var(--cad-line); border-radius: 4px; background: var(--cad-chrome); @@ -454,7 +456,7 @@ body.cad-file-dragging::after { } .cad-frame-tokens__button:hover { - background: var(--cad-accent-soft, var(--cad-chrome-raised)); + background: var(--cad-hover); } /* 자리표 칸 크기 입력 (2026-09-06) */ @@ -468,9 +470,10 @@ body.cad-file-dragging::after { .cad-frame-tokens__size input { width: 64px; - padding: 2px 4px; + height: 24px; + padding: 0 4px; border: 1px solid var(--cad-line); - border-radius: 4px; + border-radius: 6px; background: var(--cad-chrome); color: var(--cad-text); font-size: 11px; diff --git a/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts b/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts index 6cb2b8d73..e3c5561c7 100644 --- a/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts +++ b/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts @@ -1,5 +1,6 @@ /** 삽입 탭 명령 (조사표 4절 중 구현분) */ import { toast } from 'react-toastify'; +import { askText } from '../../../../ui_template/ui_template_modal'; import { saveBlockFromEntities } from '../blocks/block-library'; import { setBlockLibraryVisible } from '../components/ui-state'; import { importImageFromFile } from '../helpers/import-export-handlers/import-image-from-file'; @@ -39,13 +40,16 @@ export const INSERT_COMMANDS: CadCommand[] = [ needsSelection: true, run: () => { const entities = getSelectedEntities(); - const name = window.prompt('블록 이름', '')?.trim(); - if (!name) return '블록 작성 취소'; - void saveBlockFromEntities(name, entities).then(() => { - toast.success(`블록 '${name}' 저장 (객체 ${entities.length}개)`); - setBlockLibraryVisible(true); + // 글 묻기 = 부품 확인창(553-6 · window.prompt 대신) + void askText({ message: '블록 이름', okLabel: '저장' }).then((raw) => { + const name = raw?.trim(); + if (!name) return; + void saveBlockFromEntities(name, entities).then(() => { + toast.success(`블록 '${name}' 저장 (객체 ${entities.length}개)`); + setBlockLibraryVisible(true); + }); }); - return `블록 '${name}' 저장`; + return '블록 이름 입력'; }, }, { diff --git a/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx b/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx index 3fc99300a..0743bbbe3 100644 --- a/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx +++ b/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx @@ -1,5 +1,6 @@ /** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) */ import type { FC } from 'react'; +import { showConfirmDialog } from '../../../../ui_template/ui_template_modal'; import { deleteBlock, exportBlocksToFile, @@ -45,9 +46,10 @@ export const BlockLibraryPanel: FC = () => { type="button" title="삭제" onClick={() => { - if (window.confirm(`블록 '${block.name}'을 삭제하시겠습니까?`)) { - deleteBlock(block.name); - } + // 위험 확인 = 부품 확인창(553-6 · window.confirm 대신) + void showConfirmDialog(`블록 '${block.name}'을 삭제하시겠습니까?`, '삭제').then( + (ok) => ok && deleteBlock(block.name), + ); }} > ✕ diff --git a/B07_DesignDetail/openwebcad/src/components/Button.tsx b/B07_DesignDetail/openwebcad/src/components/Button.tsx index f95ddc2c4..c02ce12fa 100644 --- a/B07_DesignDetail/openwebcad/src/components/Button.tsx +++ b/B07_DesignDetail/openwebcad/src/components/Button.tsx @@ -39,7 +39,7 @@ export const Button: FC = ({ }) => { // 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만. const classParts = [ - 'font-semibold py-4 h-10 flex flex-row justify-start w-full items-center', + 'font-semibold py-4 h-[42px] flex flex-row justify-start w-full items-center', size === 'regular' ? 'pl-2 pr-2 gap-2' : '', size === 'small' ? 'pl-2 pr-2 gap-0' : '', className || '', diff --git a/B07_DesignDetail/openwebcad/src/components/DropdownButton.tsx b/B07_DesignDetail/openwebcad/src/components/DropdownButton.tsx deleted file mode 100644 index 995abf0c5..000000000 --- a/B07_DesignDetail/openwebcad/src/components/DropdownButton.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { CSSProperties, FC, ReactNode } from 'react'; -import useLocalStorageState from 'use-local-storage-state'; -import { LOCAL_STORAGE_KEY } from '../App.types.ts'; -import { keyboardHandler } from '../helpers/keyboard-handler.ts'; -import { Button } from './Button.tsx'; -import { Icon, IconName } from './Icon/Icon.tsx'; - -interface DropdownButtonProps { - label?: string; - title?: string; - iconName?: IconName; - iconComponent?: ReactNode; - active?: boolean; - onClick?: () => void; - className?: string; - style?: CSSProperties; - buttonStyle?: CSSProperties; - dataId: string; - children?: ReactNode; - defaultOpen?: boolean; -} - -export const DropdownButton: FC = ({ - label, - title, - iconName, - iconComponent, - className, - style, - buttonStyle, - dataId, - children, - defaultOpen = false, -}) => { - const [isOpen, setIsOpen] = useLocalStorageState( - `${LOCAL_STORAGE_KEY.DROPDOWN}___${dataId}`, - { defaultValue: defaultOpen } - ); - - const classParts = [ - 'flex flex-col gap-2 relative', - className || '', - isOpen ? ' bg-slate-900' : '', - ]; - return ( -
-
- ); -}; diff --git a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css index 8d313cc24..aea05f221 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css +++ b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css @@ -7,15 +7,15 @@ min-width: 160px; padding: 4px; border: 1px solid var(--color-border); - border-radius: 6px; + border-radius: var(--radius-cards); background: var(--color-surface-raised); - box-shadow: var(--shadow-sm); + box-shadow: var(--shadow-lg); } .cad-snap-menu > button { padding: 4px 10px; border: 0; - border-radius: 4px; + border-radius: var(--radius-sm); background: transparent; color: var(--color-text); font-size: 13px; @@ -25,5 +25,5 @@ .cad-snap-menu > button:hover, .cad-snap-menu > button:focus-visible { - background: var(--color-surface-hover, rgba(127, 127, 127, 0.15)); + background: var(--color-hover); } diff --git a/B07_DesignDetail/openwebcad/src/main.tsx b/B07_DesignDetail/openwebcad/src/main.tsx index 38280e159..81e9a91f6 100644 --- a/B07_DesignDetail/openwebcad/src/main.tsx +++ b/B07_DesignDetail/openwebcad/src/main.tsx @@ -7,6 +7,7 @@ import { SNAP_POINT_DISTANCE } from './App.consts'; import App from './App.tsx'; import { TOOL_STATE_MACHINES } from './commands/registry'; import { ScreenCanvasDrawController } from './drawControllers/screenCanvas.drawController'; +import { injectBaseStyles } from '../../../ui_template/ui_template_elements_styles'; import { registerAutoSave } from './helpers/autosave.ts'; import { registerCadDebugHook } from './helpers/debug-hook.ts'; import { draw } from './helpers/draw'; @@ -47,6 +48,7 @@ import { ActorEvent, type DrawEvent } from './tools/tool.types'; // 호스트 앱의 화이트/블랙 모드를 먼저 붙인 뒤 렌더한다. syncThemeFromHost(); +injectBaseStyles(); // 부품 단추 · 확인창 꼴(553-6 · 앱과 같은 한 벌) // 부모가 `?bare` 로 띄우면 첫 그림부터 캔버스만 — 도면을 넘길 때 다시 정함(522-2) if (new URLSearchParams(window.location.search).has('bare')) setBare(true); diff --git a/B07_DesignDetail/openwebcad/tsconfig.app.json b/B07_DesignDetail/openwebcad/tsconfig.app.json index 87ee7f72c..79edf6ba4 100644 --- a/B07_DesignDetail/openwebcad/tsconfig.app.json +++ b/B07_DesignDetail/openwebcad/tsconfig.app.json @@ -25,6 +25,15 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "../../A00_Common/key_shortcut.ts", "../../A00_Common/input_prefs.ts"], + "include": [ + "src", + "../../A00_Common/key_shortcut.ts", + "../../A00_Common/input_prefs.ts", + "../../ui_template/ui_template_modal.ts", + "../../ui_template/ui_template_button.ts", + "../../ui_template/ui_template_icons.ts", + "../../ui_template/ui_template_elements_base.ts", + "../../ui_template/ui_template_elements_styles.ts" + ], "exclude": ["node_modules"] }