moru-krds-react 0.1.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.
Files changed (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/accordion/index.d.ts +63 -0
  4. package/dist/accordion/index.d.ts.map +1 -0
  5. package/dist/accordion/index.js +145 -0
  6. package/dist/accordion/index.js.map +1 -0
  7. package/dist/alert/index.d.ts +44 -0
  8. package/dist/alert/index.d.ts.map +1 -0
  9. package/dist/alert/index.js +66 -0
  10. package/dist/alert/index.js.map +1 -0
  11. package/dist/avatar/index.d.ts +102 -0
  12. package/dist/avatar/index.d.ts.map +1 -0
  13. package/dist/avatar/index.js +146 -0
  14. package/dist/avatar/index.js.map +1 -0
  15. package/dist/badge/index.d.ts +49 -0
  16. package/dist/badge/index.d.ts.map +1 -0
  17. package/dist/badge/index.js +41 -0
  18. package/dist/badge/index.js.map +1 -0
  19. package/dist/breadcrumb/index.d.ts +43 -0
  20. package/dist/breadcrumb/index.d.ts.map +1 -0
  21. package/dist/breadcrumb/index.js +43 -0
  22. package/dist/breadcrumb/index.js.map +1 -0
  23. package/dist/button/index.d.ts +78 -0
  24. package/dist/button/index.d.ts.map +1 -0
  25. package/dist/button/index.js +83 -0
  26. package/dist/button/index.js.map +1 -0
  27. package/dist/checkbox/index.d.ts +97 -0
  28. package/dist/checkbox/index.d.ts.map +1 -0
  29. package/dist/checkbox/index.js +110 -0
  30. package/dist/checkbox/index.js.map +1 -0
  31. package/dist/combobox/index.d.ts +135 -0
  32. package/dist/combobox/index.d.ts.map +1 -0
  33. package/dist/combobox/index.js +416 -0
  34. package/dist/combobox/index.js.map +1 -0
  35. package/dist/context-menu/index.d.ts +94 -0
  36. package/dist/context-menu/index.d.ts.map +1 -0
  37. package/dist/context-menu/index.js +399 -0
  38. package/dist/context-menu/index.js.map +1 -0
  39. package/dist/critical-alert/index.d.ts +39 -0
  40. package/dist/critical-alert/index.d.ts.map +1 -0
  41. package/dist/critical-alert/index.js +50 -0
  42. package/dist/critical-alert/index.js.map +1 -0
  43. package/dist/data-table/index.d.ts +94 -0
  44. package/dist/data-table/index.d.ts.map +1 -0
  45. package/dist/data-table/index.js +128 -0
  46. package/dist/data-table/index.js.map +1 -0
  47. package/dist/disclosure/index.d.ts +35 -0
  48. package/dist/disclosure/index.d.ts.map +1 -0
  49. package/dist/disclosure/index.js +54 -0
  50. package/dist/disclosure/index.js.map +1 -0
  51. package/dist/drawer/index.d.ts +106 -0
  52. package/dist/drawer/index.d.ts.map +1 -0
  53. package/dist/drawer/index.js +352 -0
  54. package/dist/drawer/index.js.map +1 -0
  55. package/dist/empty-state/index.d.ts +86 -0
  56. package/dist/empty-state/index.d.ts.map +1 -0
  57. package/dist/empty-state/index.js +138 -0
  58. package/dist/empty-state/index.js.map +1 -0
  59. package/dist/env.d.js +1 -0
  60. package/dist/env.d.js.map +1 -0
  61. package/dist/field/index.d.ts +149 -0
  62. package/dist/field/index.d.ts.map +1 -0
  63. package/dist/field/index.js +121 -0
  64. package/dist/field/index.js.map +1 -0
  65. package/dist/file-upload/index.d.ts +140 -0
  66. package/dist/file-upload/index.d.ts.map +1 -0
  67. package/dist/file-upload/index.js +249 -0
  68. package/dist/file-upload/index.js.map +1 -0
  69. package/dist/footer/index.d.ts +155 -0
  70. package/dist/footer/index.d.ts.map +1 -0
  71. package/dist/footer/index.js +186 -0
  72. package/dist/footer/index.js.map +1 -0
  73. package/dist/header/index.d.ts +170 -0
  74. package/dist/header/index.d.ts.map +1 -0
  75. package/dist/header/index.js +258 -0
  76. package/dist/header/index.js.map +1 -0
  77. package/dist/identifier/index.d.ts +38 -0
  78. package/dist/identifier/index.d.ts.map +1 -0
  79. package/dist/identifier/index.js +55 -0
  80. package/dist/identifier/index.js.map +1 -0
  81. package/dist/in-page-navigation/index.d.ts +71 -0
  82. package/dist/in-page-navigation/index.d.ts.map +1 -0
  83. package/dist/in-page-navigation/index.js +137 -0
  84. package/dist/in-page-navigation/index.js.map +1 -0
  85. package/dist/index.d.ts +57 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +47 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/input-group/index.d.ts +66 -0
  90. package/dist/input-group/index.d.ts.map +1 -0
  91. package/dist/input-group/index.js +85 -0
  92. package/dist/input-group/index.js.map +1 -0
  93. package/dist/lib/krds.d.ts +45 -0
  94. package/dist/lib/krds.d.ts.map +1 -0
  95. package/dist/lib/krds.js +11 -0
  96. package/dist/lib/krds.js.map +1 -0
  97. package/dist/lib/link.d.ts +34 -0
  98. package/dist/lib/link.d.ts.map +1 -0
  99. package/dist/lib/link.js +8 -0
  100. package/dist/lib/link.js.map +1 -0
  101. package/dist/main-menu/index.d.ts +123 -0
  102. package/dist/main-menu/index.d.ts.map +1 -0
  103. package/dist/main-menu/index.js +613 -0
  104. package/dist/main-menu/index.js.map +1 -0
  105. package/dist/masthead/index.d.ts +25 -0
  106. package/dist/masthead/index.d.ts.map +1 -0
  107. package/dist/masthead/index.js +21 -0
  108. package/dist/masthead/index.js.map +1 -0
  109. package/dist/modal/index.d.ts +105 -0
  110. package/dist/modal/index.d.ts.map +1 -0
  111. package/dist/modal/index.js +285 -0
  112. package/dist/modal/index.js.map +1 -0
  113. package/dist/pagination/index.d.ts +58 -0
  114. package/dist/pagination/index.d.ts.map +1 -0
  115. package/dist/pagination/index.js +82 -0
  116. package/dist/pagination/index.js.map +1 -0
  117. package/dist/progress/index.d.ts +58 -0
  118. package/dist/progress/index.d.ts.map +1 -0
  119. package/dist/progress/index.js +125 -0
  120. package/dist/progress/index.js.map +1 -0
  121. package/dist/radio/index.d.ts +111 -0
  122. package/dist/radio/index.d.ts.map +1 -0
  123. package/dist/radio/index.js +136 -0
  124. package/dist/radio/index.js.map +1 -0
  125. package/dist/resize/index.d.ts +118 -0
  126. package/dist/resize/index.d.ts.map +1 -0
  127. package/dist/resize/index.js +342 -0
  128. package/dist/resize/index.js.map +1 -0
  129. package/dist/scroll-area/index.d.ts +40 -0
  130. package/dist/scroll-area/index.d.ts.map +1 -0
  131. package/dist/scroll-area/index.js +52 -0
  132. package/dist/scroll-area/index.js.map +1 -0
  133. package/dist/select/index.d.ts +67 -0
  134. package/dist/select/index.d.ts.map +1 -0
  135. package/dist/select/index.js +61 -0
  136. package/dist/select/index.js.map +1 -0
  137. package/dist/separator/index.d.ts +47 -0
  138. package/dist/separator/index.d.ts.map +1 -0
  139. package/dist/separator/index.js +61 -0
  140. package/dist/separator/index.js.map +1 -0
  141. package/dist/side-navigation/index.d.ts +78 -0
  142. package/dist/side-navigation/index.d.ts.map +1 -0
  143. package/dist/side-navigation/index.js +188 -0
  144. package/dist/side-navigation/index.js.map +1 -0
  145. package/dist/skeleton/index.d.ts +76 -0
  146. package/dist/skeleton/index.d.ts.map +1 -0
  147. package/dist/skeleton/index.js +78 -0
  148. package/dist/skeleton/index.js.map +1 -0
  149. package/dist/skip-link/index.d.ts +46 -0
  150. package/dist/skip-link/index.d.ts.map +1 -0
  151. package/dist/skip-link/index.js +37 -0
  152. package/dist/skip-link/index.js.map +1 -0
  153. package/dist/spinner/index.d.ts +58 -0
  154. package/dist/spinner/index.d.ts.map +1 -0
  155. package/dist/spinner/index.js +36 -0
  156. package/dist/spinner/index.js.map +1 -0
  157. package/dist/step-indicator/index.d.ts +41 -0
  158. package/dist/step-indicator/index.d.ts.map +1 -0
  159. package/dist/step-indicator/index.js +63 -0
  160. package/dist/step-indicator/index.js.map +1 -0
  161. package/dist/structured-list/index.d.ts +150 -0
  162. package/dist/structured-list/index.d.ts.map +1 -0
  163. package/dist/structured-list/index.js +194 -0
  164. package/dist/structured-list/index.js.map +1 -0
  165. package/dist/tab/index.d.ts +108 -0
  166. package/dist/tab/index.d.ts.map +1 -0
  167. package/dist/tab/index.js +198 -0
  168. package/dist/tab/index.js.map +1 -0
  169. package/dist/table/index.d.ts +75 -0
  170. package/dist/table/index.d.ts.map +1 -0
  171. package/dist/table/index.js +91 -0
  172. package/dist/table/index.js.map +1 -0
  173. package/dist/tag/index.d.ts +71 -0
  174. package/dist/tag/index.d.ts.map +1 -0
  175. package/dist/tag/index.js +45 -0
  176. package/dist/tag/index.js.map +1 -0
  177. package/dist/text-input/index.d.ts +69 -0
  178. package/dist/text-input/index.d.ts.map +1 -0
  179. package/dist/text-input/index.js +70 -0
  180. package/dist/text-input/index.js.map +1 -0
  181. package/dist/text-list/index.d.ts +71 -0
  182. package/dist/text-list/index.d.ts.map +1 -0
  183. package/dist/text-list/index.js +79 -0
  184. package/dist/text-list/index.js.map +1 -0
  185. package/dist/textarea/index.d.ts +29 -0
  186. package/dist/textarea/index.d.ts.map +1 -0
  187. package/dist/textarea/index.js +69 -0
  188. package/dist/textarea/index.js.map +1 -0
  189. package/dist/toast/index.d.ts +76 -0
  190. package/dist/toast/index.d.ts.map +1 -0
  191. package/dist/toast/index.js +183 -0
  192. package/dist/toast/index.js.map +1 -0
  193. package/dist/tooltip/index.d.ts +84 -0
  194. package/dist/tooltip/index.d.ts.map +1 -0
  195. package/dist/tooltip/index.js +329 -0
  196. package/dist/tooltip/index.js.map +1 -0
  197. package/package.json +57 -0
  198. package/src/accordion/index.tsx +251 -0
  199. package/src/alert/index.tsx +111 -0
  200. package/src/avatar/index.tsx +272 -0
  201. package/src/badge/index.tsx +91 -0
  202. package/src/breadcrumb/index.tsx +125 -0
  203. package/src/button/index.tsx +190 -0
  204. package/src/checkbox/index.tsx +247 -0
  205. package/src/combobox/index.tsx +700 -0
  206. package/src/context-menu/index.tsx +623 -0
  207. package/src/critical-alert/index.tsx +102 -0
  208. package/src/data-table/index.tsx +289 -0
  209. package/src/disclosure/index.tsx +87 -0
  210. package/src/drawer/index.tsx +516 -0
  211. package/src/empty-state/index.tsx +256 -0
  212. package/src/env.d.ts +10 -0
  213. package/src/field/index.tsx +307 -0
  214. package/src/file-upload/index.tsx +492 -0
  215. package/src/footer/index.tsx +393 -0
  216. package/src/header/index.tsx +498 -0
  217. package/src/identifier/index.tsx +96 -0
  218. package/src/in-page-navigation/index.tsx +258 -0
  219. package/src/index.ts +72 -0
  220. package/src/input-group/index.tsx +197 -0
  221. package/src/lib/krds.ts +75 -0
  222. package/src/lib/link.tsx +35 -0
  223. package/src/main-menu/index.tsx +912 -0
  224. package/src/masthead/index.tsx +50 -0
  225. package/src/modal/index.tsx +457 -0
  226. package/src/pagination/index.tsx +198 -0
  227. package/src/progress/index.tsx +198 -0
  228. package/src/radio/index.tsx +283 -0
  229. package/src/resize/index.tsx +541 -0
  230. package/src/scroll-area/index.tsx +96 -0
  231. package/src/select/index.tsx +136 -0
  232. package/src/separator/index.tsx +107 -0
  233. package/src/side-navigation/index.tsx +336 -0
  234. package/src/skeleton/index.tsx +172 -0
  235. package/src/skip-link/index.tsx +89 -0
  236. package/src/spinner/index.tsx +98 -0
  237. package/src/step-indicator/index.tsx +122 -0
  238. package/src/structured-list/index.tsx +395 -0
  239. package/src/tab/index.tsx +348 -0
  240. package/src/table/index.tsx +186 -0
  241. package/src/tag/index.tsx +127 -0
  242. package/src/text-input/index.tsx +156 -0
  243. package/src/text-list/index.tsx +177 -0
  244. package/src/textarea/index.tsx +113 -0
  245. package/src/toast/index.tsx +311 -0
  246. package/src/tooltip/index.tsx +496 -0
  247. package/styles/krds-tailwind.css +335 -0
@@ -0,0 +1,613 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { UiLink } from "../lib/link.js";
4
+ import {
5
+ useCallback,
6
+ useEffect,
7
+ useId,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import { cx } from "../lib/krds.js";
13
+ const NEW_WINDOW_TITLE = "\uC0C8 \uCC3D \uC5F4\uB9BC";
14
+ function isExternalHref(href) {
15
+ return !href.startsWith("/");
16
+ }
17
+ function idBase(raw) {
18
+ return raw.replace(/[^a-zA-Z0-9_-]/g, "") || "gnb";
19
+ }
20
+ function MenuAction({ href, newWindow, children, ...rest }) {
21
+ if (!href) {
22
+ return /* @__PURE__ */ jsx("button", { type: "button", ...rest, children });
23
+ }
24
+ if (newWindow || isExternalHref(href)) {
25
+ return /* @__PURE__ */ jsx(
26
+ "a",
27
+ {
28
+ href,
29
+ target: newWindow ? "_blank" : void 0,
30
+ rel: newWindow ? "noopener noreferrer" : void 0,
31
+ title: newWindow ? NEW_WINDOW_TITLE : void 0,
32
+ ...rest,
33
+ children
34
+ }
35
+ );
36
+ }
37
+ return /* @__PURE__ */ jsx(UiLink, { href, ...rest, children });
38
+ }
39
+ const key = (item, index) => item.id ?? index;
40
+ function firstPanelIndex(groups) {
41
+ return groups.findIndex((group) => (group.items?.length ?? 0) > 0);
42
+ }
43
+ function hasItems(group) {
44
+ return (group.items?.length ?? 0) > 0;
45
+ }
46
+ function GroupPanel({
47
+ group,
48
+ titleLevel
49
+ }) {
50
+ const Title = `h${titleLevel}`;
51
+ const DescTitle = `h${Math.min(titleLevel + 1, 6)}`;
52
+ const items = group.items ?? [];
53
+ const described = items.some((item) => item.description);
54
+ const shortcut = group.shortcut;
55
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "gnb-sub-content", children: [
57
+ /* @__PURE__ */ jsx(Title, { className: "sub-title", children: shortcut ? /* @__PURE__ */ jsxs(Fragment, { children: [
58
+ group.title ?? group.label,
59
+ /* @__PURE__ */ jsxs(
60
+ MenuAction,
61
+ {
62
+ href: shortcut.href,
63
+ newWindow: shortcut.newWindow,
64
+ className: "krds-btn link basic small",
65
+ children: [
66
+ /* @__PURE__ */ jsx("span", { className: "underline", children: shortcut.label ?? "\uBC14\uB85C\uAC00\uAE30" }),
67
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-angle right", "aria-hidden": "true" })
68
+ ]
69
+ }
70
+ )
71
+ ] }) : /* @__PURE__ */ jsx("span", { children: group.title ?? group.label }) }),
72
+ /* @__PURE__ */ jsx("ul", { className: cx(described && "type-description"), children: items.map((item, index) => {
73
+ const external = item.newWindow || (item.href ? isExternalHref(item.href) : false);
74
+ if (described) {
75
+ return /* @__PURE__ */ jsxs("li", { children: [
76
+ /* @__PURE__ */ jsx(DescTitle, { className: "tit", children: /* @__PURE__ */ jsxs(
77
+ MenuAction,
78
+ {
79
+ href: item.href,
80
+ newWindow: item.newWindow,
81
+ "aria-current": item.current ? "page" : void 0,
82
+ onClick: item.onSelect,
83
+ children: [
84
+ item.label,
85
+ external && /* @__PURE__ */ jsx("i", { className: "svg-icon ico-go", "aria-hidden": "true" })
86
+ ]
87
+ }
88
+ ) }),
89
+ item.description && /* @__PURE__ */ jsx("p", { className: "txt", children: item.description })
90
+ ] }, key(item, index));
91
+ }
92
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
93
+ MenuAction,
94
+ {
95
+ href: item.href,
96
+ newWindow: item.newWindow,
97
+ className: cx(item.current && "active"),
98
+ "aria-current": item.current ? "page" : void 0,
99
+ onClick: item.onSelect,
100
+ children: item.label
101
+ }
102
+ ) }, key(item, index));
103
+ }) })
104
+ ] }),
105
+ group.banner && /* @__PURE__ */ jsxs("div", { className: "gnb-sub-banner", children: [
106
+ group.banner.badge && /* @__PURE__ */ jsx("span", { className: "krds-badge bg-secondary", children: group.banner.badge }),
107
+ /* @__PURE__ */ jsxs(
108
+ MenuAction,
109
+ {
110
+ href: group.banner.href,
111
+ newWindow: group.banner.newWindow,
112
+ className: "krds-btn medium text",
113
+ onClick: group.banner.onSelect,
114
+ children: [
115
+ group.banner.label,
116
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-angle right", "aria-hidden": "true" })
117
+ ]
118
+ }
119
+ )
120
+ ] })
121
+ ] });
122
+ }
123
+ function MainMenu({
124
+ items,
125
+ label = "\uBA54\uC778 \uBA54\uB274",
126
+ titleLevel = 2,
127
+ backdrop = true,
128
+ className,
129
+ onKeyDown,
130
+ onBlur,
131
+ ref,
132
+ ...rest
133
+ }) {
134
+ const uid = idBase(useId());
135
+ const navRef = useRef(null);
136
+ const listRefs = useRef([]);
137
+ const [openIndex, setOpenIndex] = useState(null);
138
+ const [activeGroup, setActiveGroup] = useState({});
139
+ const [mounted, setMounted] = useState(false);
140
+ useEffect(() => setMounted(true), []);
141
+ const close = useCallback(() => setOpenIndex(null), []);
142
+ const triggerAt = useCallback((index) => {
143
+ const triggers = navRef.current?.querySelectorAll(".gnb-main-trigger");
144
+ return triggers?.[index] ?? null;
145
+ }, []);
146
+ useEffect(() => {
147
+ if (openIndex === null || !backdrop) return;
148
+ const needsGutter = document.body.scrollHeight > window.innerHeight;
149
+ document.body.classList.add("is-gnb-web");
150
+ if (needsGutter) document.body.classList.add("hasScrollY");
151
+ return () => {
152
+ document.body.classList.remove("is-gnb-web");
153
+ if (needsGutter) document.body.classList.remove("hasScrollY");
154
+ };
155
+ }, [openIndex, backdrop]);
156
+ useEffect(() => {
157
+ if (openIndex === null) return;
158
+ function handleDown(event) {
159
+ if (!navRef.current?.contains(event.target)) close();
160
+ }
161
+ document.addEventListener("mousedown", handleDown);
162
+ return () => document.removeEventListener("mousedown", handleDown);
163
+ }, [openIndex, close]);
164
+ useEffect(() => {
165
+ if (openIndex === null) return;
166
+ function handleKey(event) {
167
+ if (event.key !== "Escape") return;
168
+ const trigger = triggerAt(openIndex);
169
+ close();
170
+ trigger?.focus();
171
+ }
172
+ document.addEventListener("keydown", handleKey);
173
+ return () => document.removeEventListener("keydown", handleKey);
174
+ }, [openIndex, close, triggerAt]);
175
+ useEffect(() => {
176
+ if (openIndex === null) return;
177
+ const list = listRefs.current[openIndex];
178
+ if (!list) return;
179
+ const panel = list.querySelector(".gnb-sub-list.active");
180
+ list.style.minHeight = panel ? `${panel.scrollHeight}px` : "";
181
+ return () => {
182
+ list.style.minHeight = "";
183
+ };
184
+ }, [openIndex, activeGroup, items]);
185
+ function toggleTop(index) {
186
+ setOpenIndex((current) => current === index ? null : index);
187
+ }
188
+ function handleKeyDown(event) {
189
+ onKeyDown?.(event);
190
+ if (event.defaultPrevented) return;
191
+ const target = event.target;
192
+ if (!target || target.dataset?.trigger !== "gnb") return;
193
+ const move = (direction) => {
194
+ const sibling = direction === "next" ? target.closest("li")?.nextElementSibling : target.closest("li")?.previousElementSibling;
195
+ sibling?.querySelector("[data-trigger]")?.focus();
196
+ };
197
+ switch (event.key) {
198
+ case "Home":
199
+ event.preventDefault();
200
+ triggerAt(0)?.focus();
201
+ break;
202
+ case "End":
203
+ event.preventDefault();
204
+ triggerAt(items.length - 1)?.focus();
205
+ break;
206
+ case "ArrowRight":
207
+ case "ArrowDown":
208
+ event.preventDefault();
209
+ move("next");
210
+ break;
211
+ case "ArrowLeft":
212
+ case "ArrowUp":
213
+ event.preventDefault();
214
+ move("prev");
215
+ break;
216
+ default:
217
+ break;
218
+ }
219
+ }
220
+ function handleBlur(event) {
221
+ onBlur?.(event);
222
+ const next = event.relatedTarget;
223
+ if (next && !event.currentTarget.contains(next)) close();
224
+ }
225
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
226
+ /* @__PURE__ */ jsx(
227
+ "nav",
228
+ {
229
+ ref: (node) => {
230
+ navRef.current = node;
231
+ if (typeof ref === "function") ref(node);
232
+ else if (ref) ref.current = node;
233
+ },
234
+ className: cx("krds-main-menu", className),
235
+ "aria-label": label,
236
+ onKeyDown: handleKeyDown,
237
+ onBlur: handleBlur,
238
+ ...rest,
239
+ children: /* @__PURE__ */ jsx("div", { className: "inner", children: /* @__PURE__ */ jsx("ul", { className: "gnb-menu", children: items.map((top, index) => {
240
+ const groups = top.items ?? [];
241
+ if (groups.length === 0) {
242
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
243
+ MenuAction,
244
+ {
245
+ href: top.href,
246
+ newWindow: top.newWindow,
247
+ className: "gnb-main-trigger is-link",
248
+ "data-trigger": "gnb",
249
+ "aria-current": top.current ? "page" : void 0,
250
+ onClick: top.onSelect,
251
+ children: top.label
252
+ }
253
+ ) }, key(top, index));
254
+ }
255
+ const open = openIndex === index;
256
+ const panelId = `${uid}-gnb-${index}`;
257
+ const activeIndex = activeGroup[index] ?? firstPanelIndex(groups);
258
+ const single = groups.length === 1 && hasItems(groups[0]);
259
+ return /* @__PURE__ */ jsxs("li", { children: [
260
+ /* @__PURE__ */ jsx(
261
+ "button",
262
+ {
263
+ type: "button",
264
+ className: cx("gnb-main-trigger", open && "active"),
265
+ "data-trigger": "gnb",
266
+ "aria-expanded": open,
267
+ "aria-haspopup": true,
268
+ "aria-controls": panelId,
269
+ "aria-current": open ? "true" : top.current ? "page" : void 0,
270
+ onClick: () => toggleTop(index),
271
+ children: top.label
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsx(
275
+ "div",
276
+ {
277
+ id: panelId,
278
+ className: cx(
279
+ "gnb-toggle-wrap",
280
+ open && "is-open",
281
+ "max-h-[70vh] overflow-y-auto"
282
+ ),
283
+ children: /* @__PURE__ */ jsx(
284
+ "div",
285
+ {
286
+ className: "gnb-main-list",
287
+ "data-has-submenu": single ? void 0 : "true",
288
+ ref: (node) => {
289
+ listRefs.current[index] = node;
290
+ },
291
+ children: single ? /* @__PURE__ */ jsx(
292
+ "div",
293
+ {
294
+ className: cx(
295
+ "gnb-sub-list",
296
+ "single-list",
297
+ groups[0].banner?.side && "between"
298
+ ),
299
+ children: /* @__PURE__ */ jsx(GroupPanel, { group: groups[0], titleLevel })
300
+ }
301
+ ) : /* @__PURE__ */ jsx("ul", { children: groups.map((group, groupIndex) => {
302
+ if (!hasItems(group)) {
303
+ const external = group.newWindow || (group.href ? isExternalHref(group.href) : false);
304
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
305
+ MenuAction,
306
+ {
307
+ href: group.href,
308
+ newWindow: group.newWindow,
309
+ className: cx(
310
+ "gnb-sub-trigger",
311
+ "is-link",
312
+ external && "external-link"
313
+ ),
314
+ "data-trigger": "gnb",
315
+ "aria-current": group.current ? "page" : void 0,
316
+ onClick: group.onSelect,
317
+ children: group.label
318
+ }
319
+ ) }, key(group, groupIndex));
320
+ }
321
+ const active = activeIndex === groupIndex;
322
+ const subId = `${panelId}-sub-${groupIndex}`;
323
+ return /* @__PURE__ */ jsxs("li", { children: [
324
+ /* @__PURE__ */ jsx(
325
+ "button",
326
+ {
327
+ type: "button",
328
+ className: cx("gnb-sub-trigger", active && "active"),
329
+ "data-trigger": "gnb",
330
+ "aria-expanded": active,
331
+ "aria-haspopup": true,
332
+ "aria-controls": subId,
333
+ onClick: () => {
334
+ setActiveGroup((current) => ({
335
+ ...current,
336
+ [index]: groupIndex
337
+ }));
338
+ group.onSelect?.();
339
+ },
340
+ children: group.label
341
+ }
342
+ ),
343
+ /* @__PURE__ */ jsx(
344
+ "div",
345
+ {
346
+ id: subId,
347
+ className: cx(
348
+ "gnb-sub-list",
349
+ active && "active",
350
+ group.banner?.side && "between"
351
+ ),
352
+ children: /* @__PURE__ */ jsx(GroupPanel, { group, titleLevel })
353
+ }
354
+ )
355
+ ] }, key(group, groupIndex));
356
+ }) })
357
+ }
358
+ )
359
+ }
360
+ )
361
+ ] }, key(top, index));
362
+ }) }) })
363
+ }
364
+ ),
365
+ backdrop && mounted ? createPortal(
366
+ /* @__PURE__ */ jsx("div", { className: cx("gnb-backdrop", openIndex !== null && "active"), "aria-hidden": "true" }),
367
+ document.body
368
+ ) : null
369
+ ] });
370
+ }
371
+ const MOBILE_ENTER_DELAY = 100;
372
+ const MOBILE_LEAVE_DELAY = 400;
373
+ const PC_MIN_WIDTH = 1024;
374
+ const FOCUSABLE_SELECTOR = [
375
+ "a[href]",
376
+ "button:not([disabled])",
377
+ 'input:not([disabled]):not([type="hidden"])',
378
+ "select:not([disabled])",
379
+ "textarea:not([disabled])",
380
+ '[tabindex]:not([tabindex="-1"])'
381
+ ].join(",");
382
+ function focusables(root) {
383
+ return Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
384
+ (el) => !el.hasAttribute("inert") && el.getClientRects().length > 0
385
+ );
386
+ }
387
+ function MainMenuMobile({
388
+ items,
389
+ open,
390
+ onClose,
391
+ id = "mobile-nav",
392
+ label = "\uC804\uCCB4 \uBA54\uB274",
393
+ closeLabel = "\uC804\uCCB4\uBA54\uB274 \uB2EB\uAE30",
394
+ titleLevel = 2,
395
+ header,
396
+ bottom,
397
+ className,
398
+ style,
399
+ onKeyDown,
400
+ ...rest
401
+ }) {
402
+ const uid = idBase(useId());
403
+ const wrapRef = useRef(null);
404
+ const bodyRef = useRef(null);
405
+ const [shown, setShown] = useState(false);
406
+ const [entered, setEntered] = useState(false);
407
+ const [activeTab, setActiveTab] = useState(0);
408
+ const [openDepth3, setOpenDepth3] = useState({});
409
+ const tabId = (index) => `${uid}-m-tab-${index}`;
410
+ const panelId = (index) => `${uid}-m-panel-${index}`;
411
+ useEffect(() => {
412
+ if (!open) return;
413
+ const restore = document.activeElement;
414
+ setShown(true);
415
+ document.body.classList.add("is-gnb-mobile");
416
+ const enterTimer = window.setTimeout(() => {
417
+ setEntered(true);
418
+ wrapRef.current?.focus();
419
+ }, MOBILE_ENTER_DELAY);
420
+ return () => {
421
+ window.clearTimeout(enterTimer);
422
+ setEntered(false);
423
+ document.body.classList.remove("is-gnb-mobile");
424
+ restore?.focus?.();
425
+ };
426
+ }, [open]);
427
+ useEffect(() => {
428
+ if (open || !shown) return;
429
+ const timer = window.setTimeout(() => setShown(false), MOBILE_LEAVE_DELAY);
430
+ return () => window.clearTimeout(timer);
431
+ }, [open, shown]);
432
+ useEffect(() => {
433
+ if (!open) return;
434
+ const query = window.matchMedia(`(min-width: ${PC_MIN_WIDTH}px)`);
435
+ const handle = () => {
436
+ if (query.matches) onClose();
437
+ };
438
+ handle();
439
+ query.addEventListener("change", handle);
440
+ return () => query.removeEventListener("change", handle);
441
+ }, [open, onClose]);
442
+ useEffect(() => {
443
+ const body = bodyRef.current;
444
+ if (!open || !body) return;
445
+ const handle = () => {
446
+ const bottomReached = body.scrollTop + body.clientHeight >= body.scrollHeight - 1;
447
+ if (bottomReached) {
448
+ setActiveTab(items.length - 1);
449
+ return;
450
+ }
451
+ let next = 0;
452
+ items.forEach((_, index) => {
453
+ const panel = document.getElementById(panelId(index));
454
+ if (panel && body.scrollTop >= panel.offsetTop) next = index;
455
+ });
456
+ setActiveTab(next);
457
+ };
458
+ body.addEventListener("scroll", handle, { passive: true });
459
+ return () => body.removeEventListener("scroll", handle);
460
+ }, [open, items]);
461
+ function handleTabClick(event, index) {
462
+ event.preventDefault();
463
+ setActiveTab(index);
464
+ const panel = document.getElementById(panelId(index));
465
+ if (panel) bodyRef.current?.scrollTo({ top: panel.offsetTop, left: 0, behavior: "smooth" });
466
+ }
467
+ function handleKeyDown(event) {
468
+ onKeyDown?.(event);
469
+ if (event.defaultPrevented) return;
470
+ if (event.key === "Escape") {
471
+ event.stopPropagation();
472
+ onClose();
473
+ return;
474
+ }
475
+ if (event.key !== "Tab") return;
476
+ const wrap = wrapRef.current;
477
+ if (!wrap) return;
478
+ const nodes = focusables(wrap);
479
+ if (nodes.length === 0) {
480
+ event.preventDefault();
481
+ wrap.focus();
482
+ return;
483
+ }
484
+ const first = nodes[0];
485
+ const last = nodes[nodes.length - 1];
486
+ if (event.shiftKey && document.activeElement === first) {
487
+ event.preventDefault();
488
+ last.focus();
489
+ } else if (!event.shiftKey && document.activeElement === last) {
490
+ event.preventDefault();
491
+ first.focus();
492
+ }
493
+ }
494
+ const Title = `h${titleLevel}`;
495
+ return /* @__PURE__ */ jsx(
496
+ "div",
497
+ {
498
+ id,
499
+ className: cx("krds-main-menu-mobile", entered && "is-open", entered && "is-backdrop", className),
500
+ style: { display: shown ? "block" : "none", ...style },
501
+ onKeyDown: handleKeyDown,
502
+ ...rest,
503
+ children: /* @__PURE__ */ jsxs(
504
+ "div",
505
+ {
506
+ ref: wrapRef,
507
+ className: "gnb-wrap",
508
+ role: "dialog",
509
+ "aria-modal": "true",
510
+ "aria-label": label,
511
+ tabIndex: -1,
512
+ children: [
513
+ header && /* @__PURE__ */ jsx("div", { className: "gnb-header", children: header }),
514
+ /* @__PURE__ */ jsxs("div", { className: "gnb-body", ref: bodyRef, children: [
515
+ /* @__PURE__ */ jsxs("div", { className: "gnb-menu", role: "navigation", "aria-label": "\uBA54\uC778 \uBA54\uB274", children: [
516
+ /* @__PURE__ */ jsx("div", { className: "menu-wrap", children: /* @__PURE__ */ jsx("ul", { role: "tablist", children: items.map((top, index) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
517
+ "a",
518
+ {
519
+ href: `#${panelId(index)}`,
520
+ id: tabId(index),
521
+ className: cx("gnb-main-trigger", activeTab === index && "active"),
522
+ role: "tab",
523
+ "aria-selected": activeTab === index,
524
+ "aria-controls": panelId(index),
525
+ onClick: (event) => handleTabClick(event, index),
526
+ children: top.label
527
+ }
528
+ ) }, key(top, index))) }) }),
529
+ /* @__PURE__ */ jsx("div", { className: "submenu-wrap", children: items.map((top, index) => /* @__PURE__ */ jsxs(
530
+ "div",
531
+ {
532
+ id: panelId(index),
533
+ className: "gnb-sub-list",
534
+ role: "tabpanel",
535
+ "aria-labelledby": tabId(index),
536
+ children: [
537
+ /* @__PURE__ */ jsx(Title, { className: "sub-title", children: top.label }),
538
+ /* @__PURE__ */ jsx("ul", { children: (top.items ?? []).map((group, groupIndex) => {
539
+ const groupKey = `${index}-${groupIndex}`;
540
+ const items3 = group.items ?? [];
541
+ if (items3.length === 0) {
542
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
543
+ MenuAction,
544
+ {
545
+ href: group.href,
546
+ newWindow: group.newWindow,
547
+ className: cx("gnb-sub-trigger", group.current && "selected"),
548
+ "aria-current": group.current ? "page" : void 0,
549
+ onClick: group.onSelect,
550
+ children: group.label
551
+ }
552
+ ) }, key(group, groupIndex));
553
+ }
554
+ const expanded = openDepth3[groupKey] ?? false;
555
+ const depth3Id = `${panelId(index)}-d3-${groupIndex}`;
556
+ return /* @__PURE__ */ jsxs("li", { children: [
557
+ /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ className: cx(
562
+ "gnb-sub-trigger",
563
+ "has-depth3",
564
+ expanded && "active",
565
+ group.current && "selected"
566
+ ),
567
+ "aria-expanded": expanded,
568
+ "aria-controls": depth3Id,
569
+ onClick: () => {
570
+ setOpenDepth3((current) => ({
571
+ ...current,
572
+ [groupKey]: !expanded
573
+ }));
574
+ group.onSelect?.();
575
+ },
576
+ children: group.label
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx("div", { id: depth3Id, className: cx("depth3-wrap", expanded && "is-open"), children: /* @__PURE__ */ jsx("ul", { children: items3.map((item, itemIndex) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
580
+ MenuAction,
581
+ {
582
+ href: item.href,
583
+ newWindow: item.newWindow,
584
+ className: cx("depth3-trigger", item.current && "active"),
585
+ "aria-current": item.current ? "page" : void 0,
586
+ onClick: item.onSelect,
587
+ children: item.label
588
+ }
589
+ ) }, key(item, itemIndex))) }) })
590
+ ] }, key(group, groupIndex));
591
+ }) })
592
+ ]
593
+ },
594
+ key(top, index)
595
+ )) })
596
+ ] }),
597
+ bottom && /* @__PURE__ */ jsx("div", { className: "gnb-bottom", children: bottom })
598
+ ] }),
599
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "krds-btn medium icon", id: "close-nav", onClick: onClose, children: [
600
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: closeLabel }),
601
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-popup-close", "aria-hidden": "true" })
602
+ ] })
603
+ ]
604
+ }
605
+ )
606
+ }
607
+ );
608
+ }
609
+ export {
610
+ MainMenu,
611
+ MainMenuMobile
612
+ };
613
+ //# sourceMappingURL=index.js.map