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,249 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ useCallback,
5
+ useId,
6
+ useRef,
7
+ useState
8
+ } from "react";
9
+ import { cx } from "../lib/krds.js";
10
+ function assignRef(ref, node) {
11
+ if (typeof ref === "function") ref(node);
12
+ else if (ref) ref.current = node;
13
+ }
14
+ function idBase(raw) {
15
+ return raw.replace(/[^a-zA-Z0-9_-]/g, "") || "file";
16
+ }
17
+ function extensionOf(name) {
18
+ const m = /.\.([A-Za-z0-9]{1,10})$/.exec(name);
19
+ return m ? m[1].toLowerCase() : void 0;
20
+ }
21
+ function formatFileSize(bytes) {
22
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
23
+ if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))}KB`;
24
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
25
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`;
26
+ }
27
+ function formatFileLabel(file) {
28
+ const { name, type, size } = file;
29
+ const auto = type ? void 0 : extensionOf(name);
30
+ const shown = auto ? name.slice(0, name.length - auto.length - 1) : name;
31
+ const meta = [type ?? auto, typeof size === "number" ? formatFileSize(size) : ""].filter(Boolean);
32
+ return meta.length > 0 ? `${shown} [${meta.join(", ")}]` : shown;
33
+ }
34
+ function FileUpload({
35
+ bordered,
36
+ title,
37
+ titleAs: Tit = "h3",
38
+ description,
39
+ descriptionId,
40
+ className,
41
+ children,
42
+ ...rest
43
+ }) {
44
+ return /* @__PURE__ */ jsxs("div", { className: cx("krds-file-upload", bordered && "line", className), ...rest, children: [
45
+ (title || description) && /* @__PURE__ */ jsxs("div", { className: "file-head", children: [
46
+ title && /* @__PURE__ */ jsx(Tit, { className: "tit", children: title }),
47
+ description && /* @__PURE__ */ jsx("div", { id: descriptionId, children: typeof description === "string" ? /* @__PURE__ */ jsx("p", { children: description }) : description })
48
+ ] }),
49
+ children
50
+ ] });
51
+ }
52
+ function FileDropzone({
53
+ id,
54
+ name,
55
+ accept,
56
+ multiple,
57
+ disabled,
58
+ required,
59
+ inputRef,
60
+ describedBy,
61
+ text = "\uCCA8\uBD80\uD560 \uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB04C\uC5B4\uB2E4 \uB193\uAC70\uB098, \uD30C\uC77C \uC120\uD0DD \uBC84\uD2BC\uC744 \uB20C\uB7EC \uD30C\uC77C\uC744 \uC9C1\uC811 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
62
+ buttonLabel = "\uD30C\uC77C\uC120\uD0DD",
63
+ buttonSize = "medium",
64
+ buttonVariant,
65
+ onFiles,
66
+ onChange,
67
+ clearAfterSelect,
68
+ className,
69
+ children,
70
+ ...rest
71
+ }) {
72
+ const uid = idBase(useId());
73
+ const inputId = id ?? `file-upload-${uid}`;
74
+ const textId = `file-upload-txt-${uid}`;
75
+ const input = useRef(null);
76
+ const [dragging, setDragging] = useState(false);
77
+ const depth = useRef(0);
78
+ const setInput = useCallback(
79
+ (node) => {
80
+ input.current = node;
81
+ assignRef(inputRef, node);
82
+ },
83
+ [inputRef]
84
+ );
85
+ const emit = (list) => {
86
+ if (!list || list.length === 0) return;
87
+ const files = Array.from(list);
88
+ onFiles?.(multiple ? files : files.slice(0, 1));
89
+ };
90
+ const handleChange = (e) => {
91
+ onChange?.(e);
92
+ emit(e.currentTarget.files);
93
+ if (clearAfterSelect) e.currentTarget.value = "";
94
+ };
95
+ const enter = (e) => {
96
+ if (disabled) return;
97
+ e.preventDefault();
98
+ depth.current += 1;
99
+ setDragging(true);
100
+ };
101
+ const over = (e) => {
102
+ if (disabled) return;
103
+ e.preventDefault();
104
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
105
+ };
106
+ const leave = () => {
107
+ if (disabled) return;
108
+ depth.current = Math.max(0, depth.current - 1);
109
+ if (depth.current === 0) setDragging(false);
110
+ };
111
+ const drop = (e) => {
112
+ if (disabled) return;
113
+ e.preventDefault();
114
+ depth.current = 0;
115
+ setDragging(false);
116
+ emit(e.dataTransfer?.files);
117
+ };
118
+ return /* @__PURE__ */ jsxs(
119
+ "div",
120
+ {
121
+ className: cx("file-upload", dragging && "active", className),
122
+ ...rest,
123
+ onDragEnter: enter,
124
+ onDragOver: over,
125
+ onDragLeave: leave,
126
+ onDrop: drop,
127
+ children: [
128
+ /* @__PURE__ */ jsx("p", { className: "txt", id: textId, children: text }),
129
+ /* @__PURE__ */ jsxs("div", { className: "file-upload-btn-wrap", children: [
130
+ /* @__PURE__ */ jsx(
131
+ "input",
132
+ {
133
+ type: "file",
134
+ id: inputId,
135
+ name,
136
+ accept,
137
+ multiple,
138
+ disabled,
139
+ required,
140
+ tabIndex: -1,
141
+ "aria-describedby": cx(textId, describedBy),
142
+ ref: setInput,
143
+ onChange: handleChange
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx("label", { htmlFor: inputId, children: /* @__PURE__ */ jsxs(
147
+ "button",
148
+ {
149
+ type: "button",
150
+ className: cx("krds-btn", buttonSize, buttonVariant),
151
+ disabled,
152
+ onClick: () => input.current?.click(),
153
+ children: [
154
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-upload", "aria-hidden": "true" }),
155
+ buttonLabel
156
+ ]
157
+ }
158
+ ) })
159
+ ] }),
160
+ children
161
+ ]
162
+ }
163
+ );
164
+ }
165
+ function FileList(props) {
166
+ const {
167
+ count,
168
+ max,
169
+ unit = "\uAC1C",
170
+ counter,
171
+ onDeleteAll,
172
+ deleteAllLabel = "\uC804\uCCB4 \uD30C\uC77C \uC0AD\uC81C",
173
+ wrapClassName,
174
+ className,
175
+ children,
176
+ ...rest
177
+ } = props;
178
+ const total = counter ?? (typeof count === "number" ? /* @__PURE__ */ jsxs(Fragment, { children: [
179
+ /* @__PURE__ */ jsxs("span", { className: "current", children: [
180
+ count,
181
+ unit
182
+ ] }),
183
+ typeof max === "number" && ` / ${max}${unit}`
184
+ ] }) : null);
185
+ return /* @__PURE__ */ jsxs("div", { className: cx("file-list", wrapClassName), children: [
186
+ total && /* @__PURE__ */ jsx("div", { className: "total", children: total }),
187
+ /* @__PURE__ */ jsx("ul", { className: cx("upload-list", className), ...rest, children }),
188
+ onDeleteAll && /* @__PURE__ */ jsx("div", { className: "upload-delete-btn", children: /* @__PURE__ */ jsxs("button", { type: "button", className: "krds-btn xsmall tertiary", onClick: onDeleteAll, children: [
189
+ deleteAllLabel,
190
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-angle right", "aria-hidden": "true" })
191
+ ] }) })
192
+ ] });
193
+ }
194
+ function FileItem(props) {
195
+ const {
196
+ name,
197
+ type,
198
+ size,
199
+ status,
200
+ error,
201
+ onDelete,
202
+ deleteLabel = "\uC0AD\uC81C",
203
+ downloadHref,
204
+ downloadLabel = "\uB2E4\uC6B4\uB85C\uB4DC",
205
+ viewHref,
206
+ viewLabel = "\uBC14\uB85C\uBCF4\uAE30",
207
+ actionSize = "medium",
208
+ stackOnMobile,
209
+ className,
210
+ children,
211
+ ...rest
212
+ } = props;
213
+ const uid = idBase(useId());
214
+ const nameId = `file-name-${uid}`;
215
+ const btn = cx("krds-btn", actionSize, "text");
216
+ const hasDefaultAction = Boolean(status || downloadHref || viewHref || onDelete);
217
+ const actions = children ?? (hasDefaultAction ? /* @__PURE__ */ jsxs(Fragment, { children: [
218
+ status === "uploading" && /* @__PURE__ */ jsx("span", { className: "krds-spinner", role: "status", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "\uC5C5\uB85C\uB4DC \uC911" }) }),
219
+ status === "complete" && /* @__PURE__ */ jsx("span", { className: "ico-invalid complete", children: /* @__PURE__ */ jsx("em", { className: "sr-only", children: "\uC5C5\uB85C\uB4DC \uC644\uB8CC" }) }),
220
+ downloadHref && /* @__PURE__ */ jsxs("a", { className: btn, href: downloadHref, download: true, "aria-describedby": nameId, children: [
221
+ downloadLabel,
222
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-down", "aria-hidden": "true" })
223
+ ] }),
224
+ viewHref && /* @__PURE__ */ jsxs("a", { className: btn, href: viewHref, "aria-describedby": nameId, children: [
225
+ viewLabel,
226
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-angle right", "aria-hidden": "true" })
227
+ ] }),
228
+ onDelete && /* @__PURE__ */ jsxs("button", { type: "button", className: btn, onClick: onDelete, "aria-describedby": nameId, children: [
229
+ deleteLabel,
230
+ /* @__PURE__ */ jsx("i", { className: "svg-icon ico-delete-fill", "aria-hidden": "true" })
231
+ ] })
232
+ ] }) : null);
233
+ return /* @__PURE__ */ jsxs("li", { className: cx(error ? "is-error" : void 0, className), ...rest, children: [
234
+ /* @__PURE__ */ jsxs("div", { className: cx("file-info", stackOnMobile && "m-column"), children: [
235
+ /* @__PURE__ */ jsx("div", { className: "file-name", id: nameId, children: formatFileLabel({ name, type, size }) }),
236
+ actions ? /* @__PURE__ */ jsx("div", { className: "btn-wrap", children: actions }) : null
237
+ ] }),
238
+ error ? /* @__PURE__ */ jsx("p", { className: "file-hint-invalid", children: error }) : null
239
+ ] });
240
+ }
241
+ export {
242
+ FileDropzone,
243
+ FileItem,
244
+ FileList,
245
+ FileUpload,
246
+ formatFileLabel,
247
+ formatFileSize
248
+ };
249
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/file-upload/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useId,\n useRef,\n useState,\n type ChangeEventHandler,\n type ComponentPropsWithRef,\n type DragEvent,\n type ReactNode,\n type Ref,\n} from 'react';\nimport { cx, type KrdsSize } from '../lib/krds';\n\n/**\n * 파일 업로드 — KRDS `krds-file-upload`.\n *\n * 바깥틀(`FileUpload`) · 드롭 영역(`FileDropzone`) · 목록(`FileList` + `FileItem`)으로 나뉜다.\n * 드래그 앤 드롭은 **반드시 파일 선택 버튼과 함께** 제공한다(`FileDropzone` 이 둘을 늘 같이 그린다).\n * 유형·크기·개수 검사는 `onFiles` 에서 직접 한다 — 드롭으로 들어온 파일은 `accept` 로 걸러지지 않는다.\n * 파일 이름 표기는 {@link formatFileLabel} 이 정한 `이름 [유형, 크기]` 한 가지다.\n * 글 보기 화면의 정적 첨부 목록에는 `FileItem` 만 써도 된다.\n *\n * @example\n * <FileUpload bordered title=\"첨부파일\" description=\"hwp·pdf, 10MB 이하, 5개까지\" descriptionId=\"up-desc\">\n * <FileDropzone multiple accept=\".hwp,.pdf\" describedBy=\"up-desc\" onFiles={add} clearAfterSelect />\n * <FileList count={files.length} max={5} onDeleteAll={clear}>\n * {files.map((f) => (\n * <FileItem key={f.id} name={f.name} size={f.size} onDelete={() => remove(f.id)} />\n * ))}\n * </FileList>\n * </FileUpload>\n *\n * 자세히: docs/krds/09-부품-노트.md#파일첨부\n */\n\n/* ────────────────────────────── 표기 도우미 ────────────────────────────── */\n\n/** 넘겨받은 ref 에 노드를 꽂는다. 컴포넌트 본문에서 prop 을 직접 대입하면 컴파일러 규칙에 걸려 밖에 둔다. */\nfunction assignRef<T>(ref: Ref<T> | undefined, node: T | null): void {\n if (typeof ref === 'function') ref(node);\n else if (ref) (ref as { current: T | null }).current = node;\n}\n\n/** `useId()` 는 `«r0»` 처럼 특수문자를 담는다 — id 조각으로 쓸 수 있게 깎는다. */\nfunction idBase(raw: string): string {\n return raw.replace(/[^a-zA-Z0-9_-]/g, '') || 'file';\n}\n\n/** 이름 끝의 확장자를 뽑는다. 없으면 `undefined`. `.gitignore` 처럼 앞에 이름이 없으면 확장자로 보지 않는다. */\nfunction extensionOf(name: string): string | undefined {\n const m = /.\\.([A-Za-z0-9]{1,10})$/.exec(name);\n return m ? m[1].toLowerCase() : undefined;\n}\n\n/**\n * 파일 크기 표기 — `17KB` · `1.2MB` 꼴(공백 없음). 1KB 미만도 `1KB` 로 올린다.\n *\n * @example\n * formatFileSize(17408) // '17KB'\n */\nexport function formatFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))}KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`;\n return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`;\n}\n\n/**\n * KRDS 파일 표기 — `이름 [유형, 크기]`. `type` 을 생략하면 이름 끝의 확장자에서 뽑아 이름에서 뗀다.\n *\n * @example\n * formatFileLabel({ name: '위임장.hwp', size: 17408 }) // '위임장 [hwp, 17KB]'\n */\nexport function formatFileLabel(file: { name: string; type?: string; size?: number }): string {\n const { name, type, size } = file;\n const auto = type ? undefined : extensionOf(name);\n const shown = auto ? name.slice(0, name.length - auto.length - 1) : name;\n const meta = [type ?? auto, typeof size === 'number' ? formatFileSize(size) : ''].filter(Boolean);\n return meta.length > 0 ? `${shown} [${meta.join(', ')}]` : shown;\n}\n\n/* ────────────────────────────── 바깥 틀 ────────────────────────────── */\n\nexport type FileUploadProps = Omit<ComponentPropsWithRef<'div'>, 'title'> & {\n /** 테두리형. 다른 폼과 구분해 한 덩어리로 보일 때. */\n bordered?: boolean;\n /** 제목. */\n title?: ReactNode;\n /** 제목 태그. 기본 `h3` — 화면의 제목 위계에 맞춘다. */\n titleAs?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n /** 제목 아래 설명. **파일 유형·크기·개수 제한을 여기에 적는다**(가이드 요구). */\n description?: ReactNode;\n /** 설명 영역의 `id`. {@link FileDropzone} 의 `describedBy` 와 짝을 맞춘다. */\n descriptionId?: string;\n};\n\nexport function FileUpload({\n bordered,\n title,\n titleAs: Tit = 'h3',\n description,\n descriptionId,\n className,\n children,\n ...rest\n}: FileUploadProps) {\n return (\n <div className={cx('krds-file-upload', bordered && 'line', className)} {...rest}>\n {(title || description) && (\n <div className=\"file-head\">\n {title && <Tit className=\"tit\">{title}</Tit>}\n {description && (\n <div id={descriptionId}>\n {typeof description === 'string' ? <p>{description}</p> : description}\n </div>\n )}\n </div>\n )}\n {children}\n </div>\n );\n}\n\n/* ────────────────────────── 드롭 영역 + 파일 선택 ────────────────────────── */\n\nexport type FileDropzoneProps = Omit<\n ComponentPropsWithRef<'div'>,\n 'onChange' | 'onDrop' | 'onDragEnter' | 'onDragLeave' | 'onDragOver'\n> & {\n /** `<input type=\"file\">` 의 id. 생략하면 `useId` 로 만든다. */\n id?: string;\n /** 폼 전송에 쓸 이름. */\n name?: string;\n /** 파일 선택창의 필터. **드롭으로 들어온 파일은 이걸로 걸러지지 않는다.** */\n accept?: string;\n /** 여러 개를 고를 수 있다. */\n multiple?: boolean;\n /** 비활성. 드롭도 받지 않는다. */\n disabled?: boolean;\n /** 필수 입력. */\n required?: boolean;\n /** 파일칸을 직접 다뤄야 할 때(값 비우기 등). */\n inputRef?: Ref<HTMLInputElement>;\n /** 파일칸에 걸 `aria-describedby`. {@link FileUpload} 의 `descriptionId` 와 짝이다. */\n describedBy?: string;\n /** 드롭 영역 안내 문구. 기본값은 킷 샘플 문구 그대로. */\n text?: ReactNode;\n /** 파일 선택 버튼 글자. */\n buttonLabel?: ReactNode;\n /** 기본 medium(48px). 같은 줄의 폼 요소와 같은 값을 준다. */\n buttonSize?: KrdsSize;\n /** 파일 선택 버튼의 위계. `primary` 는 제출 버튼과 강조가 충돌해 두지 않았다. */\n buttonVariant?: 'secondary' | 'tertiary' | 'text';\n /** 선택·드롭 공통 진입점. `multiple` 이 아니면 첫 파일 하나만 넘긴다. */\n onFiles?: (files: File[]) => void;\n /** 원래의 `change` 도 필요하면 받는다. `onFiles` 보다 먼저 불린다. */\n onChange?: ChangeEventHandler<HTMLInputElement>;\n /** 넘긴 뒤 파일칸을 비워 같은 파일 재선택을 살린다. **폼 제출로 파일을 보낸다면 켜지 마라.** */\n clearAfterSelect?: boolean;\n};\n\nexport function FileDropzone({\n id,\n name,\n accept,\n multiple,\n disabled,\n required,\n inputRef,\n describedBy,\n text = '첨부할 파일을 여기에 끌어다 놓거나, 파일 선택 버튼을 눌러 파일을 직접 선택해주세요.',\n buttonLabel = '파일선택',\n buttonSize = 'medium',\n buttonVariant,\n onFiles,\n onChange,\n clearAfterSelect,\n className,\n children,\n ...rest\n}: FileDropzoneProps) {\n const uid = idBase(useId());\n const inputId = id ?? `file-upload-${uid}`;\n const textId = `file-upload-txt-${uid}`;\n const input = useRef<HTMLInputElement | null>(null);\n const [dragging, setDragging] = useState(false);\n // dragenter/dragleave 는 자식 요소를 지날 때마다 다시 뜬다. 깊이를 세어야 테두리가 깜빡이지 않는다.\n const depth = useRef(0);\n\n const setInput = useCallback(\n (node: HTMLInputElement | null) => {\n input.current = node;\n assignRef(inputRef, node);\n },\n [inputRef],\n );\n\n const emit = (list: FileList | null | undefined) => {\n if (!list || list.length === 0) return;\n const files = Array.from(list);\n onFiles?.(multiple ? files : files.slice(0, 1));\n };\n\n const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {\n onChange?.(e);\n emit(e.currentTarget.files);\n if (clearAfterSelect) e.currentTarget.value = '';\n };\n\n const enter = (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n depth.current += 1;\n setDragging(true);\n };\n const over = (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n // 이 preventDefault 를 빼면 브라우저가 기본 동작(파일 열기)을 해 버려 drop 이 아예 오지 않는다.\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';\n };\n const leave = () => {\n if (disabled) return;\n depth.current = Math.max(0, depth.current - 1);\n if (depth.current === 0) setDragging(false);\n };\n const drop = (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n depth.current = 0;\n setDragging(false);\n emit(e.dataTransfer?.files);\n };\n\n return (\n <div\n className={cx('file-upload', dragging && 'active', className)}\n {...rest}\n onDragEnter={enter}\n onDragOver={over}\n onDragLeave={leave}\n onDrop={drop}\n >\n <p className=\"txt\" id={textId}>\n {text}\n </p>\n <div className=\"file-upload-btn-wrap\">\n {/* hidden 을 붙이지 않는다 — 킷 CSS 가 이미 감추고, hidden 은 초점까지 막아 제출이 조용히 멈춘다. */}\n <input\n type=\"file\"\n id={inputId}\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n required={required}\n tabIndex={-1}\n aria-describedby={cx(textId, describedBy)}\n ref={setInput}\n onChange={handleChange}\n />\n <label htmlFor={inputId}>\n <button\n type=\"button\"\n className={cx('krds-btn', buttonSize, buttonVariant)}\n disabled={disabled}\n // `<label>` 안의 `<button>` 은 라벨 활성화를 일으키지 못해 직접 부른다.\n onClick={() => input.current?.click()}\n >\n <i className=\"svg-icon ico-upload\" aria-hidden=\"true\" />\n {buttonLabel}\n </button>\n </label>\n </div>\n {children}\n </div>\n );\n}\n\n/* ────────────────────────────── 파일 목록 ────────────────────────────── */\n\ntype FileListBaseProps = ComponentPropsWithRef<'ul'> & {\n /** 올릴 수 있는 최대 파일 수. */\n max?: number;\n /** 세는 단위. 기본 `개`. */\n unit?: string;\n /** 전체 삭제 버튼 글자. */\n deleteAllLabel?: ReactNode;\n /** `.file-list` 에 붙일 클래스. `className` 은 `<ul class=\"upload-list\">` 로 간다. */\n wrapClassName?: string;\n};\n\n/** 카운터를 그리는 두 방법. `count`(부품이 `3개 / 10개` 를 그린다) 또는 `counter`(직접 그린다). */\ntype FileListCounterProps =\n | { count: number; counter?: ReactNode }\n // `counter={null}` 로 빠져나가지 못하게 null·undefined 를 뺀다 — 그러면 `.total` 이 안 그려진다.\n | { count?: number; counter: NonNullable<ReactNode> };\n\n/**\n * **전체 삭제 버튼은 카운터 없이 둘 수 없다** — 킷이 그 버튼을 절대 배치해서 카운터가 없으면\n * 목록 첫 항목 위에 겹친다. 타입으로 막아 둔다.\n */\nexport type FileListProps = FileListBaseProps &\n (\n | ({\n /** 전체 삭제. 카운터(`count` 또는 `counter`)가 함께 필요하다. 조건부로 `undefined` 도 받는다. */\n onDeleteAll: (() => void) | undefined;\n } & FileListCounterProps)\n | {\n onDeleteAll?: never;\n /** 올라간 파일 수. `max` 와 함께 주면 `3개 / 10개` 카운터를 그린다. */\n count?: number;\n /** 카운터를 직접 그릴 때. 주면 `count`·`max` 는 무시한다. */\n counter?: ReactNode;\n }\n );\n\n/** 유니온은 판별자가 없어 그대로 구조 분해가 안 된다 — 넓은 쪽으로 한 번 받아 쓴다. */\ntype FileListAllProps = FileListBaseProps & {\n count?: number;\n counter?: ReactNode;\n onDeleteAll?: () => void;\n};\n\nexport function FileList(props: FileListProps) {\n const {\n count,\n max,\n unit = '개',\n counter,\n onDeleteAll,\n deleteAllLabel = '전체 파일 삭제',\n wrapClassName,\n className,\n children,\n ...rest\n }: FileListAllProps = props;\n const total =\n counter ??\n (typeof count === 'number' ? (\n <>\n <span className=\"current\">\n {count}\n {unit}\n </span>\n {typeof max === 'number' && ` / ${max}${unit}`}\n </>\n ) : null);\n\n return (\n <div className={cx('file-list', wrapClassName)}>\n {total && <div className=\"total\">{total}</div>}\n <ul className={cx('upload-list', className)} {...rest}>\n {children}\n </ul>\n {onDeleteAll && (\n <div className=\"upload-delete-btn\">\n <button type=\"button\" className=\"krds-btn xsmall tertiary\" onClick={onDeleteAll}>\n {deleteAllLabel}\n <i className=\"svg-icon ico-angle right\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/* ────────────────────────────── 파일 항목 ────────────────────────────── */\n\n/** 업로드 상태 아이콘. `uploading` 스피너 · `complete` 완료 표시. */\nexport type FileItemStatus = 'uploading' | 'complete';\n\ntype FileItemBaseProps = Omit<ComponentPropsWithRef<'li'>, 'children'> & {\n /** 파일 이름. `type` 을 안 주면 여기서 확장자를 뽑아 이름에서 뗀다({@link formatFileLabel}). */\n name: string;\n /** 파일 유형. 예: `hwp`. */\n type?: string;\n /** 바이트 수. `328KB` 꼴로 바꿔 이름 옆 대괄호에 넣는다. */\n size?: number;\n /** 삭제 단추를 그린다. */\n onDelete?: () => void;\n /** 삭제 단추 글자. */\n deleteLabel?: ReactNode;\n /** 내려받기 주소. 「다운로드」와 「바로보기」는 규격상 따로 둔다. */\n downloadHref?: string;\n /** 내려받기 단추 글자. */\n downloadLabel?: ReactNode;\n /** 바로보기(미리보기) 주소. */\n viewHref?: string;\n /** 바로보기 단추 글자. */\n viewLabel?: ReactNode;\n /** 항목 버튼 크기. 킷 샘플은 medium. */\n actionSize?: KrdsSize;\n /** 좁은 화면에서 이름과 버튼을 위아래로 놓는다. 버튼이 둘 이상일 때 쓴다. */\n stackOnMobile?: boolean;\n /** 버튼 영역을 통째로 대신 그린다. */\n children?: ReactNode;\n};\n\n/** 업로드 상태와 오류는 배타다 — 등록되지 않은 파일에 스피너·완료 아이콘이 같이 뜨면 안 된다. */\nexport type FileItemProps = FileItemBaseProps &\n (\n | {\n /** 업로드 진행 표시. 완료 아이콘은 잠시 뒤 삭제 버튼으로 바꾼다. */\n status?: FileItemStatus;\n error?: never;\n }\n | {\n status?: never;\n /** 오류 문구. 「형식이 올바르지 않습니다」처럼 **무엇이 틀렸는지** 밝힌다(가이드 요구). */\n error: ReactNode;\n }\n );\n\n/** 유니온은 그대로 구조 분해가 안 된다 — 넓은 쪽으로 한 번 받아 쓴다. */\ntype FileItemAllProps = FileItemBaseProps & {\n status?: FileItemStatus;\n error?: ReactNode;\n};\n\nexport function FileItem(props: FileItemProps) {\n const {\n name,\n type,\n size,\n status,\n error,\n onDelete,\n deleteLabel = '삭제',\n downloadHref,\n downloadLabel = '다운로드',\n viewHref,\n viewLabel = '바로보기',\n actionSize = 'medium',\n stackOnMobile,\n className,\n children,\n ...rest\n }: FileItemAllProps = props;\n const uid = idBase(useId());\n const nameId = `file-name-${uid}`;\n const btn = cx('krds-btn', actionSize, 'text');\n const hasDefaultAction = Boolean(status || downloadHref || viewHref || onDelete);\n\n // 이름과 버튼을 `aria-describedby` 로 잇는다(가이드 요구) — 어느 파일의 버튼인지 읽어 준다.\n const actions = children ?? (hasDefaultAction ? (\n <>\n {status === 'uploading' && (\n <span className=\"krds-spinner\" role=\"status\">\n <span className=\"sr-only\">업로드 중</span>\n </span>\n )}\n {status === 'complete' && (\n <span className=\"ico-invalid complete\">\n <em className=\"sr-only\">업로드 완료</em>\n </span>\n )}\n {downloadHref && (\n <a className={btn} href={downloadHref} download aria-describedby={nameId}>\n {downloadLabel}\n <i className=\"svg-icon ico-down\" aria-hidden=\"true\" />\n </a>\n )}\n {viewHref && (\n <a className={btn} href={viewHref} aria-describedby={nameId}>\n {viewLabel}\n <i className=\"svg-icon ico-angle right\" aria-hidden=\"true\" />\n </a>\n )}\n {onDelete && (\n <button type=\"button\" className={btn} onClick={onDelete} aria-describedby={nameId}>\n {deleteLabel}\n <i className=\"svg-icon ico-delete-fill\" aria-hidden=\"true\" />\n </button>\n )}\n </>\n ) : null);\n\n return (\n <li className={cx(error ? 'is-error' : undefined, className)} {...rest}>\n <div className={cx('file-info', stackOnMobile && 'm-column')}>\n <div className=\"file-name\" id={nameId}>\n {formatFileLabel({ name, type, size })}\n </div>\n {actions ? <div className=\"btn-wrap\">{actions}</div> : null}\n </div>\n {error ? <p className=\"file-hint-invalid\">{error}</p> : null}\n </li>\n );\n}\n"],"mappings":";AA+GQ,SAuOF,UAtOc,KADZ;AA7GR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,UAAyB;AA2BlC,SAAS,UAAa,KAAyB,MAAsB;AACnE,MAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,WAC9B,IAAK,CAAC,IAA8B,UAAU;AACzD;AAGA,SAAS,OAAO,KAAqB;AACnC,SAAO,IAAI,QAAQ,mBAAmB,EAAE,KAAK;AAC/C;AAGA,SAAS,YAAY,MAAkC;AACrD,QAAM,IAAI,0BAA0B,KAAK,IAAI;AAC7C,SAAO,IAAI,EAAE,CAAC,EAAE,YAAY,IAAI;AAClC;AAQO,SAAS,eAAe,OAAuB;AACpD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,EAAG,QAAO;AACjD,MAAI,QAAQ,OAAO,KAAM,QAAO,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,IAAI,CAAC,CAAC;AACxE,MAAI,QAAQ,OAAO,OAAO,KAAM,QAAO,IAAI,QAAQ,OAAO,MAAM,QAAQ,CAAC,CAAC;AAC1E,SAAO,IAAI,QAAQ,OAAO,OAAO,MAAM,QAAQ,CAAC,CAAC;AACnD;AAQO,SAAS,gBAAgB,MAA8D;AAC5F,QAAM,EAAE,MAAM,MAAM,KAAK,IAAI;AAC7B,QAAM,OAAO,OAAO,SAAY,YAAY,IAAI;AAChD,QAAM,QAAQ,OAAO,KAAK,MAAM,GAAG,KAAK,SAAS,KAAK,SAAS,CAAC,IAAI;AACpE,QAAM,OAAO,CAAC,QAAQ,MAAM,OAAO,SAAS,WAAW,eAAe,IAAI,IAAI,EAAE,EAAE,OAAO,OAAO;AAChG,SAAO,KAAK,SAAS,IAAI,GAAG,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC,MAAM;AAC7D;AAiBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,SAAS,MAAM;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE,qBAAC,SAAI,WAAW,GAAG,oBAAoB,YAAY,QAAQ,SAAS,GAAI,GAAG,MACvE;AAAA,cAAS,gBACT,qBAAC,SAAI,WAAU,aACZ;AAAA,eAAS,oBAAC,OAAI,WAAU,OAAO,iBAAM;AAAA,MACrC,eACC,oBAAC,SAAI,IAAI,eACN,iBAAO,gBAAgB,WAAW,oBAAC,OAAG,uBAAY,IAAO,aAC5D;AAAA,OAEJ;AAAA,IAED;AAAA,KACH;AAEJ;AAwCO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAM,UAAU,MAAM,eAAe,GAAG;AACxC,QAAM,SAAS,mBAAmB,GAAG;AACrC,QAAM,QAAQ,OAAgC,IAAI;AAClD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,QAAM,QAAQ,OAAO,CAAC;AAEtB,QAAM,WAAW;AAAA,IACf,CAAC,SAAkC;AACjC,YAAM,UAAU;AAChB,gBAAU,UAAU,IAAI;AAAA,IAC1B;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,OAAO,CAAC,SAAsC;AAClD,QAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAChC,UAAM,QAAQ,MAAM,KAAK,IAAI;AAC7B,cAAU,WAAW,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC;AAAA,EAChD;AAEA,QAAM,eAAqD,CAAC,MAAM;AAChE,eAAW,CAAC;AACZ,SAAK,EAAE,cAAc,KAAK;AAC1B,QAAI,iBAAkB,GAAE,cAAc,QAAQ;AAAA,EAChD;AAEA,QAAM,QAAQ,CAAC,MAAiC;AAC9C,QAAI,SAAU;AACd,MAAE,eAAe;AACjB,UAAM,WAAW;AACjB,gBAAY,IAAI;AAAA,EAClB;AACA,QAAM,OAAO,CAAC,MAAiC;AAC7C,QAAI,SAAU;AAEd,MAAE,eAAe;AACjB,QAAI,EAAE,aAAc,GAAE,aAAa,aAAa;AAAA,EAClD;AACA,QAAM,QAAQ,MAAM;AAClB,QAAI,SAAU;AACd,UAAM,UAAU,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC;AAC7C,QAAI,MAAM,YAAY,EAAG,aAAY,KAAK;AAAA,EAC5C;AACA,QAAM,OAAO,CAAC,MAAiC;AAC7C,QAAI,SAAU;AACd,MAAE,eAAe;AACjB,UAAM,UAAU;AAChB,gBAAY,KAAK;AACjB,SAAK,EAAE,cAAc,KAAK;AAAA,EAC5B;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,eAAe,YAAY,UAAU,SAAS;AAAA,MAC3D,GAAG;AAAA,MACJ,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,QAAQ;AAAA,MAER;AAAA,4BAAC,OAAE,WAAU,OAAM,IAAI,QACpB,gBACH;AAAA,QACA,qBAAC,SAAI,WAAU,wBAEb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,IAAI;AAAA,cACJ;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,UAAU;AAAA,cACV,oBAAkB,GAAG,QAAQ,WAAW;AAAA,cACxC,KAAK;AAAA,cACL,UAAU;AAAA;AAAA,UACZ;AAAA,UACA,oBAAC,WAAM,SAAS,SACd;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAW,GAAG,YAAY,YAAY,aAAa;AAAA,cACnD;AAAA,cAEA,SAAS,MAAM,MAAM,SAAS,MAAM;AAAA,cAEpC;AAAA,oCAAC,OAAE,WAAU,uBAAsB,eAAY,QAAO;AAAA,gBACrD;AAAA;AAAA;AAAA,UACH,GACF;AAAA,WACF;AAAA,QACC;AAAA;AAAA;AAAA,EACH;AAEJ;AA+CO,SAAS,SAAS,OAAsB;AAC7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAsB;AACtB,QAAM,QACJ,YACC,OAAO,UAAU,WAChB,iCACE;AAAA,yBAAC,UAAK,WAAU,WACb;AAAA;AAAA,MACA;AAAA,OACH;AAAA,IACC,OAAO,QAAQ,YAAY,MAAM,GAAG,GAAG,IAAI;AAAA,KAC9C,IACE;AAEN,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,aAAa,GAC1C;AAAA,aAAS,oBAAC,SAAI,WAAU,SAAS,iBAAM;AAAA,IACxC,oBAAC,QAAG,WAAW,GAAG,eAAe,SAAS,GAAI,GAAG,MAC9C,UACH;AAAA,IACC,eACC,oBAAC,SAAI,WAAU,qBACb,+BAAC,YAAO,MAAK,UAAS,WAAU,4BAA2B,SAAS,aACjE;AAAA;AAAA,MACD,oBAAC,OAAE,WAAU,4BAA2B,eAAY,QAAO;AAAA,OAC7D,GACF;AAAA,KAEJ;AAEJ;AAuDO,SAAS,SAAS,OAAsB;AAC7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAsB;AACtB,QAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAM,SAAS,aAAa,GAAG;AAC/B,QAAM,MAAM,GAAG,YAAY,YAAY,MAAM;AAC7C,QAAM,mBAAmB,QAAQ,UAAU,gBAAgB,YAAY,QAAQ;AAG/E,QAAM,UAAU,aAAa,mBAC3B,iCACG;AAAA,eAAW,eACV,oBAAC,UAAK,WAAU,gBAAe,MAAK,UAClC,8BAAC,UAAK,WAAU,WAAU,uCAAK,GACjC;AAAA,IAED,WAAW,cACV,oBAAC,UAAK,WAAU,wBACd,8BAAC,QAAG,WAAU,WAAU,6CAAM,GAChC;AAAA,IAED,gBACC,qBAAC,OAAE,WAAW,KAAK,MAAM,cAAc,UAAQ,MAAC,oBAAkB,QAC/D;AAAA;AAAA,MACD,oBAAC,OAAE,WAAU,qBAAoB,eAAY,QAAO;AAAA,OACtD;AAAA,IAED,YACC,qBAAC,OAAE,WAAW,KAAK,MAAM,UAAU,oBAAkB,QAClD;AAAA;AAAA,MACD,oBAAC,OAAE,WAAU,4BAA2B,eAAY,QAAO;AAAA,OAC7D;AAAA,IAED,YACC,qBAAC,YAAO,MAAK,UAAS,WAAW,KAAK,SAAS,UAAU,oBAAkB,QACxE;AAAA;AAAA,MACD,oBAAC,OAAE,WAAU,4BAA2B,eAAY,QAAO;AAAA,OAC7D;AAAA,KAEJ,IACE;AAEJ,SACE,qBAAC,QAAG,WAAW,GAAG,QAAQ,aAAa,QAAW,SAAS,GAAI,GAAG,MAChE;AAAA,yBAAC,SAAI,WAAW,GAAG,aAAa,iBAAiB,UAAU,GACzD;AAAA,0BAAC,SAAI,WAAU,aAAY,IAAI,QAC5B,0BAAgB,EAAE,MAAM,MAAM,KAAK,CAAC,GACvC;AAAA,MACC,UAAU,oBAAC,SAAI,WAAU,YAAY,mBAAQ,IAAS;AAAA,OACzD;AAAA,IACC,QAAQ,oBAAC,OAAE,WAAU,qBAAqB,iBAAM,IAAO;AAAA,KAC1D;AAEJ;","names":[]}
@@ -0,0 +1,155 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { type KrdsIcon } from '../lib/krds';
3
+ /**
4
+ * 푸터 — KRDS `#krds-footer`.
5
+ *
6
+ * 조각을 규격 순서대로 조립해 쓴다 — 로고 → 연락처 → 유틸리티 링크 → 정책 링크 → 저작권 →
7
+ * 운영기관 식별자. **순서를 바꾸지 마라**(규격의 금지 사항).
8
+ * 아이디 선택자라 한 화면에 하나만 두고, 모든 조각을 `Footer` 안에 둔다.
9
+ * 좁은 화면에서 세로로 바뀌어도 DOM 순서는 같다 — 폭마다 다른 순서를 렌더하지 않는다.
10
+ *
11
+ * @example
12
+ * <Footer>
13
+ * <FooterInner>
14
+ * <FooterLogo label="○○기관"><img src="/site/logo.svg" alt="" /></FooterLogo>
15
+ * <FooterContent>
16
+ * <FooterInfo>
17
+ * <FooterAddress>(00000) 모루시 …</FooterAddress>
18
+ * <FooterContacts><FooterContact note="(평일 09~18시)">대표전화 1577-0000</FooterContact></FooterContacts>
19
+ * </FooterInfo>
20
+ * <FooterLinks>
21
+ * <FooterGoLinks><FooterGoLink href="/map">찾아오시는 길</FooterGoLink></FooterGoLinks>
22
+ * </FooterLinks>
23
+ * </FooterContent>
24
+ * <FooterBottom>
25
+ * <FooterBottomText>
26
+ * <FooterMenu>
27
+ * <FooterMenuLink href="/policy/privacy" point>개인정보처리방침</FooterMenuLink>
28
+ * <FooterMenuLink href="/policy/copyright">저작권 정책</FooterMenuLink>
29
+ * </FooterMenu>
30
+ * <FooterCopyright>© 2026 …</FooterCopyright>
31
+ * </FooterBottomText>
32
+ * <FooterIdentifier orgName="○○기관" />
33
+ * </FooterBottom>
34
+ * </FooterInner>
35
+ * </Footer>
36
+ *
37
+ * 자세히: docs/krds/09-부품-노트.md#푸터
38
+ */
39
+ /** 푸터 바깥틀 — `<footer id="krds-footer">`. 한 화면에 하나만 둔다. */
40
+ export type FooterProps = Omit<ComponentPropsWithRef<'footer'>, 'id'>;
41
+ export declare function Footer({ className, children, ...rest }: FooterProps): import("react").JSX.Element;
42
+ /** 관련 사이트 띠 — 킷 `.foot-quick`. 선택 사항이고, 있으면 푸터의 맨 위다. */
43
+ export declare function FooterQuick({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
44
+ /**
45
+ * 관련 사이트 한 칸 — 킷 `.link`. `href` 를 주면 `<a>`, 없으면 레이어를 여는 `<button>` 이다.
46
+ * 아이콘은 킷이 `::after` 로 붙인다 — 따로 넣지 마라.
47
+ */
48
+ export type FooterQuickLinkProps = (Omit<ComponentPropsWithRef<'button'>, 'href'> & {
49
+ href?: undefined;
50
+ }) | (ComponentPropsWithRef<'a'> & {
51
+ href: string;
52
+ });
53
+ export declare function FooterQuickLink(props: FooterQuickLinkProps): import("react").JSX.Element;
54
+ /** 푸터 본문 폭 — 킷 `#krds-footer > .inner`. **`Footer` 의 직계 자식이어야 한다.** */
55
+ export declare function FooterInner({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
56
+ /**
57
+ * 서비스 로고 — 킷 `.f-logo`. 킷이 KRDS 로고를 배경 이미지로 그린다.
58
+ * `children` 에 `<img>` 를 넣으려면 그 배경을 지워야 로고가 겹치지 않는다.
59
+ */
60
+ export type FooterLogoProps = ComponentPropsWithRef<'div'> & {
61
+ /** 스크린리더가 읽을 기관·서비스 이름. 로고가 이미지든 배경이든 반드시 준다. */
62
+ label: string;
63
+ };
64
+ export declare function FooterLogo({ label, className, children, ...rest }: FooterLogoProps): import("react").JSX.Element;
65
+ /** 로고 아래 3단(연락처 · 고객센터 · 유틸리티 링크) — 킷 `.f-cnt`. 폭 배분은 킷이 한다. */
66
+ export declare function FooterContent({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
67
+ /** 연락처 묶음 — 킷 `.f-info`. `FooterAddress` 와 `FooterContacts` 가 들어간다. */
68
+ export declare function FooterInfo({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
69
+ /** 주소 한 줄 — 킷 `.info-addr`. */
70
+ export declare function FooterAddress({ className, children, ...rest }: ComponentPropsWithRef<'p'>): import("react").JSX.Element;
71
+ /** 연락 수단 목록 — 킷 `.info-cs`. 안에는 `FooterContact` 만 넣는다. */
72
+ export declare function FooterContacts({ className, children, ...rest }: ComponentPropsWithRef<'ul'>): import("react").JSX.Element;
73
+ /** 연락 수단 한 줄 — 킷 `.info-cs > li > .key-info` (+ 선택 `.more-info`). */
74
+ export type FooterContactProps = Omit<ComponentPropsWithRef<'li'>, 'children'> & {
75
+ /** 굵게 나오는 본문. 예: `대표전화 1577-1000` */
76
+ children: ReactNode;
77
+ /** 괄호 보조 설명. 예: `(유료, 평일 09시~18시)` — 굵기가 본문으로 돌아간다. */
78
+ note?: ReactNode;
79
+ /** 구분선 뒤에 붙는 덧붙임. 예: 팩스·야간 안내. */
80
+ more?: ReactNode;
81
+ };
82
+ export declare function FooterContact({ children, note, more, className, ...rest }: FooterContactProps): import("react").JSX.Element;
83
+ /** 고객센터 안내 단 — 킷 `.f-cs`. 안에는 `FooterCsItem` 을 넣는다. */
84
+ export declare function FooterCs({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
85
+ /** 고객센터 항목 한 벌 — 킷 `.f-cs dl` + `.info-tit`. */
86
+ export type FooterCsItemProps = Omit<ComponentPropsWithRef<'dl'>, 'title' | 'children'> & {
87
+ /** 항목 이름. 예: `고객센터` */
88
+ title: ReactNode;
89
+ /** 값. 예: `1577-1000` */
90
+ children: ReactNode;
91
+ };
92
+ export declare function FooterCsItem({ title, children, className, ...rest }: FooterCsItemProps): import("react").JSX.Element;
93
+ /** 유틸리티 링크 묶음 — 킷 `.f-link`. `FooterGoLinks` 와 `FooterSnsLinks` 가 들어간다. */
94
+ export declare function FooterLinks({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
95
+ /** 바로가기 링크 묶음 — 킷 `.link-go`. */
96
+ export declare function FooterGoLinks({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
97
+ /**
98
+ * 바로가기 링크 한 줄 — 킷 `.link-go > a.krds-btn.medium.text`.
99
+ * 여기에 공용 `LinkButton` 을 쓰지 마라 — 킷이 이 자리의 크기를 따로 잡아 어긋난다.
100
+ */
101
+ export type FooterGoLinkProps = ComponentPropsWithRef<'a'> & {
102
+ /** 뒤에 붙는 킷 아이콘 이름(`ico-` 뺀 것). 기본은 샘플의 `angle` + 오른쪽 회전. */
103
+ icon?: KrdsIcon | null;
104
+ /** 아이콘 회전. 방향은 이름의 일부가 아니라 별도 클래스다 — `icon="angle right"` 로 쓰지 마라. */
105
+ iconDirection?: 'up' | 'down' | 'left' | 'right';
106
+ };
107
+ export declare function FooterGoLink({ icon, iconDirection, className, children, target, title, ...rest }: FooterGoLinkProps): import("react").JSX.Element;
108
+ /** 소셜 채널 묶음 — 킷 `.link-sns`. */
109
+ export declare function FooterSnsLinks({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
110
+ /**
111
+ * 소셜 채널 아이콘 링크 — 킷 `a.krds-btn.xlarge.icon.border`. 채널 이름(`label`)이 필수다.
112
+ * 킷에 있는 아이콘은 `instagram` · `youtube` · `sns-x` · `facebook` · `blog` 다.
113
+ */
114
+ export type FooterSnsLinkProps = Omit<ComponentPropsWithRef<'a'>, 'children'> & {
115
+ /** 스크린리더가 읽을 채널 이름. 예: `인스타그램` */
116
+ label: string;
117
+ /** 킷 아이콘 이름(`ico-` 뺀 것). 예: `instagram` */
118
+ icon: KrdsIcon;
119
+ };
120
+ export declare function FooterSnsLink({ label, icon, className, target, title, ...rest }: FooterSnsLinkProps): import("react").JSX.Element;
121
+ /** 맨 아래 줄 — 킷 `.f-btm`. 안에 `FooterBottomText` 와 `FooterIdentifier` 를 넣는다. */
122
+ export declare function FooterBottom({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
123
+ /** 정책 링크 + 저작권 한 줄 — 킷 `.f-btm-text`. PC 에서 좌우 양끝으로 벌어진다. */
124
+ export declare function FooterBottomText({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
125
+ /** 정책 링크 묶음 — 킷 `.f-menu`. */
126
+ export declare function FooterMenu({ className, children, ...rest }: ComponentPropsWithRef<'div'>): import("react").JSX.Element;
127
+ /** 정책 링크 하나. **개인정보처리방침에는 `point` 를 준다** — 규격이 다른 고지사항과 구분하라고 한다. */
128
+ export type FooterMenuLinkProps = ComponentPropsWithRef<'a'> & {
129
+ /** 개인정보처리방침처럼 다른 고지사항과 구분해야 하는 링크. 굵게 그린다. */
130
+ point?: boolean;
131
+ };
132
+ export declare function FooterMenuLink({ point, className, children, ...rest }: FooterMenuLinkProps): import("react").JSX.Element;
133
+ /** 저작권 — 킷 `.f-copy`. 규격상 푸터의 마지막 정보다(식별자는 그 뒤의 별도 구획). */
134
+ export declare function FooterCopyright({ className, children, ...rest }: ComponentPropsWithRef<'p'>): import("react").JSX.Element;
135
+ /**
136
+ * 운영기관 식별자 — KRDS `.krds-identifier`. `FooterBottom` 의 **마지막 자식**으로 넣는다.
137
+ *
138
+ * 로고는 서비스 로고가 아니라 **운영 주체 기관**의 로고다(서비스 로고는 `FooterLogo`).
139
+ * 문구 형식과 요소 배치는 규격이 변형을 금지한다 — `children` 은 번역을 넣는 자리다.
140
+ * 푸터 밖에 독립해 두려면 `ui/identifier` 의 `Identifier` 를 쓴다.
141
+ */
142
+ /** KRDS 가 규정한 식별자 문구. 기관명만 갈아 끼운다. */
143
+ export declare function identifierText(orgName: string): string;
144
+ export type FooterIdentifierProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {
145
+ /** 운영 기관 이름. 로고의 대체 텍스트로도 쓴다. */
146
+ orgName: string;
147
+ /** 로고 자리에 넣을 것(보통 `<img>`). 생략하면 킷 배경 로고만 나온다. */
148
+ logo?: ReactNode;
149
+ /** 안내 문구. 생략하면 `identifierText(orgName)`. 번역본 말고 다른 문구를 넣지 마라. */
150
+ children?: ReactNode;
151
+ /** 주면 `<section aria-label>` 로 랜드마크가 된다. 본문과 같은 문구를 넣지 마라(두 번 읽힌다). */
152
+ label?: string;
153
+ };
154
+ export declare function FooterIdentifier({ orgName, logo, label, className, children, ...rest }: FooterIdentifierProps): import("react").JSX.Element;
155
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/footer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;AAEtE,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,+BAOnE;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMzF;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC,GACtE,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAS1D;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMzF;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG;IAC3D,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,+BAOlF;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAM3F;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMxF;AAED,+BAA+B;AAC/B,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,GAAG,CAAC,+BAMzF;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,IAAI,CAAC,+BAM3F;AAED,qEAAqE;AACrE,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,GAAG;IAC/E,qCAAqC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,uDAAuD;IACvD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kCAAkC;IAClC,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,kBAAkB,+BAU7F;AAED,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMtF;AAED,gDAAgD;AAChD,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IACxF,uBAAuB;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,wBAAwB;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,+BAOtF;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMzF;AAED,iCAAiC;AACjC,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAM3F;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,GAAG,CAAC,GAAG;IAC3D,4DAA4D;IAC5D,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,sEAAsE;IACtE,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAClD,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,IAAc,EACd,aAAuB,EACvB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,KAAK,EACL,GAAG,IAAI,EACR,EAAE,iBAAiB,+BAanB;AAED,gCAAgC;AAChC,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAM5F;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG;IAC9E,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,IAAI,EACJ,SAAS,EACT,MAAiB,EACjB,KAAgB,EAChB,GAAG,IAAI,EACR,EAAE,kBAAkB,+BAYpB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAM1F;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAM9F;AAED,8BAA8B;AAC9B,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,+BAMxF;AAED,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,GAAG,CAAC,GAAG;IAC7D,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,mBAAmB,+BAM1F;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,CAAC,GAAG,CAAC,+BAM3F;AAED;;;;;;GAMG;AAEH,sCAAsC;AACtC,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACnF,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,IAAI,EACJ,KAAK,EACL,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,qBAAqB,+BA2BvB"}
@@ -0,0 +1,186 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../lib/krds.js";
3
+ function Footer({ className, children, ...rest }) {
4
+ return (
5
+ // 모양이 `#krds-footer` 아이디에 걸려 있어 뿌리에는 킷 클래스가 없다. 빈 class 를 남기지 않는다.
6
+ /* @__PURE__ */ jsx("footer", { id: "krds-footer", className: cx(className) || void 0, ...rest, children })
7
+ );
8
+ }
9
+ function FooterQuick({ className, children, ...rest }) {
10
+ return /* @__PURE__ */ jsx("div", { className: cx("foot-quick", className), ...rest, children: /* @__PURE__ */ jsx("div", { className: "inner", children }) });
11
+ }
12
+ function FooterQuickLink(props) {
13
+ const cls = cx("link", props.className);
14
+ if (props.href !== void 0) {
15
+ const { className: _a, ...rest2 } = props;
16
+ return /* @__PURE__ */ jsx("a", { className: cls, ...rest2 });
17
+ }
18
+ const { className: _b, ...rest } = props;
19
+ return /* @__PURE__ */ jsx("button", { type: "button", className: cls, ...rest });
20
+ }
21
+ function FooterInner({ className, children, ...rest }) {
22
+ return /* @__PURE__ */ jsx("div", { className: cx("inner", className), ...rest, children });
23
+ }
24
+ function FooterLogo({ label, className, children, ...rest }) {
25
+ return /* @__PURE__ */ jsxs("div", { className: cx("f-logo", className), ...rest, children: [
26
+ children,
27
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
28
+ ] });
29
+ }
30
+ function FooterContent({ className, children, ...rest }) {
31
+ return /* @__PURE__ */ jsx("div", { className: cx("f-cnt", className), ...rest, children });
32
+ }
33
+ function FooterInfo({ className, children, ...rest }) {
34
+ return /* @__PURE__ */ jsx("div", { className: cx("f-info", className), ...rest, children });
35
+ }
36
+ function FooterAddress({ className, children, ...rest }) {
37
+ return /* @__PURE__ */ jsx("p", { className: cx("info-addr", className), ...rest, children });
38
+ }
39
+ function FooterContacts({ className, children, ...rest }) {
40
+ return /* @__PURE__ */ jsx("ul", { className: cx("info-cs", className), ...rest, children });
41
+ }
42
+ function FooterContact({ children, note, more, className, ...rest }) {
43
+ return /* @__PURE__ */ jsxs("li", { className: cx(className) || void 0, ...rest, children: [
44
+ /* @__PURE__ */ jsxs("span", { className: "key-info", children: [
45
+ /* @__PURE__ */ jsx("strong", { children }),
46
+ note != null && /* @__PURE__ */ jsx("span", { children: note })
47
+ ] }),
48
+ more != null && /* @__PURE__ */ jsx("span", { className: "more-info", children: more })
49
+ ] });
50
+ }
51
+ function FooterCs({ className, children, ...rest }) {
52
+ return /* @__PURE__ */ jsx("div", { className: cx("f-cs", className), ...rest, children });
53
+ }
54
+ function FooterCsItem({ title, children, className, ...rest }) {
55
+ return /* @__PURE__ */ jsxs("dl", { className: cx(className) || void 0, ...rest, children: [
56
+ /* @__PURE__ */ jsx("dt", { className: "info-tit", children: title }),
57
+ /* @__PURE__ */ jsx("dd", { children })
58
+ ] });
59
+ }
60
+ function FooterLinks({ className, children, ...rest }) {
61
+ return /* @__PURE__ */ jsx("div", { className: cx("f-link", className), ...rest, children });
62
+ }
63
+ function FooterGoLinks({ className, children, ...rest }) {
64
+ return /* @__PURE__ */ jsx("div", { className: cx("link-go", className), ...rest, children });
65
+ }
66
+ function FooterGoLink({
67
+ icon = "angle",
68
+ iconDirection = "right",
69
+ className,
70
+ children,
71
+ target,
72
+ title,
73
+ ...rest
74
+ }) {
75
+ return /* @__PURE__ */ jsxs(
76
+ "a",
77
+ {
78
+ className: cx("krds-btn", "medium", "text", className),
79
+ target,
80
+ title: title ?? (target === "_blank" ? "\uC0C8 \uCC3D \uC5F4\uAE30" : void 0),
81
+ ...rest,
82
+ children: [
83
+ children,
84
+ icon && /* @__PURE__ */ jsx("i", { className: cx(`svg-icon ico-${icon}`, iconDirection), "aria-hidden": "true" })
85
+ ]
86
+ }
87
+ );
88
+ }
89
+ function FooterSnsLinks({ className, children, ...rest }) {
90
+ return /* @__PURE__ */ jsx("div", { className: cx("link-sns", className), ...rest, children });
91
+ }
92
+ function FooterSnsLink({
93
+ label,
94
+ icon,
95
+ className,
96
+ target = "_blank",
97
+ title = "\uC0C8 \uCC3D \uC5F4\uAE30",
98
+ ...rest
99
+ }) {
100
+ return /* @__PURE__ */ jsxs(
101
+ "a",
102
+ {
103
+ className: cx("krds-btn", "xlarge", "icon", "border", className),
104
+ target,
105
+ title,
106
+ ...rest,
107
+ children: [
108
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
109
+ /* @__PURE__ */ jsx("i", { className: `svg-icon ico-${icon}`, "aria-hidden": "true" })
110
+ ]
111
+ }
112
+ );
113
+ }
114
+ function FooterBottom({ className, children, ...rest }) {
115
+ return /* @__PURE__ */ jsx("div", { className: cx("f-btm", className), ...rest, children });
116
+ }
117
+ function FooterBottomText({ className, children, ...rest }) {
118
+ return /* @__PURE__ */ jsx("div", { className: cx("f-btm-text", className), ...rest, children });
119
+ }
120
+ function FooterMenu({ className, children, ...rest }) {
121
+ return /* @__PURE__ */ jsx("div", { className: cx("f-menu", className), ...rest, children });
122
+ }
123
+ function FooterMenuLink({ point, className, children, ...rest }) {
124
+ return /* @__PURE__ */ jsx("a", { className: cx(point && "point font-bold", className) || void 0, ...rest, children });
125
+ }
126
+ function FooterCopyright({ className, children, ...rest }) {
127
+ return /* @__PURE__ */ jsx("p", { className: cx("f-copy", className), ...rest, children });
128
+ }
129
+ function identifierText(orgName) {
130
+ return `\uC774 \uB204\uB9AC\uC9D1\uC740 ${orgName} \uB204\uB9AC\uC9D1\uC785\uB2C8\uB2E4.`;
131
+ }
132
+ function FooterIdentifier({
133
+ orgName,
134
+ logo,
135
+ label,
136
+ className,
137
+ children,
138
+ ...rest
139
+ }) {
140
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
141
+ /* @__PURE__ */ jsxs("span", { className: "logo", children: [
142
+ logo,
143
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: orgName })
144
+ ] }),
145
+ /* @__PURE__ */ jsx("span", { className: "ban-txt", children: children ?? identifierText(orgName) })
146
+ ] });
147
+ if (label) {
148
+ return /* @__PURE__ */ jsx(
149
+ "section",
150
+ {
151
+ className: cx("krds-identifier", className),
152
+ "aria-label": label,
153
+ ...rest,
154
+ children: inner
155
+ }
156
+ );
157
+ }
158
+ return /* @__PURE__ */ jsx("div", { className: cx("krds-identifier", className), ...rest, children: inner });
159
+ }
160
+ export {
161
+ Footer,
162
+ FooterAddress,
163
+ FooterBottom,
164
+ FooterBottomText,
165
+ FooterContact,
166
+ FooterContacts,
167
+ FooterContent,
168
+ FooterCopyright,
169
+ FooterCs,
170
+ FooterCsItem,
171
+ FooterGoLink,
172
+ FooterGoLinks,
173
+ FooterIdentifier,
174
+ FooterInfo,
175
+ FooterInner,
176
+ FooterLinks,
177
+ FooterLogo,
178
+ FooterMenu,
179
+ FooterMenuLink,
180
+ FooterQuick,
181
+ FooterQuickLink,
182
+ FooterSnsLink,
183
+ FooterSnsLinks,
184
+ identifierText
185
+ };
186
+ //# sourceMappingURL=index.js.map