fix(ui-kit): 부품 흠 다섯 — 보조 글 줄 안 inline · 단추 실측 = 단 · 숨김 단추 · 놓기 칸 전체 누름 · 격자표 (PLAN 552-5)
- 보조 글: span · small 에 붙은 .ui-text = inline(줄 안 끊김) · p · div 는 한 줄 상자 그대로 - 단추: 위아래 여백 = 1em 기준 + 줄 높이 1 · 왼쪽 패널 양식(.ui-panel-form) 옛 규칙(글 12 · 줄 높이 normal)이 단을 덮지 않음 — B07 도면 단추 35 → 32 - 숨김: .ui-btn · .ui-icon-btn [hidden] = display none(공용 기본 꼴이 안 실린 곳에서도) - 파일 놓기 칸: 칸 어디든 누름 · 초점 Enter · Space = 파일 고르기(옛 B03 동작) · 단추 누름은 칸으로 올라가 한 번만 - 표: 격자 꼴(grid · 세로선 · 칸 테두리 · 몸 안 이름 칸 th) · 합친 칸(colSpan · rowSpan · head) · columns 비우면 머리 줄 없음 · 기본 꼴 그대로 - 부품 모음 견본: 글 줄 안 보조 글 · 격자표(수량 산출표 꼴) - 시험: test_552_5_part_fixes 새 · test_552_2_button 높이 식 기대 고침(1em · 줄 높이 1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,10 +45,12 @@ def test_여덟_단():
|
||||
def test_단추_높이_식():
|
||||
css = _text(UI / "ui_template_button.css")
|
||||
rule = re.search(r"\.ui-btn\[data-size\] \{([^}]*)\}", css).group(1)
|
||||
assert "calc((var(--ui-size-h) - 2px - var(--ui-size-font)) / 2) var(--ui-size-px)" in rule
|
||||
assert "height" not in rule # 높이를 박지 않음
|
||||
# 552-5 — 여백 = 1em 기준 · 줄 높이 1 (페이지 글 크기 · 줄 높이가 단을 안 바꿈)
|
||||
assert "calc((var(--ui-size-h) - 2px - 1em) / 2) var(--ui-size-px)" in rule
|
||||
assert "line-height: 1;" in rule
|
||||
assert not re.search(r"(^|[^-])height:", rule) # 높이를 박지 않음(line-height 만)
|
||||
# 아이콘은 글 높이 안 — 단 높이가 아이콘 때문에 늘지 않음
|
||||
assert "margin-block: calc((var(--ui-size-font) - var(--ui-size-icon)) / 2)" in css
|
||||
assert "margin-block: calc((1em - var(--ui-size-icon)) / 2)" in css
|
||||
# 32 단 = 옛 기본(여백 8 · 글 14) · 18 단 = 옛 sm(여백 2 · 글 12)
|
||||
rungs = _rungs(css)
|
||||
assert (32 - 2 - 14) / 2 == 8 and rungs[32]["px"] == "16px"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
"""552-5 부품 흠 다섯 지킴 시험 — 화면 실측은 Playwright · ORCA(부품 모음 · B07 · B03).
|
||||
|
||||
① 보조 글 — 글 줄 안 span · small 은 inline(줄 안 끊김)
|
||||
② 단추 실측 = 단 — 여백 1em 기준 · 줄 높이 1 · 왼쪽 패널 양식(.ui-panel-form) 옛 규칙이 덮지 않음
|
||||
③ [hidden] 단추 — 단추 display 가 숨김을 덮지 않음
|
||||
④ 파일 놓기 칸 — 칸 전체 누름 · Enter · Space = 파일 고르기 · 단추 누름은 한 번만
|
||||
⑤ 표 — 격자 꼴(grid · 세로선) · 합친 칸(colSpan · rowSpan · head) · 기본 꼴 그대로
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
UI = Path(__file__).resolve().parents[2] / "ui_template"
|
||||
RAW = re.compile(r"#[0-9a-fA-F]{3,8}\b|rgba?\(|--b01-|--draw-")
|
||||
|
||||
|
||||
def _t(name: str) -> str:
|
||||
return (UI / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _rule(css: str, selector: str) -> str:
|
||||
return re.search(re.escape(selector) + r" \{([^}]*)\}", css).group(1)
|
||||
|
||||
|
||||
def test_보조_글_줄_안_inline():
|
||||
css = _t("ui_template_text.css")
|
||||
assert "display: inline;" in _rule(css, ":is(span, small).ui-text")
|
||||
assert "display: flex;" in _rule(css, ".ui-text") # p · div 는 한 줄 상자 그대로
|
||||
assert not RAW.search(css)
|
||||
|
||||
|
||||
def test_단추_실측_단():
|
||||
css = _t("ui_template_button.css")
|
||||
rule = _rule(css, ".ui-btn[data-size]")
|
||||
assert "calc((var(--ui-size-h) - 2px - 1em) / 2)" in rule and "line-height: 1;" in rule
|
||||
panel = _rule(css, ".ui-panel-form .ui-btn[data-size]:not(.ui-help-btn, .ui-step-nav-btn)")
|
||||
assert "padding-block: calc((var(--ui-size-h) - 2px - 1em) / 2);" in panel
|
||||
assert "line-height: 1;" in panel
|
||||
# 덮는 옛 규칙(글 12 · 줄 높이 normal)은 그대로 — 단 규칙이 더 세게(클래스 넷 > 셋)
|
||||
overlay = (UI / "ui_template_overlay.css").read_text(encoding="utf-8")
|
||||
assert ".ui-panel-form .ui-btn:not(.ui-help-btn, .ui-step-nav-btn) {" in overlay
|
||||
|
||||
|
||||
def test_숨김_단추():
|
||||
css = _t("ui_template_button.css")
|
||||
assert "display: none !important;" in _rule(css, ":is(.ui-btn, .ui-icon-btn)[hidden]")
|
||||
|
||||
|
||||
def test_놓기_칸_전체_누름():
|
||||
ts, css = _t("ui_template_drop.ts"), _t("ui_template_drop.css")
|
||||
assert 'attrs: { role: "button" }' in ts
|
||||
assert 'root.addEventListener("click"' in ts and "ev.target !== input" in ts
|
||||
assert 'ev.key !== "Enter" && ev.key !== " "' in ts and "ev.target !== root" in ts
|
||||
assert "root.tabIndex = on ? -1 : 0" in ts
|
||||
# 단추는 칸으로 올라감 — 따로 열면 두 번 열림
|
||||
pick = re.search(r"const pick = createButton\(\{([^}]*)\}\)", ts).group(1)
|
||||
assert "onClick" not in pick
|
||||
assert "cursor: pointer;" in _rule(css, ".ui-drop")
|
||||
|
||||
|
||||
def test_표_격자_합친_칸():
|
||||
ts, css = _t("ui_template_table.ts"), _t("ui_template_table.css")
|
||||
assert "grid?: boolean" in ts and '" ui-table--grid"' in ts
|
||||
assert "export interface SpanCell" in ts
|
||||
assert "td.colSpan = span.colSpan" in ts and "td.rowSpan = span.rowSpan" in ts
|
||||
assert 'el(span?.head ? "th" : "td"' in ts
|
||||
assert "if (columns.length)" in ts # 머리 줄 없는 표
|
||||
grid = _rule(css, ".ui-table--grid :is(th, td)")
|
||||
assert "border: 1px solid var(--color-border);" in grid
|
||||
assert "border-style: hidden;" in _rule(css, ".ui-table--grid")
|
||||
# 기본 꼴 = 세로선 없음 그대로
|
||||
assert "border-left" not in _rule(css, ".ui-table td")
|
||||
assert not RAW.search(css)
|
||||
kit = _t("ui_template_ui_kit_parts.ts")
|
||||
assert "grid: true" in kit and "rowSpan: 2" in kit
|
||||
@@ -69,17 +69,28 @@
|
||||
--ui-size-gap: 8px;
|
||||
}
|
||||
|
||||
/* 단추 — 높이 = 글 한 줄 + 위아래 여백 + 테두리(아이콘은 글 높이 안에 맞춤) · 높이를 박지 않아 페이지 여백 덮어쓰기가 옛날처럼 먹음 */
|
||||
/* 단추 — 높이 = 글 한 줄(줄 높이 1 = 1em) + 위아래 여백 + 테두리(아이콘은 글 높이 안에 맞춤) · 높이를 박지 않아 페이지 여백 덮어쓰기가 옛날처럼 먹음
|
||||
여백 = 1em 기준이라 페이지가 글 크기를 바꿔도 높이 = 단 */
|
||||
.ui-btn[data-size] {
|
||||
gap: var(--ui-size-gap);
|
||||
padding: calc((var(--ui-size-h) - 2px - var(--ui-size-font)) / 2) var(--ui-size-px);
|
||||
padding: calc((var(--ui-size-h) - 2px - 1em) / 2) var(--ui-size-px);
|
||||
font-size: var(--ui-size-font);
|
||||
line-height: 1;
|
||||
border-radius: var(--ui-size-radius);
|
||||
}
|
||||
/* 왼쪽 패널 양식(`.ui-panel-form` · overlay.css)의 옛 단추 규칙(글 12 · 줄 높이 normal · 여백 8)이 단을 덮지 않게 — 글 크기 · 옆 여백은 패널 꼴 그대로 · 높이만 단 */
|
||||
.ui-panel-form .ui-btn[data-size]:not(.ui-help-btn, .ui-step-nav-btn) {
|
||||
padding-block: calc((var(--ui-size-h) - 2px - 1em) / 2);
|
||||
line-height: 1;
|
||||
}
|
||||
/* 숨김 — 단추 display 가 [hidden] 을 덮지 않게(공용 기본 꼴이 안 실린 곳에서도) */
|
||||
:is(.ui-btn, .ui-icon-btn)[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.ui-btn[data-size] > .ui-btn__icon {
|
||||
width: var(--ui-size-icon);
|
||||
height: var(--ui-size-icon);
|
||||
margin-block: calc((var(--ui-size-font) - var(--ui-size-icon)) / 2);
|
||||
margin-block: calc((1em - var(--ui-size-icon)) / 2);
|
||||
}
|
||||
.ui-btn[data-size] > .ui-btn__icon svg {
|
||||
width: 100%;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* 아이콘 단추: 고스트(ghost · 기본) · 선(outline) · 채움(filled) · 켜짐 = aria-pressed
|
||||
* 크기 = `data-size="18|20|22|24|28|32|36|42"`(높이 px) — 단추 · 아이콘 단추 · 입력 · 고르기 · 분할 · 탭 같은 값
|
||||
* 옛 `size: "sm"` = 18 · 크기를 안 주면 data-size 없이 옛 꼴(높이 32 · 아이콘 붙으면 34) 그대로
|
||||
* 실측 높이 = 단(꼴 · 페이지 글 크기 · 왼쪽 패널 양식과 상관없이) · `hidden` 이면 늘 숨음
|
||||
* 상태 = 마우스 `:hover` · 눌림 `:active` · 막힘 `disabled` — CSS 는 `[data-state]` 로도 받아 부품 모음이 굳혀 보임
|
||||
* 쓰기 = `createButton({ label: "저장", size: 24, onClick })` · `createIconButton({ icon: "settings", title: "설정", size: 24 })`
|
||||
* 글자 기호 단추(▲ ▼ ✕ ?)는 `glyph: "▲"` — 시험이 글자로 잡음
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-text);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
background-color var(--transition-fast);
|
||||
@@ -42,6 +43,7 @@
|
||||
}
|
||||
.ui-drop[aria-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ui-drop:is(.is-error, [data-state="error"]) {
|
||||
border-color: var(--color-danger);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* 꼴 = 점선 칸(올림 아이콘 · 한 줄 · 고르기 단추 · 받는 꼴 안내) · 진행(`setProgress` → 진행 막대) · 오류(`setError` → 빨간 글)
|
||||
* 크기 = 칸 최소 48 · 단추 28
|
||||
* 상태 = 기본 · 마우스(파일을 끌어 옴 = .is-over) · 막힘(`disabled`) · 오류(.is-error)
|
||||
* 누름 = 칸 어디든 누름 · 칸에 초점 두고 Enter · Space = 파일 고르기(옛 B03 놓기 칸 동작) · [파일 고르기] 단추는 같은 일
|
||||
* 쓰는 법 = `const drop = createDrop({ accept: ".las,.laz", onFiles: (files) => upload(files) }); host.append(drop.root)`
|
||||
* 대신하는 옛 변종 = B03 `b03-file__dropzone` · CAD `body.cad-file-dragging::after` · 숨은 file + 단추(B01 · B08 · M02 · cad_host · help_edit)
|
||||
* ========================================================================== */
|
||||
@@ -43,7 +44,6 @@ export function createDrop(opts: DropOptions): DropHandle {
|
||||
label: "파일 고르기",
|
||||
variant: "ghost",
|
||||
size: 28,
|
||||
onClick: () => input.click(),
|
||||
});
|
||||
const bar = createProgressBar({ thick: 6 });
|
||||
bar.root.hidden = true;
|
||||
@@ -51,6 +51,7 @@ export function createDrop(opts: DropOptions): DropHandle {
|
||||
error.hidden = true;
|
||||
const root = el("div", {
|
||||
className: "ui-drop",
|
||||
attrs: { role: "button" },
|
||||
children: [
|
||||
el("span", { className: "ui-drop__icon", children: [icon("upload", 24)] }),
|
||||
el("p", { className: "ui-drop__text", text: opts.text ?? "파일을 여기에 끌어 놓거나" }),
|
||||
@@ -69,6 +70,15 @@ export function createDrop(opts: DropOptions): DropHandle {
|
||||
give(input.files);
|
||||
input.value = ""; // 같은 파일을 다시 골라도 change
|
||||
});
|
||||
// 단추 누름도 칸으로 올라와 한 번만 엶 · 숨은 input 의 click 이 올라온 것은 거름
|
||||
root.addEventListener("click", (ev) => {
|
||||
if (ev.target !== input && !pick.disabled) input.click();
|
||||
});
|
||||
root.addEventListener("keydown", (ev) => {
|
||||
if (ev.target !== root || (ev.key !== "Enter" && ev.key !== " ")) return;
|
||||
ev.preventDefault();
|
||||
if (!pick.disabled) input.click();
|
||||
});
|
||||
const over = (on: boolean): boolean => root.classList.toggle("is-over", on && !pick.disabled);
|
||||
root.addEventListener("dragover", (ev) => {
|
||||
ev.preventDefault();
|
||||
@@ -85,6 +95,7 @@ export function createDrop(opts: DropOptions): DropHandle {
|
||||
|
||||
const setDisabled = (on: boolean): void => {
|
||||
pick.disabled = on;
|
||||
root.tabIndex = on ? -1 : 0;
|
||||
root.setAttribute("aria-disabled", String(on));
|
||||
};
|
||||
setDisabled(!!opts.disabled);
|
||||
|
||||
@@ -55,6 +55,23 @@
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 격자표 — 세로선 · 칸마다 테두리(바깥 선은 감쌈 테두리 한 줄) · 가운데 맞춤 · 몸 안 이름 칸(th) = 머리색 */
|
||||
.ui-table--grid {
|
||||
border-style: hidden;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.ui-table--grid :is(th, td) {
|
||||
border: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ui-table--grid tbody th {
|
||||
font-weight: 500;
|
||||
font-size: inherit;
|
||||
color: var(--color-text);
|
||||
background: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface));
|
||||
}
|
||||
|
||||
/* 줄 상태 — 고름 · 바뀜 · 지움 */
|
||||
.ui-table tbody tr:is(.is-active, [aria-selected="true"]) {
|
||||
background: var(--color-accent-soft);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* 부품 「표」(552-3 · 목록 18) — 머리 줄 + 줄들 · 세로선 없음 · 카드 테두리 · 넘치면 가로 스크롤.
|
||||
* 꼴 = 일반표(줄 40 · 기본) · 데이터표(`density: "data"` · 줄 28 · 글 12) · ⋯ 칸(columns 의 label "" — 줄 끝 `createRowMenu`)
|
||||
* · 한글 배지 칸(칸에 배지 요소) · 오른쪽 맞춤 칸(`align: "end"` · 수) · 빈 줄(`emptyRow`) · 펼침 줄(tr.is-sub)
|
||||
* · 격자표(`grid: true` · 세로선 · 칸마다 테두리 · 가운데 맞춤 · 몸 안 이름 칸 = th 머리색) — 수량 산출표처럼 칸을 합치는 표
|
||||
* · 합친 칸 = 칸 자리에 `{ content, colSpan, rowSpan, head }`(head = th) · columns 를 비우면 머리 줄 없이 줄로만 그림
|
||||
* 크기 = 줄 40 / 28 · 머리 36 / 28
|
||||
* 상태 = 줄 마우스(옅은 바탕) · 고름(tr.is-active / aria-selected) · 바뀜(tr.is-changed) · 지움(tr.is-removed · 줄 그음)
|
||||
* 쓰는 법 = `createTable([{ label: "이름" }, { label: "", width: "44px" }], [["임도 1", createRowMenu([…])]])`
|
||||
@@ -14,7 +16,15 @@
|
||||
import { el } from "./ui_template_elements_base";
|
||||
import "./ui_template_table.css";
|
||||
|
||||
export type Cell = string | Node;
|
||||
/** 합친 칸 · 몸 안 이름 칸(head = th) */
|
||||
export interface SpanCell {
|
||||
content: string | Node;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
head?: boolean;
|
||||
}
|
||||
|
||||
export type Cell = string | Node | SpanCell;
|
||||
|
||||
export interface Column {
|
||||
label: string;
|
||||
@@ -26,6 +36,8 @@ export interface Column {
|
||||
export interface TableOptions {
|
||||
/** 데이터표(줄 28 · 글 12) */
|
||||
density?: "data";
|
||||
/** 격자표 — 세로선 · 칸 테두리 · 가운데 맞춤 */
|
||||
grid?: boolean;
|
||||
}
|
||||
|
||||
/** 표 껍데기 — 머리 + 빈 tbody (줄은 호출 쪽이 채움 · 펼침 줄 같은 것을 끼우려고 떼어 둠) */
|
||||
@@ -34,30 +46,39 @@ export function tableShell(
|
||||
opts: TableOptions = {},
|
||||
): { root: HTMLElement; tbody: HTMLElement } {
|
||||
const table = el("table", {
|
||||
className: `ui-table${opts.density === "data" ? " ui-table--data" : ""}`,
|
||||
className: `ui-table${opts.density === "data" ? " ui-table--data" : ""}${opts.grid ? " ui-table--grid" : ""}`,
|
||||
});
|
||||
const hr = el("tr");
|
||||
for (const col of columns) {
|
||||
const th = el("th", { className: col.align ? "is-end" : "", text: col.label });
|
||||
if (col.width) th.style.width = col.width;
|
||||
hr.append(th);
|
||||
if (columns.length) {
|
||||
const hr = el("tr");
|
||||
for (const col of columns) {
|
||||
const th = el("th", { className: col.align ? "is-end" : "", text: col.label });
|
||||
if (col.width) th.style.width = col.width;
|
||||
hr.append(th);
|
||||
}
|
||||
table.append(el("thead", { children: [hr] }));
|
||||
}
|
||||
const tbody = el("tbody");
|
||||
table.append(el("thead", { children: [hr] }), tbody);
|
||||
table.append(tbody);
|
||||
return { root: el("div", { className: "ui-table-wrap", children: [table] }), tbody };
|
||||
}
|
||||
|
||||
/** 한 줄 — 칸 수는 columns 와 같게 · label "" 인 칸 = ⋯ 칸 */
|
||||
/** 한 줄 — 칸 수는 columns 와 같게 · label "" 인 칸 = ⋯ 칸 · 합친 칸은 colSpan 만큼 칸 자리를 건넘 */
|
||||
export function tableRow(columns: Column[], cells: Cell[]): HTMLTableRowElement {
|
||||
const tr = el("tr");
|
||||
cells.forEach((cell, i) => {
|
||||
let i = 0;
|
||||
// ponytail: 위 줄 rowSpan 이 차지한 칸 자리는 안 셈 — 합친 표(격자)는 columns 를 비워 맞춤 · ⋯ 칸 꼴이 필요해지면 자리 표를 둠
|
||||
for (const cell of cells) {
|
||||
const span = typeof cell === "string" || cell instanceof Node ? null : cell;
|
||||
const col = columns[i];
|
||||
const td = el("td", {
|
||||
const td = el(span?.head ? "th" : "td", {
|
||||
className: col?.align ? "is-end" : col?.label === "" ? "ui-table__menucell" : "",
|
||||
});
|
||||
td.append(cell);
|
||||
td.append(span ? span.content : (cell as string | Node));
|
||||
if (span?.colSpan) td.colSpan = span.colSpan;
|
||||
if (span?.rowSpan) td.rowSpan = span.rowSpan;
|
||||
tr.append(td);
|
||||
});
|
||||
i += span?.colSpan ?? 1;
|
||||
}
|
||||
return tr;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,3 +27,11 @@
|
||||
flex: none;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
/* 글 줄 안(span · small) = 줄을 끊지 않는 inline 꼴 · 기호 · 아이콘 사이 = 오른쪽 여백 */
|
||||
:is(span, small).ui-text {
|
||||
display: inline;
|
||||
}
|
||||
:is(span, small).ui-text > :is(.ui-text__mark, svg) {
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*
|
||||
* 꼴 = 흐린 글(muted) · 안내(hint · ⓘ 아이콘) · 상태 글(error · warning · success — 글자 기호 ✗ ! ✓ 같이) · 칸 오류(`.ui-field__error`)
|
||||
* 크기 = 글 12(기본) · 14 — data-text 로 고름
|
||||
* 자리 = p · div 는 한 줄 상자(flex) · 글 줄 안 span · small 에 클래스만 붙이면 inline(줄 안 끊김)
|
||||
* 상태 = 오류 = error 꼴(색만으로 안 가름 · 기호 같이)
|
||||
* 쓰기 = `createText("저장 안 한 값이 있음", "warning")` · `createText("단위 m", "muted", 14)`
|
||||
* 대신 = 파일마다 `__muted`(약 30 벌) · `__hint` · `__status` · `__note` · `__warn` · `.b01-sys-error` · `b03-file__error` …(목록 4장)
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ICON_NAMES, icon } from "./ui_template_icons";
|
||||
import { createInputField, createSelectField, createSlider } from "./ui_template_input";
|
||||
import { createSegment } from "./ui_template_segment";
|
||||
import { createTabs } from "./ui_template_tabs";
|
||||
import { el } from "./ui_template_elements_base";
|
||||
import { createText } from "./ui_template_text";
|
||||
import { createToggle } from "./ui_template_toggle";
|
||||
// 552-3 묶음 부품
|
||||
@@ -34,7 +35,7 @@ import { createRowMenu, type RowMenuItem } from "./ui_template_context_menu";
|
||||
import { createDrop } from "./ui_template_drop";
|
||||
import { buildModal, confirmPanel } from "./ui_template_modal";
|
||||
import { settingCard, settingRow, valueText } from "./ui_template_setting_row";
|
||||
import { createTable, type Column } from "./ui_template_table";
|
||||
import { createTable, type Cell, type Column } from "./ui_template_table";
|
||||
import { createCard } from "./ui_template_card";
|
||||
import { createPanelCard } from "./ui_template_collapsible";
|
||||
import { createBarChart, createDonut, createLineChart } from "./ui_template_elements_chart";
|
||||
@@ -122,6 +123,13 @@ const dataRows = [
|
||||
["1", "1,080"],
|
||||
["2", "640"],
|
||||
];
|
||||
/** 552-5 격자표 견본 — 수량 산출표 꼴(합친 칸 · 몸 안 이름 칸) */
|
||||
const gridRows = (): Cell[][] => [
|
||||
[{ content: "토공 수량", colSpan: 4, head: true }],
|
||||
[{ content: "깍기", rowSpan: 2, head: true }, { content: "토사", head: true }, "12.40", "m³"],
|
||||
[{ content: "암", head: true }, "3.10", "m³"],
|
||||
[{ content: "쌓기", colSpan: 2, head: true }, "8.05", "m³"],
|
||||
];
|
||||
const openMenu = (s: KitState, items = KIT_MENU): HTMLElement => {
|
||||
const menu = createRowMenu(items);
|
||||
menu.classList.add("is-open");
|
||||
@@ -243,8 +251,8 @@ export const KIT_PARTS: KitPart[] = [
|
||||
demo: (s) => (s === "default" ? row(...ICON_NAMES.map((n) => { const i = icon(n); i.title = n; i.dataset.icon = n; return i; })) : null) },
|
||||
{ key: "slider", name: "슬라이더", group: "basic", kinds: "값 하나 · 이름 · 값 글 · 3열 줄(24)", file: "ui_template_input.ts",
|
||||
demo: (s) => { if (s === "active" || s === "error") return null; const sl = createSlider({ label: "투명도", min: 0, max: 100, value: 60, format: (v) => `${v}%` }); markState(sl.input, s); return sl.root; } },
|
||||
{ key: "text", name: "보조 글 · 안내 줄", group: "basic", kinds: "흐린 글 · 안내 · 상태(오류 · 경고 · 성공 · 기호 같이) · 12 · 14", file: "ui_template_text.ts",
|
||||
demo: (s) => (s === "default" ? row(createText("흐린 글 — 마지막 저장 3 분 전"), createText("안내 — 노선을 먼저 고름", "hint"), createText("저장 안 한 값이 있음", "warning"), createText("저장됨", "success"), createText("본문 곁 14", "muted", 14)) : s === "error" ? createText("값이 범위를 벗어남", "error") : null) },
|
||||
{ key: "text", name: "보조 글 · 안내 줄", group: "basic", kinds: "흐린 글 · 안내 · 상태(오류 · 경고 · 성공 · 기호 같이) · 12 · 14 · 글 줄 안 span · small = inline", file: "ui_template_text.ts",
|
||||
demo: (s) => (s === "default" ? row(createText("흐린 글 — 마지막 저장 3 분 전"), createText("안내 — 노선을 먼저 고름", "hint"), createText("저장 안 한 값이 있음", "warning"), createText("저장됨", "success"), createText("본문 곁 14", "muted", 14), el("p", { children: ["단가 ", el("span", { className: "ui-text ui-text--muted", text: "(원/m³)" }), " 를 글 줄 안에 ", el("small", { className: "ui-text ui-text--muted", text: "· 끊김 없음" })] })) : s === "error" ? createText("값이 범위를 벗어남", "error") : null) },
|
||||
// ── 552-3 묶음 ─────────────────────────────────────────────
|
||||
{ key: "card", name: "카드", group: "compound", kinds: "선 카드 · 머리 카드 · 고르는 카드 · 여백 12 · 16", file: "ui_template_card.ts",
|
||||
demo: (s) => { const pick = (): HTMLElement => markState(createCard({ title: "새 노선", body: ["노선을 그려 시작"], pad: 12, onSelect: noop }), s);
|
||||
@@ -252,11 +260,11 @@ export const KIT_PARTS: KitPart[] = [
|
||||
{ key: "collapsible", name: "접이 카드", group: "compound", kinds: "패널 카드 · 구역 · 캐럿 ▾ ▸ · ⚙ 곁값", file: "ui_template_collapsible.ts",
|
||||
demo: (s) => { if (s !== "default" && s !== "hover") return null; const open = createPanelCard("구조물 배치", false, noop); open.append(row(createInputField({ placeholder: "측점" }).root));
|
||||
if (s === "hover") { markState(open.querySelector<HTMLElement>(".ui-collapsible__title")!, s); return open; } return col(open, createPanelCard("접힌 카드")); } },
|
||||
{ key: "table", name: "표", group: "compound", kinds: "일반표(줄 40) · 데이터표(줄 28) · ⋯ 칸 · 한글 배지 · 빈 줄 · 줄 고름 · 바뀜 · 지움", file: "ui_template_table.ts",
|
||||
{ key: "table", name: "표", group: "compound", kinds: "일반표(줄 40) · 데이터표(줄 28) · 격자표(세로선 · 합친 칸) · ⋯ 칸 · 한글 배지 · 빈 줄 · 줄 고름 · 바뀜 · 지움", file: "ui_template_table.ts",
|
||||
demo: (s) => { if (s === "disabled" || s === "error") return null; const t = s === "default" ? createTable(KIT_COLS, kitRows()) : createTable(KIT_DATA, dataRows, { density: "data" });
|
||||
const first = t.querySelector<HTMLElement>("tbody tr")!; if (s === "hover") markState(first, s); if (s === "active") first.classList.add("is-active");
|
||||
if (s !== "default") return t; const data = createTable(KIT_DATA, dataRows, { density: "data" }); const rows = data.querySelectorAll("tbody tr"); rows[0].classList.add("is-changed"); rows[1].classList.add("is-removed");
|
||||
return col(t, data, createTable(KIT_DATA, [], { empty: "노선 없음" })); } },
|
||||
return col(t, data, createTable(KIT_DATA, [], { empty: "노선 없음" }), createTable([], gridRows(), { density: "data", grid: true })); } },
|
||||
{ key: "row-menu", name: "⋯ · 오른쪽 메뉴", group: "compound", kinds: "줄 끝 ⋯(28) · 펼친 판 · 위험 항목(빨강 + 확인창) · 막힌 항목", file: "ui_template_context_menu.ts",
|
||||
demo: (s) => { if (s === "error") return null; if (s === "default") return createRowMenu(KIT_MENU); if (s === "hover") { const m = createRowMenu(KIT_MENU); markState(m.querySelector<HTMLElement>(".ui-row-menu__btn")!, s); return m; }
|
||||
return openMenu(s, s === "disabled" ? [{ label: "내보내기", onClick: noop, disabled: true }, ...KIT_MENU] : KIT_MENU); } },
|
||||
|
||||
Reference in New Issue
Block a user