From 6aa0582495d266e2be0b4a08942953f7576c9856 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 29 Oct 2026 18:42:16 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05):=20=EA=B5=90=EA=B0=81=EC=A0=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=3D=20=EA=B7=BC=EC=B2=98=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=C2=B7=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=88=84=EB=A6=84=20=3D=20=EC=A0=95=EB=B3=B4=20=EC=B0=BD=20(PL?= =?UTF-8?q?AN=20587-1=20=C2=B7=20=EC=9A=94=EA=B5=AC=20445=20C1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 교각점 고름 = 창 없이 근처(40 px) 아이콘 · 멀면 0.7 s 뒤 숨김 · 누름 = 창 열림(멀어져도 유지) - 창 열린 채 다른 교각점 = 열린 채 새 정보 · 닫힌 채 = 닫힌 채 · [닫기] = 창 닫고 고름 풂 - 부품 상자 아이콘 단추 「유리」 꼴 더함(견본 · 시험 552-2) - 새 지킴 시험 test_445_curve_icon · 398-2 · 408 도우미에 가짜 아이콘 Co-Authored-By: Claude Sonnet 5.5 --- B05_Profile/B05_Profile_UI_RouteEdit.ts | 3 +- .../B05_Profile_UI_RouteEdit_CurveBar.ts | 35 ++++- .../B05_Profile_UI_RouteEdit_CurveIcon.ts | 124 ++++++++++++++++++ ...5_Profile_UI_Style_RouteEdit_CurveIcon.css | 5 + .../logic/B05_Profile_RouteEdit_CurveIcon.ts | 25 ++++ resources/tester/helper_398_2_curve_input.cjs | 8 ++ .../tester/helper_408_radius_downstream.cjs | 3 + resources/tester/test_445_curve_icon.py | 84 ++++++++++++ resources/tester/test_552_2_button.py | 1 + ui_template/ui_template_button.css | 14 ++ ui_template/ui_template_button.ts | 4 +- ui_template/ui_template_ui_kit_parts.ts | 4 +- 12 files changed, 300 insertions(+), 10 deletions(-) create mode 100644 B05_Profile/B05_Profile_UI_RouteEdit_CurveIcon.ts create mode 100644 B05_Profile/B05_Profile_UI_Style_RouteEdit_CurveIcon.css create mode 100644 B05_Profile/logic/B05_Profile_RouteEdit_CurveIcon.ts create mode 100644 resources/tester/test_445_curve_icon.py diff --git a/B05_Profile/B05_Profile_UI_RouteEdit.ts b/B05_Profile/B05_Profile_UI_RouteEdit.ts index 990682ee0..1e44b643c 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit.ts @@ -260,7 +260,7 @@ export async function mountRouteEditor( sizeWatch.disconnect(); themeWatch(); historyControls.dispose(); // 단축키는 창(window)에 달려 있어 안 떼면 떠난 뒤에도 산다. - curveLabelBox.destroy(); + curveBar.destroy(); overlay.remove(); }; /** 초기노선 갈래 — 로직 단추 · [초기화] · 비교값(계획서 20). 서버 묻기는 `_Initial` 몫. */ @@ -423,7 +423,6 @@ export async function mountRouteEditor( draw(); }, }); - const curveLabelBox = curveBar.label; const syncCurveBar = curveBar.sync; /** 한 번의 편집을 마무리한다 — 다시 그리고, 라벨·상태줄을 맞추고, 되돌리기에 쌓는다. diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_CurveBar.ts b/B05_Profile/B05_Profile_UI_RouteEdit_CurveBar.ts index 34aadbe63..71aa9a36d 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_CurveBar.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_CurveBar.ts @@ -24,6 +24,8 @@ import { createCurveLabel, type CurveLabel, } from "./B05_Profile_UI_RouteEdit_Label"; +import { createCurveIcon } from "./B05_Profile_UI_RouteEdit_CurveIcon"; +import { openAfterPick } from "./logic/B05_Profile_RouteEdit_CurveIcon"; import { fitNote } from "./logic/B05_Profile_RouteEdit_KeyNudge"; import { offStationMcs } from "./logic/B05_Profile_RouteEdit_StationFit"; @@ -57,19 +59,27 @@ export interface CurveBarParams { toScreen: (vertex: Vertex) => [number, number]; /** 한 번의 편집을 마무리한다 — 다시 그리고 되돌리기에 쌓는다. */ applyEdit: (message: string) => void; - /** 고른 꺾임점을 푼다 — 닫기 단추와 「빈 곳 누르기」가 부른다(계획서 0-9 ㉖). */ + /** 고른 꺾임점을 푼다 — 닫기 단추와 「빈 곳 누르기」가 부른다(계획서 0-9 ㉖ · 445-C1 닫기도 고름을 풂). */ onUnselect: () => void; } export interface CurveBar { label: CurveLabel; - /** 고른 자리에 맞춰 패널을 옮겨 그린다. */ + /** 편집기를 버릴 때 — 창 · 아이콘을 칸에서 뗀다. */ + destroy: () => void; + /** 고른 자리에 맞춰 패널(열림일 때) · 아이콘(닫힘일 때)을 옮겨 그린다. */ sync: () => void; /** 끌기 · 손잡이 놓을 때 맞춤 결과를 상태줄 `message` 뒤에(430 · 스냅 끔이면 그대로). */ noteFit: (message: string, before: Vertex[], picked: number) => void; } export function createCurveBar(params: CurveBarParams): CurveBar { + /** 정보 창이 열려 있나(445-C1) — 고르면 닫힌 채 아이콘 · 아이콘 누름 = 열림 · 닫기 · 고름 풂 = 닫힘. */ + let open = false; + const icon = createCurveIcon(params.canvas, () => { + open = true; + sync(); + }); const label = createCurveLabel( { onClose: () => params.onUnselect(), @@ -201,11 +211,20 @@ export function createCurveBar(params: CurveBarParams): CurveBar { limitRadiusM, limitArcM, } = params.state(); + const rect = params.canvas.getBoundingClientRect(); if (!(picked >= 0 && picked < planned.length)) { + open = openAfterPick(open, -1); + icon.update(null, open); + label.hide(); + return; + } + open = openAfterPick(open, picked); + const [pickedX, pickedY] = params.toScreen(planned[picked]); + icon.update([pickedX + rect.left, pickedY + rect.top], open); + if (!open) { label.hide(); return; } - const rect = params.canvas.getBoundingClientRect(); // 가둠 = 도구줄 아래 · 열린 횡단 판 왼쪽 · 뜬 종단 판 위(505-3 · 판 밑에 깔려 가리지 않게) const main = params.canvas.closest(".design-page__main") ?? params.canvas.parentElement; const box = (selector: string): DOMRect | null => { @@ -282,5 +301,13 @@ export function createCurveBar(params: CurveBarParams): CurveBar { }); } - return { label, sync, noteFit }; + return { + label, + destroy: () => { + icon.destroy(); + label.destroy(); + }, + sync, + noteFit, + }; } diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_CurveIcon.ts b/B05_Profile/B05_Profile_UI_RouteEdit_CurveIcon.ts new file mode 100644 index 000000000..cc4e9181c --- /dev/null +++ b/B05_Profile/B05_Profile_UI_RouteEdit_CurveIcon.ts @@ -0,0 +1,124 @@ +/* ============================================================================= + * B05_Profile_UI_RouteEdit_CurveIcon.ts + * 교각점 정보 아이콘(445-C1) — 교각점을 고른 채 마우스가 근처일 때만 교각점 옆에 뜨는 작은 단추. + * 누르면 정보 창이 열림(멀어져도 유지) · 멀어지면 0.7 s 뒤 숨김 · 터치(마우스 없음)는 고르면 늘 보임. + * 규칙은 `logic/B05_Profile_RouteEdit_CurveIcon` · 창 열림 기억은 `_CurveBar`. + * ========================================================================== */ + +import { createIconButton } from "@ui/ui_template_elements"; +import { iconVisible, isNear } from "./logic/B05_Profile_RouteEdit_CurveIcon"; +import "./B05_Profile_UI_Style_RouteEdit_CurveIcon.css"; + +/** 멀어진 뒤 아이콘이 남는 시간(ms) — 아이콘으로 옮겨 갈 틈(등고선 자물쇠와 같은 값). */ +const HIDE_DELAY_MS = 700; +/** 아이콘은 교각점 오른쪽 위로 이만큼(px). */ +const OFFSET_PX = 14; + +export interface CurveIcon { + /** 고름 · 창 열림 · 교각점 화면 자리(클라이언트 좌표 · 고름 없으면 null)를 알린다. */ + update: (anchor: [number, number] | null, open: boolean) => void; + destroy: () => void; +} + +export function createCurveIcon(canvas: HTMLCanvasElement, onOpen: () => void): CurveIcon { + const wrap = canvas.parentElement ?? document.body; + const button = createIconButton({ + icon: "adjustments", + title: "교각점 정보 열기", + size: 24, + variant: "glass", + }); + button.classList.add("b05-curveicon"); + button.dataset.act = "curve-icon"; + button.hidden = true; + wrap.append(button); + + let anchor: [number, number] | null = null; + let open = false; + let touch = false; + let mouse: [number, number] | null = null; + let overIcon = false; + let pressing = false; + let hideTimer = 0; + + const keep = (): void => { + window.clearTimeout(hideTimer); + hideTimer = 0; + }; + const hideNow = (): void => { + keep(); + button.hidden = true; + }; + const hideLater = (): void => { + if (hideTimer) return; + hideTimer = window.setTimeout(() => { + hideTimer = 0; + button.hidden = true; + }, HIDE_DELAY_MS); + }; + + /** 지금 상태로 보임 · 숨김을 다시 정함 — 멀면 바로 숨기지 않고 틈 뒤. */ + const refresh = (): void => { + if (!anchor || open || pressing) return hideNow(); + const distance = mouse ? Math.hypot(mouse[0] - anchor[0], mouse[1] - anchor[1]) : Infinity; + if (!iconVisible(0, false, isNear(distance, overIcon), touch)) { + if (!button.hidden) hideLater(); + return; + } + keep(); + const origin = wrap.getBoundingClientRect(); + button.style.left = `${Math.round(anchor[0] - origin.left + OFFSET_PX)}px`; + button.style.top = `${Math.round(anchor[1] - origin.top - OFFSET_PX - 24)}px`; + button.hidden = false; + }; + + canvas.addEventListener("pointermove", (event) => { + touch = event.pointerType === "touch"; + mouse = [event.clientX, event.clientY]; + if (event.buttons === 0) refresh(); + }); + canvas.addEventListener("pointerdown", (event) => { + touch = event.pointerType === "touch"; + mouse = [event.clientX, event.clientY]; + pressing = true; + refresh(); + }); + // 놓은 뒤 다시 판정 — 고른 자리가 바뀌었으면 `update` 가 곧 이어서 부른다. + const release = (): void => { + pressing = false; + refresh(); + }; + canvas.addEventListener("pointerup", release); + canvas.addEventListener("pointercancel", release); + canvas.addEventListener("wheel", hideNow, { passive: true }); + canvas.addEventListener("pointerleave", () => { + mouse = null; + if (!overIcon && !button.hidden) hideLater(); + }); + button.addEventListener("pointerenter", () => { + overIcon = true; + keep(); + }); + button.addEventListener("pointerleave", () => { + overIcon = false; + hideLater(); + }); + button.addEventListener("pointerdown", (event) => event.stopPropagation()); + button.addEventListener("click", (event) => { + event.stopPropagation(); + hideNow(); + onOpen(); + }); + + return { + update: (nextAnchor, nextOpen) => { + anchor = nextAnchor; + open = nextOpen; + refresh(); + }, + destroy: () => { + hideNow(); + button.remove(); + }, + }; +} diff --git a/B05_Profile/B05_Profile_UI_Style_RouteEdit_CurveIcon.css b/B05_Profile/B05_Profile_UI_Style_RouteEdit_CurveIcon.css new file mode 100644 index 000000000..2c2b86175 --- /dev/null +++ b/B05_Profile/B05_Profile_UI_Style_RouteEdit_CurveIcon.css @@ -0,0 +1,5 @@ +/* 교각점 정보 아이콘(445-C1) — 교각점 옆에 뜨는 작은 단추 · 자리는 `_CurveIcon.ts` 가 잡음. 꼴은 부품 상자 아이콘 단추 유리. */ +.b05-curveicon { + position: absolute; + z-index: 1; /* 지도 위 표지 층 — 도구줄 · 횡단 판(2) 밑(등고선 자물쇠와 같은 층) */ +} diff --git a/B05_Profile/logic/B05_Profile_RouteEdit_CurveIcon.ts b/B05_Profile/logic/B05_Profile_RouteEdit_CurveIcon.ts new file mode 100644 index 000000000..501763374 --- /dev/null +++ b/B05_Profile/logic/B05_Profile_RouteEdit_CurveIcon.ts @@ -0,0 +1,25 @@ +/* ============================================================================= + * B05_Profile_RouteEdit_CurveIcon.ts + * 교각점 정보 창 열림 · 근처 아이콘 규칙(445-C1) — 순수 함수. 화면 쪽은 `_UI_RouteEdit_CurveIcon`. + * ========================================================================== */ + +/** 교각점에서 이만큼(px) 안에 마우스가 있으면 「근처」(노드 잡기 12 px 의 세 배쯤). */ +export const NEAR_PX = 40; + +/** 마우스가 교각점 근처인가 — 아이콘 위에 있어도 근처(`overIcon`). */ +export function isNear(distancePx: number, overIcon: boolean): boolean { + return overIcon || distancePx <= NEAR_PX; +} + +/** + * 고른 교각점이 바뀐 뒤 정보 창이 열려 있나 — 열린 채 다른 교각점을 고르면 새 교각점으로 열린 채 · + * 닫혀 있었으면 닫힌 채 · 고름이 풀리면 닫힘. + */ +export function openAfterPick(open: boolean, picked: number): boolean { + return open && picked >= 0; +} + +/** 아이콘이 보이나 — 고름 있음 · 창 닫힘 · 근처(터치는 마우스가 없어 늘 근처). */ +export function iconVisible(picked: number, open: boolean, near: boolean, touch: boolean): boolean { + return picked >= 0 && !open && (near || touch); +} diff --git a/resources/tester/helper_398_2_curve_input.cjs b/resources/tester/helper_398_2_curve_input.cjs index fa6d9c201..e48e9e0b2 100644 --- a/resources/tester/helper_398_2_curve_input.cjs +++ b/resources/tester/helper_398_2_curve_input.cjs @@ -9,6 +9,7 @@ const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B05 = path.join(ROOT, "B05_Profile"); let handlers = null; +let iconOpen = null; // 445-C1 아이콘 누름 흉내 — 정보 창을 열어야 `show` 가 불림 const shows = []; const STUBS = { "./B05_Profile_UI_RouteEdit_Label": { @@ -18,6 +19,12 @@ const STUBS = { }, centerDirectionOf: () => null, }, + "./B05_Profile_UI_RouteEdit_CurveIcon": { + createCurveIcon: (_canvas, onOpen) => { + iconOpen = onOpen; + return { update: () => {}, destroy: () => {} }; + }, + }, "@ui/ui_template_elements": { showToast: () => {} }, // 곡선 삭제 경고(430) }; @@ -118,6 +125,7 @@ function editor(snap) { const pick = (k) => { state.picked = k; made.sync(); + iconOpen(); // 아이콘 누름 = 열림(열린 채 다른 교각점 = 이어서 열림) }; const lastShow = () => shows[shows.length - 1]; return { state, status, handlers: () => handlers, drawn, arcOf, mc, pick, lastShow, markEdited }; diff --git a/resources/tester/helper_408_radius_downstream.cjs b/resources/tester/helper_408_radius_downstream.cjs index 1852090c1..4189e6634 100644 --- a/resources/tester/helper_408_radius_downstream.cjs +++ b/resources/tester/helper_408_radius_downstream.cjs @@ -17,6 +17,9 @@ const STUBS = { }, centerDirectionOf: () => null, }, + "./B05_Profile_UI_RouteEdit_CurveIcon": { + createCurveIcon: () => ({ update: () => {}, destroy: () => {} }), // 445-C1 근처 아이콘(화면 없음) + }, "@ui/ui_template_elements": { showToast: () => {} }, // 곡선 삭제 경고(430) }; diff --git a/resources/tester/test_445_curve_icon.py b/resources/tester/test_445_curve_icon.py new file mode 100644 index 000000000..164bdb411 --- /dev/null +++ b/resources/tester/test_445_curve_icon.py @@ -0,0 +1,84 @@ +"""445-C1 B05 교각점 정보 = 근처 아이콘 — 규칙(`logic/B05_Profile_RouteEdit_CurveIcon.ts`)을 Node 로 돌려 판정 + +배선 글 대조(`_CurveBar` 가 열림을 봄 · 닫기는 고름도 풂 · 아이콘은 부품 상자 유리 꼴). + + ./venv/Scripts/python.exe -m pytest resources/tester/test_445_curve_icon.py -q +""" + +import json +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +B05 = ROOT / "B05_Profile" +LOGIC = B05 / "logic" / "B05_Profile_RouteEdit_CurveIcon.ts" +TS = ROOT / "config" / "node_modules" / "typescript" + +DRIVER = r""" +const fs = require("fs"); +const ts = require(process.argv[1]); +const js = ts.transpileModule(fs.readFileSync(process.argv[2], "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, +}).outputText; +const m = { exports: {} }; +new Function("exports", "module", js)(m.exports, m); +const L = m.exports; +console.log(JSON.stringify({ + near: L.NEAR_PX, + nearIn: L.isNear(L.NEAR_PX, false), + nearOut: L.isNear(L.NEAR_PX + 1, false), + nearOnIcon: L.isNear(500, true), + keepOpenOther: L.openAfterPick(true, 3), + closedStaysClosed: L.openAfterPick(false, 3), + releaseCloses: L.openAfterPick(true, -1), + noPick: L.iconVisible(-1, false, true, false), + picked_near: L.iconVisible(2, false, true, false), + picked_far: L.iconVisible(2, false, false, false), + picked_far_touch: L.iconVisible(2, false, false, true), + open_near: L.iconVisible(2, true, true, false), +})); +""" + + +@pytest.fixture(scope="module") +def got() -> dict: + run = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", "-e", DRIVER, str(TS), str(LOGIC)], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert run.returncode == 0, run.stderr + return json.loads(run.stdout) + + +def test_근처_판정(got) -> None: + assert got["near"] == 40 + assert got["nearIn"] is True and got["nearOut"] is False + assert got["nearOnIcon"] is True # 아이콘 위는 멀어도 근처 + + +def test_창_열림_이어감(got) -> None: + assert got["keepOpenOther"] is True # 열린 채 다른 교각점 = 열린 채 + assert got["closedStaysClosed"] is False # 닫힘 = 닫힌 채 + assert got["releaseCloses"] is False # 고름 풂 = 닫힘 + + +def test_아이콘_보임(got) -> None: + assert got["noPick"] is False + assert got["picked_near"] is True and got["picked_far"] is False + assert got["picked_far_touch"] is True # 터치 = 고르면 늘 보임 + assert got["open_near"] is False # 창이 열리면 아이콘 숨김 + + +def test_배선_글() -> None: + bar = (B05 / "B05_Profile_UI_RouteEdit_CurveBar.ts").read_text(encoding="utf-8") + assert "openAfterPick" in bar and "icon.update(" in bar + assert "onClose: () => params.onUnselect()" in bar # 닫기 = 창 닫고 고름도 풂 + # 닫힘이면 창을 숨기고 아이콘만 — 창 show 보다 앞 + assert bar.index("if (!open) {") < bar.index("label.show({") + icon = (B05 / "B05_Profile_UI_RouteEdit_CurveIcon.ts").read_text(encoding="utf-8") + assert 'variant: "glass"' in icon and "createIconButton" in icon # 부품 상자 유리 꼴 + assert "document.createElement" not in icon.split("export function")[1] # 날 요소 없음 diff --git a/resources/tester/test_552_2_button.py b/resources/tester/test_552_2_button.py index ae7ad0b63..b6acf10fe 100644 --- a/resources/tester/test_552_2_button.py +++ b/resources/tester/test_552_2_button.py @@ -78,6 +78,7 @@ def test_상태(): assert '.ui-icon-btn:is(:hover, [data-state="hover"]):not(:disabled)' in css assert '.ui-icon-btn:is(:active, [data-state="active"]):not(:disabled)' in css assert '.ui-icon-btn[aria-pressed="true"]' in css and ".ui-icon-btn:disabled" in css + assert '.ui-icon-btn--glass:is(:hover, [data-state="hover"]):not(:disabled)' in css # 445-C1 src = _text(UI / "ui_template_button.ts") assert 'title: opts.title, "aria-label": opts.title' in src diff --git a/ui_template/ui_template_button.css b/ui_template/ui_template_button.css index 2503bbfd2..fe80fa98e 100644 --- a/ui_template/ui_template_button.css +++ b/ui_template/ui_template_button.css @@ -154,6 +154,20 @@ background: var(--color-accent-soft); color: var(--color-accent); } +/* 유리 — 그림 위에 뜸(색 = `.ui-btn--glass` 와 같은 공통 값) */ +.ui-icon-btn--glass { + border-color: color-mix(in srgb, var(--color-border) 65%, transparent); + border-radius: var(--radius-pills); + background: color-mix(in srgb, var(--color-surface-raised) 72%, transparent); + color: var(--color-text-body); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} +.ui-icon-btn--glass:is(:hover, [data-state="hover"]):not(:disabled) { + border-color: var(--color-primary); + background: color-mix(in srgb, var(--color-primary) 82%, transparent); + color: var(--color-primary-text); +} .ui-icon-btn:is(:active, [data-state="active"]):not(:disabled) { translate: 0 1px; } diff --git a/ui_template/ui_template_button.ts b/ui_template/ui_template_button.ts index 713d58b01..6650e507a 100644 --- a/ui_template/ui_template_button.ts +++ b/ui_template/ui_template_button.ts @@ -3,7 +3,7 @@ * 부품 「단추」 · 「아이콘 단추」 · 크기 여덟 단(552-2 · 부품 모음 `#/b01-account?view=ui-kit`) * * 꼴 = 단추: 주(filled) · 보조(ghost) · 고스트 글(pill) · 위험(danger) · 위험 고스트(danger-ghost) · 유리(glass · 그림 위) · 글 링크(link) - * 아이콘 단추: 고스트(ghost · 기본) · 선(outline) · 채움(filled) · 켜짐 = aria-pressed + * 아이콘 단추: 고스트(ghost · 기본) · 선(outline) · 채움(filled) · 유리(glass · 그림 위) · 켜짐 = aria-pressed * 크기 = `data-size="18|20|22|24|28|32|36|42"`(높이 px) — 단추 · 아이콘 단추 · 입력 · 고르기 · 분할 · 탭 같은 값 * 옛 `size: "sm"` = 18 · 크기를 안 주면 data-size 없이 옛 꼴(높이 32 · 아이콘 붙으면 34) 그대로 * 실측 높이 = 단(꼴 · 페이지 글 크기 · 왼쪽 패널 양식과 상관없이) · `hidden` 이면 늘 숨음 @@ -81,7 +81,7 @@ export function createButton(opts: ButtonOptions): HTMLButtonElement { return btn; } -export type IconButtonVariant = "ghost" | "outline" | "filled"; +export type IconButtonVariant = "ghost" | "outline" | "filled" | "glass"; export interface IconButtonOptions { /** 아이콘 이름 — 없으면 glyph */ diff --git a/ui_template/ui_template_ui_kit_parts.ts b/ui_template/ui_template_ui_kit_parts.ts index 84d426c78..f5c310ded 100644 --- a/ui_template/ui_template_ui_kit_parts.ts +++ b/ui_template/ui_template_ui_kit_parts.ts @@ -279,8 +279,8 @@ export const KIT_PARTS: KitPart[] = [ { key: "button", name: "단추", group: "basic", kinds: "주 · 보조 · 고스트 글 · 위험 · 위험 고스트 · 유리 · 글 링크 · 여덟 단", file: "ui_template_button.ts", demo: (s) => (s === "error" ? null : row(...([["저장", "filled"], ["취소", "ghost"], ["보기", "pill"], ["삭제", "danger"], ["초기화", "danger-ghost"], ["사용법", "link"]] as const).map(([label, variant]) => markState(createButton({ label, variant, size: 28, onClick: noop }), s)))), sizes: (z) => createButton({ label: String(z), size: z, iconStart: "plus", onClick: noop }) }, - { key: "icon-button", name: "아이콘 단추", group: "basic", kinds: "고스트 · 선 · 채움 · 켜짐(aria-pressed) · 글자 기호 · 여덟 단 · title 필수", file: "ui_template_button.ts", - demo: (s) => (s === "error" ? null : row(markState(createIconButton({ icon: "settings", title: "설정" }), s), markState(createIconButton({ icon: "refresh", title: "새로 고침", variant: "outline" }), s), markState(createIconButton({ icon: "plus", title: "추가", variant: "filled" }), s), markState(createIconButton({ icon: "eye", title: "보기", pressed: true }), s), markState(createIconButton({ glyph: "▲", title: "위로", variant: "outline" }), s))), + { key: "icon-button", name: "아이콘 단추", group: "basic", kinds: "고스트 · 선 · 채움 · 유리 · 켜짐(aria-pressed) · 글자 기호 · 여덟 단 · title 필수", file: "ui_template_button.ts", + demo: (s) => (s === "error" ? null : row(markState(createIconButton({ icon: "settings", title: "설정" }), s), markState(createIconButton({ icon: "refresh", title: "새로 고침", variant: "outline" }), s), markState(createIconButton({ icon: "plus", title: "추가", variant: "filled" }), s), markState(createIconButton({ icon: "eye", title: "보기", pressed: true }), s), markState(createIconButton({ glyph: "▲", title: "위로", variant: "outline" }), s), markState(createIconButton({ icon: "adjustments", title: "조정", variant: "glass" }), s))), sizes: (z) => createIconButton({ icon: "settings", title: `설정 ${z}`, size: z, variant: "outline" }) }, { key: "input", name: "입력", group: "basic", kinds: "글 · 수 · 단위 붙음 · 찾기 · 날짜 · 여러 줄 · 여덟 단", file: "ui_template_input.ts", demo: (s) => fieldDemo(s, () => { const f = createInputField({ placeholder: "폭", type: "number", unit: "m", size: 32 }); return { root: f.root, node: f.input, setError: f.setError }; }),