feat(ui-kit): 부품 모음 화면 틀 — 시스템 관리자만 · 밝음 / 어두움 나란히 · 상태 다섯 칸 · 크기 여덟 단 (PLAN 552-1)
- 대시보드 세부 페이지 `#/b01-account?view=ui-kit` 등록부 한 줄(시스템 관리 묶음 · SYSTEM_ADMIN 만) - ui_template_ui_kit.ts — 테마 칸마다 iframe(data-theme)에 지금 CSS 를 옮겨 실제 값으로 그림 - ui_template_ui_kit_parts.ts — 표준 부품 30 줄(552-2 기본 · 552-3 묶음 · 552-4 틀) · 머리 = 부품 규칙(자리 · 이름 · data-size · data-state · 색 · 아이콘) - 지금 PageKit · 공용 부품 견본(단추 · 입력 · 고르기 · 탭 · 배지 · 표 · ⋯ · 빈 상태 · 설정 줄 · 아이콘) · 나머지 빈 자리 - test_546 시스템 메뉴 기대값에 ui-kit 더함 · 새 지킴 시험 test_552_1_ui_kit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import { mountSysDefaults } from "./B01_Dashboard_UI_View_SysDefaults";
|
||||
import { mountSysLogs } from "./B01_Dashboard_UI_View_SysLogs";
|
||||
import { mountSysOverview } from "./B01_Dashboard_UI_View_SysOverview";
|
||||
import { mountSysUsers } from "./B01_Dashboard_UI_View_SysUsers";
|
||||
import { mountUiKit } from "@ui/ui_template_ui_kit";
|
||||
|
||||
/** 역할 넷 — 산림청 = 세션 is_forest_service 인 일반 사용자(회사 관리자 · 시스템 관리자가 앞섬) */
|
||||
export type ViewRole = "USER" | "ADMIN" | "FOREST" | "SYSTEM_ADMIN";
|
||||
@@ -70,6 +71,7 @@ export const VIEWS: View[] = [
|
||||
{ key: "sys-users", title: "사용자", group: "system", roles: ["SYSTEM_ADMIN"], icon: "user", mount: mountSysUsers },
|
||||
{ key: "sys-defaults", title: "설계 기본값", group: "system", roles: ["SYSTEM_ADMIN"], icon: "database", mount: mountSysDefaults },
|
||||
{ key: "sys-logs", title: "시스템 로그", group: "system", roles: ["SYSTEM_ADMIN"], icon: "fileText", mount: mountSysLogs },
|
||||
{ key: "ui-kit", title: "부품 모음", group: "system", roles: ["SYSTEM_ADMIN"], icon: "adjustments", mount: mountUiKit },
|
||||
// 도움
|
||||
{ key: "guide", title: "사용자 가이드", group: "help", roles: ALL, icon: "book", mount: mountGuide },
|
||||
{ key: "chat", title: "지식 챗봇", group: "help", roles: ALL, icon: "chatbot", mount: mountChat },
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_역할별_메뉴():
|
||||
assert system == [
|
||||
*work, "company:people", "company:company",
|
||||
"system:sys-overview", "system:sys-companies", "system:sys-users",
|
||||
"system:sys-defaults", "system:sys-logs", *help_,
|
||||
"system:sys-defaults", "system:sys-logs", "system:ui-kit", *help_,
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
"""552-1 부품 모음 화면 지킴 시험 — 화면 확인은 ORCA.
|
||||
|
||||
· `#/b01-account?view=ui-kit` = 시스템 관리자만(다른 역할은 메뉴에 없고 주소로 와도 홈)
|
||||
· 부품 목록 = 열쇠 겹침 없음 · 세 묶음(552-2 기본 · 552-3 묶음 · 552-4 틀) · 파일은 ui_template_ 안
|
||||
· 크기 여덟 단 · 상태 다섯 칸 · 밝음 / 어두움 두 칸(iframe data-theme) · 날 색 없음
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from test_546_b01_shell import MENU, _run_views
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
UI = ROOT / "ui_template"
|
||||
|
||||
|
||||
def _text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
def test_시스템_관리자만():
|
||||
out = _run_views(
|
||||
MENU + "console.log(JSON.stringify(['USER','ADMIN','FOREST','SYSTEM_ADMIN'].map("
|
||||
"(r) => [menu(r).includes('system:ui-kit'), viewFor('ui-kit', r).key])));"
|
||||
)
|
||||
assert out == [[False, "home"], [False, "home"], [False, "home"], [True, "ui-kit"]]
|
||||
|
||||
|
||||
def test_부품_목록():
|
||||
src = _text(UI / "ui_template_ui_kit_parts.ts")
|
||||
rows = re.findall(
|
||||
r'\{ key: "([^"]+)", name: "[^"]+", group: "(\w+)",[^\n]*file: "([^"]+)"', src
|
||||
)
|
||||
keys = [k for k, _, _ in rows]
|
||||
assert len(keys) == len(set(keys)) >= 25
|
||||
assert {g for _, g, _ in rows} == {"basic", "compound", "frame"}
|
||||
assert all(f.startswith("ui_template_") and f.endswith(".ts") for _, _, f in rows)
|
||||
# 묶음 차례 그대로 — 창마다 자기 덩어리만 고침
|
||||
groups = [g for _, g, _ in rows]
|
||||
assert groups == sorted(groups, key=["basic", "compound", "frame"].index)
|
||||
assert "KIT_SIZES = [18, 20, 22, 24, 28, 32, 36, 42] as const" in src
|
||||
assert 'KIT_STATES = ["default", "hover", "active", "disabled", "error"] as const' in src
|
||||
# 규칙 머리 — 크기 · 상태 · 색 · 아이콘
|
||||
for word in (
|
||||
'data-size="18|20|22|24|28|32|36|42"',
|
||||
'data-state="hover"',
|
||||
"--draw-*",
|
||||
"icon(name, size)",
|
||||
):
|
||||
assert word in src, word
|
||||
|
||||
|
||||
def test_밝음_어두움_두_칸():
|
||||
view = _text(UI / "ui_template_ui_kit.ts")
|
||||
assert 'panes.append(themePane("light"), themePane("dark"))' in view
|
||||
assert "doc.documentElement.dataset.theme = theme" in view
|
||||
assert "frame.srcdoc" in view # 표준 모드 문서
|
||||
css = _text(UI / "ui_template_ui_kit.css")
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,8}\b|rgba?\(", css) # 색 = 공통 값만
|
||||
for path in (view, _text(UI / "ui_template_ui_kit_parts.ts"), css):
|
||||
assert len(path.splitlines()) <= 700
|
||||
@@ -0,0 +1,145 @@
|
||||
/* 부품 모음 화면(552-1) — 밝음 · 어두움 두 칸 · 칸 안은 iframe 문서 */
|
||||
.b01-pg.ui-kit {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.ui-kit__panes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.ui-kit__pane-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ui-kit__frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards, 10px);
|
||||
}
|
||||
|
||||
/* iframe 문서 */
|
||||
.ui-kit__doc {
|
||||
margin: 0;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
/* 셸 별칭만 받고 셸 배치(100dvh · grid)는 풂 */
|
||||
.b01-shell.ui-kit__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ui-kit__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ui-kit__group-title {
|
||||
margin: 0;
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui-kit__part {
|
||||
padding: 12px;
|
||||
background: var(--color-surface-raised);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards, 10px);
|
||||
}
|
||||
|
||||
.ui-kit__part-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 8px;
|
||||
margin-bottom: 8px;
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.ui-kit__part-name {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui-kit__part-flag {
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.ui-kit__states {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.ui-kit__legend {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 4px 12px;
|
||||
background: var(--color-bg);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.ui-kit__state {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 상태가 기본 하나뿐인 견본(표 · 배지 …)은 다섯 칸을 다 씀 */
|
||||
.ui-kit__state--wide {
|
||||
grid-column: 1 / -1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ui-kit__slot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.ui-kit__demo-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ui-kit__sizes {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ui-kit__size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
border: 1px dashed var(--color-accent);
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/* =============================================================================
|
||||
* ui_template_ui_kit.ts
|
||||
* 부품 모음 화면(552-1) — 대시보드 세부 페이지 `#/b01-account?view=ui-kit` · 시스템 관리자만.
|
||||
* 밝음 · 어두움을 나란히 — 칸마다 iframe(`<html data-theme>`)에 지금 페이지 CSS 를 옮겨 실제 테마 값으로 그림.
|
||||
* 부품 줄 = `ui_template_ui_kit_parts.ts` 목록 · 상태 다섯 칸(기본 · 마우스 · 눌림 · 막힘 · 오류) · 견본이 없으면 빈 자리.
|
||||
* 크기 자 = 단추 · 입력 높이 여덟 단(18 ~ 42).
|
||||
* ========================================================================== */
|
||||
|
||||
import { pageFrame } from "../B01_Dashboard/B01_Dashboard_UI_PageKit";
|
||||
import {
|
||||
KIT_GROUP_LABEL,
|
||||
KIT_PARTS,
|
||||
KIT_SIZES,
|
||||
KIT_STATES,
|
||||
KIT_STATE_LABEL,
|
||||
type KitGroup,
|
||||
type KitPart,
|
||||
} from "./ui_template_ui_kit_parts";
|
||||
import "./ui_template_ui_kit.css";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
function node<K extends keyof HTMLElementTagNameMap>(
|
||||
tag: K,
|
||||
className: string,
|
||||
text?: string,
|
||||
): HTMLElementTagNameMap[K] {
|
||||
const n = document.createElement(tag);
|
||||
n.className = className;
|
||||
if (text !== undefined) n.textContent = text;
|
||||
return n;
|
||||
}
|
||||
|
||||
/** 크기 자 — 여덟 단 높이 상자 */
|
||||
function sizeRuler(): HTMLElement {
|
||||
const box = node("section", "ui-kit__group");
|
||||
box.append(node("h3", "ui-kit__group-title", "크기 여덟 단 (높이 px · data-size)"));
|
||||
const line = node("div", "ui-kit__sizes");
|
||||
for (const h of KIT_SIZES) {
|
||||
const cell = node("div", "ui-kit__size", String(h));
|
||||
cell.style.height = `${h}px`;
|
||||
cell.dataset.size = String(h);
|
||||
line.append(cell);
|
||||
}
|
||||
box.append(line);
|
||||
return box;
|
||||
}
|
||||
|
||||
function partRow(part: KitPart): HTMLElement {
|
||||
const item = node("article", "ui-kit__part");
|
||||
item.dataset.part = part.key;
|
||||
const head = node("header", "ui-kit__part-head");
|
||||
head.append(
|
||||
node("strong", "ui-kit__part-name", part.name),
|
||||
node("span", "ui-kit__part-kinds", part.kinds),
|
||||
node("code", "ui-kit__part-file", part.file),
|
||||
);
|
||||
if (part.legacy) head.append(node("span", "ui-kit__part-flag", "지금 꼴"));
|
||||
item.append(head);
|
||||
const states = node("div", "ui-kit__states");
|
||||
const shown = KIT_STATES.map((s) => part.demo?.(s)); // null = 그 상태 없음 · undefined = 견본 없음
|
||||
const onlyDefault = shown[0] && shown.slice(1).every((x) => x === null);
|
||||
KIT_STATES.forEach((s, i) => {
|
||||
if (onlyDefault && i > 0) return;
|
||||
const cell = node("div", `ui-kit__state${onlyDefault ? " ui-kit__state--wide" : ""}`);
|
||||
cell.dataset.state = s;
|
||||
cell.append(shown[i] ?? node("span", "ui-kit__slot", shown[i] === null ? "-" : "빈 자리"));
|
||||
states.append(cell);
|
||||
});
|
||||
item.append(states);
|
||||
return item;
|
||||
}
|
||||
|
||||
/** 한 테마 칸 내용 — 상태 머리 · 크기 자 · 묶음별 부품 줄 */
|
||||
export function renderKit(): HTMLElement {
|
||||
// 셸 별칭(`--b01-*`)을 받으려고 .b01-shell 로 감쌈 — 배치 규칙은 CSS 에서 풂
|
||||
const root = node("div", "b01-shell ui-kit__body");
|
||||
const legend = node("div", "ui-kit__states ui-kit__legend");
|
||||
for (const s of KIT_STATES) legend.append(node("span", "ui-kit__state", KIT_STATE_LABEL[s]));
|
||||
root.append(legend, sizeRuler());
|
||||
for (const group of Object.keys(KIT_GROUP_LABEL) as KitGroup[]) {
|
||||
const box = node("section", "ui-kit__group");
|
||||
box.dataset.group = group;
|
||||
box.append(node("h3", "ui-kit__group-title", KIT_GROUP_LABEL[group]));
|
||||
box.append(...KIT_PARTS.filter((p) => p.group === group).map(partRow));
|
||||
root.append(box);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/** 테마 칸 — iframe 에 지금 문서의 CSS 를 옮기고 `<html data-theme>` 를 박음 */
|
||||
function themePane(theme: Theme): HTMLElement {
|
||||
const pane = node("div", "ui-kit__pane");
|
||||
pane.dataset.theme = theme;
|
||||
pane.append(node("h3", "ui-kit__pane-title", theme === "light" ? "밝음" : "어두움"));
|
||||
const frame = node("iframe", "ui-kit__frame");
|
||||
frame.title = `부품 모음 — ${theme === "light" ? "밝음" : "어두움"}`;
|
||||
pane.append(frame);
|
||||
frame.addEventListener("load", () => {
|
||||
const doc = frame.contentDocument;
|
||||
if (!doc) return;
|
||||
doc.documentElement.dataset.theme = theme;
|
||||
doc.documentElement.lang = "ko";
|
||||
for (const css of document.head.querySelectorAll('style, link[rel="stylesheet"]'))
|
||||
doc.head.append(css.cloneNode(true));
|
||||
doc.body.className = "ui-kit__doc";
|
||||
doc.body.append(renderKit());
|
||||
const fit = (): void => {
|
||||
frame.style.height = `${doc.documentElement.scrollHeight}px`;
|
||||
};
|
||||
new ResizeObserver(fit).observe(doc.body);
|
||||
fit();
|
||||
});
|
||||
frame.srcdoc = "<!doctype html><html><head></head><body></body></html>";
|
||||
return pane;
|
||||
}
|
||||
|
||||
export function mountUiKit(host: HTMLElement): void {
|
||||
const page = pageFrame(
|
||||
host,
|
||||
"부품 모음",
|
||||
"모든 화면이 다시 쓰는 부품 — 밝음 · 어두움 · 상태별 · 빈 자리는 552-2 ~ 552-4 가 채움",
|
||||
);
|
||||
page.root.classList.add("ui-kit");
|
||||
const panes = node("div", "ui-kit__panes");
|
||||
panes.append(themePane("light"), themePane("dark"));
|
||||
page.body.append(panes);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/* =============================================================================
|
||||
* ui_template_ui_kit_parts.ts
|
||||
* 부품 모음 화면(552-1)의 부품 목록 — 한 줄 = 표준 부품 하나 · 화면은 `ui_template_ui_kit.ts`.
|
||||
*
|
||||
* 부품 규칙(552-2 ~ 552-4 가 따름):
|
||||
* 자리 = `ui_template/` 한 곳 · 부품 하나 = `ui_template_<이름>.ts` (+ 같은 이름 `.css`) · 700 줄 넘으면 `_<갈래>` 로 나눔
|
||||
* 이름 = 만들기 `create<부품>(opts)` → 요소 또는 { root, … } 손잡이 · 클래스 `ui-<부품>` · 속 `ui-<부품>__<조각>` · 꼴 `ui-<부품>--<꼴>`
|
||||
* 크기 = `data-size="18|20|22|24|28|32|36|42"`(높이 px · 여덟 단) — 단추 · 아이콘 단추 · 입력 · 고르기 · 분할 고르기 · 탭 같은 값
|
||||
* 상태 = 마우스 `:hover` · 눌림 `:active` · 고름 `.is-active` / `aria-selected` · 막힘 `disabled` / `aria-disabled` · 오류 `.is-error` / `aria-invalid`
|
||||
* → CSS 는 `:is(:hover, [data-state="hover"])` · `:is(:active, [data-state="active"])` 로 써서 이 화면이 상태를 굳혀 보임
|
||||
* 색 = 공통 값(`--color-*` · `--radius-*` · `--shadow-*`)만 · 날 색 · `--b01-*` · 그림 값(`--draw-*`) 금지
|
||||
* 글꼴 = `--font-ui` 상속 · 글자 크기 = `--text-*`
|
||||
* 아이콘 = `ui_template_icons.ts` 의 `icon(name, size)` 만 · 그림 이모지 금지 · 글자 기호(→ ▾ ▲▼ ✓)는 시험이 잡아 그대로
|
||||
* 옛 이름 = 지금 공용 클래스(`ui-btn--filled` · `ui-collapsible__title` · `ui-sidebar-actions` …)는 부품이 그대로 냄(시험 · 가이드 촬영)
|
||||
* 머리 = 파일 머리에 무엇 · 꼴 · 크기 · 상태 · 쓰는 법 한 줄씩
|
||||
* 부품을 만들면 아래 자기 줄의 `file` 을 맞추고 `demo` 를 채움 — 빈 칸 = 「빈 자리」로 보임.
|
||||
* 몫 = 552-2 기본 · 552-3 묶음 · 552-4 틀 — 세 묶음이 떨어져 있어 서로 줄을 안 건드림.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createInputField, createSelectField } from "./ui_template_elements";
|
||||
import { icon } from "./ui_template_icons";
|
||||
import {
|
||||
badge,
|
||||
dataTable,
|
||||
emptyState,
|
||||
rowMenu,
|
||||
settingCard,
|
||||
settingRow,
|
||||
tabBar,
|
||||
valueText,
|
||||
} from "../B01_Dashboard/B01_Dashboard_UI_PageKit";
|
||||
|
||||
/** 상태 줄 다섯 칸 */
|
||||
export const KIT_STATES = ["default", "hover", "active", "disabled", "error"] as const;
|
||||
export type KitState = (typeof KIT_STATES)[number];
|
||||
export const KIT_STATE_LABEL: Record<KitState, string> = {
|
||||
default: "기본",
|
||||
hover: "마우스",
|
||||
active: "눌림",
|
||||
disabled: "막힘",
|
||||
error: "오류",
|
||||
};
|
||||
|
||||
/** 단추 · 입력 높이 여덟 단(요구 377 · 378) */
|
||||
export const KIT_SIZES = [18, 20, 22, 24, 28, 32, 36, 42] as const;
|
||||
|
||||
export type KitGroup = "basic" | "compound" | "frame";
|
||||
export const KIT_GROUP_LABEL: Record<KitGroup, string> = {
|
||||
basic: "기본 부품 (552-2)",
|
||||
compound: "묶음 부품 (552-3)",
|
||||
frame: "틀 (552-4)",
|
||||
};
|
||||
|
||||
export interface KitPart {
|
||||
key: string;
|
||||
name: string;
|
||||
group: KitGroup;
|
||||
/** 꼴 · 갈래 */
|
||||
kinds: string;
|
||||
/** 만들 파일(`ui_template/` 안) */
|
||||
file: string;
|
||||
/** 지금 견본 — 상태마다 하나 · null = 그 상태 없음 · 없으면 빈 자리 */
|
||||
demo?: (state: KitState) => HTMLElement | null;
|
||||
/** 견본이 옮기기 전 PageKit · 옛 공용 부품인지 */
|
||||
legacy?: boolean;
|
||||
}
|
||||
|
||||
/** 상태 굳히기 — 마우스 · 눌림 · 오류 = data-state · 막힘 = disabled */
|
||||
export function markState<T extends HTMLElement>(node: T, state: KitState): T {
|
||||
if (state === "disabled") {
|
||||
if ("disabled" in node) (node as unknown as { disabled: boolean }).disabled = true;
|
||||
node.setAttribute("aria-disabled", "true");
|
||||
} else if (state !== "default") {
|
||||
node.dataset.state = state;
|
||||
if (state === "error") {
|
||||
node.classList.add("is-error");
|
||||
node.setAttribute("aria-invalid", "true");
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
const row = (...children: HTMLElement[]): HTMLElement => {
|
||||
const box = document.createElement("div");
|
||||
box.className = "ui-kit__demo-row";
|
||||
box.append(...children);
|
||||
return box;
|
||||
};
|
||||
|
||||
const noop = (): void => {};
|
||||
|
||||
// prettier-ignore
|
||||
export const KIT_PARTS: KitPart[] = [
|
||||
// ── 552-2 기본 ─────────────────────────────────────────────
|
||||
{ key: "button", name: "단추", group: "basic", kinds: "주 · 보조 · 고스트 · 위험 · 위험 고스트 · 글 링크 · 여덟 단", file: "ui_template_button.ts", legacy: true,
|
||||
demo: (s) => row(markState(createButton({ label: "저장", onClick: noop }), s), markState(createButton({ label: "취소", variant: "ghost", onClick: noop }), s), markState(createButton({ label: "삭제", variant: "danger", onClick: noop }), s)) },
|
||||
{ key: "icon-button", name: "아이콘 단추", group: "basic", kinds: "고스트 · 선 · 채움 · 여덟 단 · title 필수", file: "ui_template_button.ts" },
|
||||
{ key: "input", name: "입력", group: "basic", kinds: "글 · 수 · 단위 붙음 · 찾기 · 날짜 · 여덟 단", file: "ui_template_input.ts", legacy: true,
|
||||
demo: (s) => { const f = createInputField({ placeholder: "이름" }); markState(f.input, s); return f.root; } },
|
||||
{ key: "select", name: "고르기", group: "basic", kinds: "한 개 · 여덟 단", file: "ui_template_input.ts", legacy: true,
|
||||
demo: (s) => { const f = createSelectField({ options: [{ value: "a", text: "임도" }, { value: "b", text: "예정노선" }] }); markState(f.select, s); return f.root; } },
|
||||
{ key: "check", name: "체크 · 라디오", group: "basic", kinds: "체크 · 라디오 · 글 붙음", file: "ui_template_checkbox.ts" },
|
||||
{ key: "toggle", name: "켜기", group: "basic", kinds: "켬 · 끔 · 글 붙음", file: "ui_template_toggle.ts" },
|
||||
{ key: "segment", name: "분할 고르기", group: "basic", kinds: "글 · 아이콘 · 여덟 단", file: "ui_template_segment.ts" },
|
||||
{ key: "tabs", name: "탭", group: "basic", kinds: "밑줄 탭(한 꼴) · 수 배지 · 문서 탭은 꼴 그대로", file: "ui_template_tabs.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? tabBar([{ key: "a", label: "개요" }, { key: "b", label: "구성원" }, { key: "c", label: "기록" }], "a", noop).root : null) },
|
||||
{ key: "badge", name: "배지", group: "basic", kinds: "상태 · 역할 · 종류 · 수 · 램프(✓ 같이)", file: "ui_template_badge.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? row(badge("진행 중", "accent"), badge("완료", "success"), badge("멈춤", "warning"), badge("오류", "danger"), badge("임시", "neutral")) : null) },
|
||||
{ key: "tooltip", name: "툴팁", group: "basic", kinds: "마우스 글 · 값 풍선", file: "ui_template_hover_info.ts" },
|
||||
{ key: "icon", name: "아이콘", group: "basic", kinds: "Tabler 선 · 16 · 18 · 24 · 32", file: "ui_template_icons.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? row(icon("home"), icon("folder"), icon("route"), icon("search"), icon("bell"), icon("users")) : null) },
|
||||
// ── 552-3 묶음 ─────────────────────────────────────────────
|
||||
{ key: "card", name: "카드", group: "compound", kinds: "선 카드 · 머리 카드 · 고를 수 있는 카드", file: "ui_template_card.ts" },
|
||||
{ key: "collapsible", name: "접이 카드", group: "compound", kinds: "패널 카드 · 구역 · 캐럿 셰브런", file: "ui_template_collapsible.ts" },
|
||||
{ key: "panel-head", name: "패널 머리", group: "compound", kinds: "제목 · 부제 · 오른쪽 단추", file: "ui_template_panel_head.ts" },
|
||||
{ key: "table", name: "표", group: "compound", kinds: "데이터표(28 · 밀도) · 일반표(40) · ⋯ 칸 · 한글 배지 · 빈 줄", file: "ui_template_table.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? dataTable([{ label: "이름" }, { label: "상태", width: "90px" }, { label: "연장", width: "80px", align: "end" }, { label: "", width: "44px" }], [["임도 1 노선", badge("진행 중", "accent"), "1,080 m", rowMenu([{ label: "열기", onClick: noop }])], ["임도 2 노선", badge("완료", "success"), "640 m", rowMenu([{ label: "열기", onClick: noop }])]]) : null) },
|
||||
{ key: "row-menu", name: "⋯ · 오른쪽 메뉴", group: "compound", kinds: "줄 끝 ⋯ · 오른쪽 누름 메뉴 · 위험 항목", file: "ui_template_context_menu.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? rowMenu([{ label: "이름 바꾸기", onClick: noop }, { label: "삭제", danger: true, onClick: noop }]) : null) },
|
||||
{ key: "empty", name: "빈 상태", group: "compound", kinds: "선 아이콘 · 한 줄 · 주 단추 · 사용법", file: "ui_template_empty.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? emptyState({ icon: "folder", text: "아직 프로젝트가 없음" }) : null) },
|
||||
{ key: "modal", name: "모달 · 확인창", group: "compound", kinds: "보통 · 넓음 · 확인 · 위험 확인", file: "ui_template_modal.ts" },
|
||||
{ key: "drawer", name: "오른쪽 서랍", group: "compound", kinds: "상세 · 설정", file: "ui_template_side_panels.ts" },
|
||||
{ key: "toast", name: "알림", group: "compound", kinds: "토스트 4꼴 · 띠 알림 · 로딩 가림막", file: "ui_template_toast.ts" },
|
||||
{ key: "toolbar", name: "도구줄", group: "compound", kinds: "단추 묶음 · 나눔선 · 찾기 · 고르기", file: "ui_template_toolbar.ts" },
|
||||
{ key: "steps", name: "단계 줄", group: "compound", kinds: "가로 막대 · 세로 목록 · compact", file: "ui_template_workflow_layout.ts" },
|
||||
{ key: "stat", name: "숫자 카드", group: "compound", kinds: "값 · 단위 · 변화", file: "ui_template_stat.ts" },
|
||||
{ key: "progress", name: "진행 막대", group: "compound", kinds: "막대 · 원", file: "ui_template_progress.ts" },
|
||||
{ key: "mini-chart", name: "작은 그래프", group: "compound", kinds: "막대 · 원 · 선", file: "ui_template_elements_chart.ts" },
|
||||
{ key: "setting-row", name: "설정 줄", group: "compound", kinds: "제목 · 설명 · 오른쪽 값 · 조작", file: "ui_template_setting_row.ts", legacy: true,
|
||||
demo: (s) => (s === "default" ? settingCard([settingRow("회사 이름", "보고서 머리에 들어감", valueText("산림설계")), settingRow("단위", "", valueText(null))]) : null) },
|
||||
{ key: "drop", name: "파일 놓기 칸", group: "compound", kinds: "끌어 놓기 · 고르기 단추 · 진행", file: "ui_template_drop.ts" },
|
||||
// ── 552-4 틀 ───────────────────────────────────────────────
|
||||
{ key: "work-frame", name: "작업 화면 틀", group: "frame", kinds: "왼쪽 패널 · 본문 · 아래 단추 줄 · [?][<][>] · 머리 줄 없음 · ☰ 서랍", file: "ui_template_overlay.ts" },
|
||||
{ key: "page-frame", name: "일반 화면 틀", group: "frame", kinds: "페이지 머리(제목 · 설명 · 주 단추) · 몸통", file: "ui_template_general_layout.ts" },
|
||||
{ key: "public-frame", name: "공개 화면 틀", group: "frame", kinds: "공개 머리 · 본문 · 바닥글", file: "ui_template_public_layout.ts" },
|
||||
];
|
||||
Reference in New Issue
Block a user