for-humanity 0.4.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +6 -5
- package/dist/browser.js +39 -0
- package/dist/cli.css +350 -262
- package/dist/cli.js +1174 -364
- package/package.json +8 -11
- package/src/component/widget/{shell → cursor-face}/_constant/cursor-face.ts +5 -0
- package/src/component/widget/cursor-face/wg-cursor-face.css +15 -0
- package/src/component/widget/cursor-face/wg-cursor-face.tsx +153 -0
- package/src/component/widget/navigation/_constant/navigation-data-schema.ts +35 -0
- package/src/component/widget/navigation/_constant/navigation.ts +9 -0
- package/src/component/widget/navigation/_function/restore-navigation-scroll.ts +59 -0
- package/src/{content → component/widget/navigation/_function}/to-doc-groups/_to-doc-branches.ts +2 -2
- package/src/{content → component/widget/navigation/_function}/to-doc-groups/_to-doc-group-branches.ts +3 -3
- package/src/{content → component/widget/navigation/_function}/to-doc-groups/to-doc-groups.ts +1 -1
- package/src/component/widget/navigation/_function/to-navigation-branch-id.ts +6 -0
- package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-document.ts +14 -0
- package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-group.ts +14 -0
- package/src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts +25 -0
- package/src/component/widget/navigation/_function/to-navigation-json.ts +8 -0
- package/src/component/widget/{shell → navigation}/_function/to-toc-groups.ts +2 -2
- package/src/{type → component/widget/navigation/_type}/doc-group.ts +1 -1
- package/src/component/widget/navigation/_type/navigation-data.ts +83 -0
- package/src/component/widget/navigation/_type/navigation-focus-snapshot.ts +21 -0
- package/src/component/widget/navigation/_wg-navigation-branch-toggle.css +28 -0
- package/src/component/widget/navigation/_wg-navigation-branch-toggle.tsx +48 -0
- package/src/component/widget/{shell/_wg-shell-nav-list.css → navigation/_wg-navigation-nav-list.css} +24 -16
- package/src/component/widget/navigation/_wg-navigation-nav-list.tsx +106 -0
- package/src/component/widget/{shell/_wg-shell-nav.css → navigation/_wg-navigation-nav.css} +9 -3
- package/src/component/widget/navigation/_wg-navigation-nav.tsx +43 -0
- package/src/component/widget/navigation/_wg-navigation-scroll.tsx +117 -0
- package/src/component/widget/{shell/_wg-shell-toc.css → navigation/_wg-navigation-toc.css} +39 -24
- package/src/component/widget/navigation/_wg-navigation-toc.tsx +155 -0
- package/src/component/widget/navigation/wg-navigation.css +282 -0
- package/src/component/widget/navigation/wg-navigation.tsx +479 -0
- package/src/component/widget/prose/_function/rehype-sections/rehype-sections.ts +1 -1
- package/src/component/widget/shell/wg-shell.css +13 -269
- package/src/component/widget/shell/wg-shell.tsx +16 -97
- package/src/component/widget/shell-controls/wg-shell-controls.css +4 -0
- package/src/component/widget/shell-controls/wg-shell-controls.tsx +170 -0
- package/src/constant/asset.ts +0 -5
- package/src/constant/copy.ts +5 -0
- package/src/entry/browser/browser.tsx +22 -0
- package/src/entry/cli/_constant/crawling.ts +14 -0
- package/src/{app.tsx → entry/cli/_function/create-app.tsx} +6 -6
- package/src/{dev.ts → entry/cli/_function/create-dev-app.ts} +9 -1
- package/src/{content → entry/cli/_function/create-processor}/create-processor.ts +3 -3
- package/src/{content → entry/cli/_function}/read-docs.ts +21 -2
- package/src/{content → entry/cli/_function}/read-home.ts +1 -1
- package/src/entry/cli/_function/to-crawling-app.ts +42 -0
- package/src/{content/read-docs.test.ts → entry/cli/cli.test.ts} +211 -31
- package/src/{cli.ts → entry/cli/cli.ts} +31 -15
- package/src/store/navigation/_constant/tree.ts +6 -0
- package/src/store/navigation/create-navigation-stores.test.ts +92 -0
- package/src/store/navigation/create-navigation-stores.ts +73 -0
- package/src/store/navigation/navigation-state.ts +46 -0
- package/src/store/navigation/navigation-storage.ts +14 -0
- package/src/store/navigation/to-navigation-scroll-state.ts +42 -0
- package/src/store/navigation/to-navigation-tree-state.ts +19 -0
- package/src/store/navigation/use-navigation-tree-store.ts +12 -0
- package/src/style/token.css +3 -2
- package/src/type/doc-content.ts +1 -1
- package/src/type/doc-file-data.ts +1 -1
- package/src/{component/widget/shell/_type → type}/doc-outline.ts +1 -1
- package/src/type/site-assets.ts +2 -2
- package/src/type/site-config.ts +10 -1
- package/dist/client.js +0 -7
- package/src/client.ts +0 -335
- package/src/component/widget/shell/_constant/navigation.ts +0 -9
- package/src/component/widget/shell/_function/bind-mobile-navigation.ts +0 -147
- package/src/component/widget/shell/_function/bind-navigation-overflow.ts +0 -47
- package/src/component/widget/shell/_function/bind-navigation-scroll.ts +0 -84
- package/src/component/widget/shell/_function/restore-navigation-scroll.ts +0 -47
- package/src/component/widget/shell/_wg-shell-nav-list.tsx +0 -87
- package/src/component/widget/shell/_wg-shell-nav.tsx +0 -49
- package/src/component/widget/shell/_wg-shell-toc.tsx +0 -57
- /package/src/{constant → component/widget/navigation/_constant}/locale.ts +0 -0
- /package/src/component/widget/{shell → navigation}/_constant/reading-line.ts +0 -0
- /package/src/{type → component/widget/navigation/_type}/doc-branch.ts +0 -0
- /package/src/component/widget/{shell → navigation}/_type/toc-group.ts +0 -0
- /package/src/{constant → entry/cli/_constant}/cli.ts +0 -0
- /package/src/{content/rehype-assets.ts → entry/cli/_function/create-processor/_rehype-assets.ts} +0 -0
- /package/src/{content/rehype-heading-ids.ts → entry/cli/_function/create-processor/_rehype-heading-ids.ts} +0 -0
- /package/src/{content/remark-report.ts → entry/cli/_function/create-processor/_remark-report.ts} +0 -0
- /package/src/{component/widget/prose/_type → type}/doc-section.ts +0 -0
package/src/component/widget/{shell/_wg-shell-nav-list.css → navigation/_wg-navigation-nav-list.css}
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 재귀 문서 목록 · 단계별 가지와 조상 경로의 1px 트리
|
|
3
3
|
*/
|
|
4
|
-
.
|
|
4
|
+
.wg_navigationNavList__root {
|
|
5
5
|
margin: 0 0 0 var(--app-space-tree-inset);
|
|
6
6
|
padding: 0;
|
|
7
7
|
list-style: none;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
.
|
|
10
|
+
.wg_navigationNavList__branch {
|
|
11
11
|
/* 하위 묶음도 문서와 같은 첫 줄 기준으로 가지 연결 */
|
|
12
12
|
position: relative;
|
|
13
13
|
display: block;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
.
|
|
31
|
+
.wg_navigationNavList__link {
|
|
32
32
|
/* 가로 가지는 링크 첫 줄 기준 · 여러 줄 제목에서도 연결 위치 유지 */
|
|
33
33
|
position: relative;
|
|
34
34
|
display: block;
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
.
|
|
71
|
+
.wg_navigationNavList__link--active {
|
|
72
72
|
color: var(--app-color-link);
|
|
73
73
|
font-weight: var(--app-font-weight-active);
|
|
74
74
|
|
|
@@ -77,13 +77,21 @@
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
.
|
|
80
|
+
.wg_navigationNavList__item {
|
|
81
81
|
/* 하위 목록을 포함해 다음 형제까지 줄기 연결 */
|
|
82
82
|
display: flow-root;
|
|
83
83
|
|
|
84
84
|
/* 세로 줄기는 각 항목 기준 · 마지막 항목의 첫 줄 가운데에서 종료 */
|
|
85
85
|
position: relative;
|
|
86
86
|
|
|
87
|
+
&:has(> .wg_navigationBranchToggle__root) > .wg_navigationNavList__link {
|
|
88
|
+
padding-inline-end: calc(var(--app-size-tree-toggle) + var(--app-space-inline));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&:has(> .wg_navigationBranchToggle__root) > .wg_navigationNavList__branch {
|
|
92
|
+
padding-inline-end: calc(var(--app-size-tree-toggle) + var(--app-space-inline));
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
&::before {
|
|
88
96
|
position: absolute;
|
|
89
97
|
top: 0;
|
|
@@ -119,53 +127,53 @@
|
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
/* 뒤 항목까지의 세로 경로 · 같은 목록의 형제 안에서만 연결 */
|
|
122
|
-
&:where(:has(~ .
|
|
130
|
+
&:where(:has(~ .wg_navigationNavList__item:hover))::before {
|
|
123
131
|
border-left-color: var(--app-color-link);
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
&:where(:has(~ .
|
|
134
|
+
&:where(:has(~ .wg_navigationNavList__item :focus-visible))::before {
|
|
127
135
|
border-left-color: var(--app-color-link);
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
& > .
|
|
138
|
+
& > .wg_navigationNavList__root {
|
|
131
139
|
margin-left: var(--app-space-tree-indent);
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
/* 하위 문서를 탐색할 때 조상 묶음의 가지도 함께 연결 */
|
|
135
|
-
&:where(:hover) > .
|
|
143
|
+
&:where(:hover) > .wg_navigationNavList__branch::before {
|
|
136
144
|
border-top-color: var(--app-color-link);
|
|
137
145
|
}
|
|
138
146
|
|
|
139
|
-
&:where(:has(:focus-visible)) > .
|
|
147
|
+
&:where(:has(:focus-visible)) > .wg_navigationNavList__branch::before {
|
|
140
148
|
border-top-color: var(--app-color-link);
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
/* 자식 문서를 탐색할 때 클릭 가능한 부모 문서의 가지도 연결 */
|
|
144
|
-
&:where(:hover) > .
|
|
152
|
+
&:where(:hover) > .wg_navigationNavList__link::before {
|
|
145
153
|
border-top-color: var(--app-color-link);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
&:where(:has(:focus-visible)) > .
|
|
156
|
+
&:where(:has(:focus-visible)) > .wg_navigationNavList__link::before {
|
|
149
157
|
border-top-color: var(--app-color-link);
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
.
|
|
161
|
+
.wg_navigationNavList__item--trail {
|
|
154
162
|
&::before {
|
|
155
163
|
border-left-color: var(--app-color-link);
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
|
|
159
|
-
.
|
|
167
|
+
.wg_navigationNavList__item--current {
|
|
160
168
|
&::after {
|
|
161
169
|
border-left-color: var(--app-color-link);
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
& > .
|
|
172
|
+
& > .wg_navigationNavList__branch::before {
|
|
165
173
|
border-top-color: var(--app-color-link);
|
|
166
174
|
}
|
|
167
175
|
|
|
168
|
-
& > .
|
|
176
|
+
& > .wg_navigationNavList__link::before {
|
|
169
177
|
border-top-color: var(--app-color-link);
|
|
170
178
|
}
|
|
171
179
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import {Fragment} from "hono/jsx";
|
|
3
|
+
import {toNavigationBranchId} from "@/component/widget/navigation/_function/to-navigation-branch-id";
|
|
4
|
+
import type {NavigationGroup} from "@/component/widget/navigation/_type/navigation-data";
|
|
5
|
+
import {WgNavigationBranchToggle} from "@/component/widget/navigation/_wg-navigation-branch-toggle";
|
|
6
|
+
import "./_wg-navigation-nav-list.css";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 한 단계의 문서 목록과 하위 묶음 · 재귀에서도 같은 탐색 표현 유지
|
|
10
|
+
*/
|
|
11
|
+
export interface WgNavigationNavListProps {
|
|
12
|
+
/**
|
|
13
|
+
* 현재 단계의 문서와 하위 묶음
|
|
14
|
+
*/
|
|
15
|
+
group: NavigationGroup;
|
|
16
|
+
/**
|
|
17
|
+
* 사용자가 접은 가지
|
|
18
|
+
*/
|
|
19
|
+
collapsed: Record<string, boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* 브라우저에서 가지 선택 변경
|
|
22
|
+
*/
|
|
23
|
+
onToggle?: (key: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* 부모 버튼과 연결할 가지 키 · 루트 묶음·문서 모두 같은 규칙 사용
|
|
26
|
+
*/
|
|
27
|
+
branchKey: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const WgNavigationNavList = (props: WgNavigationNavListProps) => {
|
|
31
|
+
const entries = [
|
|
32
|
+
...props.group.docs.map((doc) => {
|
|
33
|
+
const active = doc.active;
|
|
34
|
+
return {kind: "document" as const, key: `doc:${doc.id}`, doc, active, current: doc.current};
|
|
35
|
+
}),
|
|
36
|
+
...props.group.groups.map((group) => ({
|
|
37
|
+
kind: "group" as const,
|
|
38
|
+
key: `group:${JSON.stringify(group.path)}`,
|
|
39
|
+
group,
|
|
40
|
+
current: group.current,
|
|
41
|
+
})),
|
|
42
|
+
];
|
|
43
|
+
const currentIndex = entries.findIndex((entry) => entry.current);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ul
|
|
47
|
+
className={clsx("wg_navigationNavList__root")}
|
|
48
|
+
aria-label={props.group.name}
|
|
49
|
+
id={toNavigationBranchId(props.branchKey)}
|
|
50
|
+
hidden={props.collapsed[props.branchKey] === true}
|
|
51
|
+
>
|
|
52
|
+
{/**
|
|
53
|
+
* 현재 경로 앞의 줄기와 도착 가지 · 문서 링크는 원래 파일 URL 사용
|
|
54
|
+
*/}
|
|
55
|
+
{entries.map((entry, index) => (
|
|
56
|
+
<li
|
|
57
|
+
className={clsx("wg_navigationNavList__item", {
|
|
58
|
+
"wg_navigationNavList__item--trail": index < currentIndex,
|
|
59
|
+
"wg_navigationNavList__item--current": entry.current,
|
|
60
|
+
})}
|
|
61
|
+
key={entry.key}
|
|
62
|
+
>
|
|
63
|
+
{/**
|
|
64
|
+
* 문서 링크 · 부모도 독립 페이지, 현재 한 문서에만 페이지 상태 부여
|
|
65
|
+
*/}
|
|
66
|
+
{entry.kind === "document" && (
|
|
67
|
+
<Fragment>
|
|
68
|
+
<a
|
|
69
|
+
className={clsx("wg_navigationNavList__link", {"wg_navigationNavList__link--active": entry.active})}
|
|
70
|
+
href={`/${entry.doc.id}/`}
|
|
71
|
+
title={entry.doc.label}
|
|
72
|
+
aria-current={entry.active ? "page" : undefined}
|
|
73
|
+
>
|
|
74
|
+
{entry.doc.name}
|
|
75
|
+
</a>
|
|
76
|
+
{/**
|
|
77
|
+
* 하위 문서 · 부모 아래에서도 기존 목록의 들여쓰기와 키보드 이동 유지
|
|
78
|
+
*/}
|
|
79
|
+
{entry.doc.children.length > 0 && (
|
|
80
|
+
<WgNavigationBranchToggle branchKey={entry.key} label={entry.doc.name} collapsed={props.collapsed} onToggle={props.onToggle} />
|
|
81
|
+
)}
|
|
82
|
+
{entry.doc.children.length > 0 && (
|
|
83
|
+
<WgNavigationNavList
|
|
84
|
+
group={{name: entry.doc.name, path: props.group.path, current: entry.current, docs: entry.doc.children, groups: []}}
|
|
85
|
+
collapsed={props.collapsed}
|
|
86
|
+
onToggle={props.onToggle}
|
|
87
|
+
branchKey={entry.key}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
</Fragment>
|
|
91
|
+
)}
|
|
92
|
+
{/**
|
|
93
|
+
* 하위 묶음 · 같은 목록을 재귀로 이어 깊이에 따라 들여쓰기
|
|
94
|
+
*/}
|
|
95
|
+
{entry.kind === "group" && (
|
|
96
|
+
<Fragment>
|
|
97
|
+
<span className={clsx("wg_navigationNavList__branch")}>{entry.group.name}</span>
|
|
98
|
+
<WgNavigationBranchToggle branchKey={entry.key} label={entry.group.name} collapsed={props.collapsed} onToggle={props.onToggle} />
|
|
99
|
+
<WgNavigationNavList group={entry.group} collapsed={props.collapsed} onToggle={props.onToggle} branchKey={entry.key} />
|
|
100
|
+
</Fragment>
|
|
101
|
+
)}
|
|
102
|
+
</li>
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 읽는 순서의 문서 목록 · 작은 회색 묶음과 링크색 문서
|
|
3
3
|
*/
|
|
4
|
-
.
|
|
4
|
+
.wg_navigationNav__root {
|
|
5
5
|
font-size: var(--app-font-size-dense);
|
|
6
6
|
line-height: var(--app-line-height-tight);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.
|
|
9
|
+
.wg_navigationNav__docs {
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-direction: column;
|
|
12
12
|
gap: var(--app-space-stack);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
.
|
|
15
|
+
.wg_navigationNav__group {
|
|
16
|
+
padding-inline-end: calc(var(--app-size-tree-toggle) + var(--app-space-inline));
|
|
16
17
|
margin: 0 0 4px;
|
|
17
18
|
color: var(--app-color-text-muted);
|
|
18
19
|
font-size: var(--app-font-size-group);
|
|
19
20
|
line-height: calc(var(--app-font-size-dense) * var(--app-line-height-tight));
|
|
20
21
|
text-box: trim-start cap alphabetic;
|
|
21
22
|
}
|
|
23
|
+
|
|
24
|
+
.wg_navigationNav__docGroup {
|
|
25
|
+
/* 묶음 접기 버튼의 첫 행 기준 */
|
|
26
|
+
position: relative;
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type {NavigationGroup} from "@/component/widget/navigation/_type/navigation-data";
|
|
3
|
+
import {WgNavigationBranchToggle} from "@/component/widget/navigation/_wg-navigation-branch-toggle";
|
|
4
|
+
import {WgNavigationNavList} from "@/component/widget/navigation/_wg-navigation-nav-list";
|
|
5
|
+
import {copy_nav_aria_label} from "@/constant/copy";
|
|
6
|
+
import "./_wg-navigation-nav.css";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 서버·브라우저가 공유하는 문서 트리
|
|
10
|
+
*/
|
|
11
|
+
export interface WgNavigationNavProps {
|
|
12
|
+
/**
|
|
13
|
+
* 본문을 제외한 읽는 순서의 문서 묶음
|
|
14
|
+
*/
|
|
15
|
+
groups: NavigationGroup[];
|
|
16
|
+
/**
|
|
17
|
+
* 사용자가 접은 가지
|
|
18
|
+
*/
|
|
19
|
+
collapsed: Record<string, boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* 브라우저에서 가지 선택 변경 · 서버 출력에서는 생략
|
|
22
|
+
*/
|
|
23
|
+
onToggle?: (key: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const WgNavigationNav = (props: WgNavigationNavProps) => {
|
|
27
|
+
return (
|
|
28
|
+
<nav className={clsx("wg_navigationNav__root")} aria-label={copy_nav_aria_label}>
|
|
29
|
+
{/**
|
|
30
|
+
* 문서 이동 · 클릭하지 않는 묶음과 독립 문서
|
|
31
|
+
*/}
|
|
32
|
+
<div className={clsx("wg_navigationNav__docs")}>
|
|
33
|
+
{props.groups.map((group) => (
|
|
34
|
+
<section className={clsx("wg_navigationNav__docGroup")} key={JSON.stringify(group.path)} aria-label={group.name}>
|
|
35
|
+
<div className={clsx("wg_navigationNav__group")}>{group.name}</div>
|
|
36
|
+
<WgNavigationBranchToggle branchKey={`group:${JSON.stringify(group.path)}`} label={group.name} collapsed={props.collapsed} onToggle={props.onToggle} />
|
|
37
|
+
<WgNavigationNavList group={group} branchKey={`group:${JSON.stringify(group.path)}`} collapsed={props.collapsed} onToggle={props.onToggle} />
|
|
38
|
+
</section>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
</nav>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import {type Child, type DOMAttributes, type RefObject, useLayoutEffect, useRef, useState} from "hono/jsx";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 탐색 좌표를 제어하는 부모와 넘침 표시를 소유하는 스크롤 영역의 경계
|
|
6
|
+
*/
|
|
7
|
+
export interface WgNavigationScrollProps {
|
|
8
|
+
/**
|
|
9
|
+
* 배치별 좌표 복원과 저장에 필요한 실제 스크롤 요소
|
|
10
|
+
*/
|
|
11
|
+
portRef: RefObject<HTMLDivElement | null>;
|
|
12
|
+
/**
|
|
13
|
+
* 문서 목록과 현재 페이지 목차의 독립된 스크롤 수명
|
|
14
|
+
*/
|
|
15
|
+
kind: "documents" | "outline";
|
|
16
|
+
/**
|
|
17
|
+
* JavaScript가 연결된 뒤에만 손잡이를 넘침 표시로 대체
|
|
18
|
+
*/
|
|
19
|
+
ready: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 탐색 링크를 포함한 영역 내용
|
|
22
|
+
*/
|
|
23
|
+
children: Child;
|
|
24
|
+
/**
|
|
25
|
+
* 이동 직전 좌표 보존과 모바일 모달 닫기
|
|
26
|
+
*/
|
|
27
|
+
onClick?: DOMAttributes["onClick"];
|
|
28
|
+
/**
|
|
29
|
+
* 실제 스크롤 좌표가 바뀔 때 부모 저장소에 반영
|
|
30
|
+
*/
|
|
31
|
+
onScroll?: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const WgNavigationScroll = (props: WgNavigationScrollProps) => {
|
|
35
|
+
const [overflow, setOverflow] = useState({above: false, below: false});
|
|
36
|
+
const frameRef = useRef(0);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 소수 픽셀 반올림을 제외한 실제 남은 내용만 위·아래 흐림으로 알림
|
|
40
|
+
*/
|
|
41
|
+
const updateOverflow = () => {
|
|
42
|
+
frameRef.current = 0;
|
|
43
|
+
if (props.portRef.current === null) return;
|
|
44
|
+
const above = props.portRef.current.scrollTop > 1;
|
|
45
|
+
const below = props.portRef.current.scrollHeight - props.portRef.current.clientHeight - props.portRef.current.scrollTop > 1;
|
|
46
|
+
setOverflow((state) => (state.above === above && state.below === below ? state : {above, below}));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 연속 스크롤과 관찰자 알림은 프레임당 한 번 측정
|
|
51
|
+
*/
|
|
52
|
+
const scheduleOverflow = () => {
|
|
53
|
+
if (frameRef.current === 0) frameRef.current = requestAnimationFrame(updateOverflow);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 네이티브 좌표는 바로 저장하고 시각적 넘침만 프레임에 맞춰 갱신
|
|
58
|
+
*/
|
|
59
|
+
const handlePortScroll: DOMAttributes["onScroll"] = (_event) => {
|
|
60
|
+
props.onScroll?.();
|
|
61
|
+
scheduleOverflow();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 영역과 내용의 크기를 함께 추적 · Hono 최초 mount의 분리된 fragment 측정은 연결 직후 실행
|
|
66
|
+
*/
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
if (!props.ready || props.portRef.current === null) return;
|
|
69
|
+
const observer = new ResizeObserver(scheduleOverflow);
|
|
70
|
+
observer.observe(props.portRef.current);
|
|
71
|
+
if (props.portRef.current.firstElementChild !== null) observer.observe(props.portRef.current.firstElementChild);
|
|
72
|
+
let active = true;
|
|
73
|
+
addEventListener("pageshow", scheduleOverflow);
|
|
74
|
+
if (props.portRef.current.isConnected) {
|
|
75
|
+
updateOverflow();
|
|
76
|
+
} else {
|
|
77
|
+
// Hono는 최초 layout effect를 fragment에서 실행하므로 같은 script의 paint 전 microtask에서 측정한다
|
|
78
|
+
queueMicrotask(() => {
|
|
79
|
+
if (active) updateOverflow();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return () => {
|
|
83
|
+
active = false;
|
|
84
|
+
observer.disconnect();
|
|
85
|
+
removeEventListener("pageshow", scheduleOverflow);
|
|
86
|
+
cancelAnimationFrame(frameRef.current);
|
|
87
|
+
frameRef.current = 0;
|
|
88
|
+
};
|
|
89
|
+
}, [props.portRef, props.ready]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 접힘 선택의 렌더가 바뀐 직후 측정 · 좌표 복원은 부모의 연결 후 처리보다 앞서 저장하지 않음
|
|
93
|
+
*/
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
if (props.ready && props.portRef.current?.isConnected) updateOverflow();
|
|
96
|
+
}, [props.children, props.ready]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
// biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: 부모의 링크 이벤트 위임이며 자식 a·button의 네이티브 키보드 동작을 그대로 소비한다
|
|
100
|
+
<div
|
|
101
|
+
ref={props.ready ? props.portRef : undefined}
|
|
102
|
+
className={clsx(
|
|
103
|
+
"wg_navigation__scroll",
|
|
104
|
+
props.kind === "documents" && "wg_navigation__documents",
|
|
105
|
+
props.kind === "outline" && "wg_navigation__headings",
|
|
106
|
+
props.ready && "wg_navigation__scroll--ready",
|
|
107
|
+
props.ready && overflow.above && "wg_navigation__scroll--above",
|
|
108
|
+
props.ready && overflow.below && "wg_navigation__scroll--below",
|
|
109
|
+
)}
|
|
110
|
+
data-navigation-scroll={props.kind}
|
|
111
|
+
onClick={props.onClick}
|
|
112
|
+
onScroll={handlePortScroll}
|
|
113
|
+
>
|
|
114
|
+
{props.children}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* 와이드 화면의 오른쪽 목차 · 회색 제목·묶음과 본문 글색의 이동 대상 구분
|
|
3
3
|
*/
|
|
4
|
-
.
|
|
4
|
+
.wg_navigationToc__root {
|
|
5
5
|
font-size: var(--app-font-size-dense);
|
|
6
6
|
line-height: var(--app-line-height-tight);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.
|
|
9
|
+
.wg_navigationToc__label {
|
|
10
10
|
margin-bottom: var(--app-space-inline);
|
|
11
11
|
color: var(--app-color-text-muted);
|
|
12
12
|
font-size: var(--app-font-size-label);
|
|
@@ -14,20 +14,31 @@
|
|
|
14
14
|
letter-spacing: var(--app-letter-spacing-label);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
.
|
|
18
|
-
margin:
|
|
17
|
+
.wg_navigationToc__group {
|
|
18
|
+
margin: 0 0 4px;
|
|
19
|
+
padding-inline-end: calc(var(--app-size-tree-toggle) + var(--app-space-inline));
|
|
19
20
|
color: var(--app-color-text-muted);
|
|
20
21
|
font-size: var(--app-font-size-group);
|
|
21
22
|
line-height: calc(var(--app-font-size-dense) * var(--app-line-height-tight));
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
.
|
|
25
|
+
.wg_navigationToc__groupSection {
|
|
26
|
+
/* 가름 제목과 그 하위 목록을 하나의 접힘 대상으로 연결 */
|
|
27
|
+
position: relative;
|
|
28
|
+
display: flow-root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.wg_navigationToc__groupSection--named {
|
|
32
|
+
margin-top: var(--app-space-stack);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.wg_navigationToc__list {
|
|
25
36
|
margin: 0 0 0 var(--app-space-tree-inset);
|
|
26
37
|
padding: 0;
|
|
27
38
|
list-style: none;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
|
-
.
|
|
41
|
+
.wg_navigationToc__link {
|
|
31
42
|
/* 가로 가지는 링크 첫 줄 기준 · 여러 줄 제목에서도 연결 위치 유지 */
|
|
32
43
|
position: relative;
|
|
33
44
|
display: block;
|
|
@@ -68,7 +79,7 @@
|
|
|
68
79
|
}
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
.
|
|
82
|
+
.wg_navigationToc__link--active {
|
|
72
83
|
color: var(--app-color-text-strong);
|
|
73
84
|
font-weight: var(--app-font-weight-active);
|
|
74
85
|
|
|
@@ -77,13 +88,17 @@
|
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
.
|
|
91
|
+
.wg_navigationToc__item {
|
|
81
92
|
/* 하위 목록의 margin을 포함해 다음 항목까지 줄기 연결 */
|
|
82
93
|
display: flow-root;
|
|
83
94
|
|
|
84
95
|
/* 세로 줄기는 항목 전체 기준 · 중간 항목의 소제목 옆에서도 연결 */
|
|
85
96
|
position: relative;
|
|
86
97
|
|
|
98
|
+
&:has(> .wg_navigationBranchToggle__root) > .wg_navigationToc__link {
|
|
99
|
+
padding-inline-end: calc(var(--app-size-tree-toggle) + var(--app-space-inline));
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
&::before {
|
|
88
103
|
position: absolute;
|
|
89
104
|
top: 0;
|
|
@@ -119,48 +134,48 @@
|
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
/* 뒤 항목까지의 세로 경로 · 같은 목록의 형제 안에서만 연결 */
|
|
122
|
-
&:where(:has(~ .
|
|
137
|
+
&:where(:has(~ .wg_navigationToc__item:hover))::before {
|
|
123
138
|
border-left-color: var(--app-color-text-strong);
|
|
124
139
|
}
|
|
125
140
|
|
|
126
|
-
&:where(:has(~ .
|
|
141
|
+
&:where(:has(~ .wg_navigationToc__item :focus-visible))::before {
|
|
127
142
|
border-left-color: var(--app-color-text-strong);
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/* 하위 제목을 탐색할 때 상위 절의 가지도 연결 */
|
|
131
|
-
&:where(:hover) > .
|
|
146
|
+
&:where(:hover) > .wg_navigationToc__link::before {
|
|
132
147
|
border-top-color: var(--app-color-text-strong);
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
&:where(:has(:focus-visible)) > .
|
|
150
|
+
&:where(:has(:focus-visible)) > .wg_navigationToc__link::before {
|
|
136
151
|
border-top-color: var(--app-color-text-strong);
|
|
137
152
|
}
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
.
|
|
155
|
+
.wg_navigationToc__item--trail {
|
|
141
156
|
&::before {
|
|
142
157
|
border-left-color: var(--app-color-text-strong);
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
|
|
146
|
-
.
|
|
161
|
+
.wg_navigationToc__item--current {
|
|
147
162
|
&::after {
|
|
148
163
|
border-left-color: var(--app-color-text-strong);
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
/* 현재 소제목의 상위 절은 글 강조 없이 경로의 가로 가지 연결 */
|
|
152
|
-
& > .
|
|
167
|
+
& > .wg_navigationToc__link::before {
|
|
153
168
|
border-top-color: var(--app-color-text-strong);
|
|
154
169
|
}
|
|
155
170
|
}
|
|
156
171
|
|
|
157
|
-
.
|
|
172
|
+
.wg_navigationToc__sub {
|
|
158
173
|
margin: var(--app-space-tree-row) 0 4px var(--app-space-tree-indent);
|
|
159
174
|
padding: 0;
|
|
160
175
|
list-style: none;
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
.
|
|
178
|
+
.wg_navigationToc__subItem {
|
|
164
179
|
/* 하위 줄기는 소제목 항목 기준 · 마지막 소제목 첫 줄에서 종료 */
|
|
165
180
|
position: relative;
|
|
166
181
|
|
|
@@ -199,28 +214,28 @@
|
|
|
199
214
|
}
|
|
200
215
|
|
|
201
216
|
/* 뒤 항목까지의 세로 경로 · 같은 목록의 형제 안에서만 연결 */
|
|
202
|
-
&:where(:has(~ .
|
|
217
|
+
&:where(:has(~ .wg_navigationToc__subItem:hover))::before {
|
|
203
218
|
border-left-color: var(--app-color-text-strong);
|
|
204
219
|
}
|
|
205
220
|
|
|
206
|
-
&:where(:has(~ .
|
|
221
|
+
&:where(:has(~ .wg_navigationToc__subItem :focus-visible))::before {
|
|
207
222
|
border-left-color: var(--app-color-text-strong);
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
|
|
211
|
-
.
|
|
226
|
+
.wg_navigationToc__subItem--trail {
|
|
212
227
|
&::before {
|
|
213
228
|
border-left-color: var(--app-color-text-strong);
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
|
|
217
|
-
.
|
|
232
|
+
.wg_navigationToc__subItem--current {
|
|
218
233
|
&::after {
|
|
219
234
|
border-left-color: var(--app-color-text-strong);
|
|
220
235
|
}
|
|
221
236
|
}
|
|
222
237
|
|
|
223
|
-
.
|
|
238
|
+
.wg_navigationToc__subLink {
|
|
224
239
|
/* 하위 가지도 링크 첫 줄 기준 · 줄바꿈된 제목 옆으로 선이 늘어나지 않음 */
|
|
225
240
|
position: relative;
|
|
226
241
|
display: block;
|
|
@@ -261,7 +276,7 @@
|
|
|
261
276
|
}
|
|
262
277
|
}
|
|
263
278
|
|
|
264
|
-
.
|
|
279
|
+
.wg_navigationToc__subLink--active {
|
|
265
280
|
color: var(--app-color-text-strong);
|
|
266
281
|
font-weight: var(--app-font-weight-active);
|
|
267
282
|
|