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,198 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useMemo,
10
+ useState
11
+ } from "react";
12
+ import { cx } from "../lib/krds.js";
13
+ const TabContext = createContext(null);
14
+ function useTabContext(name) {
15
+ const ctx = useContext(TabContext);
16
+ if (!ctx) throw new Error(`<${name}> \uC740 <Tab.Root> \uC548\uC5D0\uC11C\uB9CC \uC4F8 \uC218 \uC788\uB2E4.`);
17
+ return ctx;
18
+ }
19
+ function idSafe(value) {
20
+ return encodeURIComponent(value).replace(/%/g, "_");
21
+ }
22
+ function TabRoot({
23
+ value,
24
+ defaultValue,
25
+ onValueChange,
26
+ activation = "manual",
27
+ className,
28
+ children,
29
+ ...rest
30
+ }) {
31
+ const reactId = useId();
32
+ const [inner, setInner] = useState(defaultValue);
33
+ const controlled = value !== void 0;
34
+ const selected = controlled ? value : inner;
35
+ const select = useCallback(
36
+ (next) => {
37
+ if (!controlled) setInner(next);
38
+ onValueChange?.(next);
39
+ },
40
+ [controlled, onValueChange]
41
+ );
42
+ const claimDefault = useCallback(
43
+ (next) => {
44
+ if (controlled) return;
45
+ setInner((prev) => prev ?? next);
46
+ },
47
+ [controlled]
48
+ );
49
+ const ctx = useMemo(
50
+ () => ({
51
+ selected,
52
+ select,
53
+ claimDefault,
54
+ triggerId: (v) => `${reactId}tab-${idSafe(v)}`,
55
+ panelId: (v) => `${reactId}panel-${idSafe(v)}`,
56
+ activation
57
+ }),
58
+ [selected, select, claimDefault, reactId, activation]
59
+ );
60
+ return /* @__PURE__ */ jsx(TabContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { className: cx("krds-tab-area", className), ...rest, children }) });
61
+ }
62
+ function TabList({
63
+ variant = "line",
64
+ full = true,
65
+ label,
66
+ listClassName,
67
+ className,
68
+ children,
69
+ onKeyDown,
70
+ ...rest
71
+ }) {
72
+ const { select, activation } = useTabContext("Tab.List");
73
+ function handleKeyDown(event) {
74
+ onKeyDown?.(event);
75
+ if (event.defaultPrevented) return;
76
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
77
+ const list = event.currentTarget.querySelector('ul[role="tablist"]');
78
+ if (!list) return;
79
+ const buttons = Array.from(
80
+ list.querySelectorAll('button.btn-tab[role="tab"]:not([disabled])')
81
+ );
82
+ const current = event.target.closest("button.btn-tab");
83
+ const from = current ? buttons.indexOf(current) : -1;
84
+ if (from < 0) return;
85
+ let to;
86
+ switch (event.key) {
87
+ case "ArrowRight":
88
+ to = from + 1;
89
+ break;
90
+ case "ArrowLeft":
91
+ to = from - 1;
92
+ break;
93
+ case "Home":
94
+ to = 0;
95
+ break;
96
+ case "End":
97
+ to = buttons.length - 1;
98
+ break;
99
+ default:
100
+ return;
101
+ }
102
+ if (to < 0 || to >= buttons.length) {
103
+ event.preventDefault();
104
+ return;
105
+ }
106
+ event.preventDefault();
107
+ const next = buttons[to];
108
+ next.focus();
109
+ const nextValue = next.dataset.tabValue;
110
+ if (activation === "auto" && nextValue !== void 0) select(nextValue);
111
+ }
112
+ return /* @__PURE__ */ jsx("div", { className: cx("tab", variant, full && "full", className), onKeyDown: handleKeyDown, ...rest, children: /* @__PURE__ */ jsx("ul", { role: "tablist", "aria-label": label, className: listClassName, children }) });
113
+ }
114
+ function TabTrigger({
115
+ value,
116
+ itemClassName,
117
+ className,
118
+ children,
119
+ onClick,
120
+ type = "button",
121
+ ...rest
122
+ }) {
123
+ const { selected, select, claimDefault, triggerId, panelId } = useTabContext("Tab.Trigger");
124
+ const active = selected === value;
125
+ useEffect(() => {
126
+ claimDefault(value);
127
+ }, [claimDefault, value]);
128
+ return /* @__PURE__ */ jsx("li", { role: "none", className: cx(active && "active", itemClassName), children: /* @__PURE__ */ jsxs(
129
+ "button",
130
+ {
131
+ id: triggerId(value),
132
+ role: "tab",
133
+ "aria-selected": active,
134
+ "aria-controls": panelId(value),
135
+ tabIndex: active ? 0 : -1,
136
+ type,
137
+ className: cx("btn-tab", className),
138
+ "data-tab-value": value,
139
+ onClick: (event) => {
140
+ onClick?.(event);
141
+ if (!event.defaultPrevented) select(value);
142
+ },
143
+ ...rest,
144
+ children: [
145
+ children,
146
+ active && /* @__PURE__ */ jsx("i", { className: "sr-only", children: " \uC120\uD0DD\uB428" })
147
+ ]
148
+ }
149
+ ) });
150
+ }
151
+ function TabPanels({ className, children, ...rest }) {
152
+ return /* @__PURE__ */ jsx("div", { className: cx("tab-conts-wrap", className), ...rest, children });
153
+ }
154
+ function TabPanel({
155
+ value,
156
+ srHeading,
157
+ srHeadingLevel = 3,
158
+ keepMounted = true,
159
+ className,
160
+ children,
161
+ ...rest
162
+ }) {
163
+ const { selected, triggerId, panelId } = useTabContext("Tab.Panel");
164
+ const active = selected === value;
165
+ if (!active && !keepMounted) return null;
166
+ const Heading = `h${srHeadingLevel}`;
167
+ return /* @__PURE__ */ jsxs(
168
+ "section",
169
+ {
170
+ id: panelId(value),
171
+ role: "tabpanel",
172
+ "aria-labelledby": triggerId(value),
173
+ className: cx("tab-conts", active && "active", className),
174
+ "data-quick-nav": "false",
175
+ ...rest,
176
+ children: [
177
+ srHeading && /* @__PURE__ */ jsx(Heading, { className: "sr-only", children: srHeading }),
178
+ children
179
+ ]
180
+ }
181
+ );
182
+ }
183
+ const Tab = {
184
+ Root: TabRoot,
185
+ List: TabList,
186
+ Trigger: TabTrigger,
187
+ Panels: TabPanels,
188
+ Panel: TabPanel
189
+ };
190
+ export {
191
+ Tab,
192
+ TabList,
193
+ TabPanel,
194
+ TabPanels,
195
+ TabRoot,
196
+ TabTrigger
197
+ };
198
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/tab/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n type KeyboardEvent as ReactKeyboardEvent,\n} from 'react';\nimport { cx } from '../lib/krds';\n\n/**\n * 탭 — KRDS `krds-tab-area`.\n *\n * 같은 자리에서 내용을 갈아 끼운다. 접었다 펴는 목록은 `ui/accordion` 이다.\n * `Tab.Trigger` 와 `Tab.Panel` 을 같은 `value` 로 짝지운다.\n * **탭에 사용 불가 상태를 쓰지 않는다**(규격) — 내용이 없으면 탭을 지우거나 패널에서 설명한다.\n * 탭이 6개를 넘으면 킷이 가로 스크롤만 시키므로 넘기지 않는 편이 맞다. 세로 탭은 킷에 없다.\n * 비제어로 쓸 때 `defaultValue` 를 생략하면 서버 렌더 결과에 열린 패널이 없다 — 되도록 명시한다.\n *\n * @example\n * <Tab.Root defaultValue=\"online\">\n * <Tab.List label=\"청구 방법\" variant=\"line\">\n * <Tab.Trigger value=\"online\">온라인</Tab.Trigger>\n * <Tab.Trigger value=\"post\">우편·팩스</Tab.Trigger>\n * </Tab.List>\n * <Tab.Panels>\n * <Tab.Panel value=\"online\">…</Tab.Panel>\n * <Tab.Panel value=\"post\">…</Tab.Panel>\n * </Tab.Panels>\n * </Tab.Root>\n *\n * 자세히: docs/krds/09-부품-노트.md#탭\n */\n\n/** 탭 막대 모양. `line` 밑줄형(기본) · `fill` 버튼형. */\nexport type TabVariant = 'line' | 'fill';\n\n/**\n * 화살표로 초점을 옮길 때 곧바로 선택할 것인가.\n * `manual`(기본)이 KRDS 가이드의 동작이고, `auto` 는 패널이 가벼울 때만 쓴다.\n */\nexport type TabActivation = 'manual' | 'auto';\n\ntype TabContextValue = {\n selected: string | undefined;\n select: (value: string) => void;\n /** 첫 `Tab.Trigger` 가 마운트하면서 기본 선택값을 채운다. */\n claimDefault: (value: string) => void;\n triggerId: (value: string) => string;\n panelId: (value: string) => string;\n activation: TabActivation;\n};\n\nconst TabContext = createContext<TabContextValue | null>(null);\n\nfunction useTabContext(name: string): TabContextValue {\n const ctx = useContext(TabContext);\n if (!ctx) throw new Error(`<${name}> 은 <Tab.Root> 안에서만 쓸 수 있다.`);\n return ctx;\n}\n\n/** id 로 쓸 수 있게 다듬는다. 값에 공백·한글·`/` 가 들어와도 찾을 수 있어야 한다. */\nfunction idSafe(value: string): string {\n return encodeURIComponent(value).replace(/%/g, '_');\n}\n\nexport type TabRootProps = Omit<ComponentPropsWithRef<'div'>, 'onChange'> & {\n /** 제어 모드. 주면 상태를 바깥이 갖는다. */\n value?: string;\n /** 비제어 모드의 첫 선택값. **생략하면 서버 렌더 결과에 열린 패널이 없다** — 되도록 명시한다. */\n defaultValue?: string;\n /** 선택이 바뀔 때. */\n onValueChange?: (value: string) => void;\n /** 화살표 이동이 곧바로 선택할지. 기본 `manual`. */\n activation?: TabActivation;\n};\n\n/** 탭 바깥틀 — 킷 `krds-tab-area`. 안에 `Tab.List` 와 `Tab.Panels` 를 둔다. */\nexport function TabRoot({\n value,\n defaultValue,\n onValueChange,\n activation = 'manual',\n className,\n children,\n ...rest\n}: TabRootProps) {\n const reactId = useId();\n const [inner, setInner] = useState<string | undefined>(defaultValue);\n\n const controlled = value !== undefined;\n const selected = controlled ? value : inner;\n\n const select = useCallback(\n (next: string) => {\n if (!controlled) setInner(next);\n onValueChange?.(next);\n },\n [controlled, onValueChange],\n );\n\n // Trigger 의 마운트 효과가 다시 돌지 않도록 신원을 고정한다.\n const claimDefault = useCallback(\n (next: string) => {\n if (controlled) return;\n // 마운트 순서대로 도니 실제로 값을 채우는 건 **첫 번째** Trigger 뿐이다.\n setInner((prev) => prev ?? next);\n },\n [controlled],\n );\n\n const ctx = useMemo<TabContextValue>(\n () => ({\n selected,\n select,\n claimDefault,\n triggerId: (v) => `${reactId}tab-${idSafe(v)}`,\n panelId: (v) => `${reactId}panel-${idSafe(v)}`,\n activation,\n }),\n [selected, select, claimDefault, reactId, activation],\n );\n\n return (\n <TabContext.Provider value={ctx}>\n <div className={cx('krds-tab-area', className)} {...rest}>\n {children}\n </div>\n </TabContext.Provider>\n );\n}\n\nexport type TabListProps = ComponentPropsWithRef<'div'> & {\n /** 탭 막대 모양. 기본 `line` — 생략할 수 없다(둘 다 없으면 활성 표시가 사라진다). */\n variant?: TabVariant;\n /** 탭이 가로를 꽉 채운다. 기본 true — false 면 `line` 이 밑줄 대신 테두리 상자로 그려진다. */\n full?: boolean;\n /** 탭 막대의 이름(`aria-label`). */\n label?: string;\n /** `<ul role=\"tablist\">` 에 얹을 클래스. */\n listClassName?: string;\n};\n\n/**\n * 탭 막대 — 킷 `.tab.line.full > ul[role=tablist]`.\n *\n * ←→ 로 초점을 옮기고 Home/End 로 양 끝으로 간다(순환하지 않는다).\n * **모든 탭이 Tab 순서에 남는다** — KRDS 가 「Tab/Shift+Tab 으로 접근」을 못박아 로빙 tabindex 를\n * 쓰지 않는다.\n */\nexport function TabList({\n variant = 'line',\n full = true,\n label,\n listClassName,\n className,\n children,\n onKeyDown,\n ...rest\n}: TabListProps) {\n const { select, activation } = useTabContext('Tab.List');\n\n function handleKeyDown(event: ReactKeyboardEvent<HTMLDivElement>) {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n\n const list = event.currentTarget.querySelector('ul[role=\"tablist\"]');\n if (!list) return;\n // 사용 불가 탭은 건너뛴다(규격상 없어야 하지만 들어오면 초점이 갇힌다).\n const buttons = Array.from(\n list.querySelectorAll<HTMLButtonElement>('button.btn-tab[role=\"tab\"]:not([disabled])'),\n );\n const current = (event.target as HTMLElement).closest<HTMLButtonElement>('button.btn-tab');\n const from = current ? buttons.indexOf(current) : -1;\n if (from < 0) return;\n\n let to: number;\n switch (event.key) {\n case 'ArrowRight':\n to = from + 1;\n break;\n case 'ArrowLeft':\n to = from - 1;\n break;\n case 'Home':\n to = 0;\n break;\n case 'End':\n to = buttons.length - 1;\n break;\n default:\n return;\n }\n // 킷과 같이 양 끝에서 멈춘다(순환하지 않는다).\n if (to < 0 || to >= buttons.length) {\n event.preventDefault();\n return;\n }\n\n event.preventDefault();\n const next = buttons[to];\n next.focus();\n const nextValue = next.dataset.tabValue;\n if (activation === 'auto' && nextValue !== undefined) select(nextValue);\n }\n\n return (\n <div className={cx('tab', variant, full && 'full', className)} onKeyDown={handleKeyDown} {...rest}>\n <ul role=\"tablist\" aria-label={label} className={listClassName}>\n {children}\n </ul>\n </div>\n );\n}\n\nexport type TabTriggerProps = Omit<ComponentPropsWithRef<'button'>, 'value'> & {\n /** 이 탭이 여는 패널의 값. 같은 값을 `Tab.Panel` 에 준다. */\n value: string;\n /** `<li>` 에 얹을 클래스. `.active` 가 붙는 요소도 버튼이 아니라 이 `<li>` 다. */\n itemClassName?: string;\n};\n\n/**\n * 탭 하나 — 킷 마크업은 `<li>` + `button.btn-tab` 이고, **`role=\"tab\"` 은 버튼에 붙인다.**\n *\n * 킷 원문은 `role=\"tab\"` 과 ARIA 속성을 `<li>` 에 얹고 그 안에 버튼을 넣는데, 그러면\n * 「누를 수 있는 것 안에 누를 수 있는 것」이 되어 axe `nested-interactive`(serious)로 걸린다\n * — 실제로 표준사전 화면에서 3건 잡혔다. ARIA 규격(APG Tabs)도 초점을 받는 요소가 곧 `role=\"tab\"` 이다.\n * 그래서 역할·상태는 버튼으로 옮기고, `<li>` 는 `role=\"none\"` 으로 둔다.\n * 모양은 그대로다 — 킷 CSS 가 보는 `.active` 는 여전히 `<li>` 에 붙는다.\n *\n * 초점은 굴러다닌다(roving tabindex) — 선택된 탭만 Tab 키로 들어오고, 좌우 화살표로 옮긴다(APG).\n * 선택된 탭에는 「선택됨」 문구가 소리로 함께 나간다(직접 짤 때 가장 자주 빠뜨리는 곳이다).\n */\nexport function TabTrigger({\n value,\n itemClassName,\n className,\n children,\n onClick,\n type = 'button',\n ...rest\n}: TabTriggerProps) {\n const { selected, select, claimDefault, triggerId, panelId } = useTabContext('Tab.Trigger');\n const active = selected === value;\n\n // 비제어 + defaultValue 없음이면 첫 탭이 기본값이 된다.\n useEffect(() => {\n claimDefault(value);\n }, [claimDefault, value]);\n\n return (\n <li role=\"none\" className={cx(active && 'active', itemClassName)}>\n <button\n id={triggerId(value)}\n role=\"tab\"\n aria-selected={active}\n aria-controls={panelId(value)}\n tabIndex={active ? 0 : -1}\n type={type}\n className={cx('btn-tab', className)}\n data-tab-value={value}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) select(value);\n }}\n {...rest}\n >\n {children}\n {/* 킷 원문 그대로 — 앞 공백까지 같다. */}\n {active && <i className=\"sr-only\">{' 선택됨'}</i>}\n </button>\n </li>\n );\n}\n\n/** 패널 묶음 — 킷 `tab-conts-wrap`. **빼면 패널 폭이 줄어든다.** */\nexport function TabPanels({ className, children, ...rest }: ComponentPropsWithRef<'div'>) {\n return (\n <div className={cx('tab-conts-wrap', className)} {...rest}>\n {children}\n </div>\n );\n}\n\nexport type TabPanelProps = ComponentPropsWithRef<'section'> & {\n /** 짝이 되는 `Tab.Trigger` 의 값. */\n value: string;\n /** 제목 이동(H 키)에만 잡히는 숨은 패널 제목. 탭 글자와 다른 설명이 필요할 때만 준다. */\n srHeading?: string;\n /** `srHeading` 의 제목 단계. 기본 3. */\n srHeadingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * 안 보이는 패널도 DOM 에 남길 것인가. 기본 true 가 킷 동작이다(폼 입력값이 살아 있다).\n * false 는 무거운 목록이나 열 때마다 초기화해야 하는 폼에 쓴다.\n */\n keepMounted?: boolean;\n};\n\n/**\n * 탭 패널 — 킷 `section.tab-conts`. 자식을 여럿 넣으면 24px 간격이 자동으로 붙는다.\n */\nexport function TabPanel({\n value,\n srHeading,\n srHeadingLevel = 3,\n keepMounted = true,\n className,\n children,\n ...rest\n}: TabPanelProps) {\n const { selected, triggerId, panelId } = useTabContext('Tab.Panel');\n const active = selected === value;\n\n if (!active && !keepMounted) return null;\n\n const Heading = `h${srHeadingLevel}` as 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n\n return (\n <section\n id={panelId(value)}\n role=\"tabpanel\"\n aria-labelledby={triggerId(value)}\n className={cx('tab-conts', active && 'active', className)}\n // 쪽 안 내비게이션이 감춰진 패널을 목차로 긁어가지 않게 하는 표식.\n data-quick-nav=\"false\"\n {...rest}\n >\n {srHeading && <Heading className=\"sr-only\">{srHeading}</Heading>}\n {children}\n </section>\n );\n}\n\n/** 합성용 묶음. `Root` · `List` · `Trigger` · `Panels` · `Panel`. */\nexport const Tab = {\n Root: TabRoot,\n List: TabList,\n Trigger: TabTrigger,\n Panels: TabPanels,\n Panel: TabPanel,\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,75 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ /**
3
+ * 표 — KRDS `krds-table-wrap` + `tbl data`.
4
+ *
5
+ * 표를 손으로 짜는 자리에서 쓴다. 열을 데이터로 넘기고 정렬·고정 헤더까지 필요하면
6
+ * `ui/data-table` 의 `DataTable` 이 그 조립을 대신한다.
7
+ * `caption` 은 **화면에 보이지 않는 보조기기 설명**이라 필수이고, 보이는 제목은 표 바깥에 따로 둔다.
8
+ * `columnWidths` 를 주지 않으면 킷의 `table-layout:fixed` 때문에 모든 열이 같은 폭이 된다.
9
+ * `<table>` 에 `grid`·`flex` 같은 display 클래스를 붙이면 행·열 관계가 사라진다.
10
+ *
11
+ * @example
12
+ * <Table caption="사용자 목록 — 아이디, 이름, 권한 순으로 제공합니다." columnWidths={['30%', undefined, '20%']}>
13
+ * <thead>
14
+ * <tr><Th scope="col">아이디</Th><Th scope="col">이름</Th><Th scope="col">권한</Th></tr>
15
+ * </thead>
16
+ * <tbody>
17
+ * {rows.length === 0 ? <TableEmpty colSpan={3} /> : rows.map((r) => (
18
+ * <tr key={r.id}><Th scope="row">{r.id}</Th><Td>{r.name}</Td><Td>{r.role}</Td></tr>
19
+ * ))}
20
+ * </tbody>
21
+ * </Table>
22
+ *
23
+ * 자세히: docs/krds/09-부품-노트.md#표
24
+ */
25
+ /**
26
+ * 표 감싸개 — 킷 `krds-table-wrap`. 킷의 표 스타일이 전부 이 감싸개의 자손 선택자다.
27
+ *
28
+ * `Table` 이 스스로 그리므로 보통은 직접 쓸 일이 없다 — `<table>` 을 맨손으로 짤 때만 쓴다.
29
+ */
30
+ export type TableWrapProps = ComponentPropsWithRef<'div'> & {
31
+ /** 좁은 화면에서 셀을 줄바꿈하지 않고 가로로 굴린다. 기본 켬 — 끄면 좁은 칸 안의 표가 삐져나온다. */
32
+ mobScroll?: boolean;
33
+ /** 폭에 상관없이 항상 가로 스크롤(킷 `scroll`). 열이 아주 많은 관리 표에. */
34
+ scroll?: boolean;
35
+ };
36
+ export declare function TableWrap({ mobScroll, scroll, className, children, ...rest }: TableWrapProps): import("react").JSX.Element;
37
+ export type TableProps = ComponentPropsWithRef<'table'> & {
38
+ /** 보조기기가 표보다 먼저 읽는 설명. 필수다. 열 구성을 다 적는다. 화면에는 보이지 않는다. */
39
+ caption: ReactNode;
40
+ /** 열 너비. `undefined` 인 열은 남은 폭을 나눠 갖는다(`['30%', undefined]`). 안 주면 모두 같은 폭이다. */
41
+ columnWidths?: Array<string | number | undefined>;
42
+ /** 감싸개(`krds-table-wrap`)에 붙일 클래스. 표 바깥 여백은 여기로 준다. */
43
+ wrapClassName?: string;
44
+ /** 감싸개로 흘려보낼 나머지 속성. */
45
+ wrapProps?: Omit<TableWrapProps, 'className' | 'children' | 'mobScroll' | 'scroll'>;
46
+ } & Pick<TableWrapProps, 'mobScroll' | 'scroll'>;
47
+ export declare function Table({ caption, columnWidths, wrapClassName, wrapProps, mobScroll, scroll, className, children, ...rest }: TableProps): import("react").JSX.Element;
48
+ /**
49
+ * 머리 칸 — `<th>`. 열 머리는 `scope="col"`, 행 머리는 `scope="row"` 다.
50
+ *
51
+ * `scope` 는 필수다 — 없으면 스크린리더가 셀과 머리의 짝을 알려 주지 못한다.
52
+ */
53
+ export type ThProps = Omit<ComponentPropsWithRef<'th'>, 'scope'> & {
54
+ scope: 'col' | 'row' | 'colgroup' | 'rowgroup';
55
+ /** 양적 데이터 열. 오른쪽 정렬 + 자릿수 고정. 날짜·전화번호에는 주지 마라. */
56
+ numeric?: boolean;
57
+ /** 머리글을 화면에서만 감춘다. 조작 버튼만 든 열처럼 이름이 보일 필요는 없는 열에 쓴다. */
58
+ hiddenLabel?: boolean;
59
+ };
60
+ export declare function Th({ scope, numeric, hiddenLabel, className, children, ...rest }: ThProps): import("react").JSX.Element;
61
+ /** 값 칸 — `<td>`. 값이 비면 규격대로 대시(`-`)를 넣는다. */
62
+ export type TdProps = ComponentPropsWithRef<'td'> & {
63
+ /** 숫자 열. 오른쪽 정렬 + `tabular-nums`. `Th` 의 같은 이름과 짝을 맞춰 쓴다. */
64
+ numeric?: boolean;
65
+ /** 값이 비었을 때 넣을 것. 기본 `-`. 정말로 비워야 하면 `empty={null}`. */
66
+ empty?: ReactNode;
67
+ };
68
+ export declare function Td({ numeric, empty, className, children, ...rest }: TdProps): import("react").JSX.Element;
69
+ /** 「자료 없음」 행. 킷에 빈 상태 규칙이 없어 유틸리티로 그린다. `<tbody>` 안에 넣는다. */
70
+ export type TableEmptyProps = ComponentPropsWithRef<'td'> & {
71
+ /** 표의 전체 열 개수. 덜 주면 빈 문구가 표 가운데에 서지 않는다. */
72
+ colSpan: number;
73
+ };
74
+ export declare function TableEmpty({ colSpan, className, children, ...rest }: TableEmptyProps): import("react").JSX.Element;
75
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/table/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG;IAC1D,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,SAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,cAAc,+BAiBnG;AAED,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC,OAAO,CAAC,GAAG;IACxD,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;IACnB,iFAAiF;IACjF,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,SAAS,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC,CAAC;CACrF,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;AAKjD,wBAAgB,KAAK,CAAC,EACpB,OAAO,EACP,YAAY,EACZ,aAAa,EACb,SAAS,EACT,SAAgB,EAChB,MAAM,EACN,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,UAAU,+BAmCZ;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,GAAG;IACjE,KAAK,EAAE,KAAK,GAAG,KAAK,GAAG,UAAU,GAAG,UAAU,CAAC;IAC/C,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,OAAO,+BAUxF;AAED,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG;IAClD,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAQF,wBAAgB,EAAE,CAAC,EAAE,OAAO,EAAE,KAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,OAAO,+BAMjF;AAED,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG;IAC1D,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,SAAS,EACT,QAA0B,EAC1B,GAAG,IAAI,EACR,EAAE,eAAe,+BAQjB"}
@@ -0,0 +1,91 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../lib/krds.js";
3
+ function TableWrap({ mobScroll = true, scroll, className, children, ...rest }) {
4
+ return /* @__PURE__ */ jsx(
5
+ "div",
6
+ {
7
+ className: cx(
8
+ "krds-table-wrap",
9
+ mobScroll && "mob-scroll",
10
+ scroll && "scroll",
11
+ // 킷의 `mob-scroll` 은 nowrap 만 걸고 스크롤은 모바일 미디어쿼리에만 있다.
12
+ // PC 폭에서 열이 많은 표가 넘치면 글자가 옆 칸 위로 겹친다 — 여기서 스크롤을 켠다.
13
+ mobScroll && "overflow-x-auto",
14
+ className
15
+ ),
16
+ ...rest,
17
+ children
18
+ }
19
+ );
20
+ }
21
+ const LAYOUT_KILLERS = /* @__PURE__ */ new Set(["grid", "flex", "inline-grid", "inline-flex", "block", "hidden"]);
22
+ function Table({
23
+ caption,
24
+ columnWidths,
25
+ wrapClassName,
26
+ wrapProps,
27
+ mobScroll = true,
28
+ scroll,
29
+ className,
30
+ children,
31
+ ...rest
32
+ }) {
33
+ if (process.env.NODE_ENV !== "production" && className) {
34
+ const killers = className.split(/\s+/).filter((c) => LAYOUT_KILLERS.has(c));
35
+ if (killers.length) {
36
+ console.warn(
37
+ `[krds-react Table] <table> \uC5D0 display \uB97C \uBC14\uAFB8\uB294 \uD074\uB798\uC2A4\uAC00 \uBD99\uC5C8\uB2E4: ${killers.join(" ")} \u2014 \uD589\xB7\uC5F4 \uAD00\uACC4\uAC00 \uC0AC\uB77C\uC9C4\uB2E4.`
38
+ );
39
+ }
40
+ }
41
+ return /* @__PURE__ */ jsx(TableWrap, { mobScroll, scroll, className: wrapClassName, ...wrapProps, children: /* @__PURE__ */ jsxs(
42
+ "table",
43
+ {
44
+ className: cx("tbl data", className),
45
+ style: (scroll || mobScroll) && !columnWidths ? { tableLayout: "auto" } : void 0,
46
+ ...rest,
47
+ children: [
48
+ /* @__PURE__ */ jsx("caption", { children: caption }),
49
+ columnWidths && /* @__PURE__ */ jsx("colgroup", { children: columnWidths.map((width, i) => (
50
+ // 열 순서가 곧 신원이다 — 인덱스를 key 로 쓰는 것이 맞다.
51
+ /* @__PURE__ */ jsx("col", { style: width === void 0 ? void 0 : { width } }, i)
52
+ )) }),
53
+ children
54
+ ]
55
+ }
56
+ ) });
57
+ }
58
+ function Th({ scope, numeric, hiddenLabel, className, children, ...rest }) {
59
+ return /* @__PURE__ */ jsx(
60
+ "th",
61
+ {
62
+ scope,
63
+ className: cx(numeric && "text-right tabular-nums", className),
64
+ ...rest,
65
+ children: hiddenLabel ? /* @__PURE__ */ jsx("span", { className: "sr-only", children }) : children
66
+ }
67
+ );
68
+ }
69
+ function isBlank(children) {
70
+ if (children === null || children === void 0 || children === "") return true;
71
+ return Array.isArray(children) && children.length === 0;
72
+ }
73
+ function Td({ numeric, empty = "-", className, children, ...rest }) {
74
+ return /* @__PURE__ */ jsx("td", { className: cx(numeric && "text-right tabular-nums", className), ...rest, children: isBlank(children) ? empty : children });
75
+ }
76
+ function TableEmpty({
77
+ colSpan,
78
+ className,
79
+ children = "\uB4F1\uB85D\uB41C \uC790\uB8CC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
80
+ ...rest
81
+ }) {
82
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan, className: cx("py-14 text-center text-fg-subtle", className), ...rest, children }) });
83
+ }
84
+ export {
85
+ Table,
86
+ TableEmpty,
87
+ TableWrap,
88
+ Td,
89
+ Th
90
+ };
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/table/index.tsx"],"sourcesContent":["import type { ComponentPropsWithRef, ReactNode } from 'react';\nimport { cx } from '../lib/krds';\n\n/**\n * 표 — KRDS `krds-table-wrap` + `tbl data`.\n *\n * 표를 손으로 짜는 자리에서 쓴다. 열을 데이터로 넘기고 정렬·고정 헤더까지 필요하면\n * `ui/data-table` 의 `DataTable` 이 그 조립을 대신한다.\n * `caption` 은 **화면에 보이지 않는 보조기기 설명**이라 필수이고, 보이는 제목은 표 바깥에 따로 둔다.\n * `columnWidths` 를 주지 않으면 킷의 `table-layout:fixed` 때문에 모든 열이 같은 폭이 된다.\n * `<table>` 에 `grid`·`flex` 같은 display 클래스를 붙이면 행·열 관계가 사라진다.\n *\n * @example\n * <Table caption=\"사용자 목록 — 아이디, 이름, 권한 순으로 제공합니다.\" columnWidths={['30%', undefined, '20%']}>\n * <thead>\n * <tr><Th scope=\"col\">아이디</Th><Th scope=\"col\">이름</Th><Th scope=\"col\">권한</Th></tr>\n * </thead>\n * <tbody>\n * {rows.length === 0 ? <TableEmpty colSpan={3} /> : rows.map((r) => (\n * <tr key={r.id}><Th scope=\"row\">{r.id}</Th><Td>{r.name}</Td><Td>{r.role}</Td></tr>\n * ))}\n * </tbody>\n * </Table>\n *\n * 자세히: docs/krds/09-부품-노트.md#표\n */\n\n/**\n * 표 감싸개 — 킷 `krds-table-wrap`. 킷의 표 스타일이 전부 이 감싸개의 자손 선택자다.\n *\n * `Table` 이 스스로 그리므로 보통은 직접 쓸 일이 없다 — `<table>` 을 맨손으로 짤 때만 쓴다.\n */\nexport type TableWrapProps = ComponentPropsWithRef<'div'> & {\n /** 좁은 화면에서 셀을 줄바꿈하지 않고 가로로 굴린다. 기본 켬 — 끄면 좁은 칸 안의 표가 삐져나온다. */\n mobScroll?: boolean;\n /** 폭에 상관없이 항상 가로 스크롤(킷 `scroll`). 열이 아주 많은 관리 표에. */\n scroll?: boolean;\n};\n\nexport function TableWrap({ mobScroll = true, scroll, className, children, ...rest }: TableWrapProps) {\n return (\n <div\n className={cx(\n 'krds-table-wrap',\n mobScroll && 'mob-scroll',\n scroll && 'scroll',\n // 킷의 `mob-scroll` 은 nowrap 만 걸고 스크롤은 모바일 미디어쿼리에만 있다.\n // PC 폭에서 열이 많은 표가 넘치면 글자가 옆 칸 위로 겹친다 — 여기서 스크롤을 켠다.\n mobScroll && 'overflow-x-auto',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n}\n\nexport type TableProps = ComponentPropsWithRef<'table'> & {\n /** 보조기기가 표보다 먼저 읽는 설명. 필수다. 열 구성을 다 적는다. 화면에는 보이지 않는다. */\n caption: ReactNode;\n /** 열 너비. `undefined` 인 열은 남은 폭을 나눠 갖는다(`['30%', undefined]`). 안 주면 모두 같은 폭이다. */\n columnWidths?: Array<string | number | undefined>;\n /** 감싸개(`krds-table-wrap`)에 붙일 클래스. 표 바깥 여백은 여기로 준다. */\n wrapClassName?: string;\n /** 감싸개로 흘려보낼 나머지 속성. */\n wrapProps?: Omit<TableWrapProps, 'className' | 'children' | 'mobScroll' | 'scroll'>;\n} & Pick<TableWrapProps, 'mobScroll' | 'scroll'>;\n\n/** `<table>` 의 display 를 갈아엎어 행·열 관계를 없애는 클래스들. */\nconst LAYOUT_KILLERS = new Set(['grid', 'flex', 'inline-grid', 'inline-flex', 'block', 'hidden']);\n\nexport function Table({\n caption,\n columnWidths,\n wrapClassName,\n wrapProps,\n mobScroll = true,\n scroll,\n className,\n children,\n ...rest\n}: TableProps) {\n if (process.env.NODE_ENV !== 'production' && className) {\n const killers = className.split(/\\s+/).filter((c) => LAYOUT_KILLERS.has(c));\n if (killers.length) {\n // 조용히 망가지지 않게 막는다.\n console.warn(\n `[krds-react Table] <table> 에 display 를 바꾸는 클래스가 붙었다: ${killers.join(' ')} — 행·열 관계가 사라진다.`,\n );\n }\n }\n\n return (\n <TableWrap mobScroll={mobScroll} scroll={scroll} className={wrapClassName} {...wrapProps}>\n {/* 가로로 스크롤되는 표는 열 폭을 내용에 맞춘다 — 킷이 `table{table-layout:fixed}` 를 전역으로 못 박아\n 폭을 안 주면 열이 전부 같은 너비가 되고, `whitespace-nowrap` 인 칸(날짜·조작 단추)이 잘리지 않고\n **옆 칸 위로 흘러넘쳐** 글자가 겹친다. `ui/data-table` 과 같은 판단이다.\n 폭을 손으로 준 표(`columnWidths`)와 스크롤이 아예 없는 표는 그대로 둔다. */}\n <table\n className={cx('tbl data', className)}\n style={(scroll || mobScroll) && !columnWidths ? { tableLayout: 'auto' } : undefined}\n {...rest}\n >\n <caption>{caption}</caption>\n {columnWidths && (\n <colgroup>\n {columnWidths.map((width, i) => (\n // 열 순서가 곧 신원이다 — 인덱스를 key 로 쓰는 것이 맞다.\n <col key={i} style={width === undefined ? undefined : { width }} />\n ))}\n </colgroup>\n )}\n {children}\n </table>\n </TableWrap>\n );\n}\n\n/**\n * 머리 칸 — `<th>`. 열 머리는 `scope=\"col\"`, 행 머리는 `scope=\"row\"` 다.\n *\n * `scope` 는 필수다 — 없으면 스크린리더가 셀과 머리의 짝을 알려 주지 못한다.\n */\nexport type ThProps = Omit<ComponentPropsWithRef<'th'>, 'scope'> & {\n scope: 'col' | 'row' | 'colgroup' | 'rowgroup';\n /** 양적 데이터 열. 오른쪽 정렬 + 자릿수 고정. 날짜·전화번호에는 주지 마라. */\n numeric?: boolean;\n /** 머리글을 화면에서만 감춘다. 조작 버튼만 든 열처럼 이름이 보일 필요는 없는 열에 쓴다. */\n hiddenLabel?: boolean;\n};\n\nexport function Th({ scope, numeric, hiddenLabel, className, children, ...rest }: ThProps) {\n return (\n <th\n scope={scope}\n className={cx(numeric && 'text-right tabular-nums', className)}\n {...rest}\n >\n {hiddenLabel ? <span className=\"sr-only\">{children}</span> : children}\n </th>\n );\n}\n\n/** 값 칸 — `<td>`. 값이 비면 규격대로 대시(`-`)를 넣는다. */\nexport type TdProps = ComponentPropsWithRef<'td'> & {\n /** 숫자 열. 오른쪽 정렬 + `tabular-nums`. `Th` 의 같은 이름과 짝을 맞춰 쓴다. */\n numeric?: boolean;\n /** 값이 비었을 때 넣을 것. 기본 `-`. 정말로 비워야 하면 `empty={null}`. */\n empty?: ReactNode;\n};\n\n/** `-` 로 바꿔야 하는 빈 값인가. `0` 과 `false` 는 값이다. */\nfunction isBlank(children: ReactNode): boolean {\n if (children === null || children === undefined || children === '') return true;\n return Array.isArray(children) && children.length === 0;\n}\n\nexport function Td({ numeric, empty = '-', className, children, ...rest }: TdProps) {\n return (\n <td className={cx(numeric && 'text-right tabular-nums', className)} {...rest}>\n {isBlank(children) ? empty : children}\n </td>\n );\n}\n\n/** 「자료 없음」 행. 킷에 빈 상태 규칙이 없어 유틸리티로 그린다. `<tbody>` 안에 넣는다. */\nexport type TableEmptyProps = ComponentPropsWithRef<'td'> & {\n /** 표의 전체 열 개수. 덜 주면 빈 문구가 표 가운데에 서지 않는다. */\n colSpan: number;\n};\n\nexport function TableEmpty({\n colSpan,\n className,\n children = '등록된 자료가 없습니다.',\n ...rest\n}: TableEmptyProps) {\n return (\n <tr>\n <td colSpan={colSpan} className={cx('py-14 text-center text-fg-subtle', className)} {...rest}>\n {children}\n </td>\n </tr>\n );\n}\n"],"mappings":"AAyCI,cA0DE,YA1DF;AAxCJ,SAAS,UAAU;AAsCZ,SAAS,UAAU,EAAE,YAAY,MAAM,QAAQ,WAAW,UAAU,GAAG,KAAK,GAAmB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,UAAU;AAAA;AAAA;AAAA,QAGV,aAAa;AAAA,QACb;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAcA,MAAM,iBAAiB,oBAAI,IAAI,CAAC,QAAQ,QAAQ,eAAe,eAAe,SAAS,QAAQ,CAAC;AAEzF,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,MAAI,QAAQ,IAAI,aAAa,gBAAgB,WAAW;AACtD,UAAM,UAAU,UAAU,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,eAAe,IAAI,CAAC,CAAC;AAC1E,QAAI,QAAQ,QAAQ;AAElB,cAAQ;AAAA,QACN,oHAAwD,QAAQ,KAAK,GAAG,CAAC;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AAEA,SACE,oBAAC,aAAU,WAAsB,QAAgB,WAAW,eAAgB,GAAG,WAK7E;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,YAAY,SAAS;AAAA,MACnC,QAAQ,UAAU,cAAc,CAAC,eAAe,EAAE,aAAa,OAAO,IAAI;AAAA,MACzE,GAAG;AAAA,MAEJ;AAAA,4BAAC,aAAS,mBAAQ;AAAA,QACjB,gBACC,oBAAC,cACE,uBAAa,IAAI,CAAC,OAAO;AAAA;AAAA,UAExB,oBAAC,SAAY,OAAO,UAAU,SAAY,SAAY,EAAE,MAAM,KAApD,CAAuD;AAAA,SAClE,GACH;AAAA,QAED;AAAA;AAAA;AAAA,EACH,GACF;AAEJ;AAeO,SAAS,GAAG,EAAE,OAAO,SAAS,aAAa,WAAW,UAAU,GAAG,KAAK,GAAY;AACzF,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,WAAW,2BAA2B,SAAS;AAAA,MAC5D,GAAG;AAAA,MAEH,wBAAc,oBAAC,UAAK,WAAU,WAAW,UAAS,IAAU;AAAA;AAAA,EAC/D;AAEJ;AAWA,SAAS,QAAQ,UAA8B;AAC7C,MAAI,aAAa,QAAQ,aAAa,UAAa,aAAa,GAAI,QAAO;AAC3E,SAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW;AACxD;AAEO,SAAS,GAAG,EAAE,SAAS,QAAQ,KAAK,WAAW,UAAU,GAAG,KAAK,GAAY;AAClF,SACE,oBAAC,QAAG,WAAW,GAAG,WAAW,2BAA2B,SAAS,GAAI,GAAG,MACrE,kBAAQ,QAAQ,IAAI,QAAQ,UAC/B;AAEJ;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,GAAG;AACL,GAAoB;AAClB,SACE,oBAAC,QACC,8BAAC,QAAG,SAAkB,WAAW,GAAG,oCAAoC,SAAS,GAAI,GAAG,MACrF,UACH,GACF;AAEJ;","names":[]}
@@ -0,0 +1,71 @@
1
+ import type { ComponentPropsWithRef, MouseEventHandler } from 'react';
2
+ import { type KrdsSize } from '../lib/krds';
3
+ /**
4
+ * 태그 — KRDS `krds-tag-wrap` + `krds-btn-tag`.
5
+ *
6
+ * 필터 조건을 보이고 지우는 표시다. 누르지 않는 상태 표시는 `Badge` 를 쓴다.
7
+ * **반드시 `TagWrap` 으로 감싼다** — 킷 규칙이 전부 감싸개의 자손 선택자라 없으면 맨 글자가 된다.
8
+ * 크기도 `TagWrap` 이 정한다. 한 묶음에는 `Tag`(비대화형)나 `TagButton`(대화형) 한 종류만 넣는다.
9
+ * 색 prop 은 없다 — 규격이 색으로 뜻을 전하지 말라고 한다.
10
+ *
11
+ * @example
12
+ * <TagWrap size="medium">
13
+ * <Tag onDelete={() => remove('서울')}>서울</Tag>
14
+ * <Tag onDelete={() => remove('2024')}>2024</Tag>
15
+ * </TagWrap>
16
+ * <TagWrap>
17
+ * <TagButton selected={on} onClick={toggle}>진행 중</TagButton>
18
+ * </TagWrap>
19
+ *
20
+ * 자세히: docs/krds/09-부품-노트.md#태그
21
+ */
22
+ /** 태그 크기. 킷에 있는 셋뿐이다 — 높이 24 / 32 / 40px. */
23
+ export type TagSize = Extract<KrdsSize, 'small' | 'medium' | 'large'>;
24
+ export type TagWrapProps = ComponentPropsWithRef<'div'> & {
25
+ /** 기본 medium(32px). 킷 기본값과 같다. 이 묶음 안 태그 전부에 적용된다. */
26
+ size?: TagSize;
27
+ };
28
+ /**
29
+ * 태그 묶음 — 킷 `krds-tag-wrap`. 태그 하나만 놓을 때도 반드시 이걸로 감싼다.
30
+ *
31
+ * 간격은 킷이 크기별로 잡는다 — `ml-*` 를 손으로 붙이지 않는다.
32
+ * 「전체 해제」 같은 일괄 버튼은 이 묶음 **밖**의 형제로, 오른쪽에 둔다.
33
+ */
34
+ export declare function TagWrap({ size, className, children, ...rest }: TagWrapProps): import("react").JSX.Element;
35
+ export type TagProps = ComponentPropsWithRef<'span'> & {
36
+ /** 주면 삭제 단추가 붙는다 — 설정된 필터·정렬 옵션을 해제하는 자리다. */
37
+ onDelete?: MouseEventHandler<HTMLButtonElement>;
38
+ /** 삭제 단추의 스크린리더 이름. 생략하면 `[children] 옵션 삭제`(children 이 문자열일 때). */
39
+ deleteLabel?: string;
40
+ };
41
+ /**
42
+ * 태그 — 킷 `krds-btn-tag`. 누르지 않는 레이블이 기본이고 `onDelete` 를 주면 삭제 단추가 붙는다.
43
+ *
44
+ * 삭제 아이콘은 킷이 그린다 — `<i class="svg-icon">` 를 따로 넣지 마라.
45
+ */
46
+ export declare function Tag({ onDelete, deleteLabel, className, children, ...rest }: TagProps): import("react").JSX.Element;
47
+ export type TagLinkProps = ComponentPropsWithRef<'a'>;
48
+ /**
49
+ * 링크형 태그 — 킷 `krds-btn-tag link`. **이동에만 쓴다**(동작은 `TagButton`).
50
+ *
51
+ * `.link` 가 하는 일은 hover/active 에서 밑줄을 켜는 것뿐이다. 색 변형이 아니다.
52
+ */
53
+ export declare function TagLink({ className, children, target, title, ...rest }: TagLinkProps): import("react").JSX.Element;
54
+ export type TagButtonProps = ComponentPropsWithRef<'button'> & {
55
+ /**
56
+ * 선택 상태. 주면 `aria-pressed` 와 체크 아이콘이 붙는다.
57
+ * 생략하면 실행 단추가 되므로 `aria-label="[레이블] 필터 옵션 추가"` 를 직접 준다.
58
+ */
59
+ selected?: boolean;
60
+ };
61
+ /**
62
+ * 대화형 태그 — `<button class="krds-btn-tag">`. 필터 조건을 켜고 끄는 자리다.
63
+ *
64
+ * **상태를 스스로 갖지 않는다(controlled).** 필터 조건은 목록·질의와 함께 움직여야 하므로
65
+ * 화면 쪽이 들고 `selected` 로 내려 준다. 선택 여부는 색이 아니라 체크 아이콘으로 알린다.
66
+ *
67
+ * @example
68
+ * <TagButton selected={picked.has('진행')} onClick={() => toggle('진행')}>진행 중</TagButton>
69
+ */
70
+ export declare function TagButton({ selected, className, children, type, ...rest }: TagButtonProps): import("react").JSX.Element;
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tag/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,iBAAiB,EAAa,MAAM,OAAO,CAAC;AACjF,OAAO,EAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;;;;;;;;;;GAkBG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG;IACxD,sDAAsD;IACtD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EAAE,IAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,+BAMtF;AAOD,MAAM,MAAM,QAAQ,GAAG,qBAAqB,CAAC,MAAM,CAAC,GAAG;IACrD,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAChD,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,QAAQ,+BAWpF;AAED,MAAM,MAAM,YAAY,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,+BAYpF;AAED,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,QAAQ,CAAC,GAAG;IAC7D;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,IAAe,EACf,GAAG,IAAI,EACR,EAAE,cAAc,+BAOhB"}
@@ -0,0 +1,45 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../lib/krds.js";
3
+ function TagWrap({ size = "medium", className, children, ...rest }) {
4
+ return /* @__PURE__ */ jsx("div", { className: cx("krds-tag-wrap", size, className), ...rest, children });
5
+ }
6
+ function deleteName(children) {
7
+ return typeof children === "string" && children.trim() ? `${children.trim()} \uC635\uC158 \uC0AD\uC81C` : "\uC0AD\uC81C";
8
+ }
9
+ function Tag({ onDelete, deleteLabel, className, children, ...rest }) {
10
+ return /* @__PURE__ */ jsxs("span", { className: cx("krds-btn-tag", className), ...rest, children: [
11
+ children,
12
+ onDelete && /* @__PURE__ */ jsx("button", { type: "button", className: "btn-delete", onClick: onDelete, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: deleteLabel ?? deleteName(children) }) })
13
+ ] });
14
+ }
15
+ function TagLink({ className, children, target, title, ...rest }) {
16
+ return /* @__PURE__ */ jsx(
17
+ "a",
18
+ {
19
+ className: cx("krds-btn-tag", "link", className),
20
+ target,
21
+ title: title ?? (target === "_blank" ? "\uC0C8 \uCC3D \uC5F4\uB9BC" : void 0),
22
+ ...rest,
23
+ children
24
+ }
25
+ );
26
+ }
27
+ function TagButton({
28
+ selected,
29
+ className,
30
+ children,
31
+ type = "button",
32
+ ...rest
33
+ }) {
34
+ return /* @__PURE__ */ jsxs("button", { type, className: cx("krds-btn-tag", className), "aria-pressed": selected, ...rest, children: [
35
+ selected && /* @__PURE__ */ jsx("i", { className: "svg-icon ico-checkbox size-4 shrink-0", "aria-hidden": "true" }),
36
+ children
37
+ ] });
38
+ }
39
+ export {
40
+ Tag,
41
+ TagButton,
42
+ TagLink,
43
+ TagWrap
44
+ };
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/tag/index.tsx"],"sourcesContent":["import type { ComponentPropsWithRef, MouseEventHandler, ReactNode } from 'react';\nimport { cx, type KrdsSize } from '../lib/krds';\n\n/**\n * 태그 — KRDS `krds-tag-wrap` + `krds-btn-tag`.\n *\n * 필터 조건을 보이고 지우는 표시다. 누르지 않는 상태 표시는 `Badge` 를 쓴다.\n * **반드시 `TagWrap` 으로 감싼다** — 킷 규칙이 전부 감싸개의 자손 선택자라 없으면 맨 글자가 된다.\n * 크기도 `TagWrap` 이 정한다. 한 묶음에는 `Tag`(비대화형)나 `TagButton`(대화형) 한 종류만 넣는다.\n * 색 prop 은 없다 — 규격이 색으로 뜻을 전하지 말라고 한다.\n *\n * @example\n * <TagWrap size=\"medium\">\n * <Tag onDelete={() => remove('서울')}>서울</Tag>\n * <Tag onDelete={() => remove('2024')}>2024</Tag>\n * </TagWrap>\n * <TagWrap>\n * <TagButton selected={on} onClick={toggle}>진행 중</TagButton>\n * </TagWrap>\n *\n * 자세히: docs/krds/09-부품-노트.md#태그\n */\n\n/** 태그 크기. 킷에 있는 셋뿐이다 — 높이 24 / 32 / 40px. */\nexport type TagSize = Extract<KrdsSize, 'small' | 'medium' | 'large'>;\n\nexport type TagWrapProps = ComponentPropsWithRef<'div'> & {\n /** 기본 medium(32px). 킷 기본값과 같다. 이 묶음 안 태그 전부에 적용된다. */\n size?: TagSize;\n};\n\n/**\n * 태그 묶음 — 킷 `krds-tag-wrap`. 태그 하나만 놓을 때도 반드시 이걸로 감싼다.\n *\n * 간격은 킷이 크기별로 잡는다 — `ml-*` 를 손으로 붙이지 않는다.\n * 「전체 해제」 같은 일괄 버튼은 이 묶음 **밖**의 형제로, 오른쪽에 둔다.\n */\nexport function TagWrap({ size = 'medium', className, children, ...rest }: TagWrapProps) {\n return (\n <div className={cx('krds-tag-wrap', size, className)} {...rest}>\n {children}\n </div>\n );\n}\n\n/** 삭제 단추의 스크린리더 이름(`[태그 레이블] 옵션 삭제`). children 이 문자열일 때만 만들 수 있다. */\nfunction deleteName(children: ReactNode): string {\n return typeof children === 'string' && children.trim() ? `${children.trim()} 옵션 삭제` : '삭제';\n}\n\nexport type TagProps = ComponentPropsWithRef<'span'> & {\n /** 주면 삭제 단추가 붙는다 — 설정된 필터·정렬 옵션을 해제하는 자리다. */\n onDelete?: MouseEventHandler<HTMLButtonElement>;\n /** 삭제 단추의 스크린리더 이름. 생략하면 `[children] 옵션 삭제`(children 이 문자열일 때). */\n deleteLabel?: string;\n};\n\n/**\n * 태그 — 킷 `krds-btn-tag`. 누르지 않는 레이블이 기본이고 `onDelete` 를 주면 삭제 단추가 붙는다.\n *\n * 삭제 아이콘은 킷이 그린다 — `<i class=\"svg-icon\">` 를 따로 넣지 마라.\n */\nexport function Tag({ onDelete, deleteLabel, className, children, ...rest }: TagProps) {\n return (\n <span className={cx('krds-btn-tag', className)} {...rest}>\n {children}\n {onDelete && (\n <button type=\"button\" className=\"btn-delete\" onClick={onDelete}>\n <span className=\"sr-only\">{deleteLabel ?? deleteName(children)}</span>\n </button>\n )}\n </span>\n );\n}\n\nexport type TagLinkProps = ComponentPropsWithRef<'a'>;\n\n/**\n * 링크형 태그 — 킷 `krds-btn-tag link`. **이동에만 쓴다**(동작은 `TagButton`).\n *\n * `.link` 가 하는 일은 hover/active 에서 밑줄을 켜는 것뿐이다. 색 변형이 아니다.\n */\nexport function TagLink({ className, children, target, title, ...rest }: TagLinkProps) {\n return (\n <a\n className={cx('krds-btn-tag', 'link', className)}\n target={target}\n // 새 창으로 열리면 미리 알린다 — KRDS 접근성 요구.\n title={title ?? (target === '_blank' ? '새 창 열림' : undefined)}\n {...rest}\n >\n {children}\n </a>\n );\n}\n\nexport type TagButtonProps = ComponentPropsWithRef<'button'> & {\n /**\n * 선택 상태. 주면 `aria-pressed` 와 체크 아이콘이 붙는다.\n * 생략하면 실행 단추가 되므로 `aria-label=\"[레이블] 필터 옵션 추가\"` 를 직접 준다.\n */\n selected?: boolean;\n};\n\n/**\n * 대화형 태그 — `<button class=\"krds-btn-tag\">`. 필터 조건을 켜고 끄는 자리다.\n *\n * **상태를 스스로 갖지 않는다(controlled).** 필터 조건은 목록·질의와 함께 움직여야 하므로\n * 화면 쪽이 들고 `selected` 로 내려 준다. 선택 여부는 색이 아니라 체크 아이콘으로 알린다.\n *\n * @example\n * <TagButton selected={picked.has('진행')} onClick={() => toggle('진행')}>진행 중</TagButton>\n */\nexport function TagButton({\n selected,\n className,\n children,\n type = 'button',\n ...rest\n}: TagButtonProps) {\n return (\n <button type={type} className={cx('krds-btn-tag', className)} aria-pressed={selected} {...rest}>\n {selected && <i className=\"svg-icon ico-checkbox size-4 shrink-0\" aria-hidden=\"true\" />}\n {children}\n </button>\n );\n}\n"],"mappings":"AAuCI,cAyBA,YAzBA;AAtCJ,SAAS,UAAyB;AAoC3B,SAAS,QAAQ,EAAE,OAAO,UAAU,WAAW,UAAU,GAAG,KAAK,GAAiB;AACvF,SACE,oBAAC,SAAI,WAAW,GAAG,iBAAiB,MAAM,SAAS,GAAI,GAAG,MACvD,UACH;AAEJ;AAGA,SAAS,WAAW,UAA6B;AAC/C,SAAO,OAAO,aAAa,YAAY,SAAS,KAAK,IAAI,GAAG,SAAS,KAAK,CAAC,+BAAW;AACxF;AAcO,SAAS,IAAI,EAAE,UAAU,aAAa,WAAW,UAAU,GAAG,KAAK,GAAa;AACrF,SACE,qBAAC,UAAK,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,MACjD;AAAA;AAAA,IACA,YACC,oBAAC,YAAO,MAAK,UAAS,WAAU,cAAa,SAAS,UACpD,8BAAC,UAAK,WAAU,WAAW,yBAAe,WAAW,QAAQ,GAAE,GACjE;AAAA,KAEJ;AAEJ;AASO,SAAS,QAAQ,EAAE,WAAW,UAAU,QAAQ,OAAO,GAAG,KAAK,GAAiB;AACrF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,gBAAgB,QAAQ,SAAS;AAAA,MAC/C;AAAA,MAEA,OAAO,UAAU,WAAW,WAAW,+BAAW;AAAA,MACjD,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAmBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,GAAG;AACL,GAAmB;AACjB,SACE,qBAAC,YAAO,MAAY,WAAW,GAAG,gBAAgB,SAAS,GAAG,gBAAc,UAAW,GAAG,MACvF;AAAA,gBAAY,oBAAC,OAAE,WAAU,yCAAwC,eAAY,QAAO;AAAA,IACpF;AAAA,KACH;AAEJ;","names":[]}
@@ -0,0 +1,69 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { type FieldProps } from '../field';
3
+ import { type KrdsFieldSize } from '../lib/krds';
4
+ /**
5
+ * 텍스트 입력 — KRDS `krds-input`.
6
+ *
7
+ * `TextInput` 은 `<input>` 하나만 그린다. 레이블·도움말까지 한꺼번에 필요하면 {@link TextField},
8
+ * 골격을 조각으로 짤 때는 `ui/field` 의 `Field` 를 쓴다.
9
+ * 크기 기본이 medium(48px)이라 같은 줄의 버튼과 어긋나지 않는다(킷 기본은 large 56px).
10
+ * **`size` 를 `<textarea>` 에 붙이면 높이가 48px 로 찌부러진다** — 그래서 `ui/textarea` 에는 없다.
11
+ * 돋보기가 겹친 검색칸은 {@link SearchInput}, 제안 목록까지 필요하면 `ui/combobox` 다.
12
+ *
13
+ * @example
14
+ * <TextField id="consult_name" label="성명" hint="주민등록상 성명" required placeholder="예) 김모루" />
15
+ * <InputGroup>
16
+ * <InputGroupItem><TextInput id="q" /></InputGroupItem>
17
+ * <Button variant="primary" type="submit">검색</Button>
18
+ * </InputGroup>
19
+ *
20
+ * 자세히: docs/krds/09-부품-노트.md#텍스트입력
21
+ */
22
+ /**
23
+ * 입력칸 하나 — `<input class="krds-input">`. 골격은 `ui/field` 가 맡는다.
24
+ * HTML `size` 속성(글자 수)은 지원하지 않는다 — 너비는 `className` 으로 잡는다.
25
+ */
26
+ export type TextInputProps = Omit<ComponentPropsWithRef<'input'>, 'size'> & {
27
+ /** 기본 medium(48px). 같은 줄의 버튼과 같은 값을 준다. */
28
+ size?: KrdsFieldSize;
29
+ };
30
+ export declare function TextInput({ size, type, className, ...rest }: TextInputProps): import("react").JSX.Element;
31
+ /**
32
+ * 레이블 · 도움말까지 붙은 텍스트 한 줄 — `Field` + `TextInput` 을 조립한 편의 부품이다.
33
+ *
34
+ * 골격 prop(`hint` · `state` · `message` · `error` · `required` · `action` · `deletable` …)은
35
+ * `Field` 의 것을 그대로 받고 나머지는 `<input>` 으로 간다.
36
+ * 컨트롤이 입력칸이 아니거나 한 칸에 요소가 여럿이면 `Field` 를 직접 쓴다.
37
+ */
38
+ export type TextFieldProps = Omit<TextInputProps, 'className'> & Pick<FieldProps, 'label' | 'hideLabel' | 'hint' | 'state' | 'message' | 'error' | 'action' | 'deletable' | 'describedBy' | 'contentProps' | 'standalone'> & {
39
+ /** `<label for>` 로 이어야 하므로 필수다(서버 컴포넌트라 `useId` 를 쓸 수 없다). */
40
+ id: string;
41
+ /** `.form-group` 에 붙는다. 바깥 여백은 여기서 조절한다. */
42
+ className?: string;
43
+ /** `<input>` 에 붙는다(너비 고정 등). */
44
+ inputClassName?: string;
45
+ };
46
+ export declare function TextField({ id, label, hideLabel, hint, state, message, error, required, action, deletable, describedBy, contentProps, standalone, className, inputClassName, size, 'aria-describedby': ariaDescribedBy, ...rest }: TextFieldProps): import("react").JSX.Element;
47
+ /**
48
+ * 돋보기가 겹쳐진 검색 입력 — 킷 `sch-input`.
49
+ *
50
+ * 기본이 `type="submit"` 이라 `<form action>` 안에 두면 JS 없이도 검색이 된다.
51
+ * 실시간 제안 목록·검색어 삭제까지 필요하면 `ui/combobox` 의 `SearchCombobox` 다.
52
+ *
53
+ * @example
54
+ * <form action="/search">
55
+ * <SearchInput name="q" placeholder="검색어를 입력하세요" />
56
+ * </form>
57
+ */
58
+ export type SearchInputProps = Omit<TextInputProps, 'type'> & {
59
+ /** 돋보기 버튼의 접근 가능한 이름. 아이콘만 있는 버튼이라 반드시 있어야 한다. */
60
+ buttonLabel?: string;
61
+ /** 버튼에 그대로 흘릴 속성(onClick 등). */
62
+ buttonProps?: Omit<ComponentPropsWithRef<'button'>, 'type' | 'className' | 'children'>;
63
+ /** 버튼 종류. 기본 `submit`. */
64
+ buttonType?: 'submit' | 'button';
65
+ /** `.sch-input` 감싸개에 붙는다. 너비·여백은 여기서 조절한다. */
66
+ wrapperClassName?: string;
67
+ };
68
+ export declare function SearchInput({ size, buttonLabel, buttonProps, buttonType, wrapperClassName, className, ...rest }: SearchInputProps): import("react").JSX.Element;
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text-input/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAM,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,GAAG;IAC1E,2CAA2C;IAC3C,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,IAAe,EAAE,IAAa,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,cAAc,+BAE/F;AAID;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAC5D,IAAI,CACF,UAAU,EACR,OAAO,GACP,WAAW,GACX,MAAM,GACN,OAAO,GACP,SAAS,GACT,OAAO,GACP,QAAQ,GACR,WAAW,GACX,aAAa,GACb,cAAc,GACd,YAAY,CACf,GAAG;IACF,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEJ,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,SAAS,EACT,IAAI,EACJ,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,SAAS,EACT,cAAc,EACd,IAAe,EACf,kBAAkB,EAAE,eAAe,EACnC,GAAG,IAAI,EACR,EAAE,cAAc,+BAsBhB;AAID;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG;IAC5D,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,WAAW,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC,CAAC;IACvF,0BAA0B;IAC1B,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACjC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,IAAe,EACf,WAAkB,EAClB,WAAW,EACX,UAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,gBAAgB,+BAUlB"}