@nexus-cross/design-system 1.0.4-beta.1 → 1.0.4

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 (327) hide show
  1. package/cursor-rules/nexus-project-setup.mdc +167 -145
  2. package/cursor-rules/nexus-ui-api.mdc +894 -311
  3. package/cursor-rules/nexus-ui-components.mdc +179 -91
  4. package/dist/accordion.js +7 -7
  5. package/dist/accordion.mjs +2 -2
  6. package/dist/alert.d.mts +17 -0
  7. package/dist/alert.d.ts +17 -0
  8. package/dist/alert.js +16 -0
  9. package/dist/alert.mjs +3 -0
  10. package/dist/avatar.js +4 -4
  11. package/dist/avatar.mjs +2 -2
  12. package/dist/badge.d.mts +18 -0
  13. package/dist/badge.d.ts +18 -0
  14. package/dist/badge.js +16 -0
  15. package/dist/badge.mjs +3 -0
  16. package/dist/breadcrumb.d.mts +15 -0
  17. package/dist/breadcrumb.d.ts +15 -0
  18. package/dist/breadcrumb.js +12 -0
  19. package/dist/breadcrumb.mjs +3 -0
  20. package/dist/button.js +4 -4
  21. package/dist/button.mjs +2 -2
  22. package/dist/carousel.js +8 -8
  23. package/dist/carousel.mjs +2 -2
  24. package/dist/checkbox.js +5 -5
  25. package/dist/checkbox.mjs +2 -2
  26. package/dist/chip.d.mts +1 -1
  27. package/dist/chip.d.ts +1 -1
  28. package/dist/chip.js +4 -4
  29. package/dist/chip.mjs +2 -2
  30. package/dist/chunks/{chunk-5TBXVD56.js → chunk-2MC7XJSE.js} +16 -6
  31. package/dist/chunks/chunk-2N2EPBO4.js +120 -0
  32. package/dist/chunks/{chunk-BEA727LO.mjs → chunk-2RPRCWKV.mjs} +57 -26
  33. package/dist/chunks/chunk-2UPGFY6E.mjs +76 -0
  34. package/dist/chunks/{chunk-5I2VRWWU.js → chunk-2ZXDXO4I.js} +32 -38
  35. package/dist/chunks/{chunk-RLP3U52D.mjs → chunk-33UFQJIO.mjs} +22 -40
  36. package/dist/chunks/{chunk-AOVU67NI.mjs → chunk-3HHJORN7.mjs} +23 -25
  37. package/dist/chunks/{chunk-TWHDXCKR.js → chunk-3PCNRCTB.js} +8 -8
  38. package/dist/chunks/chunk-3ZWN66YH.js +53 -0
  39. package/dist/chunks/chunk-4ENXP7WH.js +224 -0
  40. package/dist/chunks/{chunk-YEGPB7A7.js → chunk-5JHJNN2K.js} +4 -4
  41. package/dist/chunks/chunk-5PQ3UCKF.js +99 -0
  42. package/dist/chunks/chunk-6BWOKTVQ.mjs +87 -0
  43. package/dist/chunks/chunk-6DBRL6NA.mjs +81 -0
  44. package/dist/chunks/{chunk-C3E7CSKG.mjs → chunk-6FMDO6TT.mjs} +7 -8
  45. package/dist/chunks/{chunk-76CY4STF.js → chunk-7G65JBTN.js} +34 -66
  46. package/dist/chunks/{chunk-GMIGQ5VP.mjs → chunk-ADO7PDLY.mjs} +15 -35
  47. package/dist/chunks/{chunk-ZINDMFOI.js → chunk-AFSEYJZT.js} +24 -32
  48. package/dist/chunks/chunk-B5O6W3Z4.mjs +73 -0
  49. package/dist/chunks/{chunk-6FHK6CBR.js → chunk-B6G5TJRO.js} +7 -8
  50. package/dist/chunks/chunk-BBLBTOP4.js +205 -0
  51. package/dist/chunks/{chunk-5XVEYVYA.js → chunk-BLGQHR3M.js} +5 -8
  52. package/dist/chunks/{chunk-LVTD2UQN.mjs → chunk-BPUQ2CO2.mjs} +2 -2
  53. package/dist/chunks/{chunk-AWBGWBFS.js → chunk-BSZ2LN6E.js} +21 -27
  54. package/dist/chunks/chunk-C2DSAJTL.js +109 -0
  55. package/dist/chunks/{chunk-KWPIEHD2.mjs → chunk-CA3SOLI3.mjs} +1 -1
  56. package/dist/chunks/{chunk-MPKRXMCJ.js → chunk-CSJDDREF.js} +7 -10
  57. package/dist/chunks/chunk-CUTMLBC3.mjs +86 -0
  58. package/dist/chunks/chunk-CV4GMFWP.js +174 -0
  59. package/dist/chunks/{chunk-SXLMI7CZ.mjs → chunk-CWMLTXOH.mjs} +162 -234
  60. package/dist/chunks/chunk-CZC76ZD5.js +10 -0
  61. package/dist/chunks/chunk-DICN6GKE.js +99 -0
  62. package/dist/chunks/{chunk-TLTEUIBY.js → chunk-DLFV7ZZV.js} +4 -4
  63. package/dist/chunks/chunk-DO6VK2QQ.mjs +108 -0
  64. package/dist/chunks/chunk-DYPPVXQF.js +143 -0
  65. package/dist/chunks/{chunk-GQP7IXH2.mjs → chunk-ECVAVQUY.mjs} +23 -31
  66. package/dist/chunks/{chunk-3CHRUKSJ.mjs → chunk-EHAUUUWB.mjs} +3 -3
  67. package/dist/chunks/chunk-EJY7IVSK.mjs +31 -0
  68. package/dist/chunks/{chunk-G4XJG7XI.js → chunk-EVOOTSY5.js} +3 -10
  69. package/dist/chunks/chunk-FUIBYZZ4.mjs +98 -0
  70. package/dist/chunks/{chunk-VH5FF6DZ.mjs → chunk-GX6GSWX3.mjs} +7 -7
  71. package/dist/chunks/chunk-H2G5FMRN.mjs +75 -0
  72. package/dist/chunks/chunk-H2V7RHYV.mjs +120 -0
  73. package/dist/chunks/{chunk-DDMZSNTG.js → chunk-HFBTS42N.js} +168 -240
  74. package/dist/chunks/{chunk-OGUTGD4J.mjs → chunk-HNLI646G.mjs} +71 -39
  75. package/dist/chunks/{chunk-C6H2UNOX.js → chunk-IE4DGLMH.js} +13 -21
  76. package/dist/chunks/{chunk-U76LT5GE.js → chunk-IOSIQLZL.js} +2 -2
  77. package/dist/chunks/{chunk-ZCMKIB5U.js → chunk-J5ZKGPBY.js} +33 -41
  78. package/dist/chunks/{chunk-JUZHWKGS.mjs → chunk-K2TBLM3F.mjs} +6 -12
  79. package/dist/chunks/chunk-KZ7S5VN2.js +7 -0
  80. package/dist/chunks/{chunk-FHPHDK3O.mjs → chunk-LI7SFBUQ.mjs} +4 -4
  81. package/dist/chunks/{chunk-7OEK2KX3.mjs → chunk-LMMON5AU.mjs} +4 -4
  82. package/dist/chunks/{chunk-6DZVNFVY.js → chunk-LOQXCHKL.js} +4 -12
  83. package/dist/chunks/{chunk-377KBJQG.js → chunk-MA2VCCIY.js} +19 -22
  84. package/dist/chunks/chunk-MCKOWMLS.mjs +8 -0
  85. package/dist/chunks/chunk-MMCA33FW.mjs +85 -0
  86. package/dist/chunks/{chunk-TR5JBBEA.mjs → chunk-MRRKW5QN.mjs} +31 -39
  87. package/dist/chunks/{chunk-76K6KXCT.js → chunk-NFIPQZ4O.js} +1 -1
  88. package/dist/chunks/chunk-NZHK76R3.js +109 -0
  89. package/dist/chunks/{chunk-I7YJB2F5.js → chunk-OMN5YQCE.js} +3 -3
  90. package/dist/chunks/{chunk-B7UU3FSS.mjs → chunk-P2T72N62.mjs} +4 -7
  91. package/dist/chunks/chunk-P3DZKXG4.js +116 -0
  92. package/dist/chunks/chunk-P73MEU7N.mjs +150 -0
  93. package/dist/chunks/chunk-PDJTSQOC.js +59 -0
  94. package/dist/chunks/{chunk-RXVYL4AU.js → chunk-PEIEVKD5.js} +7 -13
  95. package/dist/chunks/chunk-PI464222.mjs +5 -0
  96. package/dist/chunks/{chunk-IJG7J2VU.mjs → chunk-QZ4QR3XV.mjs} +30 -36
  97. package/dist/chunks/{chunk-6NTASYZO.js → chunk-RS3SBY3I.js} +61 -30
  98. package/dist/chunks/{chunk-AKIBUO5A.mjs → chunk-RX5UKRYK.mjs} +19 -26
  99. package/dist/chunks/chunk-S2GMEC43.js +109 -0
  100. package/dist/chunks/{chunk-WJ2OVQD3.mjs → chunk-SGNRVYYQ.mjs} +19 -25
  101. package/dist/chunks/{chunk-IB5UCYQY.mjs → chunk-SJMCPSVH.mjs} +15 -5
  102. package/dist/chunks/chunk-UKRU46PH.mjs +182 -0
  103. package/dist/chunks/{chunk-WNFJ4NJN.mjs → chunk-ULGYTBCT.mjs} +3 -11
  104. package/dist/chunks/{chunk-TXYYBJBQ.js → chunk-VGO4Z2WH.js} +73 -41
  105. package/dist/chunks/{chunk-LBKBCI2K.mjs → chunk-VIGRCJAE.mjs} +3 -10
  106. package/dist/chunks/chunk-VVXQZ4XH.mjs +93 -0
  107. package/dist/chunks/{chunk-Z7OKV6NW.mjs → chunk-W4GG5A7K.mjs} +12 -20
  108. package/dist/chunks/chunk-WGGBE4ZD.mjs +201 -0
  109. package/dist/chunks/{chunk-WSWD5ZUJ.js → chunk-WKCXACMZ.js} +20 -27
  110. package/dist/chunks/{chunk-GSLIY6WW.js → chunk-X2SHTVZQ.js} +17 -37
  111. package/dist/chunks/chunk-X3CTJ7TD.js +108 -0
  112. package/dist/chunks/{chunk-U6KOUYWX.mjs → chunk-XG6QG65W.mjs} +7 -10
  113. package/dist/chunks/chunk-XGIJZ3NZ.js +160 -0
  114. package/dist/chunks/chunk-YB5ZKHVB.js +64 -0
  115. package/dist/chunks/{chunk-Q7H6LCNN.js → chunk-YCG4FZC3.js} +23 -25
  116. package/dist/chunks/{chunk-JZ3PWHKS.mjs → chunk-YLO4UKSC.mjs} +18 -21
  117. package/dist/chunks/chunk-YO5MSDPX.mjs +36 -0
  118. package/dist/chunks/{chunk-Q2TMXHPK.js → chunk-YZV6FWE7.js} +25 -43
  119. package/dist/chunks/chunk-ZI4LN2B2.js +96 -0
  120. package/dist/chunks/chunk-ZU4AWAFT.mjs +137 -0
  121. package/dist/chunks/chunk-ZWSIIGA3.mjs +58 -0
  122. package/dist/countdown.js +4 -4
  123. package/dist/countdown.mjs +2 -2
  124. package/dist/counter.js +2 -2
  125. package/dist/counter.mjs +1 -1
  126. package/dist/data-list.js +5 -5
  127. package/dist/data-list.mjs +4 -4
  128. package/dist/date-picker.d.mts +17 -0
  129. package/dist/date-picker.d.ts +17 -0
  130. package/dist/date-picker.js +12 -0
  131. package/dist/date-picker.mjs +3 -0
  132. package/dist/divider.js +4 -4
  133. package/dist/divider.mjs +2 -2
  134. package/dist/drawer.d.mts +1 -1
  135. package/dist/drawer.d.ts +1 -1
  136. package/dist/drawer.js +11 -11
  137. package/dist/drawer.mjs +2 -2
  138. package/dist/dropdown-menu.d.mts +30 -0
  139. package/dist/dropdown-menu.d.ts +30 -0
  140. package/dist/dropdown-menu.js +32 -0
  141. package/dist/dropdown-menu.mjs +3 -0
  142. package/dist/ellipsis.js +3 -3
  143. package/dist/ellipsis.mjs +2 -2
  144. package/dist/empty-state.d.mts +16 -0
  145. package/dist/empty-state.d.ts +16 -0
  146. package/dist/empty-state.js +16 -0
  147. package/dist/empty-state.mjs +3 -0
  148. package/dist/error-boundary.d.mts +1 -1
  149. package/dist/error-boundary.d.ts +1 -1
  150. package/dist/error-boundary.js +2 -2
  151. package/dist/error-boundary.mjs +1 -1
  152. package/dist/index.d.mts +15 -0
  153. package/dist/index.d.ts +15 -0
  154. package/dist/index.js +322 -200
  155. package/dist/index.mjs +49 -35
  156. package/dist/infinite-scroll.js +3 -3
  157. package/dist/infinite-scroll.mjs +2 -2
  158. package/dist/marquee.js +3 -3
  159. package/dist/marquee.mjs +2 -2
  160. package/dist/modal/index.js +13 -13
  161. package/dist/modal/index.mjs +4 -4
  162. package/dist/number-input.d.mts +1 -1
  163. package/dist/number-input.d.ts +1 -1
  164. package/dist/number-input.js +5 -5
  165. package/dist/number-input.mjs +2 -2
  166. package/dist/nx-image.d.mts +13 -0
  167. package/dist/nx-image.d.ts +13 -0
  168. package/dist/nx-image.js +12 -0
  169. package/dist/nx-image.mjs +3 -0
  170. package/dist/pagination.d.mts +1 -0
  171. package/dist/pagination.d.ts +1 -0
  172. package/dist/pagination.js +5 -5
  173. package/dist/pagination.mjs +2 -2
  174. package/dist/popover.d.mts +1 -1
  175. package/dist/popover.d.ts +1 -1
  176. package/dist/popover.js +8 -8
  177. package/dist/popover.mjs +2 -2
  178. package/dist/price-input.d.mts +36 -0
  179. package/dist/price-input.d.ts +36 -0
  180. package/dist/price-input.js +16 -0
  181. package/dist/price-input.mjs +3 -0
  182. package/dist/progress.d.mts +18 -0
  183. package/dist/progress.d.ts +18 -0
  184. package/dist/progress.js +16 -0
  185. package/dist/progress.mjs +3 -0
  186. package/dist/radio-group.js +6 -6
  187. package/dist/radio-group.mjs +2 -2
  188. package/dist/schemas/_all.json +2356 -404
  189. package/dist/schemas/accordion.json +14 -11
  190. package/dist/schemas/alert.json +49 -0
  191. package/dist/schemas/avatar.json +13 -7
  192. package/dist/schemas/badge.json +76 -0
  193. package/dist/schemas/breadcrumb.json +47 -0
  194. package/dist/schemas/button.json +27 -9
  195. package/dist/schemas/carousel.json +10 -4
  196. package/dist/schemas/carouselButton.json +20 -0
  197. package/dist/schemas/carouselDots.json +17 -0
  198. package/dist/schemas/carouselSlide.json +20 -0
  199. package/dist/schemas/checkBox.json +33 -8
  200. package/dist/schemas/chip.json +15 -6
  201. package/dist/schemas/clientOnly.json +19 -0
  202. package/dist/schemas/countdown.json +10 -7
  203. package/dist/schemas/counter.json +15 -9
  204. package/dist/schemas/dataList.json +10 -10
  205. package/dist/schemas/datePicker.json +56 -0
  206. package/dist/schemas/divider.json +8 -5
  207. package/dist/schemas/drawer.json +24 -2
  208. package/dist/schemas/drawerClose.json +24 -0
  209. package/dist/schemas/drawerContent.json +9 -6
  210. package/dist/schemas/drawerDescription.json +20 -0
  211. package/dist/schemas/drawerTitle.json +20 -0
  212. package/dist/schemas/drawerTrigger.json +24 -0
  213. package/dist/schemas/dropdownMenu.json +83 -0
  214. package/dist/schemas/ellipsis.json +17 -7
  215. package/dist/schemas/emptyState.json +44 -0
  216. package/dist/schemas/errorBoundary.json +22 -0
  217. package/dist/schemas/infiniteScroll.json +18 -9
  218. package/dist/schemas/marquee.json +12 -6
  219. package/dist/schemas/modalCall.json +81 -3
  220. package/dist/schemas/modalTemplate.json +67 -14
  221. package/dist/schemas/numberInput.json +42 -12
  222. package/dist/schemas/nxImage.json +56 -0
  223. package/dist/schemas/pagination.json +13 -6
  224. package/dist/schemas/popover.json +18 -9
  225. package/dist/schemas/priceInput.json +104 -0
  226. package/dist/schemas/progress.json +63 -0
  227. package/dist/schemas/radioGroup.json +19 -9
  228. package/dist/schemas/radioItem.json +12 -5
  229. package/dist/schemas/select.json +15 -9
  230. package/dist/schemas/selectItem.json +7 -4
  231. package/dist/schemas/skeleton.json +12 -6
  232. package/dist/schemas/slider.json +78 -0
  233. package/dist/schemas/spinner.json +11 -4
  234. package/dist/schemas/stepper.json +73 -0
  235. package/dist/schemas/switch.json +23 -5
  236. package/dist/schemas/tab.json +17 -14
  237. package/dist/schemas/table.json +75 -0
  238. package/dist/schemas/tableRow.json +32 -0
  239. package/dist/schemas/tagInput.json +70 -0
  240. package/dist/schemas/tdColumn.json +107 -0
  241. package/dist/schemas/textArea.json +72 -7
  242. package/dist/schemas/textInput.json +72 -10
  243. package/dist/schemas/themeProvider.json +65 -0
  244. package/dist/schemas/toastOptions.json +81 -0
  245. package/dist/schemas/toaster.json +76 -0
  246. package/dist/schemas/toggleGroup.json +88 -0
  247. package/dist/schemas/tooltip.json +12 -9
  248. package/dist/schemas/virtualGrid.json +22 -11
  249. package/dist/schemas/virtualList.json +14 -8
  250. package/dist/schemas.d.mts +1360 -81
  251. package/dist/schemas.d.ts +1360 -81
  252. package/dist/schemas.js +725 -273
  253. package/dist/schemas.mjs +698 -273
  254. package/dist/select.js +6 -6
  255. package/dist/select.mjs +2 -2
  256. package/dist/skeleton.js +3 -3
  257. package/dist/skeleton.mjs +2 -2
  258. package/dist/slider.d.mts +20 -0
  259. package/dist/slider.d.ts +20 -0
  260. package/dist/slider.js +16 -0
  261. package/dist/slider.mjs +3 -0
  262. package/dist/spinner.js +3 -3
  263. package/dist/spinner.mjs +2 -2
  264. package/dist/stepper.d.mts +20 -0
  265. package/dist/stepper.d.ts +20 -0
  266. package/dist/stepper.js +16 -0
  267. package/dist/stepper.mjs +3 -0
  268. package/dist/styles/layer.d.mts +3 -0
  269. package/dist/styles/layer.d.ts +3 -0
  270. package/dist/styles/layer.js +18 -0
  271. package/dist/styles/layer.mjs +16 -0
  272. package/dist/styles.css +3872 -2
  273. package/dist/styles.js +2 -5
  274. package/dist/styles.layered.css +3875 -0
  275. package/dist/styles.mjs +1 -4
  276. package/dist/switch.js +4 -4
  277. package/dist/switch.mjs +2 -2
  278. package/dist/tab.js +5 -5
  279. package/dist/tab.mjs +2 -2
  280. package/dist/table.d.mts +2 -2
  281. package/dist/table.d.ts +2 -2
  282. package/dist/table.js +9 -9
  283. package/dist/table.mjs +3 -3
  284. package/dist/tag-input.d.mts +21 -0
  285. package/dist/tag-input.d.ts +21 -0
  286. package/dist/tag-input.js +16 -0
  287. package/dist/tag-input.mjs +3 -0
  288. package/dist/text-area.d.mts +5 -1
  289. package/dist/text-area.d.ts +5 -1
  290. package/dist/text-area.js +4 -4
  291. package/dist/text-area.mjs +2 -2
  292. package/dist/text-input.d.mts +5 -1
  293. package/dist/text-input.d.ts +5 -1
  294. package/dist/text-input.js +4 -4
  295. package/dist/text-input.mjs +2 -2
  296. package/dist/toast.js +5 -5
  297. package/dist/toast.mjs +2 -2
  298. package/dist/toggle-group.d.mts +36 -0
  299. package/dist/toggle-group.d.ts +36 -0
  300. package/dist/toggle-group.js +16 -0
  301. package/dist/toggle-group.mjs +3 -0
  302. package/dist/tooltip.js +5 -5
  303. package/dist/tooltip.mjs +2 -2
  304. package/dist/utils/cn.d.mts +0 -10
  305. package/dist/utils/cn.d.ts +0 -10
  306. package/dist/utils/cn.js +2 -2
  307. package/dist/utils/cn.mjs +1 -1
  308. package/dist/virtual-scroll.js +4 -4
  309. package/dist/virtual-scroll.mjs +2 -2
  310. package/package.json +90 -17
  311. package/scripts/setup-cursor-rules.cjs +6 -6
  312. package/dist/chunks/chunk-7MT3QYE6.js +0 -92
  313. package/dist/chunks/chunk-FA2OPP3U.mjs +0 -140
  314. package/dist/chunks/chunk-FKZI2HTI.js +0 -104
  315. package/dist/chunks/chunk-NCQDOPBR.mjs +0 -86
  316. package/dist/chunks/chunk-NTN55ZIX.mjs +0 -113
  317. package/dist/chunks/chunk-Q7GQVAYY.js +0 -88
  318. package/dist/chunks/chunk-QJNQCLMV.js +0 -25
  319. package/dist/chunks/chunk-UDQXLI5Y.mjs +0 -81
  320. package/dist/chunks/chunk-UR6JOKVB.mjs +0 -65
  321. package/dist/chunks/chunk-WVIEIRK2.js +0 -136
  322. package/dist/chunks/chunk-XALPBGSC.mjs +0 -23
  323. package/dist/schemas/typography.json +0 -70
  324. package/dist/typography.d.mts +0 -19
  325. package/dist/typography.d.ts +0 -19
  326. package/dist/typography.js +0 -102
  327. package/dist/typography.mjs +0 -79
@@ -1,41 +1,57 @@
1
1
  ---
2
- description: "@nexus-cross/design-system 컴포넌트 API 레퍼런스모든 공용 컴포넌트의 props와 사용 예시"
2
+ description: "@nexus-cross/design-system component API referenceprops and usage examples for all shared components"
3
3
  globs: "**/*.tsx,**/*.jsx,**/*.ts"
4
4
  alwaysApply: false
5
5
  ---
6
6
 
7
7
  # @nexus-cross/design-system — Component API Reference
8
8
 
9
- 모든 컴포넌트는 `@nexus-cross/design-system`에서 import.
9
+ All components are imported from `@nexus-cross/design-system`.
10
10
 
11
11
  ---
12
12
 
13
13
  ## Button
14
14
 
15
- 인터랙티브 버튼. `asChild`로 렌더링 요소 변경 가능.
15
+ Interactive button. semantic(color) x variant(style) 2-axis system. Rendering element changeable via asChild.
16
16
 
17
17
  | Prop | Type | Default | Description |
18
18
  |---|---|---|---|
19
- | `variant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost' \| 'danger'` | `'primary'` | 시각 스타일 |
20
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 크기 |
21
- | `asChild` | `boolean` | `false` | 자식 요소로 렌더링 |
22
- | `detectDoubleClick` | `boolean` | `false` | 500ms 더블 클릭 방지 |
23
- | `disabled` | `boolean` | - | 비활성 (`aria-disabled` 자동) |
24
- | `className` | `string` | - | 스타일 오버라이드 |
19
+ | `semantic` | `'primary'` \| `'secondary'` \| `'normal'` \| `'danger'` | `"primary"` | Color theme (primary=main, secondary=sub, normal=neutral, danger=danger) |
20
+ | `variant` | `'contained'` \| `'outlined'` \| `'subtle'` \| `'ghost'` | `"contained"` | Visual style (contained=filled, outlined=border, subtle=light bg, ghost=transparent) |
21
+ | `size` | `'xl'` \| `'lg'` \| `'md'` \| `'sm'` | `"md"` | Size |
22
+ | `radius` | `'default'` \| `'circle'` | `"default"` | Corner radius (default=size-based radius, circle=pill shape) |
23
+ | `asChild` | `boolean` | - | If true, renders as child element (Slot pattern) |
24
+ | `detectDoubleClick` | `boolean` | - | Prevent double click within 500ms |
25
+ | `disabled` | `boolean` | - | Disabled (auto aria-disabled) |
26
+ | `children` | `ReactNode` | - | Button content (ReactNode) |
27
+ | `onClick` | `ReactNode` | - | Click event handler |
28
+ | `type` | `'button'` \| `'submit'` \| `'reset'` | - | HTML button type (default: button) |
29
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
30
+ | `className` | `string` | - | Style override |
31
+
32
+ 2-axis variant: semantic (color) × variant (style). sizes: xl, lg, md, sm. radius: default, circle.
25
33
 
26
34
  ```tsx
27
- <Button variant="primary" size="lg">확인</Button>
35
+ // Default (primary + contained)
36
+ <Button>Confirm</Button>
28
37
 
29
- <Button variant="outline" disabled>비활성</Button>
38
+ // semantic × variant combinations
39
+ <Button semantic="primary" variant="contained" size="lg">Primary Contained</Button>
40
+ <Button semantic="secondary" variant="outlined" size="md">Secondary Outlined</Button>
41
+ <Button semantic="normal" variant="subtle" size="sm">Normal Subtle</Button>
42
+ <Button semantic="danger" variant="ghost">Danger Ghost</Button>
30
43
 
31
- // 로딩 상태 (Spinner를 children에 직접 배치)
44
+ // Pill shape (radius="circle")
45
+ <Button semantic="primary" variant="contained" radius="circle">Pill</Button>
46
+
47
+ // Loading state
32
48
  <Button disabled>
33
- <Spinner size={16} /> 처리 중...
49
+ <Spinner size={16} /> Processing...
34
50
  </Button>
35
51
 
36
- // <a> 태그로 렌더링
37
- <Button asChild variant="ghost">
38
- <a href="/settings">설정</a>
52
+ // Render as <a> tag
53
+ <Button asChild semantic="normal" variant="ghost">
54
+ <a href="/settings">Settings</a>
39
55
  </Button>
40
56
  ```
41
57
 
@@ -43,42 +59,95 @@ alwaysApply: false
43
59
 
44
60
  ## TextInput
45
61
 
46
- 텍스트 입력 필드. prefix/suffix 아이콘 지원.
62
+ Text input field. Supports label, description, prefix/suffix icons, clearable, character counter.
47
63
 
48
64
  | Prop | Type | Default | Description |
49
65
  |---|---|---|---|
50
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 크기 |
51
- | `error` | `boolean` | `false` | 에러 상태 (`aria-invalid` 자동) |
52
- | `prefixIcon` | `ReactNode` | - | 앞쪽 아이콘 |
53
- | `suffixIcon` | `ReactNode` | - | 뒤쪽 아이콘 |
54
- | `onValueChange` | `(value: string) => void` | - | 변경 콜백 |
66
+ | `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
67
+ | `error` | `boolean` | - | Error state (auto aria-invalid) |
68
+ | `prefixIcon` | `ReactNode` | - | Prefix icon (ReactNode) |
69
+ | `suffixIcon` | `ReactNode` | - | Suffix icon (ReactNode) |
70
+ | `label` | `ReactNode` | - | Label above input field (ReactNode) |
71
+ | `description` | `ReactNode` | - | Description below input field (ReactNode, red on error) |
72
+ | `showCount` | `boolean` | - | Show character count (requires maxLength) |
73
+ | `maxLength` | `number` | - | Maximum character count |
74
+ | `clearable` | `boolean` | - | Clear input via X button |
75
+ | `placeholder` | `string` | - | Placeholder |
76
+ | `disabled` | `boolean` | - | Disabled |
77
+ | `readOnly` | `boolean` | - | Read-only |
78
+ | `value` | `string` | - | Input value (controlled mode) |
79
+ | `defaultValue` | `string` | - | Initial value (uncontrolled mode) |
80
+ | `type` | `string` | - | Input type (text, password, email, url, etc.) |
81
+ | `name` | `string` | - | Form field name |
82
+ | `id` | `string` | - | Element ID (for label htmlFor binding) |
83
+ | `autoFocus` | `boolean` | - | Auto focus |
84
+ | `autoComplete` | `string` | - | Autocomplete hint (on, off, email, etc.) |
85
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
86
+ | `onChange` | `ReactNode` | - | Native change event handler |
87
+ | `onBlur` | `ReactNode` | - | Blur callback |
88
+ | `onFocus` | `ReactNode` | - | Focus callback |
89
+ | `className` | `string` | - | Style override |
55
90
 
56
91
  ```tsx
57
- <TextInput placeholder="이메일" size="md" />
92
+ <TextInput placeholder="Email" size="md" />
58
93
 
59
94
  <TextInput
60
- error
95
+ label="Email"
96
+ description="Enter your email address"
97
+ placeholder="example@email.com"
98
+ clearable
99
+ />
100
+
101
+ <TextInput
102
+ label="Search"
61
103
  prefixIcon={<SearchIcon />}
104
+ clearable
62
105
  onValueChange={(v) => setQuery(v)}
63
106
  />
107
+
108
+ <TextInput
109
+ label="Nickname"
110
+ description="2-20 characters"
111
+ showCount
112
+ maxLength={20}
113
+ clearable
114
+ error
115
+ />
64
116
  ```
65
117
 
66
118
  ---
67
119
 
68
120
  ## TextArea
69
121
 
70
- 여러 텍스트 입력. 글자 카운터 내장.
122
+ Multi-line text input with label, description, character counter, and resize modes.
71
123
 
72
124
  | Prop | Type | Default | Description |
73
125
  |---|---|---|---|
74
- | `error` | `boolean` | `false` | 에러 상태 (`aria-invalid` 자동) |
75
- | `showCount` | `boolean` | `false` | 글자 표시 (`maxLength` 필요) |
76
- | `maxLength` | `number` | - | 최대 글자 |
77
- | `onValueChange` | `(value: string) => void` | - | 변경 콜백 |
126
+ | `label` | `string` | - | Label text above the textarea |
127
+ | `description` | `string` | - | Helper/error description below the textarea |
128
+ | `size` | `'lg'` \| `'md'` | - | Font size variant (lg: 16px, md: 14px) |
129
+ | `resize` | `'default'` \| `'auto'` \| `'none'` | - | Resize mode (default: manual, auto: auto-grow, none: fixed) |
130
+ | `error` | `boolean` | - | Error state (auto aria-invalid) |
131
+ | `showCount` | `boolean` | - | Show character count (requires maxLength) |
132
+ | `maxLength` | `number` | - | Maximum character count |
133
+ | `placeholder` | `string` | - | Placeholder |
134
+ | `rows` | `number` | - | Visible row count |
135
+ | `disabled` | `boolean` | - | Disabled |
136
+ | `readOnly` | `boolean` | - | Read-only |
137
+ | `value` | `string` | - | Input value (controlled mode) |
138
+ | `defaultValue` | `string` | - | Initial value (uncontrolled mode) |
139
+ | `name` | `string` | - | Form field name |
140
+ | `id` | `string` | - | Element ID |
141
+ | `autoFocus` | `boolean` | - | Auto focus |
142
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
143
+ | `onChange` | `ReactNode` | - | Native change event handler |
144
+ | `onBlur` | `ReactNode` | - | Blur callback |
145
+ | `onFocus` | `ReactNode` | - | Focus callback |
146
+ | `className` | `string` | - | Style override |
78
147
 
79
148
  ```tsx
80
149
  <TextArea
81
- placeholder="내용을 입력하세요"
150
+ placeholder="Enter your content"
82
151
  maxLength={500}
83
152
  showCount
84
153
  rows={4}
@@ -89,23 +158,37 @@ alwaysApply: false
89
158
 
90
159
  ## Select
91
160
 
92
- 드롭다운 선택. Radix Select 기반.
161
+ Dropdown select. Based on Radix Select. Used with SelectItem.
93
162
 
94
163
  | Prop | Type | Default | Description |
95
164
  |---|---|---|---|
96
- | `value` | `string` | - | 선택된 |
97
- | `onValueChange` | `(value: string) => void` | - | 변경 콜백 |
98
- | `placeholder` | `string` | - | 플레이스홀더 |
99
- | `variant` | `'default' \| 'outline'` | `'default'` | 트리거 스타일 |
100
- | `size` | `'sm' \| 'md' \| 'lg' \| 'full'` | `'full'` | 너비 |
101
- | `disabled` | `boolean` | - | 비활성 |
102
- | `displayComponent` | `ReactNode` | - | 트리거에 커스텀 표시 |
165
+ | `value` | `string` | - | Selected value |
166
+ | `placeholder` | `string` | - | Placeholder |
167
+ | `variant` | `'default'` \| `'outline'` | `"default"` | Trigger style |
168
+ | `size` | `'sm'` \| `'md'` \| `'lg'` \| `'full'` | `"full"` | Width |
169
+ | `disabled` | `boolean` | - | Disabled |
170
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
171
+ | `displayComponent` | `ReactNode` | - | Custom display in trigger (ReactNode) |
172
+ | `children` | `ReactNode` | - | SelectItem list (ReactNode, required) |
173
+ | `className` | `string` | - | Wrapper style |
174
+ | `triggerClassName` | `string` | - | Trigger style override |
175
+
176
+ ### SelectItem
177
+
178
+ Individual option within Select.
179
+
180
+ | Prop | Type | Default | Description |
181
+ |---|---|---|---|
182
+ | `value` | `string` | - | Item value |
183
+ | `children` | `ReactNode` | - | Item content (ReactNode, required) |
184
+ | `disabled` | `boolean` | - | Disabled |
185
+ | `className` | `string` | - | Style override |
103
186
 
104
187
  ```tsx
105
- <Select value={lang} onValueChange={setLang} placeholder="언어 선택">
106
- <SelectItem value="ko">한국어</SelectItem>
188
+ <Select value={lang} onValueChange={setLang} placeholder="Select language">
189
+ <SelectItem value="ko">Korean</SelectItem>
107
190
  <SelectItem value="en">English</SelectItem>
108
- <SelectItem value="ja">日本語</SelectItem>
191
+ <SelectItem value="ja">Japanese</SelectItem>
109
192
  </Select>
110
193
  ```
111
194
 
@@ -113,22 +196,30 @@ alwaysApply: false
113
196
 
114
197
  ## CheckBox
115
198
 
116
- 체크박스. 네이티브 `<input>` 기반, square/round 형태 지원.
199
+ Checkbox. Native input-based, supports square/round shapes.
117
200
 
118
201
  | Prop | Type | Default | Description |
119
202
  |---|---|---|---|
120
- | `size` | `'sm' \| 'md'` | `'md'` | 크기 |
121
- | `shape` | `'square' \| 'round'` | `'square'` | 형태 |
122
- | `checked` | `boolean` | - | 체크 상태 |
123
- | `indeterminate` | `boolean` | `false` | 불확정 상태 (`aria-checked="mixed"`) |
124
- | `onCheckedChange` | `(checked: boolean) => void` | - | 체크 변경 콜백 |
125
- | `label` | `ReactNode` | - | 라벨 텍스트 |
203
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
204
+ | `shape` | `'square'` \| `'round'` | `"square"` | Shape |
205
+ | `checked` | `boolean` | - | Checked state |
206
+ | `indeterminate` | `boolean` | - | Indeterminate state (aria-checked="mixed") |
207
+ | `disabled` | `boolean` | - | Disabled |
208
+ | `readOnly` | `boolean` | - | Read-only |
209
+ | `label` | `ReactNode` | - | Label text (ReactNode) |
210
+ | `children` | `ReactNode` | - | Label alternative content (ReactNode) |
211
+ | `name` | `string` | - | Form field name |
212
+ | `id` | `string` | - | Element ID |
213
+ | `value` | `string` | - | Value for form submission |
214
+ | `onCheckedChange` | `ReactNode` | - | Checked state change callback (checked: boolean) => void |
215
+ | `onChange` | `ReactNode` | - | Native change event handler |
216
+ | `className` | `string` | - | Style override |
126
217
 
127
218
  ```tsx
128
219
  <CheckBox
129
220
  checked={agreed}
130
221
  onCheckedChange={setAgreed}
131
- label="이용약관에 동의합니다"
222
+ label="I agree to the terms of service"
132
223
  />
133
224
 
134
225
  <CheckBox shape="round" size="sm" checked indeterminate />
@@ -136,23 +227,36 @@ alwaysApply: false
136
227
 
137
228
  ---
138
229
 
139
- ## RadioGroup / RadioItem
230
+ ## RadioGroup
140
231
 
141
- 라디오 그룹. 네이티브 `<input type="radio">` 기반.
232
+ Radio group. Used with RadioItem.
142
233
 
143
- | RadioGroup Prop | Type | Default | Description |
234
+ | Prop | Type | Default | Description |
144
235
  |---|---|---|---|
145
- | `name` | `string` (필수) | - | form name |
146
- | `value` | `string` | - | 선택된 |
147
- | `onValueChange` | `(value: string) => void` | - | 변경 콜백 |
148
- | `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | 배치 방향 |
149
- | `size` | `'sm' \| 'md'` | `'md'` | 크기 |
150
- | `aria-label` | `string` | - | 접근성 라벨 |
236
+ | `name` | `string` | - | Form name (required) |
237
+ | `value` | `string` | - | Selected value (controlled) |
238
+ | `defaultValue` | `string` | - | Initial value (uncontrolled) |
239
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
240
+ | `orientation` | `'horizontal'` \| `'vertical'` | `"vertical"` | Layout direction |
241
+ | `disabled` | `boolean` | - | Disabled |
242
+ | `children` | `ReactNode` | - | RadioItem list (ReactNode, required) |
243
+ | `aria-label` | `string` | - | Accessibility label |
244
+ | `aria-labelledby` | `string` | - | Accessibility label reference ID |
245
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
246
+ | `className` | `string` | - | Style override |
247
+
248
+ ### RadioItem
249
+
250
+ Individual option within RadioGroup.
151
251
 
152
- | RadioItem Prop | Type | Description |
153
- |---|---|---|
154
- | `value` | `string` (필수) | 항목 |
155
- | `label` | `ReactNode` | 라벨 텍스트 |
252
+ | Prop | Type | Default | Description |
253
+ |---|---|---|---|
254
+ | `value` | `string` | - | Item value (required) |
255
+ | `size` | `'sm'` \| `'md'` | - | Size (overrides group) |
256
+ | `label` | `ReactNode` | - | Label text (ReactNode) |
257
+ | `children` | `ReactNode` | - | Label alternative content (ReactNode) |
258
+ | `disabled` | `boolean` | - | Disabled |
259
+ | `className` | `string` | - | Style override |
156
260
 
157
261
  ```tsx
158
262
  <RadioGroup
@@ -160,11 +264,11 @@ alwaysApply: false
160
264
  value={plan}
161
265
  onValueChange={setPlan}
162
266
  orientation="horizontal"
163
- aria-label="요금제 선택"
267
+ aria-label="Select plan"
164
268
  >
165
- <RadioItem value="free" label="무료" />
166
- <RadioItem value="pro" label="프로" />
167
- <RadioItem value="enterprise" label="엔터프라이즈" />
269
+ <RadioItem value="free" label="Free" />
270
+ <RadioItem value="pro" label="Pro" />
271
+ <RadioItem value="enterprise" label="Enterprise" />
168
272
  </RadioGroup>
169
273
  ```
170
274
 
@@ -172,14 +276,19 @@ alwaysApply: false
172
276
 
173
277
  ## Switch
174
278
 
175
- 토글 스위치. 네이티브 checkbox 기반, `role="switch"`.
279
+ Toggle switch. Native checkbox-based, role="switch".
176
280
 
177
281
  | Prop | Type | Default | Description |
178
282
  |---|---|---|---|
179
- | `size` | `'sm' \| 'md'` | `'md'` | 크기 |
180
- | `checked` | `boolean` | - | on/off 상태 |
181
- | `onCheckedChange` | `(checked: boolean) => void` | - | 변경 콜백 |
182
- | `disabled` | `boolean` | - | 비활성 |
283
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
284
+ | `checked` | `boolean` | - | On/off state |
285
+ | `disabled` | `boolean` | - | Disabled |
286
+ | `readOnly` | `boolean` | - | Read-only |
287
+ | `name` | `string` | - | Form field name |
288
+ | `id` | `string` | - | Element ID |
289
+ | `onCheckedChange` | `ReactNode` | - | Toggle state change callback (checked: boolean) => void |
290
+ | `onChange` | `ReactNode` | - | Native change event handler (ChangeEvent) |
291
+ | `className` | `string` | - | Style override |
183
292
 
184
293
  ```tsx
185
294
  <Switch checked={darkMode} onCheckedChange={setDarkMode} />
@@ -189,24 +298,27 @@ alwaysApply: false
189
298
 
190
299
  ## Chip
191
300
 
192
- 칩/태그/뱃지. `asChild`로 렌더링 요소 변경 가능.
301
+ Chip/tag/badge. Close button displayed via onClose prop.
193
302
 
194
303
  | Prop | Type | Default | Description |
195
304
  |---|---|---|---|
196
- | `variant` | `'default' \| 'filled' \| 'outline' \| 'accent'` | `'default'` | 스타일 |
197
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 크기 |
198
- | `asChild` | `boolean` | `false` | 자식 요소로 렌더링 |
199
- | `disabled` | `boolean` | - | 비활성 (`aria-disabled` 자동) |
200
- | `onClose` | `(e: MouseEvent) => void` | - | 닫기 버튼 표시 및 콜백 |
305
+ | `variant` | `'default'` \| `'filled'` \| `'outline'` \| `'accent'` | `"default"` | Style |
306
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
307
+ | `asChild` | `boolean` | - | If true, renders as child element (Slot pattern) |
308
+ | `disabled` | `boolean` | - | Disabled (auto aria-disabled) |
309
+ | `children` | `ReactNode` | - | Chip content (ReactNode) |
310
+ | `onClose` | `ReactNode` | - | Close button click callback (e: MouseEvent) => void. Shows X button when provided |
311
+ | `onClick` | `ReactNode` | - | Click event handler |
312
+ | `className` | `string` | - | Style override |
201
313
 
202
314
  ```tsx
203
315
  <Chip variant="accent" size="sm">New</Chip>
204
316
 
205
317
  <Chip onClose={() => removeTag(id)}>React</Chip>
206
318
 
207
- // <li>로 렌더링
319
+ // Render as <li>
208
320
  <Chip asChild variant="filled">
209
- <li>리스트 칩</li>
321
+ <li>List chip</li>
210
322
  </Chip>
211
323
  ```
212
324
 
@@ -214,31 +326,73 @@ alwaysApply: false
214
326
 
215
327
  ## Spinner
216
328
 
217
- 로딩 인디케이터. SVG 기반. `role="status"` 내장.
329
+ Loading indicator. SVG-based. Built-in role="status".
218
330
 
219
331
  | Prop | Type | Default | Description |
220
332
  |---|---|---|---|
221
- | `size` | `number` | `20` | px 크기 |
222
- | `className` | `string` | - | 색상 오버라이드 |
223
- | `aria-label` | `string` | `'Loading'` | 접근성 라벨 |
333
+ | `size` | `number` | `20` | Size in px |
334
+ | `color` | `string` | - | Color (CSS color value, default currentColor) |
335
+ | `aria-label` | `string` | `"Loading"` | Accessibility label |
336
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
337
+ | `className` | `string` | - | Color override etc. |
224
338
 
225
339
  ```tsx
226
340
  <Spinner size={24} />
227
341
 
228
- <Spinner size={14} className="text-white" aria-label="로딩 중" />
342
+ <Spinner size={14} className="text-white" aria-label="Loading" />
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Skeleton
348
+
349
+ Skeleton loading placeholder. Size/shape via className. With children, wraps transparently to maintain actual size.
350
+
351
+ | Prop | Type | Default | Description |
352
+ |---|---|---|---|
353
+ | `as` | `'div'` \| `'span'` | `"div"` | Rendered tag |
354
+ | `circle` | `boolean` | `false` | Circle skeleton (rounded-full) |
355
+ | `width` | `string` \| `number` | - | Width (e.g. '100px', '50%', 200) |
356
+ | `height` | `string` \| `number` | - | Height (e.g. '16px', 40) |
357
+ | `children` | `ReactNode` | - | Inner content (shown when loaded, maintains actual size) |
358
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
359
+ | `className` | `string` | - | Style override |
360
+
361
+ ```tsx
362
+ // Basic usage (size via className)
363
+ <Skeleton className="h-4 w-48" />
364
+ <Skeleton className="h-3 w-32" />
365
+
366
+ // Circular avatar skeleton
367
+ <Skeleton circle width={40} height={40} />
368
+
369
+ // width/height props
370
+ <Skeleton width="100%" height={120} className="rounded-lg" />
371
+
372
+ // Match children size
373
+ <Skeleton>
374
+ <p>Skeleton will match this text size</p>
375
+ </Skeleton>
376
+
377
+ // With DataList
378
+ <DataList list={data} skeletonElement={<MySkeleton />} skeletonCount={5}>
379
+ {({ item }) => <Card key={item.id} {...item} />}
380
+ </DataList>
229
381
  ```
230
382
 
231
383
  ---
232
384
 
233
385
  ## Divider
234
386
 
235
- 구분선. 수평/수직 방향, 실선/점선/파선 지원.
387
+ Divider. Supports horizontal/vertical, solid/dashed/dotted.
236
388
 
237
389
  | Prop | Type | Default | Description |
238
390
  |---|---|---|---|
239
- | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | 방향 |
240
- | `variant` | `'solid' \| 'dashed' \| 'dotted'` | `'solid'` | 스타일 |
241
- | `color` | `string` | - | 커스텀 색상 (CSS ) |
391
+ | `orientation` | `'horizontal'` \| `'vertical'` | `"horizontal"` | Direction |
392
+ | `variant` | `'solid'` \| `'dashed'` \| `'dotted'` | `"solid"` | Line style |
393
+ | `color` | `string` | - | Custom color (CSS value) |
394
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
395
+ | `className` | `string` | - | Style override |
242
396
 
243
397
  ```tsx
244
398
  <Divider />
@@ -249,19 +403,22 @@ alwaysApply: false
249
403
 
250
404
  ## Tooltip
251
405
 
252
- 툴팁. Radix Tooltip 기반. 단독 사용 가능 (Provider 내장).
406
+ Tooltip. Based on Radix Tooltip. Built-in Provider.
253
407
 
254
408
  | Prop | Type | Default | Description |
255
409
  |---|---|---|---|
256
- | `content` | `ReactNode` | - | 툴팁 내용 |
257
- | `variant` | `'dark' \| 'light'` | `'dark'` | 스타일 |
258
- | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | 위치 |
259
- | `align` | `'start' \| 'center' \| 'end'` | `'center'` | 정렬 |
260
- | `delayDuration` | `number` | `200` | 표시 지연 (ms) |
261
- | `disabled` | `boolean` | `false` | 비활성 |
410
+ | `children` | `ReactNode` | - | Trigger element (ReactNode, required) |
411
+ | `content` | `ReactNode` | - | Tooltip content (ReactNode, required) |
412
+ | `variant` | `'dark'` \| `'light'` | `"dark"` | Style |
413
+ | `side` | `'top'` \| `'right'` \| `'bottom'` \| `'left'` | `"top"` | Position |
414
+ | `align` | `'start'` \| `'center'` \| `'end'` | `"center"` | Alignment |
415
+ | `delayDuration` | `number` | `200` | Show delay (ms) |
416
+ | `disabled` | `boolean` | `false` | Disabled |
417
+ | `className` | `string` | - | Content style |
418
+ | `triggerClassName` | `string` | - | Trigger style |
262
419
 
263
420
  ```tsx
264
- <Tooltip content="복사됨!" side="bottom">
421
+ <Tooltip content="Copied!" side="bottom">
265
422
  <button>📋</button>
266
423
  </Tooltip>
267
424
  ```
@@ -270,21 +427,25 @@ alwaysApply: false
270
427
 
271
428
  ## Popover
272
429
 
273
- 팝오버. Radix Popover 기반. 컴포저블 패턴과 단일 패턴 모두 지원.
430
+ Popover. Based on Radix Popover.
274
431
 
275
432
  | Prop | Type | Default | Description |
276
433
  |---|---|---|---|
277
- | `trigger` | `ReactNode` | - | 트리거 요소 |
278
- | `children` | `ReactNode` | - | 팝오버 내용 |
279
- | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | 위치 |
280
- | `align` | `'start' \| 'center' \| 'end'` | `'center'` | 정렬 |
281
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 너비 |
282
- | `open` | `boolean` | - | 제어 모드 |
283
- | `onOpenChange` | `(open: boolean) => void` | - | 열림/닫힘 콜백 |
434
+ | `trigger` | `ReactNode` | - | Trigger element (ReactNode, required) |
435
+ | `side` | `'top'` \| `'right'` \| `'bottom'` \| `'left'` | `"bottom"` | Position |
436
+ | `align` | `'start'` \| `'center'` \| `'end'` | `"center"` | Alignment |
437
+ | `sideOffset` | `number` | `4` | Position offset (px) |
438
+ | `alignOffset` | `number` | - | Alignment offset (px) |
439
+ | `open` | `boolean` | - | Controlled mode |
440
+ | `onOpenChange` | `ReactNode` | - | Open/close state change callback (open: boolean) => void |
441
+ | `onClickTrigger` | `ReactNode` | - | Callback on trigger element click. Passed to PopoverPrimitive.Trigger onClick. |
442
+ | `children` | `ReactNode` | - | Popover body (ReactNode) |
443
+ | `className` | `string` | - | Content style |
444
+ | `arrowClassName` | `string` | - | Arrow style |
284
445
 
285
446
  ```tsx
286
- <Popover trigger={<Button variant="outline">메뉴</Button>}>
287
- <div className="p-4">팝오버 내용</div>
447
+ <Popover trigger={<Button variant="outline">Menu</Button>}>
448
+ <div className="p-4">Popover content</div>
288
449
  </Popover>
289
450
  ```
290
451
 
@@ -292,27 +453,29 @@ alwaysApply: false
292
453
 
293
454
  ## Accordion
294
455
 
295
- 아코디언. 단순 `items` 배열 방식과 컴포저블 방식 모두 지원.
456
+ Accordion. Supports both items array and composable patterns.
296
457
 
297
458
  | Prop | Type | Default | Description |
298
459
  |---|---|---|---|
299
- | `items` | `AccordionItemData[]` | - | 항목 배열 |
300
- | `type` | `'single' \| 'multiple'` | `'single'` | 단일/다중 열기 |
301
- | `collapsible` | `boolean` | `true` | 전부 접기 가능 |
302
- | `value` / `defaultValue` | `string \| string[]` | - | 제어/비제어 |
303
- | `onValueChange` | `(value) => void` | - | 변경 콜백 |
460
+ | `items` | `object`[] | - | Accordion item array (required) |
461
+ | `type` | `'single'` \| `'multiple'` | `"single"` | Single/multiple open mode |
462
+ | `collapsible` | `boolean` | `true` | Allow collapsing all |
463
+ | `value` | `string` \| `string`[] | - | Controlled mode |
464
+ | `defaultValue` | `string` \| `string`[] | - | Uncontrolled initial value |
465
+ | `onValueChange` | `ReactNode` | - | Open item change callback (value: string | string[]) => void |
466
+ | `className` | `string` | - | Root style |
304
467
 
305
468
  ```tsx
306
469
  <Accordion items={[
307
- { id: '1', trigger: 'FAQ 1', content: '답변 1' },
308
- { id: '2', trigger: 'FAQ 2', content: '답변 2' },
470
+ { id: '1', trigger: 'FAQ 1', content: 'Answer 1' },
471
+ { id: '2', trigger: 'FAQ 2', content: 'Answer 2' },
309
472
  ]} />
310
473
 
311
- // 컴포저블 방식
474
+ // Composable pattern
312
475
  <AccordionRoot type="single" collapsible>
313
476
  <AccordionItem value="item-1">
314
- <AccordionTrigger>제목</AccordionTrigger>
315
- <AccordionContent>내용</AccordionContent>
477
+ <AccordionTrigger>Title</AccordionTrigger>
478
+ <AccordionContent>Content</AccordionContent>
316
479
  </AccordionItem>
317
480
  </AccordionRoot>
318
481
  ```
@@ -321,24 +484,41 @@ alwaysApply: false
321
484
 
322
485
  ## Drawer
323
486
 
324
- 드로어/바텀시트. Vaul 기반. 4방향 지원.
487
+ Drawer/bottom sheet. Based on Vaul. Compound component pattern.
325
488
 
326
489
  | Prop | Type | Default | Description |
327
490
  |---|---|---|---|
328
- | `direction` | `'bottom' \| 'top' \| 'left' \| 'right'` | `'bottom'` | 방향 |
329
- | `showHandle` | `boolean` | `true` | 핸들 표시 (top/bottom만) |
330
- | `blur` | `'none' \| 'sm' \| 'md'` | `'none'` | 오버레이 블러 |
491
+ | `direction` | `'bottom'` \| `'top'` \| `'left'` \| `'right'` | `"bottom"` | Direction |
492
+ | `open` | `boolean` | - | Open state (controlled mode) |
493
+ | `onOpenChange` | `ReactNode` | - | Open state change callback (open: boolean) => void |
494
+ | `dismissible` | `boolean` | - | Allow close via swipe/outside click (default true) |
495
+ | `modal` | `boolean` | - | Modal mode (default true). If false, background is interactive |
496
+ | `shouldScaleBackground` | `boolean` | - | Background scale effect (default false) |
497
+ | `children` | `ReactNode` | - | Drawer sub-components (ReactNode, required) |
498
+
499
+ ### DrawerContent
500
+
501
+ Drawer.Content area.
502
+
503
+ | Prop | Type | Default | Description |
504
+ |---|---|---|---|
505
+ | `direction` | `'bottom'` \| `'top'` \| `'left'` \| `'right'` | `"bottom"` | Direction (Context takes priority) |
506
+ | `blur` | `'none'` \| `'sm'` \| `'md'` | `"none"` | Overlay blur |
507
+ | `showHandle` | `boolean` | `true` | Show handle bar |
508
+ | `children` | `ReactNode` | - | Content area (ReactNode) |
509
+ | `overlayClassName` | `string` | - | Overlay style |
510
+ | `className` | `string` | - | Panel style |
331
511
 
332
512
  ```tsx
333
513
  <Drawer direction="bottom">
334
514
  <Drawer.Trigger asChild>
335
- <Button>열기</Button>
515
+ <Button>Open</Button>
336
516
  </Drawer.Trigger>
337
517
  <Drawer.Content>
338
- <Drawer.Title>제목</Drawer.Title>
339
- <Drawer.Description>설명</Drawer.Description>
518
+ <Drawer.Title>Title</Drawer.Title>
519
+ <Drawer.Description>Description</Drawer.Description>
340
520
  <Drawer.Close asChild>
341
- <Button variant="ghost">닫기</Button>
521
+ <Button variant="ghost">Close</Button>
342
522
  </Drawer.Close>
343
523
  </Drawer.Content>
344
524
  </Drawer>
@@ -346,353 +526,756 @@ alwaysApply: false
346
526
 
347
527
  ---
348
528
 
349
- ## Modal (함수형 API)
529
+ ## Modal
350
530
 
351
- 명령형 모달. 함수 호출로 열고 닫는다.
531
+ Modal template. All modal components must be wrapped with ModalTemplate.
352
532
 
353
- **중요: 모달 컴포넌트는 반드시 `ModalTemplate`으로 감싸야 한다.**
533
+ | Prop | Type | Default | Description |
534
+ |---|---|---|---|
535
+ | `title` | `ReactNode` | - | Header title (ReactNode) |
536
+ | `desc` | `ReactNode` | - | Header description (ReactNode) |
537
+ | `layout` | `'default'` \| `'bottom-sheet'` \| `'slide-left'` \| `'slide-right'` \| `'full-page'` \| `'full-page-reverse'` \| `'draggable'` | `"default"` | Layout |
538
+ | `showDim` | `boolean` | `true` | Show dim overlay |
539
+ | `dimClose` | `boolean` | `true` | Close on dim click |
540
+ | `hideHeader` | `boolean` | `false` | Hide header |
541
+ | `hideFooter` | `boolean` | `true` | Hide footer |
542
+ | `footer` | `ReactNode` | - | Custom footer (ReactElement) |
543
+ | `animation` | `object` | - | Modal animation |
544
+ | `enableDrag` | `boolean` | `true` | Enable drag (bottom-sheet/draggable layouts) |
545
+ | `dragPersistKey` | `string` | - | Drag position persistence key |
546
+ | `close` | `ReactNode` | - | Modal close function (isAnimation?: boolean) => void (auto-injected) |
547
+ | `children` | `ReactNode` | - | Modal body (ReactNode, required) |
548
+ | `className` | `string` | - | Root wrapper style |
549
+ | `innerClassName` | `string` | - | Modal body style |
550
+ | `bodyClassName` | `string` | - | Body area style |
551
+ | `footerClassName` | `string` | - | Footer area style |
552
+ | `dimClassName` | `string` | - | Dim overlay style |
553
+ | `headerClassName` | `string` | - | Header area style |
554
+
555
+ ### modal()
556
+
557
+ modal() function call options. component automatically receives close/resolve as props.
354
558
 
355
- ### 모달 컴포넌트 작성법
559
+ | Prop | Type | Default | Description |
560
+ |---|---|---|---|
561
+ | `component` | `ReactNode` | - | Modal component (required). Automatically receives close/resolve as props |
562
+ | `props` | `Record<string, any>` | - | Props to pass to component |
563
+ | `id` | `string` | - | Modal ID (used for duplicate check) |
564
+ | `layout` | `'default'` \| `'bottom-sheet'` \| `'slide-left'` \| `'slide-right'` \| `'full-page'` \| `'full-page-reverse'` \| `'draggable'` | - | Layout |
565
+ | `animation` | `object` | - | Modal animation |
566
+ | `scrollEnable` | `boolean` | - | Allow background scroll |
567
+ | `isToggle` | `boolean` | - | Toggle mode (close on re-call of same modal) |
568
+ | `isAlone` | `boolean` | - | Alone mode (close all existing modals before opening) |
569
+ | `duplicateCheck` | `boolean` | - | Prevent duplicate opening of same component |
570
+ | `disableEscapeKeyPress` | `boolean` | - | Disable close via ESC key |
571
+ | `componentName` | `string` | - | Modal identifier name (used for duplicate check, modal search) |
572
+ | `onOpen` | `ReactNode` | - | Callback when modal opens |
573
+ | `onClose` | `ReactNode` | - | Callback when modal closes |
574
+
575
+ **IMPORTANT: Modal components MUST be wrapped with `ModalTemplate`.**
356
576
 
357
577
  ```tsx
358
578
  import { ModalTemplate } from '@nexus-cross/design-system/modal';
359
579
 
360
- // 모달 컴포넌트는 반드시 close와 resolve를 props로 받는다
361
580
  function MyModal({ close, resolve }: { close: () => void; resolve: (value: any) => void }) {
362
581
  return (
363
582
  <ModalTemplate
364
- title="모달 제목"
365
- desc="모달 설명 (선택)"
583
+ title="Modal Title"
584
+ desc="Modal description (optional)"
366
585
  close={close}
367
- layout="default" // 'default' | 'bottom-sheet' | 'slide-left' | 'slide-right' | 'full-page' | 'draggable'
368
- hideFooter // footer 없으면 하단 패딩 자동
586
+ layout="default"
587
+ hideFooter
369
588
  >
370
589
  <div className="space-y-4">
371
- <p className="text-text-secondary">모달 내용</p>
372
- <Button onClick={() => resolve({ confirmed: true })}>확인</Button>
590
+ <p className="text-text-secondary">Modal content</p>
591
+ <Button onClick={() => resolve({ confirmed: true })}>Confirm</Button>
373
592
  </div>
374
593
  </ModalTemplate>
375
594
  );
376
595
  }
377
596
  ```
378
597
 
379
- ### ModalTemplate Props
380
-
381
- | Prop | Type | Default | Description |
382
- |---|---|---|---|
383
- | `title` | `ReactNode` | - | 헤더 제목 |
384
- | `desc` | `ReactNode` | - | 헤더 설명 |
385
- | `close` | `() => void` (필수) | - | 닫기 함수 (props로 자동 주입됨) |
386
- | `layout` | `'default' \| 'bottom-sheet' \| 'slide-left' \| 'slide-right' \| 'full-page' \| 'full-page-reverse' \| 'draggable'` | `'default'` | 레이아웃 |
387
- | `showDim` | `boolean` | `true` | 딤 배경 표시 |
388
- | `dimClose` | `boolean` | `true` | 딤 클릭 시 닫기 |
389
- | `hideHeader` | `boolean` | `false` | 헤더 숨김 |
390
- | `hideFooter` | `boolean` | `true` | 푸터 숨김 |
391
- | `footer` | `ReactElement` | - | 커스텀 푸터 |
392
- | `enableDrag` | `boolean` | `false` | 드래그 활성화 (draggable 레이아웃) |
393
- | `dragPersistKey` | `string` | - | 드래그 위치 저장 키 |
394
- | `innerClassName` | `string` | - | 모달 본체 스타일 오버라이드 |
395
- | `bodyClassName` | `string` | - | 바디 영역 스타일 오버라이드 |
396
-
397
- ### 모달 호출
598
+ ### Calling a Modal
398
599
 
399
600
  ```tsx
400
601
  import { modal, useModal, ModalContainer } from '@nexus-cross/design-system/modal';
401
602
 
402
- // 루트에 ModalContainer 배치 필수
603
+ // ModalContainer MUST be placed at the app root
403
604
  <ModalContainer />
404
605
 
405
- // 방법 1: modal() 함수
606
+ // Method 1: modal() function
406
607
  const result = await modal({
407
608
  component: MyModal,
408
- props: { /* 추가 props */ },
609
+ props: { /* additional props */ },
409
610
  });
410
611
 
411
- // 방법 2: useModal()
612
+ // Method 2: useModal() hook
412
613
  const { modal: openModal } = useModal();
413
614
  openModal({
414
615
  component: MyModal,
415
- options: { isAlone: true }, // 단독 모달 (다른 모달 위에 안 쌓임)
616
+ options: { isAlone: true },
416
617
  });
417
618
  ```
418
619
 
419
- ### 금지 사항
620
+ ### Prohibited
420
621
 
421
- - ModalTemplate 없이 `<div>`만으로 모달 컴포넌트를 만들지 않는다
422
- - `close` prop 직접 정의하지 않는다 (시스템이 자동 주입)
423
- - 모달 내에서 별도 dim/overlay 구현하지 않는다
622
+ - Do NOT create modal components without ModalTemplate (using plain `<div>`)
623
+ - Do NOT define the `close` prop manually (the system injects it automatically)
624
+ - Do NOT implement a separate dim/overlay inside the modal
424
625
 
425
626
  ---
426
627
 
427
- ## Toast (함수형 API)
628
+ ## Tab
428
629
 
429
- 토스트 알림. Sonner 기반.
630
+ Tab navigation. line/pill variants.
430
631
 
431
- ```tsx
432
- import { toast, Toaster } from '@nexus-cross/design-system';
433
-
434
- // 루트에 Toaster 배치
435
- <Toaster position="top-right" />
632
+ | Prop | Type | Default | Description |
633
+ |---|---|---|---|
634
+ | `items` | `object`[] | - | Tab item array (required) |
635
+ | `activeKey` | `string` | - | Controlled mode active key |
636
+ | `defaultActiveKey` | `string` | - | Uncontrolled initial key |
637
+ | `variant` | `'line'` \| `'pill'` | `"line"` | Style |
638
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
639
+ | `destroyInactive` | `boolean` | `false` | Unmount inactive panels |
640
+ | `onTabChange` | `ReactNode` | - | Tab change callback (key: string) => void |
641
+ | `className` | `string` | - | Root style |
642
+ | `tabListClassName` | `string` | - | Tab list style |
643
+ | `tabPanelClassName` | `string` | - | Tab panel style |
436
644
 
437
- // 사용
438
- toast('저장되었습니다');
439
- toast.success('성공!');
440
- toast.error('오류가 발생했습니다');
441
- toast.loading('처리 중...');
645
+ ```tsx
646
+ <Tab
647
+ items={[
648
+ { key: 'a', label: 'Tab A', children: <p>A</p> },
649
+ { key: 'b', label: 'Tab B', children: <p>B</p> },
650
+ ]}
651
+ defaultActiveKey="a"
652
+ variant="pill"
653
+ />
442
654
  ```
443
655
 
444
656
  ---
445
657
 
446
- ## InfiniteScroll
658
+ ## Carousel
447
659
 
448
- 무한 스크롤. IntersectionObserver 기반.
660
+ Carousel. Based on Embla Carousel. Sub-components: CarouselSlide, CarouselPrev, CarouselNext, CarouselDots.
449
661
 
450
- | Prop | Type | Description |
451
- |---|---|---|
452
- | `list` | `unknown[]` | 현재 데이터 배열 |
453
- | `totalCount` | `number` | 전체 개수 (또는 `hasMore` 사용) |
454
- | `hasMore` | `boolean` | 있는지 (또는 `totalCount` 사용) |
455
- | `handleLoadMore` | `() => void` | 추가 로드 콜백 |
456
- | `loading` | `boolean` | 로딩 상태 |
662
+ | Prop | Type | Default | Description |
663
+ |---|---|---|---|
664
+ | `opts` | `Record<string, any>` | - | Embla options (loop, align, etc.) |
665
+ | `plugins` | `ReactNode`[] | - | Embla plugins |
666
+ | `onApiChange` | `ReactNode` | - | Embla API change callback (api: CarouselApi) => void |
667
+ | `children` | `ReactNode` | - | Carousel slides and sub-components (ReactNode) |
668
+ | `className` | `string` | - | Style override |
669
+
670
+ Sub-components: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
457
671
 
458
672
  ```tsx
459
- <InfiniteScroll
460
- list={items}
461
- totalCount={100}
462
- loading={isLoading}
463
- handleLoadMore={fetchMore}
464
- >
465
- {items.map(item => <Card key={item.id} {...item} />)}
466
- </InfiniteScroll>
673
+ <Carousel opts={{ loop: true }}>
674
+ <CarouselSlide>Slide 1</CarouselSlide>
675
+ <CarouselSlide>Slide 2</CarouselSlide>
676
+ <CarouselPrev />
677
+ <CarouselNext />
678
+ <CarouselDots />
679
+ </Carousel>
467
680
  ```
468
681
 
469
682
  ---
470
683
 
471
- ## Ellipsis
684
+ ## Pagination
472
685
 
473
- 텍스트 말줄임. 더보기/접기 토글 내장.
686
+ Pagination. Previous/next + page number buttons.
474
687
 
475
688
  | Prop | Type | Default | Description |
476
689
  |---|---|---|---|
477
- | `content` | `ReactNode` | - | 내용 |
478
- | `lineClamp` | `number` | `2` | 제한 |
479
- | `triggerMore` | `ReactNode` | `'more'` | 더보기 텍스트 |
480
- | `triggerLess` | `ReactNode` | `'less'` | 접기 텍스트 |
690
+ | `currentPage` | `number` | - | Current page (1-based, required) |
691
+ | `totalPages` | `number` | - | Total page count (required) |
692
+ | `siblingCount` | `number` | `1` | Number of pages shown on each side of current |
693
+ | `showEdges` | `boolean` | - | Always show first/last page |
694
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
695
+ | `onPageChange` | `ReactNode` | - | Page change callback (page: number) => void, required |
696
+ | `className` | `string` | - | <nav> style |
481
697
 
482
698
  ```tsx
483
- <Ellipsis content={longText} lineClamp={3} triggerMore="더보기" triggerLess="접기" />
699
+ <Pagination currentPage={2} totalPages={10} onPageChange={setPage} />
484
700
  ```
485
701
 
486
702
  ---
487
703
 
488
- ## Hooks
704
+ ## Avatar
489
705
 
490
- ### useModal
706
+ Avatar. Supports image, fallback text, and children.
707
+
708
+ | Prop | Type | Default | Description |
709
+ |---|---|---|---|
710
+ | `src` | `string` | - | Image URL |
711
+ | `alt` | `string` | - | Alt text |
712
+ | `fallback` | `ReactNode` | - | Displayed on image load failure (ReactNode) |
713
+ | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
714
+ | `shape` | `'circle'` \| `'square'` | `"circle"` | Shape |
715
+ | `children` | `ReactNode` | - | Custom image element (e.g. Next.js Image) |
716
+ | `onImageError` | `ReactNode` | - | Image load error callback () => void |
717
+ | `className` | `string` | - | Style override |
491
718
 
492
719
  ```tsx
493
- const { open, close } = useModal();
494
- open(MyComponent, { title: '제목' });
720
+ <Avatar src="/user.png" alt="User" size="lg" />
721
+ <Avatar fallback="JD" shape="square" size="sm" />
495
722
  ```
496
723
 
497
- ### useInView
724
+ ---
498
725
 
499
- ```tsx
500
- const { ref, inView } = useInView({ threshold: 0.5 });
501
- <div ref={ref}>{inView && <LazyContent />}</div>
502
- ```
726
+ ## Counter
503
727
 
504
- ### useCheckDevice
728
+ Number count animation.
729
+
730
+ | Prop | Type | Default | Description |
731
+ |---|---|---|---|
732
+ | `endValue` | `number` | - | Target value (required) |
733
+ | `startValue` | `number` | `0` | Start value |
734
+ | `duration` | `number` | `1500` | Animation duration (ms) |
735
+ | `delay` | `number` | `0` | Start delay (ms) |
736
+ | `separator` | `boolean` | `true` | Thousands separator |
737
+ | `digits` | `number` | `0` | Decimal places |
738
+ | `triggerOnView` | `boolean` | `false` | Start on viewport entry |
739
+ | `onEnd` | `ReactNode` | - | Count complete callback () => void |
740
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
741
+ | `className` | `string` | - | Style override |
505
742
 
506
743
  ```tsx
507
- const { isMobile, isTablet, isDesktop } = useCheckDevice();
744
+ <Counter endValue={1234} duration={2000} separator />
508
745
  ```
509
746
 
510
- ### useClickOutside
747
+ ---
748
+
749
+ ## Countdown
750
+
751
+ Countdown timer.
752
+
753
+ | Prop | Type | Default | Description |
754
+ |---|---|---|---|
755
+ | `endTimestamp` | `number` | - | End time (Unix ms, required) |
756
+ | `separator` | `ReactNode` | `":"` | Separator (ReactNode) |
757
+ | `showDays` | `boolean` | `true` | Show days unit |
758
+ | `labels` | `object` | - | Unit labels |
759
+ | `render` | `ReactNode` | - | Custom render function |
760
+ | `onEnd` | `ReactNode` | - | Countdown end callback () => void |
761
+ | `className` | `string` | - | Style override |
511
762
 
512
763
  ```tsx
513
- const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
514
- <div ref={ref}>드롭다운 내용</div>
764
+ <Countdown endTimestamp={Date.now() + 60_000} showDays={false} onEnd={handleEnd} />
515
765
  ```
516
766
 
517
767
  ---
518
768
 
519
- ## Pagination
769
+ ## Marquee
520
770
 
521
- 페이지네이션. 이전/다음 + 번호 버튼.
771
+ Marquee (scrolling text/elements).
522
772
 
523
773
  | Prop | Type | Default | Description |
524
774
  |---|---|---|---|
525
- | `currentPage` | `number` | - | 현재 페이지 (1부터) |
526
- | `totalPages` | `number` | - | 전체 페이지 |
527
- | `siblingCount` | `number` | `1` | 현재 페이지 양옆 표시 개수 |
528
- | `onPageChange` | `(page: number) => void` | - | 페이지 변경 콜백 |
529
- | `size` | `'sm' \| 'md'` | `'md'` | 크기 |
775
+ | `direction` | `'left'` \| `'right'` \| `'up'` \| `'down'` | `"left"` | Direction |
776
+ | `speed` | `number` | `40` | Animation speed (seconds) |
777
+ | `pauseOnHover` | `boolean` | `false` | Pause on hover |
778
+ | `gap` | `number` | `16` | Item gap (px) |
779
+ | `children` | `ReactNode` | - | Content to repeat (ReactNode, required) |
780
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
781
+ | `className` | `string` | - | Style override |
530
782
 
531
783
  ```tsx
532
- <Pagination currentPage={2} totalPages={10} onPageChange={setPage} />
784
+ <Marquee direction="left" speed={30} pauseOnHover>
785
+ <span>Scrolling text</span>
786
+ </Marquee>
533
787
  ```
534
788
 
535
789
  ---
536
790
 
537
- ## Avatar
791
+ ## VirtualList
538
792
 
539
- 아바타. 이미지, 폴백 텍스트, children 지원.
793
+ Virtual scroll list. Based on @tanstack/react-virtual.
540
794
 
541
795
  | Prop | Type | Default | Description |
542
796
  |---|---|---|---|
543
- | `src` | `string` | - | 이미지 URL |
544
- | `alt` | `string` | - | 대체 텍스트 |
545
- | `fallback` | `ReactNode` | - | 이미지 로드 실패 표시 |
546
- | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | 크기 |
547
- | `shape` | `'circle' \| 'square'` | `'circle'` | 형태 |
548
- | `onImageError` | `() => void` | - | 이미지 에러 콜백 |
797
+ | `items` | `ReactNode`[] | - | Data array (required) |
798
+ | `estimateSize` | `number` \| `ReactNode` | - | Estimated item height (number or (index) => number, required) |
799
+ | `renderItem` | `ReactNode` | - | Item renderer (item, index, virtualItem) => ReactNode (required) |
800
+ | `overscan` | `number` | `5` | Overscan count |
801
+ | `gap` | `number` | `0` | Item gap (px) |
802
+ | `className` | `string` | - | Scroll container style |
803
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
804
+ | `endReachedThreshold` | `number` | `200` | End detection threshold (px) |
805
+ | `onEndReached` | `ReactNode` | - | End reached callback () => void |
806
+
807
+ ### VirtualGrid
808
+
809
+ Virtual scroll grid. Based on @tanstack/react-virtual.
810
+
811
+ | Prop | Type | Default | Description |
812
+ |---|---|---|---|
813
+ | `items` | `ReactNode`[] | - | Data array (required) |
814
+ | `columns` | `number` | - | Column count (required) |
815
+ | `estimateSize` | `number` \| `ReactNode` | - | Estimated item height (required) |
816
+ | `renderItem` | `ReactNode` | - | Item renderer (item, index) => ReactNode (required) |
817
+ | `overscan` | `number` | `3` | Overscan count |
818
+ | `gap` | `number` | `0` | Item gap (px) |
819
+ | `className` | `string` | - | Scroll container style |
820
+ | `style` | `ReactNode` | - | Inline style (CSSProperties) |
821
+ | `endReachedThreshold` | `number` | `200` | End detection threshold (px) |
822
+ | `onEndReached` | `ReactNode` | - | End reached callback () => void |
549
823
 
550
824
  ```tsx
551
- <Avatar src="/user.png" alt="User" size="lg" />
552
- <Avatar fallback="JD" shape="square" size="sm" />
825
+ <VirtualList
826
+ items={data}
827
+ estimateSize={48}
828
+ renderItem={(item) => <div>{item.name}</div>}
829
+ onEndReached={loadMore}
830
+ />
831
+ ```
832
+
833
+ ### VirtualGrid
834
+
835
+ Same as VirtualList + `columns: number` (required).
836
+
837
+ ```tsx
838
+ <VirtualGrid items={data} estimateSize={120} columns={3} renderItem={(item) => <Card {...item} />} />
553
839
  ```
554
840
 
555
841
  ---
556
842
 
557
- ## Counter
843
+ ## DataList
558
844
 
559
- 숫자 카운트 애니메이션.
845
+ Data list. Automatically handles loading/skeleton/empty/data states based on list. Built-in ErrorBoundary.
560
846
 
561
847
  | Prop | Type | Default | Description |
562
848
  |---|---|---|---|
563
- | `endValue` | `number` (필수) | - | 목표 |
564
- | `startValue` | `number` | `0` | 시작 |
565
- | `duration` | `number` | `1500` | 애니메이션 시간 (ms) |
566
- | `delay` | `number` | `0` | 시작 지연 (ms) |
567
- | `separator` | `boolean` | `true` | 단위 구분 |
568
- | `digits` | `number` | `0` | 소수 자릿수 |
569
- | `triggerOnView` | `boolean` | `false` | 뷰포트 진입 시작 |
570
- | `onEnd` | `() => void` | - | 완료 콜백 |
849
+ | `list` | `ReactNode`[] | - | Data array to render. null = loading state (required) |
850
+ | `noDataMessage` | `ReactNode` | - | Message for empty array (string | ReactElement) |
851
+ | `errorFallback` | `ReactNode` | - | Fallback on error (ReactNode) |
852
+ | `loadingElement` | `ReactNode` | - | Custom loading element (default: Spinner) |
853
+ | `skeletonElement` | `ReactNode` | - | Skeleton element during loading (ReactElement) |
854
+ | `skeletonCount` | `number` | `3` | Skeleton repeat count |
855
+ | `loading` | `boolean` | `false` | Force loading state |
856
+ | `children` | `ReactNode` | - | Item render function: ({ item, index }) => ReactNode (required) |
857
+ | `className` | `string` | - | Root element style |
858
+
859
+ When list is null → **loading**, [] → **empty state**, array → **render**. ErrorBoundary built-in.
860
+
861
+ - **Default loading**: Without skeletonElement, a Spinner is shown automatically
862
+ - **Skeleton loading**: Pass a custom skeleton component to skeletonElement, rendered skeletonCount times
571
863
 
572
864
  ```tsx
573
- <Counter endValue={1234} duration={2000} separator />
865
+ // Basic usage Spinner shown automatically when list is null
866
+ <DataList list={users} noDataMessage="No users found">
867
+ {({ item, index }) => <UserCard key={item.id} user={item} />}
868
+ </DataList>
869
+
870
+ // Skeleton loading — implement and pass a custom skeleton component
871
+ function UserSkeleton() {
872
+ return (
873
+ <div className="flex items-center gap-3 px-4 py-3">
874
+ <Skeleton circle width={32} height={32} />
875
+ <div className="flex-1 space-y-1.5">
876
+ <Skeleton className="h-3 w-24" />
877
+ <Skeleton className="h-2.5 w-16" />
878
+ </div>
879
+ </div>
880
+ );
881
+ }
882
+
883
+ <DataList
884
+ list={products}
885
+ skeletonElement={<UserSkeleton />}
886
+ skeletonCount={5}
887
+ >
888
+ {({ item }) => <ProductCard key={item.id} {...item} />}
889
+ </DataList>
574
890
  ```
575
891
 
576
892
  ---
577
893
 
578
- ## Countdown
894
+ ## InfiniteScroll
579
895
 
580
- 카운트다운 타이머.
896
+ Infinite scroll. Based on IntersectionObserver.
581
897
 
582
898
  | Prop | Type | Default | Description |
583
899
  |---|---|---|---|
584
- | `endTimestamp` | `number` (필수) | - | 종료 시각 (Unix ms) |
585
- | `separator` | `ReactNode` | `':'` | 구분자 |
586
- | `showDays` | `boolean` | `true` | 단위 표시 |
587
- | `labels` | `{ days?, hours?, minutes?, seconds? }` | - | 단위 라벨 |
588
- | `onEnd` | `() => void` | - | 완료 콜백 |
589
- | `render` | `(timeLeft: TimeLeft) => ReactNode` | - | 커스텀 렌더링 |
900
+ | `list` | `ReactNode`[] | - | Current data array (required) |
901
+ | `totalCount` | `number` | - | Total count (mutually exclusive with hasMore) |
902
+ | `hasMore` | `boolean` | - | Has more items (mutually exclusive with totalCount) |
903
+ | `tag` | `string` | `"div"` | Children wrapper tag |
904
+ | `rootMargin` | `number` | `100` | Detection margin (px) |
905
+ | `loading` | `boolean` | - | Loading state |
906
+ | `loadingElement` | `ReactNode` | - | Custom loading element |
907
+ | `handleLoadMore` | `ReactNode` | - | Load more callback () => void, required |
908
+ | `scrollTarget` | `ReactNode` | - | Scroll target element (HTMLElement | Document | MutableRefObject) |
909
+ | `children` | `ReactNode` | - | List item rendering (ReactNode, required) |
910
+ | `className` | `string` | - | Style override |
590
911
 
591
912
  ```tsx
592
- <Countdown endTimestamp={Date.now() + 60_000} showDays={false} onEnd={handleEnd} />
913
+ <InfiniteScroll
914
+ list={items}
915
+ totalCount={100}
916
+ loading={isLoading}
917
+ handleLoadMore={fetchMore}
918
+ >
919
+ {items.map(item => <Card key={item.id} {...item} />)}
920
+ </InfiniteScroll>
593
921
  ```
594
922
 
595
923
  ---
596
924
 
597
- ## Marquee
925
+ ## Ellipsis
598
926
 
599
- 마퀴(흐르는 텍스트/요소).
927
+ Text ellipsis. Built-in show more/less toggle.
600
928
 
601
929
  | Prop | Type | Default | Description |
602
930
  |---|---|---|---|
603
- | `direction` | `'left' \| 'right' \| 'up' \| 'down'` | `'left'` | 방향 |
604
- | `speed` | `number` | `40` | 애니메이션 속도 (초) |
605
- | `pauseOnHover` | `boolean` | `false` | 호버 일시정지 |
606
- | `gap` | `number` | `16` | 아이템 간격 (px) |
931
+ | `content` | `ReactNode` | `""` | Body text (ReactNode) |
932
+ | `lineClamp` | `number` | `2` | Line clamp limit |
933
+ | `triggerMore` | `ReactNode` | `"more"` | Show more text (ReactNode) |
934
+ | `triggerLess` | `ReactNode` | `"less"` | Show less text (ReactNode) |
935
+ | `defaultShortened` | `boolean` | `true` | Initial collapsed state |
936
+ | `observingEnvs` | `boolean`[] | - | Re-measure on external condition change |
937
+ | `onShowMoreLessClick` | `ReactNode` | - | Show more/less click callback () => void |
938
+ | `className` | `string` | - | Style override |
607
939
 
608
940
  ```tsx
609
- <Marquee direction="left" speed={30} pauseOnHover>
610
- <span>흐르는 텍스트</span>
611
- </Marquee>
941
+ <Ellipsis content={longText} lineClamp={3} triggerMore="more" triggerLess="less" />
612
942
  ```
613
943
 
614
944
  ---
615
945
 
616
- ## Tab
946
+ ## NumberInput
617
947
 
618
- 네비게이션. line/pill 변형.
948
+ Number input. Accelerated increment on long press. Exposes increment/decrement methods via ref. numberInputBind(ref, direction) binds same acceleration to external buttons.
619
949
 
620
950
  | Prop | Type | Default | Description |
621
951
  |---|---|---|---|
622
- | `items` | `TabItem[]` (필수) | - | 항목 배열 |
623
- | `activeKey` | `string` | - | 제어 모드 활성 |
624
- | `defaultActiveKey` | `string` | - | 비제어 모드 초기 키 |
625
- | `variant` | `'line' \| 'pill'` | `'line'` | 스타일 |
626
- | `size` | `'sm' \| 'md'` | `'md'` | 크기 |
627
- | `destroyInactive` | `boolean` | `false` | 비활성 패널 언마운트 |
628
- | `onTabChange` | `(key: string) => void` | - | 변경 콜백 |
952
+ | `value` | `number` \| `string` | - | Current value |
953
+ | `size` | `'sm'` \| `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
954
+ | `error` | `boolean` | - | Error state |
955
+ | `min` | `number` | - | Minimum value |
956
+ | `max` | `number` | - | Maximum value |
957
+ | `step` | `number` | `1` | Step increment |
958
+ | `digit` | `number` | `0` | Decimal places |
959
+ | `hideButtons` | `boolean` | `false` | Hide default spin buttons. Use with numberInputBind for external button event binding |
960
+ | `disabled` | `boolean` | - | Disabled |
961
+ | `readOnly` | `boolean` | - | Read-only (includes hiding spin buttons) |
962
+ | `placeholder` | `string` | - | Placeholder |
963
+ | `name` | `string` | - | Form field name |
964
+ | `id` | `string` | - | Element ID |
965
+ | `autoFocus` | `boolean` | - | Auto focus |
966
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: number | undefined) => void |
967
+ | `onBlur` | `ReactNode` | - | Blur callback |
968
+ | `onFocus` | `ReactNode` | - | Focus callback |
969
+ | `className` | `string` | - | Style override |
970
+
971
+ Press-and-hold accelerates increment/decrement (100ms → 75ms → 50ms → 30ms). Default is right-side vertical spin buttons. Use `numberInputBind(ref, direction)` to bind the same acceleration events to external buttons for free placement.
629
972
 
630
973
  ```tsx
631
- <Tab
632
- items={[
633
- { key: 'a', label: 'Tab A', children: <p>A</p> },
634
- { key: 'b', label: 'Tab B', children: <p>B</p> },
635
- ]}
636
- defaultActiveKey="a"
637
- variant="pill"
974
+ // Basic usage — right-side vertical spin buttons
975
+ <NumberInput
976
+ value={count}
977
+ onValueChange={setCount}
978
+ min={0}
979
+ max={100}
980
+ step={5}
638
981
  />
982
+
983
+ // External buttons — bind events with numberInputBind
984
+ const ref = useRef<NumberInputRef>(null);
985
+
986
+ <NumberInput ref={ref} value={count} onValueChange={setCount} hideButtons />
987
+
988
+ // Buttons can be placed anywhere, completely separate from NumberInput
989
+ <button {...numberInputBind(ref, 'decrement')}><MinusIcon /></button>
990
+ <button {...numberInputBind(ref, 'increment')}><PlusIcon /></button>
991
+
992
+ // Single invocation (onClick)
993
+ <button onClick={() => ref.current?.increment()}>+1</button>
639
994
  ```
640
995
 
641
996
  ---
642
997
 
643
- ## Carousel
998
+ ## PriceInput
644
999
 
645
- 캐러셀. Embla Carousel 기반. 컴포저블 패턴.
1000
+ Price/amount input field with prefix, suffix, balance display, and auto-fill on balance click.
646
1001
 
647
1002
  | Prop | Type | Default | Description |
648
1003
  |---|---|---|---|
649
- | `opts` | `EmblaOptionsType` | `{ loop: false }` | Embla 옵션 |
650
- | `plugins` | `EmblaPluginType[]` | - | Embla 플러그인 |
651
- | `onApiChange` | `(api: CarouselApi) => void` | - | API 변경 콜백 |
1004
+ | `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
1005
+ | `error` | `boolean` | - | Error state (shows danger border) |
1006
+ | `prefix` | `boolean` \| `ReactNode` | `true` | Prefix element. true = "$", ReactNode = custom content, false = hidden |
1007
+ | `suffix` | `ReactNode` | - | Suffix element after the input (e.g. "CROSSD", icon, ReactNode) |
1008
+ | `label` | `ReactNode` | - | Label displayed above the input (ReactNode) |
1009
+ | `description` | `ReactNode` | - | Description below the input. Turns red on error (ReactNode) |
1010
+ | `balance` | `string` \| `number` | - | Balance value in the header. Clicking fills the input |
1011
+ | `balanceLabel` | `string` | `"Balance"` | Label text for balance display |
1012
+ | `balanceContent` | `ReactNode` | - | Custom balance display (ReactNode). Replaces default text, keeps click-to-fill |
1013
+ | `maxBalance` | `number` | - | Max allowed value. Exceeding auto-applies error state and turns balance red |
1014
+ | `separator` | `boolean` | `false` | Display thousands separator (comma). onValueChange receives raw value without commas |
1015
+ | `align` | `'left'` \| `'right'` | `"left"` | Text alignment inside the input |
1016
+ | `disabled` | `boolean` | - | Disabled |
1017
+ | `placeholder` | `string` | - | Placeholder |
1018
+ | `value` | `ReactNode` | - | Controlled value |
1019
+ | `id` | `string` | - | Element ID |
1020
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
1021
+ | `onBalanceClick` | `ReactNode` | - | Balance click callback (balance: string | number) => void |
1022
+ | `className` | `string` | - | Style override |
652
1023
 
653
- 서브 컴포넌트: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
1024
+ ---
654
1025
 
655
- ```tsx
656
- <Carousel opts={{ loop: true }}>
657
- <CarouselSlide>슬라이드 1</CarouselSlide>
658
- <CarouselSlide>슬라이드 2</CarouselSlide>
659
- <CarouselPrev />
660
- <CarouselNext />
661
- <CarouselDots />
662
- </Carousel>
663
- ```
1026
+ ## Badge
1027
+
1028
+ Badge indicator. Dot or count display. Wraps children when provided.
1029
+
1030
+ | Prop | Type | Default | Description |
1031
+ |---|---|---|---|
1032
+ | `count` | `number` | - | Badge count number |
1033
+ | `max` | `number` | `99` | Max count (shows "99+" when exceeded) |
1034
+ | `dot` | `boolean` | `false` | Show as dot instead of count |
1035
+ | `showZero` | `boolean` | `false` | Show badge when count is 0 |
1036
+ | `variant` | `'danger'` \| `'primary'` \| `'secondary'` \| `'info'` \| `'success'` \| `'warning'` | `"danger"` | Color variant |
1037
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
1038
+ | `offset` | `any` | - | Position offset [x, y] in px |
1039
+ | `children` | `ReactNode` | - | Anchor element to attach badge to (ReactNode) |
1040
+ | `className` | `string` | - | Style override |
1041
+
1042
+ ---
1043
+
1044
+ ## Progress
1045
+
1046
+ Progress bar. Linear progress indicator with percentage display.
1047
+
1048
+ | Prop | Type | Default | Description |
1049
+ |---|---|---|---|
1050
+ | `value` | `number` | `0` | Current progress value |
1051
+ | `max` | `number` | `100` | Maximum value |
1052
+ | `variant` | `'primary'` \| `'success'` \| `'warning'` \| `'danger'` \| `'info'` | `"primary"` | Color variant |
1053
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Bar height |
1054
+ | `showValue` | `boolean` | `false` | Show percentage text |
1055
+ | `indeterminate` | `boolean` | `false` | Indeterminate loading animation |
1056
+ | `label` | `string` | - | Label text above the bar |
1057
+ | `className` | `string` | - | Style override |
1058
+
1059
+ ---
1060
+
1061
+ ## Alert
1062
+
1063
+ Alert / Banner. Inline notification with icon, title, description.
1064
+
1065
+ | Prop | Type | Default | Description |
1066
+ |---|---|---|---|
1067
+ | `variant` | `'info'` \| `'success'` \| `'warning'` \| `'danger'` | `"info"` | Alert type / color |
1068
+ | `title` | `string` | - | Alert title (bold) |
1069
+ | `children` | `ReactNode` | - | Alert description (ReactNode) |
1070
+ | `icon` | `ReactNode` | - | Custom icon (ReactNode). Auto icon by variant if omitted |
1071
+ | `closable` | `boolean` | `false` | Show close button |
1072
+ | `onClose` | `ReactNode` | - | Close callback () => void |
1073
+ | `action` | `ReactNode` | - | Action area (ReactNode, e.g. Button) |
1074
+ | `className` | `string` | - | Style override |
1075
+
1076
+ ---
1077
+
1078
+ ## EmptyState
1079
+
1080
+ Empty state placeholder. Shown when data is empty or unavailable.
1081
+
1082
+ | Prop | Type | Default | Description |
1083
+ |---|---|---|---|
1084
+ | `icon` | `ReactNode` | - | Custom icon (ReactNode). Default inbox icon |
1085
+ | `title` | `string` | - | Title text |
1086
+ | `description` | `string` | - | Description text |
1087
+ | `action` | `ReactNode` | - | Action area (ReactNode, e.g. Button) |
1088
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Overall size |
1089
+ | `children` | `ReactNode` | - | Additional content (ReactNode) |
1090
+ | `className` | `string` | - | Style override |
664
1091
 
665
1092
  ---
666
1093
 
667
- ## VirtualList / VirtualGrid
1094
+ ## Breadcrumb
668
1095
 
669
- 가상 스크롤 리스트/그리드. @tanstack/react-virtual 기반.
1096
+ Breadcrumb navigation. Shows current location path.
1097
+
1098
+ | Prop | Type | Default | Description |
1099
+ |---|---|---|---|
1100
+ | `items` | `object`[] | - | Breadcrumb items array (required) |
1101
+ | `separator` | `ReactNode` | - | Custom separator (ReactNode). Default: chevron |
1102
+ | `maxItems` | `number` | - | Max visible items (collapses middle with "...") |
1103
+ | `className` | `string` | - | Style override |
1104
+
1105
+ ---
670
1106
 
671
- ### VirtualList
1107
+ ## Stepper
1108
+
1109
+ Stepper. Step-by-step progress indicator.
672
1110
 
673
1111
  | Prop | Type | Default | Description |
674
1112
  |---|---|---|---|
675
- | `items` | `T[]` (필수) | - | 데이터 배열 |
676
- | `estimateSize` | `number \| (index: number) => number` (필수) | - | 예상 아이템 높이 |
677
- | `renderItem` | `(item: T, index: number) => ReactNode` (필수) | - | 렌더러 |
678
- | `overscan` | `number` | `5` | 오버스캔 |
679
- | `gap` | `number` | `0` | 아이템 간격 |
680
- | `onEndReached` | `() => void` | - | 도달 콜백 |
681
- | `endReachedThreshold` | `number` | `200` | 끝 감지 임계값 (px) |
1113
+ | `steps` | `object`[] | - | Step items array (required) |
1114
+ | `current` | `number` | `0` | Current step index (0-based) |
1115
+ | `status` | `'process'` \| `'error'` | `"process"` | Current step status |
1116
+ | `orientation` | `'horizontal'` \| `'vertical'` | `"horizontal"` | Layout direction |
1117
+ | `size` | `'sm'` \| `'md'` | `"md"` | Size |
1118
+ | `className` | `string` | - | Style override |
1119
+
1120
+ ---
1121
+
1122
+ ## DropdownMenu
1123
+
1124
+ Dropdown menu. Based on Radix DropdownMenu. Action menu for context/more menus.
1125
+
1126
+ | Prop | Type | Default | Description |
1127
+ |---|---|---|---|
1128
+ | `children` | `ReactNode` | - | Trigger element (ReactNode, required) |
1129
+ | `items` | `object`[] | - | Menu items array (required) |
1130
+ | `align` | `'start'` \| `'center'` \| `'end'` | `"start"` | Alignment |
1131
+ | `side` | `'top'` \| `'right'` \| `'bottom'` \| `'left'` | `"bottom"` | Position |
1132
+ | `className` | `string` | - | Trigger wrapper style |
1133
+ | `contentClassName` | `string` | - | Content panel style |
1134
+
1135
+ ---
1136
+
1137
+ ## ToggleGroup
1138
+
1139
+ Toggle group / Segment control. Based on Radix ToggleGroup.
1140
+
1141
+ | Prop | Type | Default | Description |
1142
+ |---|---|---|---|
1143
+ | `type` | `'single'` \| `'multiple'` | `"single"` | Selection mode |
1144
+ | `items` | `object`[] | - | Toggle items array (required) |
1145
+ | `value` | `ReactNode` | - | Controlled value (string | string[]) |
1146
+ | `defaultValue` | `ReactNode` | - | Default value |
1147
+ | `onValueChange` | `ReactNode` | - | Value change callback |
1148
+ | `variant` | `'default'` \| `'outline'` | `"default"` | Style variant |
1149
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
1150
+ | `disabled` | `boolean` | - | Disable all items |
1151
+ | `className` | `string` | - | Style override |
1152
+
1153
+ ---
1154
+
1155
+ ## Slider
1156
+
1157
+ Slider / Range input. Based on Radix Slider. Supports single and range mode.
1158
+
1159
+ | Prop | Type | Default | Description |
1160
+ |---|---|---|---|
1161
+ | `value` | `number`[] | - | Controlled value (number[]). Use [50] for single, [20, 80] for range |
1162
+ | `defaultValue` | `number`[] | - | Default value |
1163
+ | `min` | `number` | `0` | Minimum |
1164
+ | `max` | `number` | `100` | Maximum |
1165
+ | `step` | `number` | `1` | Step increment |
1166
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Track height / thumb size |
1167
+ | `showValue` | `boolean` | `false` | Show current value text |
1168
+ | `formatValue` | `ReactNode` | - | Value format function (value: number) => string |
1169
+ | `label` | `string` | - | Label text |
1170
+ | `disabled` | `boolean` | - | Disabled |
1171
+ | `onValueChange` | `ReactNode` | - | Value change callback (value: number[]) => void |
1172
+ | `onValueCommit` | `ReactNode` | - | Committed value callback (on pointer up) |
1173
+ | `className` | `string` | - | Style override |
1174
+
1175
+ ---
1176
+
1177
+ ## TagInput
1178
+
1179
+ Tag input. Enter key to add, Backspace to delete last tag.
1180
+
1181
+ | Prop | Type | Default | Description |
1182
+ |---|---|---|---|
1183
+ | `value` | `string`[] | - | Controlled tags array |
1184
+ | `defaultValue` | `string`[] | - | Default tags |
1185
+ | `onChange` | `ReactNode` | - | Tags change callback (tags: string[]) => void |
1186
+ | `placeholder` | `string` | `"태그 입력 후 Enter"` | Input placeholder |
1187
+ | `max` | `number` | - | Maximum number of tags |
1188
+ | `disabled` | `boolean` | - | Disabled |
1189
+ | `allowDuplicates` | `boolean` | `false` | Allow duplicate tags |
1190
+ | `label` | `string` | - | Label text |
1191
+ | `description` | `string` | - | Helper text below input |
1192
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
1193
+ | `className` | `string` | - | Style override |
1194
+
1195
+ ---
1196
+
1197
+ ## NxImage
1198
+
1199
+ Enhanced image. Lazy loading, fallback, aspect-ratio support.
1200
+
1201
+ | Prop | Type | Default | Description |
1202
+ |---|---|---|---|
1203
+ | `src` | `string` | - | Image source URL |
1204
+ | `alt` | `string` | - | Alt text |
1205
+ | `fallback` | `ReactNode` | - | Custom fallback element (ReactNode) |
1206
+ | `fallbackSrc` | `string` | - | Fallback image URL on error |
1207
+ | `aspectRatio` | `string` | - | CSS aspect-ratio (e.g. "16/9", "1/1") |
1208
+ | `objectFit` | `'cover'` \| `'contain'` \| `'fill'` \| `'none'` | `"cover"` | Object-fit mode |
1209
+ | `lazy` | `boolean` | `true` | Enable lazy loading (loading="lazy") |
1210
+ | `wrapperClassName` | `string` | - | Wrapper div style |
1211
+ | `className` | `string` | - | Image element style |
1212
+
1213
+ ---
1214
+
1215
+ ## DatePicker
1216
+
1217
+ DatePicker. Calendar popup for date selection. Based on react-day-picker.
1218
+
1219
+ | Prop | Type | Default | Description |
1220
+ |---|---|---|---|
1221
+ | `value` | `ReactNode` | - | Controlled date value (Date) |
1222
+ | `defaultValue` | `ReactNode` | - | Default date (Date) |
1223
+ | `onChange` | `ReactNode` | - | Date change callback (date: Date | undefined) => void |
1224
+ | `placeholder` | `string` | `"날짜 선택"` | Placeholder text |
1225
+ | `disabled` | `boolean` | `false` | Disabled |
1226
+ | `minDate` | `ReactNode` | - | Minimum selectable date (Date) |
1227
+ | `maxDate` | `ReactNode` | - | Maximum selectable date (Date) |
1228
+ | `locale` | `'ko'` \| `'en'` | `"ko"` | Calendar locale |
1229
+ | `formatStr` | `string` | `"yyyy-MM-dd"` | Date format string (date-fns format) |
1230
+ | `className` | `string` | - | Style override |
1231
+
1232
+ ---
1233
+
1234
+ ## Hooks
1235
+
1236
+ ### useModal
682
1237
 
683
1238
  ```tsx
684
- <VirtualList
685
- items={data}
686
- estimateSize={48}
687
- renderItem={(item) => <div>{item.name}</div>}
688
- onEndReached={loadMore}
689
- />
1239
+ const { open, close } = useModal();
1240
+ open(MyComponent, { title: 'Title' });
690
1241
  ```
691
1242
 
692
- ### VirtualGrid
1243
+ ### useInView
693
1244
 
694
- VirtualList와 동일 + `columns: number` (필수).
1245
+ ```tsx
1246
+ const { ref, inView } = useInView({ threshold: 0.5 });
1247
+ <div ref={ref}>{inView && <LazyContent />}</div>
1248
+ ```
1249
+
1250
+ ### useCheckDevice
695
1251
 
696
1252
  ```tsx
697
- <VirtualGrid items={data} estimateSize={120} columns={3} renderItem={(item) => <Card {...item} />} />
1253
+ const { isMobile, isTablet, isDesktop } = useCheckDevice();
698
1254
  ```
1255
+
1256
+ ### useClickOutside
1257
+
1258
+ ```tsx
1259
+ const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
1260
+ <div ref={ref}>Dropdown content</div>
1261
+ ```
1262
+
1263
+ ---
1264
+
1265
+ ## Toast (Imperative API)
1266
+
1267
+ Toast notifications. Sonner-based.
1268
+
1269
+ ```tsx
1270
+ import { toast, Toaster } from '@nexus-cross/design-system';
1271
+
1272
+ // Place Toaster at app root
1273
+ <Toaster position="top-right" />
1274
+
1275
+ // Usage
1276
+ toast('Saved successfully');
1277
+ toast.success('Success!');
1278
+ toast.error('An error occurred');
1279
+ toast.loading('Processing...');
1280
+ ```
1281
+