Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-09-30 17:39:54 +09:00
15 changed files with 342 additions and 239 deletions
+1
View File
@@ -103,6 +103,7 @@ export const ui_locales_m2 = {
M02_RootShared: ["회사 공용", "Company shared"],
M02_RootReadOnly: ["읽기", "read-only"],
M02_RootNone: ["볼 수 있는 층이 없음", "Nothing to show"],
M02_PeoplePick: ["사람 고르기", "Pick a person"],
M02_TreeSearch: ["항목 찾기", "Find item"],
M02_TreeEmptyEdit: [
"항목이 없음 — 맨 위 줄을 오른쪽 클릭해 폴더 · 항목을 더함",
+35 -1
View File
@@ -1,10 +1,44 @@
/* 공용 나무 보기 — 줄 모양은 공용 ui-sidebar-row(ui_template_overlay.css) */
/* 공용 나무 보기 — 줄 모양은 공용 ui-sidebar-row(ui_template_overlay.css) ·
* 탐색기 모양(들여쓰기 안내선 · 접기 화살 · 폴더/항목 아이콘 · 폴더 굵게 · 고른 줄 배경) */
.ui-tree__kids {
position: relative;
display: flex;
flex-direction: column;
gap: 2px;
}
/* 들여쓰기 안내선 — 부모 줄 화살 한가운데에서 자식 끝까지(줄 들임 = spacing-8 + 깊이 × spacing-16) */
.ui-tree__kids::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: calc(var(--spacing-8) + var(--tree-depth, 0) * var(--spacing-16) + var(--spacing-12) / 2);
border-left: 1px solid var(--color-border);
pointer-events: none;
}
.ui-tree__label {
display: inline-flex;
align-items: center;
min-width: 0;
}
.ui-tree__icon {
display: inline-flex;
flex: none;
margin-right: var(--spacing-4);
color: var(--color-text-secondary);
}
.ui-tree__row.is-branch .ui-tree__label {
font-weight: 600;
}
.ui-tree__row.is-active .ui-tree__icon {
color: inherit;
}
.ui-tree__branch {
display: flex;
flex-direction: column;
+18 -2
View File
@@ -23,8 +23,17 @@ export interface TreeNode {
tip?: string;
/** 오른쪽 클릭 메뉴 항목 — 부를 때마다 새로 받음 · 빈 목록 · 없음 = 메뉴 없음(기본 줄) */
menu?: () => TreeMenuItem[];
/** 자식이 없어도 폴더(빈 폴더) — 폴더 아이콘 · 굵은 글(기본 줄) */
folder?: boolean;
}
/** 기본 줄 아이콘 — 폴더 · 항목(글꼴색을 따름) */
const ICON = {
folder:
'<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M1.5 3.5h4l1.5 1.5h7.5v8h-13z" fill="none" stroke="currentColor" stroke-linejoin="round"/></svg>',
leaf: '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M3.5 1.5h6l3 3v10h-9z M9.5 1.5v3h3" fill="none" stroke="currentColor" stroke-linejoin="round"/></svg>',
};
export interface TreeMenuItem {
label: string;
run: () => void;
@@ -92,6 +101,7 @@ export function renderTree(nodes: TreeNode[], make: MakeRow, depth = 0): HTMLEle
className: "ui-tree__kids",
children: renderTree(node.children, make, depth + 1),
});
kids.style.setProperty("--tree-depth", String(depth)); // 들여쓰기 안내선 자리(CSS)
kids.hidden = !node.open;
const row = make(node, kids.hidden ? "▸" : "▾", depth, () => {
kids.hidden = !kids.hidden;
@@ -103,20 +113,26 @@ export function renderTree(nodes: TreeNode[], make: MakeRow, depth = 0): HTMLEle
});
}
/** 기본 줄 — 화살표 · 글 · 줄 수 · 깊이 들임. 잎 고름 표시(`is-active`)는 부르는 쪽이 켬. */
/** 기본 줄 — 화살표 · 폴더/항목 아이콘 · 글(폴더는 굵게) · 줄 수 · 깊이 들임.
* 잎 고름 표시(`is-active`)는 부르는 쪽이 켬. */
export function createTreeRow(
node: TreeNode,
caret: string,
depth: number,
onClick: () => void,
): HTMLButtonElement {
const branch = !!node.children?.length || !!node.folder;
const icon = el("span", { className: "ui-tree__icon" });
icon.innerHTML = branch ? ICON.folder : ICON.leaf; // 고정 SVG 글만
const button = el("button", {
className: "ui-tree__row ui-sidebar-row",
className: `ui-tree__row ui-sidebar-row${branch ? " is-branch" : ""}`,
attrs: { type: "button" },
children: [
el("span", {
className: "ui-tree__label",
children: [
el("span", { className: "ui-tree__caret", text: caret }),
icon,
el("span", { text: node.label }),
],
}),