Files
Aislo/ui_template/ui_template_ui_kit.ts
T
eomsangdonandClaude Opus 5.5 aaac17db4c 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>
2026-10-26 20:49:09 +09:00

129 lines
5.0 KiB
TypeScript

/* =============================================================================
* 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);
}