diff --git a/A00_Common/app_settings.css b/A00_Common/app_settings.css index c7d32a4d9..a1cdc7da4 100644 --- a/A00_Common/app_settings.css +++ b/A00_Common/app_settings.css @@ -1,10 +1,11 @@ -/* ☰ [설정] 창(PLAN 332-2) — 탭 줄 · 휠 방향 고름 · 표 */ +/* ☰ [설정] 창(PLAN 332-2) — 탭 줄 = 부품 보조 탭 · 휠 방향 = 부품 라디오 · CAD 별칭 표 = 부품 표 */ +.app-settings .ui-text[hidden] { + display: none; /* 부품 보조 글은 display:flex 라 hidden 을 이김 */ +} + .app-settings__tabs { - display: flex; flex-wrap: wrap; - gap: var(--spacing-8); padding-bottom: var(--spacing-8); - border-bottom: 1px solid var(--color-border); } .app-settings__radios { @@ -13,25 +14,13 @@ gap: var(--spacing-16); } -.app-settings__radios > label { - display: inline-flex; - align-items: center; - gap: var(--spacing-8); +.app-settings__radios > .ui-checkbox { min-height: 32px; - cursor: pointer; } -.app-settings__table { - width: 100%; - border-collapse: collapse; -} - -.app-settings__table th, -.app-settings__table td { - padding: var(--spacing-4) var(--spacing-8); - border-bottom: 1px solid var(--color-border); - text-align: left; - vertical-align: middle; +/* 표 머리는 창 몸통을 따라 붙음 — 부품 표 겉틀의 가로 스크롤은 풂 */ +.app-settings .ui-table-wrap { + overflow: visible; } .app-settings__table thead th { @@ -40,18 +29,6 @@ background: var(--color-surface-raised); } -.app-settings__table input[aria-invalid="true"] { - border-color: var(--color-danger); -} - -.app-settings__error { - color: var(--color-danger); -} - -.app-settings__error:empty { - display: none; -} - .app-settings__rules { margin: 0 0 var(--spacing-8); padding: 0; @@ -77,14 +54,3 @@ .app-settings__keys:only-child { grid-column: 1 / -1; } - -.app-settings__keys > kbd { - padding: 0 var(--spacing-8); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-surface-raised); - color: var(--color-text); - font-family: var(--font-body); - font-size: 0.85em; - line-height: 1.7; -} diff --git a/A00_Common/app_settings.ts b/A00_Common/app_settings.ts index 8e8efc4d5..17e6cbb27 100644 --- a/A00_Common/app_settings.ts +++ b/A00_Common/app_settings.ts @@ -7,12 +7,15 @@ * - 로그인 전엔 이 브라우저에만 남음(계정 올리기는 `b_page_state` 가 로그인 뒤에) * ========================================================================== */ +import { createBadge } from "@ui/ui_template_badge"; +import { createCheckbox } from "@ui/ui_template_checkbox"; import { createButton, el, showToast } from "@ui/ui_template_elements"; import { askLeave } from "@ui/ui_template_leave_dialog"; import { t as L } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; import { writeState } from "./b_page_state"; import { inputPrefs, type InputPrefs, type WheelZoom } from "./input_prefs"; +import { fillTabs } from "./b_page_tabs"; import { buildCadAliasTab, checkCadAliases } from "./app_settings_cad"; import "./app_settings.css"; @@ -66,7 +69,7 @@ function ruleList(keys: LocaleKey[]): HTMLElement { children: [ el("span", { className: "app-settings__keys", - children: input.split(" · ").map((text) => el("kbd", { text })), + children: input.split(" · ").map((text) => createBadge(text, "neutral")), }), ...(at < 0 ? [] : [el("span", { text: line.slice(at + 3) })]), ], @@ -81,12 +84,14 @@ function mouseTab(draft: InputPrefs): HTMLElement { attrs: { role: "radiogroup", "aria-label": L("App_Settings_Wheel") }, }); for (const [mode, key] of WHEEL) { - const input = el("input", { - attrs: { type: "radio", name: "app-settings-wheel", value: mode }, - }); - input.checked = draft.wheelZoom === mode; - input.addEventListener("change", () => (draft.wheelZoom = mode)); - group.append(el("label", { children: [input, L(key)] })); + group.append( + createCheckbox(L(key), { + radio: "app-settings-wheel", + value: mode, + checked: draft.wheelZoom === mode, + onChange: () => (draft.wheelZoom = mode), + }).root, + ); } return el("div", { children: [ @@ -114,25 +119,15 @@ export function openAppSettings(): void { cad: cad.root, keys: keysTab(), }; - const tabs = el("div", { - className: "app-settings__tabs ui-subtabs", - attrs: { role: "tablist" }, - }); + const tabs = el("div", { className: "app-settings__tabs" }); const show = (id: TabId): void => { - tabs.replaceChildren( - ...TABS.map(([tab, key]) => { - const button = createButton({ - label: L(key), - variant: tab === id ? "filled" : "ghost", - size: "sm", - onClick: () => show(tab), - }); - button.setAttribute("role", "tab"); - button.setAttribute("aria-selected", String(tab === id)); - button.dataset.settingsTab = tab; - return button; - }), - ); + fillTabs(tabs, { + tabs: TABS.map(([tab, key]) => ({ key: tab, label: L(key) })), + value: id, + kind: "pill", + data: "settingsTab", + onChange: (tab) => show(tab as TabId), + }); for (const [tab, panel] of Object.entries(panels)) panel.hidden = tab !== id; }; diff --git a/A00_Common/app_settings_cad.ts b/A00_Common/app_settings_cad.ts index 217623f03..b32884f86 100644 --- a/A00_Common/app_settings_cad.ts +++ b/A00_Common/app_settings_cad.ts @@ -8,7 +8,10 @@ * ========================================================================== */ import catalog from "../B07_DesignDetail/openwebcad/src/commands/command-catalog.json"; +import { setUiSize } from "@ui/ui_template_button"; import { el } from "@ui/ui_template_elements"; +import { createTable } from "@ui/ui_template_table"; +import { createText } from "@ui/ui_template_text"; import { t as L } from "@ui/ui_template_locale"; export interface CadCommandRow { @@ -67,13 +70,15 @@ export function buildCadAliasTab(aliases: Record): { root: HTMLElement; mark: (errors: Record) => void; } { - const message = el("p", { className: "app-settings__error", attrs: { role: "alert" } }); + const message = createText("", "error"); + message.hidden = true; const inputs = new Map(); const rows = CAD_COMMANDS.map((command) => { const input = el("input", { - className: "ui-input ui-input--sm", + className: "ui-input", attrs: { type: "text", "aria-label": `${command.id} ${L("App_Settings_Cad_Mine")}` }, }); + setUiSize(input, 24); input.value = (aliases[command.id] ?? []).join(", "); input.dataset.cadCommand = command.id; input.addEventListener("input", () => { @@ -82,25 +87,21 @@ export function buildCadAliasTab(aliases: Record): { else delete aliases[command.id]; }); inputs.set(command.id, input); - const cells = [command.id, command.label, command.aliases.join(", ")].map((text) => - el("td", { text }), - ); - return el("tr", { children: [...cells, el("td", { children: [input] })] }); + return [command.id, command.label, command.aliases.join(", "), input]; }); - const head = el("tr", { - children: ( + const table = createTable( + ( [ "App_Settings_Cad_Command", "App_Settings_Cad_Label", "App_Settings_Cad_Default", "App_Settings_Cad_Mine", ] as const - ).map((key) => el("th", { text: L(key) })), - }); - const table = el("table", { - className: "app-settings__table", - children: [el("thead", { children: [head] }), el("tbody", { children: rows })], - }); + ).map((key) => ({ label: L(key) })), + rows, + { density: "data" }, + ); + table.classList.add("app-settings__table"); const mark = (errors: Record): void => { for (const [id, input] of inputs) { const error = errors[id]; @@ -109,7 +110,8 @@ export function buildCadAliasTab(aliases: Record): { input.title = error ? L(ERROR_TEXT[error]) : ""; } const kinds = [...new Set(Object.values(errors))]; - message.textContent = kinds.map((k) => L(ERROR_TEXT[k])).join(" · "); + message.lastChild!.textContent = kinds.map((k) => L(ERROR_TEXT[k])).join(" · "); + message.hidden = !kinds.length; }; const root = el("div", { children: [el("p", { text: L("App_Settings_Cad_Help") }), message, table], diff --git a/A00_Common/app_shell.ts b/A00_Common/app_shell.ts index 018c26a4e..b79578b37 100644 --- a/A00_Common/app_shell.ts +++ b/A00_Common/app_shell.ts @@ -9,6 +9,7 @@ import { ui_locales, currentLanguageIndex, setLanguage } from "@ui/ui_template_locale"; import { createButton } from "@ui/ui_template_elements"; +import { createSegment } from "@ui/ui_template_segment"; import { THEME_STORAGE_KEY, LANG_STORAGE_KEY, ROUTES } from "@config/config_frontend"; import { navigateTo, isAuthenticated, setLeaveGuard, takeLeaveGuard } from "./router"; import { readTouchMode, setTouchMode, type TouchMode } from "./device_context"; @@ -246,35 +247,23 @@ function buildTouchModeSwitch(): HTMLElement { box.className = "app-drawer__touch"; const label = document.createElement("span"); label.textContent = L("App_TouchMode"); - const group = document.createElement("div"); - group.className = "app-drawer__row"; - group.setAttribute("role", "radiogroup"); - group.setAttribute("aria-label", L("App_TouchMode")); const modes: [TouchMode, keyof typeof ui_locales][] = [ ["auto", "App_TouchMode_Auto"], ["on", "App_TouchMode_On"], ["off", "App_TouchMode_Off"], ]; - const buttons = modes.map(([mode, key]) => { - const b = createButton({ - label: L(key), - variant: "pill", - onClick: () => { - setTouchMode(mode); - mark(); - }, - }); - b.setAttribute("role", "radio"); - b.dataset.touchMode = mode; - return b; + // 부품 「분할 고르기」(36) — 칸마다 data-touch-mode + const group = createSegment({ + options: modes.map(([mode, key]) => ({ value: mode, label: L(key) })), + value: readTouchMode(), + size: 36, + ariaLabel: L("App_TouchMode"), + onChange: (mode) => setTouchMode(mode as TouchMode), }); - const mark = (): void => { - const now = readTouchMode(); - for (const b of buttons) b.setAttribute("aria-checked", String(b.dataset.touchMode === now)); - }; - mark(); - group.append(...buttons); - box.append(label, group); + group.root.querySelectorAll("[data-value]").forEach((b) => { + b.dataset.touchMode = b.dataset.value; + }); + box.append(label, group.root); return box; } @@ -522,11 +511,8 @@ html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .ap color: var(--color-text-secondary); font-size: var(--text-caption); } -.app-drawer__touch .app-drawer__row { padding-inline: 0; } -.app-drawer__touch [aria-checked="true"] { - background: var(--color-mist-violet); - color: var(--color-accent); -} +.app-drawer__touch .ui-segment { display: flex; } +.app-drawer__touch .ui-segment__item { flex: 1 1 0; min-width: 0; } /* 휴대폰 폭 — 오른쪽 단추(언어 · 테마 · 로그인)는 서랍 안으로 */ html[data-size="compact"] .app-header { padding: 0 var(--spacing-16); gap: var(--spacing-8); } html[data-size="compact"] .app-actions { display: none; } diff --git a/B06_Section/B06_Section_UI_Cross_CutSlope.ts b/B06_Section/B06_Section_UI_Cross_CutSlope.ts index bc0e2232b..ac73fb9c8 100644 --- a/B06_Section/B06_Section_UI_Cross_CutSlope.ts +++ b/B06_Section/B06_Section_UI_Cross_CutSlope.ts @@ -105,7 +105,8 @@ export function buildCutSlopeControl(section: CrossSection, control: CutSlopeCon const reset = document.createElement("button"); reset.type = "button"; - reset.className = "b06-design__rockb-btn is-reset"; + reset.className = "ui-icon-btn ui-icon-btn--ghost b06-design__rockb-btn is-reset"; // 부품 아이콘 단추 22 + reset.setAttribute("data-size", "22"); reset.textContent = "↺"; reset.title = `표준값으로 되돌리기 (1:${standard.toFixed(2)} · ${ratioToDegrees(standard).toFixed(1)}°)`; reset.disabled = sameRatio(ratio, standard); diff --git a/B06_Section/B06_Section_UI_Cross_Design.ts b/B06_Section/B06_Section_UI_Cross_Design.ts index aa7638021..c66043b21 100644 --- a/B06_Section/B06_Section_UI_Cross_Design.ts +++ b/B06_Section/B06_Section_UI_Cross_Design.ts @@ -14,7 +14,10 @@ * 병합된다. * ========================================================================== */ +import { createButton, setUiSize } from "@ui/ui_template_button"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; +import { createSegment } from "@ui/ui_template_segment"; +import { createToggle } from "@ui/ui_template_toggle"; import type { CrossSection, DitchSide, @@ -156,25 +159,19 @@ function segment( legendEl.textContent = legend; wrap.append(legendEl); } - const group = document.createElement("div"); - group.className = "b06-design__seg-buttons"; - for (const [value, labelKey] of options) { - const button = document.createElement("button"); - button.type = "button"; - button.className = `b06-design__btn${value === selected ? " b06-design__btn--active" : ""}`; - button.textContent = L(labelKey); - button.setAttribute("aria-pressed", value === selected ? "true" : "false"); - // 선행 조건 미충족 시 비활성(상시 노출하되 클릭 불가·사유 툴팁) — E-6. - button.disabled = disabled; - if (disabled && reason) button.title = reason; - else button.addEventListener("click", () => onPick(value)); - group.append(button); - } + // 부품 분할 고르기 20(553-2) — 선행 조건 미충족 시 비활성(클릭 불가·사유 툴팁) — E-6. + const group = createSegment({ + options: options.map(([value, labelKey]) => ({ value, label: L(labelKey), disabled })), + value: selected ?? "", + size: 20, + onChange: (value) => onPick(value as T), + }).root; + if (disabled && reason) group.title = reason; wrap.append(group); return wrap; } -/** 온/오프 토글 하나(포장·2단계 경사 공용). 세그먼트와 같은 컨테이너/버튼 스타일을 쓴다. */ +/** 온/오프 토글 하나(포장·2단계 경사 공용) — 부품 켜기 칩 20(553-2). */ function toggle( legend: string, on: boolean, @@ -192,18 +189,16 @@ function toggle( legendEl.textContent = legend; wrap.append(legendEl); } - const buttons = document.createElement("div"); - buttons.className = "b06-design__seg-buttons"; - const button = document.createElement("button"); - button.type = "button"; - button.className = `b06-design__btn${on ? " b06-design__btn--active" : ""}`; - button.textContent = on ? onLabel : offLabel; - button.setAttribute("aria-pressed", on ? "true" : "false"); - button.disabled = disabled; + const button = createToggle({ + kind: "chip", + label: on ? onLabel : offLabel, + checked: on, + size: 20, + disabled, + onChange: onToggle, + }).root; if (disabled && reason) button.title = reason; - else button.addEventListener("click", onToggle); - buttons.append(button); - wrap.append(buttons); + wrap.append(button); return { wrap, button }; } @@ -375,18 +370,26 @@ export function buildDesignControls( const more = document.createElement("details"); more.className = "b06-design__more"; const moreSummary = document.createElement("summary"); - moreSummary.className = "b06-design__more-summary"; + // 부품 아이콘 단추(선) 22 꼴 — 펼침 = 눌림(aria-pressed) + moreSummary.className = "ui-icon-btn ui-icon-btn--outline b06-design__more-summary"; + setUiSize(moreSummary, 22); moreSummary.textContent = "⋯"; moreSummary.title = L("B06_Design_More"); + moreSummary.setAttribute("aria-pressed", "false"); + more.addEventListener("toggle", () => + moreSummary.setAttribute("aria-pressed", String(more.open)), + ); const morePanel = document.createElement("div"); morePanel.className = "b06-design__more-panel"; more.append(moreSummary, morePanel); // [상세](138-4) — 맨 오른쪽 · 넘침 정리(moveable) 밖이라 늘 보임 - const detail = document.createElement("button"); - detail.type = "button"; - detail.className = "b06-design__btn b06-design__detail"; - detail.textContent = L("B06_Design_Detail"); - detail.addEventListener("click", () => openCrossDetail(section)); + const detail = createButton({ + label: L("B06_Design_Detail"), + variant: "ghost", + size: 20, + onClick: () => openCrossDetail(section), + }); + detail.classList.add("b06-design__detail"); bar.append(slopeDirSeg, ...moveable, more, detail); refreshCrossDetail(section); diff --git a/B06_Section/B06_Section_UI_Cross_Line_Cell.ts b/B06_Section/B06_Section_UI_Cross_Line_Cell.ts index e4251125d..8e41d25f7 100644 --- a/B06_Section/B06_Section_UI_Cross_Line_Cell.ts +++ b/B06_Section/B06_Section_UI_Cross_Line_Cell.ts @@ -87,7 +87,9 @@ export function buildLineCell(): LineCell { const buttons = SLOTS.map(([slot, text]) => { const button = document.createElement("button"); button.type = "button"; - button.className = `b06-design__rockb-btn ${CELL_BTN}${slot}`; + // 부품 아이콘 단추 22(553-2) — 클래스 · data-size 계약(시험 흉내 DOM 이 @ui 를 안 풂) + button.className = `ui-icon-btn ui-icon-btn--ghost b06-design__rockb-btn ${CELL_BTN}${slot}`; + button.setAttribute("data-size", "22"); if (slot === "reset") button.classList.add("is-reset"); button.textContent = text; // 길게 누름 — 누르면 즉시 1회 · 반복 예약 · 이어지는 click 은 삼킴 diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index 50de72507..286c4cdb3 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -414,29 +414,7 @@ stroke-width: 8; } -/* 암 경계선 상/하/리셋 제어 (B05 측점 선 제어 ▲/▼/↺ 버튼 패턴 재활용) */ -.b06-design__rockb-btn { - width: 22px; - height: 22px; - padding: 0; - font-size: 0.7rem; - line-height: 1; - color: var(--color-text-secondary); - background: var(--color-surface); - border: none; - border-left: 1px solid var(--color-border); - cursor: pointer; -} - -.b06-design__rockb-btn:first-child { - border-left: none; -} - -.b06-design__rockb-btn:hover { - color: var(--color-text); - background: var(--color-surface-raised); -} - +/* 암 경계선 상/하/리셋 제어 — 단추 = 부품 아이콘 단추 22(553-2) · ↺ 만 경고 글색 */ .b06-design__rockb-btn.is-reset { color: var(--color-warning); } diff --git a/B06_Section/B06_Section_UI_Style_Cross_Chart.css b/B06_Section/B06_Section_UI_Style_Cross_Chart.css index 82c054f5f..5d766dca4 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Chart.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Chart.css @@ -206,19 +206,8 @@ margin-left: auto; } +/* ⋯ = 부품 아이콘 단추(선) 22 — 펼침 표시만 지움 */ .b06-design__more-summary { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - border: 1px solid var(--color-border); - border-radius: var(--radius-inputs); - background: var(--color-surface); - color: var(--color-text-secondary); - font-size: 0.9rem; - line-height: 1; - cursor: pointer; list-style: none; } @@ -226,12 +215,6 @@ display: none; } -.b06-design__more[open] .b06-design__more-summary { - color: var(--color-surface); - background: var(--color-royal-amethyst); - border-color: var(--color-royal-amethyst); -} - .b06-design__more-panel { position: absolute; right: 0; @@ -254,7 +237,7 @@ } .b06-design__seg-legend { - font-size: 0.72rem; + font-size: var(--text-caption); color: var(--color-text-secondary); white-space: nowrap; } @@ -271,31 +254,11 @@ overflow: hidden; } -.b06-design__btn { - padding: 3px 8px; - font-size: 0.72rem; - color: var(--color-text-secondary); - background: var(--color-surface); - border: none; - border-left: 1px solid var(--color-border); - cursor: pointer; -} - -.b06-design__btn:first-child { - border-left: none; -} - -.b06-design__btn--active { - color: var(--color-surface); - background: var(--color-royal-amethyst); - font-weight: var(--font-weight-medium); -} - .b06-design__areas { display: inline-flex; gap: var(--spacing-8); margin-left: auto; - font-size: 0.72rem; + font-size: var(--text-caption); font-family: var(--font-mono); } @@ -329,13 +292,11 @@ .b06-cross-card__linecell .b06-design__rockb-btn { flex: 0 0 auto; - border-left: none; } -.b06-cross-card__linecell - .b06-design__rockb-btn:not([hidden]) - ~ .b06-design__rockb-btn:not([hidden]) { - border-left: 1px solid var(--color-border); +/* 부품 아이콘 단추의 display 가 hidden 을 덮음 — 상태에 없는 칸은 숨김 */ +.b06-design__rockb-btn[hidden] { + display: none; } /* 측점별 암 절토 경사(2026-09-07) — 행의 **오른쪽 끝**(암 경계 = 왼쪽). */ @@ -349,7 +310,7 @@ .b06-design__cutslope-input { width: 44px; padding: 0 2px; - font-size: 0.72rem; + font-size: var(--text-caption); font-family: var(--font-mono); text-align: right; color: var(--color-text); @@ -367,7 +328,7 @@ .b06-design__cutslope-unit { padding: 0 2px 0 1px; - font-size: 0.72rem; + font-size: var(--text-caption); color: var(--color-text-secondary); align-self: center; } diff --git a/B07_DesignDetail/openwebcad/src/App.css b/B07_DesignDetail/openwebcad/src/App.css index 60d087d89..994e1d59c 100644 --- a/B07_DesignDetail/openwebcad/src/App.css +++ b/B07_DesignDetail/openwebcad/src/App.css @@ -27,9 +27,8 @@ --cad-text-dim: var(--color-text-secondary); --cad-text-muted: var(--color-text-muted); --cad-accent: var(--color-accent); + /* 마우스 바탕 = 부품 상자와 같은 값 · 켜짐 바탕은 --color-accent-soft */ --cad-hover: var(--color-hover); - /* 켜짐 바탕 = 앱 켜기 꼴(553-6) */ - --cad-on: var(--color-accent-soft); --cad-canvas: var(--draw-canvas); } @@ -113,21 +112,12 @@ body > canvas[data-id="canvas"] { flex: 1; min-width: 0; } +/* 접기 단추 = 아이콘 단추 부품(단 20) · 여기는 자리만 */ .cad-ribbon-tabs__collapse { - width: 24px; - height: 20px; align-self: center; - padding: 0; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: var(--cad-text-dim); - font-size: 11px; } -.cad-ribbon-tabs__collapse:hover { - background: var(--cad-hover); -} -.cad-ribbon-tabs button { +/* 리본 탭 = CAD 고유 꼴 그대로(토큰만) */ +.cad-ribbon-tabs > button:not(.ui-icon-btn) { padding: 0 14px; border: 1px solid transparent; border-bottom: 0; @@ -136,10 +126,10 @@ body > canvas[data-id="canvas"] { color: var(--cad-text-dim); font-size: 12px; } -.cad-ribbon-tabs button:hover { +.cad-ribbon-tabs > button:not(.ui-icon-btn):hover { background: var(--cad-hover); } -.cad-ribbon-tabs button[data-active="true"] { +.cad-ribbon-tabs > button[data-active="true"] { background: var(--cad-chrome); border-color: var(--cad-line); color: var(--color-text); @@ -187,53 +177,25 @@ body > canvas[data-id="canvas"] { .cad-ribbon-group__label[data-expandable="true"]:hover { color: var(--cad-accent); } +/* 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 자리만 */ .cad-ribbon-overflow { /* 리본 패널 줄이 가로 스크롤 상자라 그 안에서는 잘린다 — 화면 좌표로 띄운다 */ position: fixed; z-index: 20; - display: flex; - flex-direction: column; min-width: 170px; max-height: 72vh; - padding: 4px; - overflow-y: auto; - background: var(--cad-chrome-raised); - border: 1px solid var(--cad-line); - border-radius: var(--radius-cards); - box-shadow: var(--shadow-lg); } -.cad-ribbon-overflow button { +.cad-ribbon-overflow > .ui-ctx-menu__item { display: flex; gap: 8px; align-items: center; - padding: 5px 8px; - border: 0; - border-radius: var(--radius-sm); - background: transparent; - font-size: 11px; - text-align: left; -} -.cad-ribbon-overflow button:hover { - background: var(--cad-hover); } +/* ↶ ↷ = 아이콘 단추 부품(단 20) · 여기는 자리만 */ .cad-quick-access { display: flex; gap: 2px; align-self: center; } -.cad-ribbon-tabs .cad-quick-access button { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 4px; - line-height: 1; - font-size: 13px; -} -.cad-quick-access button:hover { - border-color: var(--cad-line); - background: var(--cad-hover); -} .cad-ribbon-props { display: flex; flex-direction: column; @@ -254,6 +216,7 @@ body > canvas[data-id="canvas"] { width: 52px; text-align: left; } +/* 리본 안 칸 = CAD 고유 꼴(토큰만) · 높이 = 크기 단 18 · 둥글기 4 */ .cad-prop select, .cad-prop input[type="number"] { height: 18px; @@ -285,7 +248,6 @@ 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; @@ -296,7 +258,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans min-width: 48px; padding: 3px 6px; border: 1px solid transparent; - border-radius: var(--radius-buttons); + border-radius: 3px; background: transparent; font-size: 10px; white-space: nowrap; @@ -331,7 +293,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-on); + background: var(--color-accent-soft); box-shadow: inset 0 -2px var(--cad-accent); } .cad-tool:disabled { @@ -376,23 +338,14 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans background: var(--cad-chrome-raised); color: var(--cad-text-dim); } +/* 꼴 = 탭 부품(`ui-tabs`) · 여기는 칸 둘이 폭을 나누는 배치만 */ .cad-inspector-tabs { display: grid; grid-template-columns: 1fr 1fr; - height: 36px; - border-bottom: 1px solid var(--cad-line); + gap: 0; } -.cad-inspector-tabs button { - border: 0; - border-bottom: 2px solid transparent; - background: var(--cad-chrome-sunken); - color: var(--cad-text-dim); - font-size: 12px; -} -.cad-inspector-tabs button[data-active="true"] { - border-bottom-color: var(--cad-accent); - background: var(--cad-chrome-raised); - color: var(--color-text); +.cad-inspector-tabs > .ui-tabs__tab { + justify-content: center; } .cad-properties { padding: 14px 12px; diff --git a/B07_DesignDetail/openwebcad/src/App.types.ts b/B07_DesignDetail/openwebcad/src/App.types.ts index db75fc6de..6a1d0346e 100644 --- a/B07_DesignDetail/openwebcad/src/App.types.ts +++ b/B07_DesignDetail/openwebcad/src/App.types.ts @@ -74,7 +74,6 @@ export interface Layer { export enum LOCAL_STORAGE_KEY { DRAWING = 'OPEN_WEB_CAD__DRAWING', - DROPDOWN = 'OPEN_WEB_CAD__DROPDOWN', } export interface StartAndEndpointEntity { diff --git a/B07_DesignDetail/openwebcad/src/App_Layers.css b/B07_DesignDetail/openwebcad/src/App_Layers.css index f45c4a0e5..040234e55 100644 --- a/B07_DesignDetail/openwebcad/src/App_Layers.css +++ b/B07_DesignDetail/openwebcad/src/App_Layers.css @@ -14,22 +14,22 @@ padding-right: 2px; min-width: 0; } -/* 도면층 버튼 — 색은 토큰으로만, 상태는 Button이 붙이는 data-* 속성으로 가른다. */ +/* 도면층 버튼 — 목록 줄 부품(`ui-sidebar-row`) 꼴 · 상태는 Button이 붙이는 data-* 속성으로 가른다. */ .cad-layer-manager button[data-type="regular"] { - background: var(--cad-chrome-sunken); - color: var(--cad-accent); + background: transparent; + color: var(--color-text-body); } .cad-layer-manager button[data-type="transparent"] { background: transparent; - color: var(--cad-accent); + color: var(--color-text-secondary); } .cad-layer-manager button:hover { - background: var(--cad-hover); + background: var(--color-hover); color: var(--color-text); } .cad-layer-manager button[data-active="true"] { - background: var(--color-primary); - color: var(--color-primary-text); + background: var(--color-accent-soft); + color: var(--color-accent); } .cad-view-controls { @@ -87,21 +87,7 @@ body.cad-file-dragging::after { pointer-events: none; } -.cad-view-controls button { - width: 34px; - height: 32px; - border: 0; - border-right: 1px solid var(--cad-line); - background: transparent; - font-size: 17px; -} -.cad-view-controls button:hover { - background: var(--cad-hover); -} -.cad-view-controls__fit { - color: var(--cad-accent); - font-size: 15px; -} +/* 단추 = 아이콘 단추 부품(단 32) · 여기는 배율 글만 */ .cad-view-controls span { min-width: 48px; color: var(--cad-text-dim); @@ -133,30 +119,16 @@ body.cad-file-dragging::after { color: var(--cad-text-muted); font: 11px Consolas, monospace; } +/* 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 자리와 명령 글꼴만 */ .cad-command-suggestions { position: absolute; z-index: 6; bottom: 100%; left: 10px; - display: flex; - flex-direction: column; min-width: 220px; - padding: 4px; - background: var(--cad-chrome-raised); - border: 1px solid var(--cad-line); - border-radius: var(--radius-cards); - box-shadow: var(--shadow-lg); } -.cad-command-suggestions button { - padding: 4px 8px; - border: 0; - border-radius: var(--radius-sm); - background: transparent; - font: 11px Consolas, monospace; - text-align: left; -} -.cad-command-suggestions button:hover { - background: var(--cad-hover); +.cad-command-suggestions > .ui-ctx-menu__item { + font: 12px Consolas, monospace; } .cad-prop--wide select { min-width: 132px; @@ -180,12 +152,13 @@ body.cad-file-dragging::after { font-size: 11px; font-weight: 600; } +/* 명령 칸 = CAD 고유 꼴(토큰만) · 높이 = 크기 단 28 */ .cad-command-area input { flex: 1; height: 28px; padding: 0 8px; border: 1px solid var(--cad-line); - border-radius: var(--radius-inputs); + border-radius: var(--radius-buttons); outline: none; background: var(--cad-chrome-sunken); color: var(--cad-text); @@ -210,23 +183,7 @@ body.cad-file-dragging::after { background: var(--cad-chrome); border-top: 1px solid var(--cad-line); } -.cad-statusbar button { - height: 22px; - padding: 0 8px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: var(--cad-text-dim); - font-size: 10px; -} -.cad-statusbar button:hover { - background: var(--cad-hover); -} -.cad-statusbar button[data-active="true"] { - border-color: var(--cad-accent); - background: var(--color-primary); - color: var(--color-primary-text); -} +/* 켜기 = 켜기 칩 부품(`ui-toggle--chip` · 단 22) */ .cad-statusbar__hint { margin-left: auto; color: var(--cad-text-muted); @@ -261,6 +218,7 @@ body.cad-file-dragging::after { font-size: 11px; color: var(--cad-text-dim); } +/* 칸 = 크기 단 24(둥글기 6) · 토큰만 */ .cad-properties-editor select, .cad-properties-editor input { height: 24px; @@ -292,59 +250,19 @@ body.cad-file-dragging::after { color: var(--cad-text); text-overflow: ellipsis; } -.cad-properties__empty { - margin: 0; - color: var(--cad-text-muted); - font-size: 11px; -} -/* 빠른 특성 — 화면 위에 뜨는 작은 상자 */ -.cad-quick-properties { - position: fixed; - z-index: 5; - top: calc(var(--cad-title-height) + var(--cad-ribbon-height) + 12px); - right: 14px; - width: 240px; - padding: 8px 10px 10px; - background: var(--cad-chrome-raised); - border: 1px solid var(--cad-line); - border-radius: var(--radius-cards); - box-shadow: var(--shadow-lg); -} -.cad-quick-properties__header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 6px; - color: var(--cad-text-dim); - font-size: 11px; -} -.cad-quick-properties__header button { - border: 0; - background: transparent; - color: var(--cad-text-muted); - font-size: 12px; -} -/* 블록 라이브러리 팔레트 (INSERT/BLOCK) */ +/* 빠른 특성 · 블록 라이브러리 — 꼴 = 떠 있는 판 부품(`ui-float-panel`) · 여기는 자리 · 폭만 */ +.cad-quick-properties, .cad-block-library { position: fixed; z-index: 5; top: calc(var(--cad-title-height) + var(--cad-ribbon-height) + 12px); right: 14px; width: 240px; - padding: 8px 10px 10px; - background: var(--cad-chrome-raised); - border: 1px solid var(--cad-line); - border-radius: var(--radius-cards); - box-shadow: var(--shadow-lg); -} -.cad-block-library__empty { - margin: 0; - color: var(--cad-text-muted); - font-size: 11px; - line-height: 1.5; } + +/* 블록 라이브러리 팔레트 (INSERT/BLOCK) */ .cad-block-library__list { max-height: 220px; margin: 0; @@ -357,41 +275,26 @@ body.cad-file-dragging::after { align-items: center; gap: 4px; } -.cad-block-library__list li button { - border: 0; - background: transparent; - color: var(--cad-text); - font-size: 12px; -} +/* 블록 줄 = 목록 줄 부품(`ui-sidebar-row`) · 지우기 = 아이콘 단추 부품(단 20) · 여기는 줄 폭만 */ .cad-block-library__name { flex: 1; + min-width: 0; overflow: hidden; - padding: 3px 4px; - text-align: left; text-overflow: ellipsis; white-space: nowrap; } -.cad-block-library__name:hover { - background: var(--cad-chrome-sunken); -} +/* 단추 = 단추 부품(보조 · 단 24) · 여기는 둘이 폭을 나눔만 */ .cad-block-library__actions { display: flex; gap: 6px; margin-top: 8px; } -.cad-block-library__actions button { +.cad-block-library__actions > .ui-btn { flex: 1; - height: 24px; - padding: 0; - border: 1px solid var(--cad-line); - border-radius: 6px; - background: transparent; - color: var(--cad-text-dim); - font-size: 11px; } /* 도각 자리표 패널 — 도각 편집으로 도면을 실었을 때만 뜬다 (2026-09-06 사용자 지시로 - 부모 사이드바에서 캐드 안으로 옮김). 도면 오른쪽 위, 리본 아래에 붙는다. */ + 부모 사이드바에서 캐드 안으로 옮김). 꼴 = 떠 있는 판 부품(`ui-float-panel`) · 여기는 자리 · 폭 · 줄 배치만. */ .cad-frame-tokens { position: fixed; /* 오른쪽 위는 진행단계 패널이 쓴다 — 아래쪽(상태막대·명령행 위)에 붙인다. */ @@ -399,36 +302,13 @@ body.cad-file-dragging::after { bottom: calc(var(--cad-status-height) + var(--cad-command-height) + 8px); z-index: 3; max-height: 46vh; - overflow-y: auto; + width: 236px; +} + +.cad-frame-tokens__body { display: flex; flex-direction: column; gap: 6px; - width: 236px; - padding: 8px; - border: 1px solid var(--cad-line); - border-radius: var(--radius-cards); - background: var(--cad-chrome-raised); - box-shadow: var(--shadow-lg); - color: var(--cad-text); -} - -.cad-frame-tokens__header { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 6px; - color: var(--cad-text-dim); - font-size: 11px; - line-height: 1.4; -} - -.cad-frame-tokens__fold { - flex: none; - border: 1px solid var(--cad-line); - border-radius: 4px; - background: transparent; - color: var(--cad-text); - cursor: pointer; } .cad-frame-tokens__group { @@ -440,41 +320,9 @@ body.cad-file-dragging::after { .cad-frame-tokens__title { width: 100%; - color: var(--cad-text-dim); - font-size: 11px; } -.cad-frame-tokens__button { - height: 22px; - padding: 0 8px; - border: 1px solid var(--cad-line); - border-radius: 4px; - background: var(--cad-chrome); - color: var(--cad-text); - font-size: 11px; - cursor: pointer; -} - -.cad-frame-tokens__button:hover { - background: var(--cad-hover); -} - -/* 자리표 칸 크기 입력 (2026-09-06) */ -.cad-frame-tokens__size { - display: flex; - align-items: center; - gap: 4px; - color: var(--cad-text-dim); - font-size: 11px; -} - -.cad-frame-tokens__size input { +/* 자리표 칸 크기 입력 (2026-09-06) — 칸 = 입력 부품(단 24) */ +.cad-frame-tokens__size > .ui-input { width: 64px; - height: 24px; - padding: 0 4px; - border: 1px solid var(--cad-line); - 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 e3c5561c7..d5d377d45 100644 --- a/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts +++ b/B07_DesignDetail/openwebcad/src/commands/commands.insert.ts @@ -1,11 +1,11 @@ /** 삽입 탭 명령 (조사표 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'; import { pickFile } from '../helpers/pick-file'; import { importDrawingFile } from '../integration/dxf-clip-paste'; +import { askText } from '../integration/ui-kit'; import { getActiveToolActor, getSelectedEntities } from '../state'; import { Tool } from '../tools'; import { imageImportToolStateMachine } from '../tools/image-import-tool'; @@ -40,14 +40,13 @@ export const INSERT_COMMANDS: CadCommand[] = [ needsSelection: true, run: () => { const entities = getSelectedEntities(); - // 글 묻기 = 부품 확인창(553-6 · window.prompt 대신) - void askText({ message: '블록 이름', okLabel: '저장' }).then((raw) => { - const name = raw?.trim(); + // 글 묻기 = 확인창 부품(window.prompt 대신 · 553-6) + void askText({ message: '블록 이름', okLabel: '저장' }).then(async (text) => { + const name = text?.trim(); if (!name) return; - void saveBlockFromEntities(name, entities).then(() => { - toast.success(`블록 '${name}' 저장 (객체 ${entities.length}개)`); - setBlockLibraryVisible(true); - }); + await saveBlockFromEntities(name, entities); + toast.success(`블록 '${name}' 저장 (객체 ${entities.length}개)`); + setBlockLibraryVisible(true); }); return '블록 이름 입력'; }, diff --git a/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx b/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx index 0743bbbe3..f53c3ffa7 100644 --- a/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx +++ b/B07_DesignDetail/openwebcad/src/components/BlockLibraryPanel.tsx @@ -1,6 +1,5 @@ -/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) */ +/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) · 떠 있는 판 부품 */ import type { FC } from 'react'; -import { showConfirmDialog } from '../../../../ui_template/ui_template_modal'; import { deleteBlock, exportBlocksToFile, @@ -8,6 +7,7 @@ import { importBlocksFromFile, } from '../blocks/block-library'; import { pickFile } from '../helpers/pick-file'; +import { askConfirm } from '../integration/ui-kit'; import { startBlockInsert } from '../tools/insert-block-tool'; import { isBlockLibraryVisible, setBlockLibraryVisible } from './ui-state'; @@ -17,58 +17,83 @@ export const BlockLibraryPanel: FC = () => { const blocks = getBlocks(); return ( -
-
- 블록 라이브러리 -
+
+ {blocks.length === 0 ? ( +
+

객체를 선택하고 BLOCK 명령으로 저장하십시오.

+
+ ) : ( +
    + {blocks.map((block) => ( +
  • + + +
  • + ))} +
+ )} - {blocks.length === 0 ? ( -

객체를 선택하고 BLOCK 명령으로 저장하십시오.

- ) : ( -
    - {blocks.map((block) => ( -
  • - - -
  • - ))} -
- )} - -
- - +
+ + +
); diff --git a/B07_DesignDetail/openwebcad/src/components/Button.tsx b/B07_DesignDetail/openwebcad/src/components/Button.tsx index c02ce12fa..4f499c0de 100644 --- a/B07_DesignDetail/openwebcad/src/components/Button.tsx +++ b/B07_DesignDetail/openwebcad/src/components/Button.tsx @@ -37,9 +37,9 @@ export const Button: FC = ({ left = null, right = null, }) => { - // 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만. + // 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만 · 높이 = 크기 단 42(글 14 · 둥글기 8) const classParts = [ - 'font-semibold py-4 h-[42px] flex flex-row justify-start w-full items-center', + 'font-semibold h-[42px] text-sm rounded-lg 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/CommandLine.tsx b/B07_DesignDetail/openwebcad/src/components/CommandLine.tsx index 5d878f70d..c3721829a 100644 --- a/B07_DesignDetail/openwebcad/src/components/CommandLine.tsx +++ b/B07_DesignDetail/openwebcad/src/components/CommandLine.tsx @@ -16,7 +16,10 @@ export const CommandLine: FC = () => { const history = getCommandHistory(); const instruction = currentPrompt().line || '명령을 입력하거나 도구를 선택하십시오.'; - const suggestions = useMemo(() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8), [text, pick]); + const suggestions = useMemo( + () => matchCommandPrefixes(pick?.base ?? text).slice(0, 8), + [text, pick] + ); const submit = (event: FormEvent) => { event.preventDefault(); @@ -102,10 +105,11 @@ export const CommandLine: FC = () => { {instruction} {text.trim() && suggestions.length > 0 && ( -
+
{suggestions.map((command) => ( {!folded && ( - <> -
- 글자 - {TEXT_TOKENS.map((token) => ( - - ))} -
-
- 그림 (칸에 비율 그대로 들어감) - {IMAGE_TOKENS.map((token) => ( - - ))} -
- {picked && ( -
- 고른 자리표 칸 크기 (mm) - - +
+

+ 누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다 +

+
+ 글자 + {TEXT_TOKENS.map((token) => ( + + ))} +
+
+ + 그림 (칸에 비율 그대로 들어감) + + {IMAGE_TOKENS.map((token) => ( + + ))} +
+ {picked && ( +
+ + 고른 자리표 칸 크기 (mm) + + + +
+ )}
)} - - )} ); }; diff --git a/B07_DesignDetail/openwebcad/src/components/InspectorPanel.tsx b/B07_DesignDetail/openwebcad/src/components/InspectorPanel.tsx index 3807a356a..b94064002 100644 --- a/B07_DesignDetail/openwebcad/src/components/InspectorPanel.tsx +++ b/B07_DesignDetail/openwebcad/src/components/InspectorPanel.tsx @@ -27,17 +27,24 @@ export const InspectorPanel: FC = ({ collapsed, onToggleCol {!collapsed && ( <> -
+ {/* 탭 부품(밑줄 탭 · 단 36) — 칸 둘이 폭을 나눠 가짐 */} +
- +
+ +
); }; diff --git a/B07_DesignDetail/openwebcad/src/components/StatusBar.tsx b/B07_DesignDetail/openwebcad/src/components/StatusBar.tsx index b34eef6e3..cf8188167 100644 --- a/B07_DesignDetail/openwebcad/src/components/StatusBar.tsx +++ b/B07_DesignDetail/openwebcad/src/components/StatusBar.tsx @@ -1,4 +1,4 @@ -/** 상태막대 — AutoCAD 하단 제도 보조 토글 */ +/** 상태막대 — AutoCAD 하단 제도 보조 토글 · 켜기 칩 부품(`ui-toggle--chip` · 단 22) */ import type { FC } from 'react'; import { getAngleStep, @@ -27,7 +27,9 @@ export const StatusBar: FC = ({ commandLineVisible, onToggleComm
- )} - {Math.round(zoom * 100)}% -
diff --git a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css index aea05f221..7cb29eeff 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css +++ b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.css @@ -1,29 +1,5 @@ -/* Shift+우클릭 한 번 스냅 메뉴(`snap-menu.ts`) — 커서 자리에 세로 목록 */ +/* Shift+우클릭 한 번 스냅 메뉴(`snap-menu.ts`) — 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 커서 자리 · 위 겹침만 */ .cad-snap-menu { position: fixed; z-index: 60; - display: flex; - flex-direction: column; - min-width: 160px; - padding: 4px; - border: 1px solid var(--color-border); - border-radius: var(--radius-cards); - background: var(--color-surface-raised); - box-shadow: var(--shadow-lg); -} - -.cad-snap-menu > button { - padding: 4px 10px; - border: 0; - border-radius: var(--radius-sm); - background: transparent; - color: var(--color-text); - font-size: 13px; - text-align: left; - cursor: pointer; -} - -.cad-snap-menu > button:hover, -.cad-snap-menu > button:focus-visible { - background: var(--color-hover); } diff --git a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.ts b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.ts index bfb33b789..81533c097 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/snap-menu.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/snap-menu.ts @@ -55,11 +55,12 @@ export function openSnapMenu(x: number, y: number, onPick: (name: string) => voi current = { onPick, element: null }; if (typeof document === 'undefined') return; const menu = document.createElement('div'); - menu.className = 'cad-snap-menu controls'; + menu.className = 'ui-ctx-menu cad-snap-menu controls'; // 꼴 = 메뉴 부품 menu.setAttribute('role', 'menu'); for (const [name, label] of SNAP_MENU) { const item = document.createElement('button'); item.type = 'button'; + item.className = 'ui-ctx-menu__item'; item.setAttribute('role', 'menuitem'); item.textContent = `${label} (${name})`; item.addEventListener('click', () => pickSnapMenu(name)); diff --git a/B07_DesignDetail/openwebcad/src/integration/ui-kit.ts b/B07_DesignDetail/openwebcad/src/integration/ui-kit.ts new file mode 100644 index 000000000..1cdc55074 --- /dev/null +++ b/B07_DesignDetail/openwebcad/src/integration/ui-kit.ts @@ -0,0 +1,18 @@ +/** + * 부품 상자(`ui_template/`) CAD 쪽 한 벌(553-6) — CAD 겉도 앱 화면과 같은 부품 꼴을 씀. + * 부품 CSS 를 싣고 단추 · 확인창 기본 꼴(`injectBaseStyles`)을 한 번 넣음 · 확인창 · 글 묻기를 다시 냄. + * React 화면은 부품 클래스(`ui-btn` · `ui-icon-btn` · `ui-toggle--chip` · `ui-tabs` · `ui-float-panel` · `ui-ctx-menu` · `ui-empty` · `ui-text` · `ui-input`)와 + * `data-size` 단을 직접 붙임 · 자리 · 폭은 App.css 의 `cad-*` 가 맡음 · 도면 그림 색(`--draw-*`)은 안 건드림. + */ +import '../../../../ui_template/ui_template_tabs.css'; +import '../../../../ui_template/ui_template_toggle.css'; +import '../../../../ui_template/ui_template_float_panel.css'; +import '../../../../ui_template/ui_template_context_menu.css'; +import '../../../../ui_template/ui_template_empty.css'; +import '../../../../ui_template/ui_template_input.css'; +import '../../../../ui_template/ui_template_text.css'; +import { injectBaseStyles } from '../../../../ui_template/ui_template_elements_styles.ts'; + +export { askConfirm, askText } from '../../../../ui_template/ui_template_modal.ts'; + +injectBaseStyles(); diff --git a/B07_DesignDetail/openwebcad/src/main.tsx b/B07_DesignDetail/openwebcad/src/main.tsx index 81e9a91f6..d5c4c0e7f 100644 --- a/B07_DesignDetail/openwebcad/src/main.tsx +++ b/B07_DesignDetail/openwebcad/src/main.tsx @@ -3,11 +3,12 @@ import { Point } from '@flatten-js/core'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { Actor, type MachineSnapshot } from 'xstate'; +// 부품 상자 CSS 를 앱 CSS 보다 먼저 실음 — 자리 · 폭은 뒤에 오는 App.css 의 `cad-*` 가 덮음 +import './integration/ui-kit.ts'; 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'; @@ -48,7 +49,6 @@ 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/src/ribbon/Ribbon.tsx b/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx index d3ec81f83..10a17d975 100644 --- a/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx +++ b/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx @@ -129,10 +129,14 @@ const Panel: FC<{ {overflowCommands.length ? ' ▾' : ''} {overflowAt && overflowCommands.length > 0 && ( -
+
{overflowCommands.map((command) => (