- 위험 채움 토큰 밝음 #e5484d → #d6363c(흰 글 3.91 → 4.72) · 어두움 5.6 · 위험 글 #c0392b 그대로 - 새 부품 계산 줄(ui_template_calc_row) — B08 수리집수면적표 b08-hydro__row · 식 칸 배치를 대신함 - 표 부품 칸 램프(ui-data-table__lamp · --only · ✓) — B08 집계표 · 거리이정표 · B09 설계내역 램프 CSS 세 벌을 걷음(표지 class 는 풍선 · 가이드용으로 남김) - 모달 넓은 중간 창(wide + width 1100px · 1100 × 680) — B09 운반 창 손 인라인 크기를 걷음 - 값 목록 규칙 꼴(rule) — ☰ 설정 규칙 줄 ul/li 를 걷음 - 접이 구역 머리(createCollapsibleHead = 패널 머리 + 접는 제목) — B08 구조물도 「조합 연결」 제목(옛 b08-sheets__title)을 대신함 - 일반 화면 틀 좁은 폭 · 입력 칸 2열 · 끝 단추 줄 — D01 · E01 이 B02 CSS 를 빌리던 것을 끊음(가이드 장면 선택자 맞춤) - 부품 모음 견본 · 지킴 시험 test_557_2 · 낡은 시험(198_1 · 247_1 · 251_1 · 257_1 · 276_1 · 551_1)은 부품 CSS · 새 class 로 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
233 lines
9.3 KiB
TypeScript
233 lines
9.3 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_ui_kit_frames.ts
|
|
* 부품 모음 화면의 틀(552-4) 견본 — `ui_template_ui_kit_parts.ts` 의 frame 줄이 부름.
|
|
* 견본 = 진짜 부품 함수 · 클래스로 그림 · 화면에 붙는(fixed) 틀은 칸 안에 놓이게 자리만 인라인으로 풂.
|
|
* ========================================================================== */
|
|
|
|
import { createIconButton } from "./ui_template_button";
|
|
import { createButton } from "./ui_template_elements";
|
|
import { createFloatPanel } from "./ui_template_float_panel";
|
|
import { createFormGrid, createPageFrame } from "./ui_template_general_layout";
|
|
import { createInputField } from "./ui_template_input";
|
|
import { createListRow } from "./ui_template_list_row";
|
|
import { createWorkflowPanelHandle } from "./ui_template_overlay";
|
|
import { createPanelHead } from "./ui_template_panel_head";
|
|
import { createPublicFrame } from "./ui_template_public_layout";
|
|
import { createSidePanels } from "./ui_template_side_panels";
|
|
import { createToolbar } from "./ui_template_toolbar";
|
|
import type { KitState } from "./ui_template_ui_kit_parts";
|
|
import { createStepBar } from "./ui_template_workflow_layout";
|
|
|
|
const noop = (): void => {};
|
|
|
|
function div(className: string, text?: string): HTMLDivElement {
|
|
const n = document.createElement("div");
|
|
n.className = className;
|
|
if (text !== undefined) n.textContent = text;
|
|
return n;
|
|
}
|
|
|
|
const wrap = (...children: HTMLElement[]): HTMLElement => {
|
|
const box = div("ui-kit__demo-row");
|
|
box.append(...children);
|
|
return box;
|
|
};
|
|
|
|
/** 마우스 · 눌림 = data-state · 막힘 = disabled */
|
|
function mark<T extends HTMLElement>(node: T, state: KitState): T {
|
|
if (state === "disabled") {
|
|
if (node instanceof HTMLButtonElement) node.disabled = true;
|
|
node.setAttribute("aria-disabled", "true");
|
|
} else if (state !== "default") node.dataset.state = state;
|
|
return node;
|
|
}
|
|
|
|
/** 아래 단추 줄 — [?][<][>] + 주 단추 */
|
|
function sidebarActions(): HTMLElement {
|
|
const bar = div("ui-sidebar-actions");
|
|
const help = createButton({ label: "?", variant: "ghost", onClick: noop });
|
|
help.classList.add("ui-help-btn");
|
|
const prev = createButton({ label: "<", variant: "ghost", onClick: noop });
|
|
const next = createButton({ label: ">", variant: "ghost", onClick: noop });
|
|
prev.classList.add("ui-step-nav-btn");
|
|
next.classList.add("ui-step-nav-btn");
|
|
bar.append(help, prev, next, createButton({ label: "저장", onClick: noop }));
|
|
return bar;
|
|
}
|
|
|
|
/** 작업 화면 틀 — 왼쪽 패널(제목 줄 · 카드 · 아래 단추 줄 · 손잡이) + 본문 자리 */
|
|
function workFrame(): HTMLElement {
|
|
const panel = document.createElement("aside");
|
|
panel.className = "ui-workflow-overlay__panel ui-workflow-overlay__panel--title";
|
|
panel.style.cssText = "position:relative;top:auto;bottom:auto;width:280px;height:260px";
|
|
const header = div("ui-workflow-overlay__header");
|
|
const title = document.createElement("h2");
|
|
title.className = "ui-workflow-overlay__title";
|
|
title.textContent = "횡단설계";
|
|
const project = document.createElement("span");
|
|
project.className = "ui-workflow-overlay__project";
|
|
project.textContent = "검증 프로젝트";
|
|
header.append(title, project);
|
|
const handle = createWorkflowPanelHandle("side");
|
|
handle.setOpen(true);
|
|
const body = div("ui-workflow-overlay__body");
|
|
body.style.cssText = "display:flex;flex-direction:column;gap:8px;padding-top:8px";
|
|
const card = div("ui-sidebar-card ui-sidebar-section");
|
|
card.append(
|
|
createPanelHead({ title: "구조물 배치", sub: "측점 3 곳" }).root,
|
|
createListRow({ label: "No.0+020 배수관", active: true, onClick: noop }),
|
|
createListRow({ label: "No.0+140 옹벽", onClick: noop }),
|
|
);
|
|
const fill = div("");
|
|
fill.style.flex = "1";
|
|
body.append(card, fill, sidebarActions());
|
|
panel.append(header, handle.root, body);
|
|
const main = div("ui-workflow-layout__main", "본문(지도 · 그림 · 표)");
|
|
main.style.cssText = "min-height:260px;padding:12px;border:1px dashed var(--color-border)";
|
|
const box = div("ui-kit__frame-demo");
|
|
box.style.cssText = "display:grid;grid-template-columns:280px 1fr;width:100%";
|
|
box.append(panel, main);
|
|
return box;
|
|
}
|
|
|
|
function pageFrame(): HTMLElement {
|
|
const f = createPageFrame(null, "구성원", "회사 사람 · 역할 · 초대");
|
|
f.setAction(createButton({ label: "초대", onClick: noop }));
|
|
f.body.append(div("ui-general-block__empty", "몸 — 표 · 카드가 들어감"));
|
|
// 입력 칸 2열 · 끝 단추 줄(557-2 · D01 · E01 등록 양식)
|
|
const actions = div("ui-general-block__actions ui-general-block__actions--end");
|
|
actions.append(
|
|
createButton({ label: "취소", variant: "ghost", onClick: noop }),
|
|
createButton({ label: "등록", onClick: noop }),
|
|
);
|
|
f.body.append(
|
|
createFormGrid([
|
|
createInputField({ label: "프로젝트 이름" }).root,
|
|
createInputField({ label: "지역" }).root,
|
|
]),
|
|
actions,
|
|
);
|
|
return f.root;
|
|
}
|
|
|
|
function publicFrame(): HTMLElement {
|
|
const f = createPublicFrame({
|
|
title: "임도 설계 자동화",
|
|
lead: "지형 자료로 노선 · 종단 · 횡단 · 수량을 한 흐름에.",
|
|
actions: [
|
|
createButton({ label: "시작하기", onClick: noop }),
|
|
createButton({ label: "더 알아보기", variant: "ghost", onClick: noop }),
|
|
],
|
|
});
|
|
f.addSection("기능").append(div("ui-general-block__empty", "구역 몸"));
|
|
f.root.style.width = "100%";
|
|
return f.root;
|
|
}
|
|
|
|
const STEP_NAMES = ["파일", "전처리", "노선", "횡단"];
|
|
|
|
function steps(state: KitState): HTMLElement | null {
|
|
if (state === "active" || state === "error") return null;
|
|
const bar = createStepBar(STEP_NAMES, 2, { compact: true });
|
|
const target = bar.querySelectorAll<HTMLButtonElement>(".ui-workflow-layout__step")[0];
|
|
mark(target, state);
|
|
if (state !== "default") return bar;
|
|
const list = createStepBar(STEP_NAMES, 2, {
|
|
orientation: "vertical",
|
|
icons: ["1", "2", "3", "4"],
|
|
});
|
|
list.style.width = "180px";
|
|
return wrap(bar, list);
|
|
}
|
|
|
|
function panelHead(): HTMLElement {
|
|
const box = div("");
|
|
box.style.cssText = "display:flex;flex-direction:column;gap:12px;width:320px";
|
|
box.append(
|
|
createPanelHead({ title: "배수관", sub: "3 개", divider: true }).root,
|
|
createPanelHead({
|
|
title: "표준 횡단면",
|
|
actions: [createButton({ label: "더함", variant: "ghost", onClick: noop })],
|
|
}).root,
|
|
);
|
|
return box;
|
|
}
|
|
|
|
function listRow(state: KitState): HTMLElement | null {
|
|
if (state === "error") return null;
|
|
const box = div("");
|
|
box.style.cssText = "display:flex;flex-direction:column;gap:2px;width:240px";
|
|
const one = mark(createListRow({ label: "B1 배수관", sub: "No.0+020", onClick: noop }), state);
|
|
box.append(one);
|
|
if (state === "default")
|
|
box.append(
|
|
createListRow({ label: "B2 옹벽", active: true, no: 2, size: 28, onClick: noop }),
|
|
createListRow({ label: "B3 측구", grip: true, trailing: "12 m", size: 36, onClick: noop }),
|
|
createListRow({ label: "B4 비탈", stripe: "var(--color-success)", onClick: noop }),
|
|
);
|
|
return box;
|
|
}
|
|
|
|
function floatPanel(): HTMLElement {
|
|
const place = (root: HTMLElement): HTMLElement => {
|
|
root.style.position = "relative";
|
|
return root;
|
|
};
|
|
const one = createFloatPanel({ title: "측정", onClose: noop, draggable: true, width: 220 });
|
|
one.body.append("거리 124.5 m · 기울기 8.2 %");
|
|
const pop = createFloatPanel({ popover: true, width: 160 });
|
|
pop.body.append("팝오버 — 머리 없음");
|
|
return wrap(place(one.root), place(pop.root));
|
|
}
|
|
|
|
function drawer(): HTMLElement {
|
|
const row = div("ui-side-panels-row");
|
|
row.style.cssText = "height:220px;width:100%";
|
|
const main = div("", "본문");
|
|
main.style.padding = "12px";
|
|
const body = (text: string): HTMLElement => div("", text);
|
|
const sp = createSidePanels({
|
|
prefKey: "ui-kit-demo",
|
|
follow: false,
|
|
defaultWidth: 280,
|
|
panels: [
|
|
{ id: "info", title: "상세", body: body("고른 줄의 값") },
|
|
{ id: "set", title: "설정", body: body("보기 설정") },
|
|
],
|
|
});
|
|
row.append(main, sp.root);
|
|
return row;
|
|
}
|
|
|
|
function toolbar(): HTMLElement {
|
|
const box = div("");
|
|
box.style.cssText = "display:flex;flex-direction:column;gap:12px;width:100%";
|
|
const items = () => [
|
|
createIconButton({ icon: "search", title: "찾기", onClick: noop }),
|
|
createIconButton({ icon: "adjustments", title: "보기", onClick: noop }),
|
|
"divider" as const,
|
|
createButton({ label: "더함", variant: "ghost", onClick: noop }),
|
|
"spacer" as const,
|
|
createButton({ label: "저장", onClick: noop }),
|
|
];
|
|
box.append(
|
|
createToolbar({ items: items(), size: 24 }),
|
|
createToolbar({ items: items() }),
|
|
createToolbar({ items: items().slice(0, 3), size: 32, glass: true }),
|
|
);
|
|
return box;
|
|
}
|
|
|
|
/** 열쇠 → 견본(상태별 · null = 그 상태 없음) */
|
|
export const FRAME_DEMOS: Record<string, (state: KitState) => HTMLElement | null> = {
|
|
"work-frame": (s) => (s === "default" ? workFrame() : null),
|
|
"page-frame": (s) => (s === "default" ? pageFrame() : null),
|
|
"public-frame": (s) => (s === "default" ? publicFrame() : null),
|
|
"float-panel": (s) => (s === "default" ? floatPanel() : null),
|
|
drawer: (s) => (s === "default" ? drawer() : null),
|
|
toolbar: (s) => (s === "default" ? toolbar() : null),
|
|
steps,
|
|
"panel-head": (s) => (s === "default" ? panelHead() : null),
|
|
"list-row": listRow,
|
|
};
|