@teamix-evo/ui 0.4.0 → 0.5.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 (511) hide show
  1. package/README.md +46 -50
  2. package/manifest.json +406 -653
  3. package/package.json +15 -34
  4. package/src/_design-system/theme-tokens/stories.tsx +1474 -0
  5. package/src/components/accordion/index.tsx +139 -0
  6. package/src/components/accordion/meta.md +162 -0
  7. package/src/components/accordion/stories.tsx +145 -0
  8. package/src/components/affix/index.tsx +149 -0
  9. package/src/components/affix/meta.md +23 -0
  10. package/src/components/affix/stories.tsx +70 -0
  11. package/src/components/alert/index.tsx +220 -0
  12. package/src/components/alert/meta.md +133 -0
  13. package/src/components/alert/stories.tsx +126 -0
  14. package/src/components/alert-dialog/index.tsx +282 -0
  15. package/src/components/alert-dialog/meta.md +86 -0
  16. package/src/components/alert-dialog/stories.tsx +163 -0
  17. package/src/components/avatar/index.tsx +159 -0
  18. package/src/components/avatar/meta.md +242 -0
  19. package/src/components/avatar/stories.tsx +241 -0
  20. package/src/components/badge/index.tsx +299 -0
  21. package/src/components/badge/meta.md +237 -0
  22. package/src/components/badge/stories.tsx +275 -0
  23. package/src/components/breadcrumb/index.tsx +137 -0
  24. package/src/components/breadcrumb/meta.md +64 -0
  25. package/src/components/breadcrumb/stories.tsx +86 -0
  26. package/src/components/button/index.tsx +202 -0
  27. package/src/components/button/meta.md +209 -0
  28. package/src/components/button/stories.tsx +215 -0
  29. package/src/components/button-group/index.tsx +211 -0
  30. package/src/components/button-group/meta.md +190 -0
  31. package/src/components/button-group/stories.tsx +182 -0
  32. package/src/components/calendar/index.tsx +240 -0
  33. package/src/components/calendar/meta.md +36 -0
  34. package/src/components/calendar/stories.tsx +36 -0
  35. package/src/components/card/index.tsx +262 -0
  36. package/src/components/card/meta.md +194 -0
  37. package/src/components/card/stories.tsx +202 -0
  38. package/src/components/carousel/index.tsx +481 -0
  39. package/src/components/carousel/meta.md +208 -0
  40. package/src/components/carousel/stories.tsx +271 -0
  41. package/src/components/cascader-select/index.tsx +308 -0
  42. package/src/components/cascader-select/meta.md +28 -0
  43. package/src/components/cascader-select/stories.tsx +118 -0
  44. package/src/components/checkbox/index.tsx +208 -0
  45. package/src/components/checkbox/meta.md +166 -0
  46. package/src/components/checkbox/stories.tsx +206 -0
  47. package/src/components/collapsible/index.tsx +72 -0
  48. package/src/components/collapsible/meta.md +87 -0
  49. package/src/components/collapsible/stories.tsx +97 -0
  50. package/src/components/color-picker/index.tsx +677 -0
  51. package/src/components/color-picker/meta.md +83 -0
  52. package/src/components/color-picker/stories.tsx +172 -0
  53. package/src/components/combobox/index.tsx +687 -0
  54. package/src/components/combobox/meta.md +83 -0
  55. package/src/components/combobox/stories.tsx +179 -0
  56. package/src/components/command/index.tsx +238 -0
  57. package/src/components/command/meta.md +48 -0
  58. package/src/components/command/stories.tsx +83 -0
  59. package/src/components/data-table/index.tsx +1537 -0
  60. package/src/components/data-table/meta.md +107 -0
  61. package/src/components/data-table/stories.tsx +1234 -0
  62. package/src/components/date-picker/index.tsx +1492 -0
  63. package/src/components/date-picker/meta.md +103 -0
  64. package/src/components/date-picker/stories.tsx +125 -0
  65. package/src/components/descriptions/index.tsx +535 -0
  66. package/src/components/descriptions/meta.md +104 -0
  67. package/src/components/descriptions/stories.tsx +389 -0
  68. package/src/components/dialog/index.tsx +255 -0
  69. package/src/components/dialog/meta.md +244 -0
  70. package/src/components/dialog/stories.tsx +330 -0
  71. package/src/components/dropdown-menu/index.tsx +301 -0
  72. package/src/components/dropdown-menu/meta.md +93 -0
  73. package/src/components/dropdown-menu/stories.tsx +124 -0
  74. package/src/components/empty/index.tsx +431 -0
  75. package/src/components/empty/meta.md +274 -0
  76. package/src/components/empty/stories.tsx +278 -0
  77. package/src/components/field/index.tsx +374 -0
  78. package/src/components/field/meta.md +283 -0
  79. package/src/components/field/stories.tsx +327 -0
  80. package/src/components/filter-bar/index.tsx +976 -0
  81. package/src/components/filter-bar/meta.md +57 -0
  82. package/src/components/filter-bar/stories.tsx +496 -0
  83. package/src/components/float-button/index.tsx +163 -0
  84. package/src/components/float-button/meta.md +73 -0
  85. package/src/components/float-button/stories.tsx +68 -0
  86. package/src/components/form/index.tsx +218 -0
  87. package/src/components/form/meta.md +16 -0
  88. package/src/components/form/stories.tsx +301 -0
  89. package/src/components/hover-card/index.tsx +103 -0
  90. package/src/components/hover-card/meta.md +71 -0
  91. package/src/components/hover-card/stories.tsx +107 -0
  92. package/src/components/icon/index.tsx +225 -0
  93. package/src/components/icon/meta.md +102 -0
  94. package/src/components/icon/stories.tsx +127 -0
  95. package/src/components/image/index.tsx +147 -0
  96. package/src/components/image/meta.md +55 -0
  97. package/src/components/image/stories.tsx +48 -0
  98. package/src/components/input/index.tsx +63 -0
  99. package/src/components/input/meta.md +57 -0
  100. package/src/components/input/stories.tsx +66 -0
  101. package/src/components/input-group/index.tsx +292 -0
  102. package/src/components/input-group/meta.md +108 -0
  103. package/src/components/input-group/stories.tsx +258 -0
  104. package/src/components/input-ip/index.tsx +288 -0
  105. package/src/components/input-ip/meta.md +48 -0
  106. package/src/components/input-ip/stories.tsx +69 -0
  107. package/src/components/input-number/index.tsx +360 -0
  108. package/src/components/input-number/meta.md +156 -0
  109. package/src/components/input-number/stories.tsx +187 -0
  110. package/src/components/item/index.tsx +368 -0
  111. package/src/components/item/meta.md +395 -0
  112. package/src/components/item/stories.tsx +432 -0
  113. package/src/components/label/index.tsx +44 -0
  114. package/src/components/label/meta.md +68 -0
  115. package/src/components/label/stories.tsx +64 -0
  116. package/src/components/mentions/index.tsx +369 -0
  117. package/src/components/mentions/meta.md +118 -0
  118. package/src/components/mentions/stories.tsx +153 -0
  119. package/src/components/menubar/index.tsx +313 -0
  120. package/src/components/menubar/meta.md +52 -0
  121. package/src/components/menubar/stories.tsx +99 -0
  122. package/src/components/navigation-menu/index.tsx +216 -0
  123. package/src/components/navigation-menu/meta.md +66 -0
  124. package/src/components/navigation-menu/stories.tsx +137 -0
  125. package/src/components/page-header/index.tsx +218 -0
  126. package/src/components/page-header/meta.md +211 -0
  127. package/src/components/page-header/stories.tsx +290 -0
  128. package/src/components/page-shell/index.tsx +119 -0
  129. package/src/components/page-shell/meta.md +115 -0
  130. package/src/components/page-shell/stories.tsx +154 -0
  131. package/src/components/pagination/index.tsx +580 -0
  132. package/src/components/pagination/meta.md +39 -0
  133. package/src/components/pagination/stories.tsx +319 -0
  134. package/src/components/popconfirm/index.tsx +477 -0
  135. package/src/components/popconfirm/meta.md +164 -0
  136. package/src/components/popconfirm/stories.tsx +231 -0
  137. package/src/components/popover/index.tsx +181 -0
  138. package/src/components/popover/meta.md +97 -0
  139. package/src/components/popover/stories.tsx +169 -0
  140. package/src/components/progress/index.tsx +176 -0
  141. package/src/components/progress/meta.md +95 -0
  142. package/src/components/progress/stories.tsx +115 -0
  143. package/src/components/radio-group/index.tsx +78 -0
  144. package/src/components/radio-group/meta.md +137 -0
  145. package/src/components/radio-group/stories.tsx +285 -0
  146. package/src/components/rate/index.tsx +284 -0
  147. package/src/components/rate/meta.md +63 -0
  148. package/src/components/rate/stories.tsx +107 -0
  149. package/src/components/resizable/index.tsx +122 -0
  150. package/src/components/resizable/meta.md +142 -0
  151. package/src/components/resizable/stories.tsx +105 -0
  152. package/src/components/scroll-area/index.tsx +83 -0
  153. package/src/components/scroll-area/meta.md +85 -0
  154. package/src/components/scroll-area/stories.tsx +82 -0
  155. package/src/components/select/index.tsx +325 -0
  156. package/src/components/select/meta.md +183 -0
  157. package/src/components/select/stories.tsx +210 -0
  158. package/src/components/separator/index.tsx +51 -0
  159. package/src/components/separator/meta.md +68 -0
  160. package/src/components/separator/stories.tsx +53 -0
  161. package/src/components/sheet/index.tsx +293 -0
  162. package/src/components/sheet/meta.md +226 -0
  163. package/src/components/sheet/stories.tsx +405 -0
  164. package/src/components/sidebar/index.tsx +715 -0
  165. package/src/components/sidebar/meta.md +14 -0
  166. package/src/components/sidebar/{sidebar.stories.tsx → stories.tsx} +250 -330
  167. package/src/components/skeleton/index.tsx +415 -0
  168. package/src/components/skeleton/meta.md +210 -0
  169. package/src/components/skeleton/stories.tsx +197 -0
  170. package/src/components/slider/index.tsx +222 -0
  171. package/src/components/slider/meta.md +125 -0
  172. package/src/components/slider/stories.tsx +109 -0
  173. package/src/components/sonner/index.tsx +149 -0
  174. package/src/components/sonner/meta.md +156 -0
  175. package/src/components/sonner/stories.tsx +285 -0
  176. package/src/components/spinner/index.tsx +335 -0
  177. package/src/components/spinner/meta.md +172 -0
  178. package/src/components/spinner/stories.tsx +197 -0
  179. package/src/components/statistic/{statistic.tsx → index.tsx} +25 -26
  180. package/src/components/statistic/meta.md +94 -0
  181. package/src/components/statistic/stories.tsx +85 -0
  182. package/src/components/steps/index.tsx +500 -0
  183. package/src/components/steps/meta.md +245 -0
  184. package/src/components/steps/stories.tsx +287 -0
  185. package/src/components/switch/index.tsx +175 -0
  186. package/src/components/switch/meta.md +107 -0
  187. package/src/components/switch/stories.tsx +154 -0
  188. package/src/components/table/index.tsx +350 -0
  189. package/src/components/table/meta.md +148 -0
  190. package/src/components/table/stories.tsx +189 -0
  191. package/src/components/tabs/index.tsx +379 -0
  192. package/src/components/tabs/meta.md +244 -0
  193. package/src/components/tabs/stories.tsx +312 -0
  194. package/src/components/tag/index.tsx +774 -0
  195. package/src/components/tag/meta.md +256 -0
  196. package/src/components/tag/stories.tsx +431 -0
  197. package/src/components/textarea/index.tsx +62 -0
  198. package/src/components/textarea/meta.md +77 -0
  199. package/src/components/textarea/stories.tsx +112 -0
  200. package/src/components/time-picker/index.tsx +887 -0
  201. package/src/components/time-picker/meta.md +102 -0
  202. package/src/components/time-picker/stories.tsx +125 -0
  203. package/src/components/timeline/index.tsx +422 -0
  204. package/src/components/timeline/meta.md +352 -0
  205. package/src/components/timeline/stories.tsx +369 -0
  206. package/src/components/toggle/index.tsx +74 -0
  207. package/src/components/toggle/meta.md +76 -0
  208. package/src/components/toggle/stories.tsx +66 -0
  209. package/src/components/toggle-group/index.tsx +167 -0
  210. package/src/components/toggle-group/meta.md +141 -0
  211. package/src/components/toggle-group/stories.tsx +124 -0
  212. package/src/components/tooltip/index.tsx +144 -0
  213. package/src/components/tooltip/meta.md +186 -0
  214. package/src/components/tooltip/stories.tsx +212 -0
  215. package/src/components/transfer/index.tsx +639 -0
  216. package/src/components/transfer/meta.md +76 -0
  217. package/src/components/transfer/stories.tsx +221 -0
  218. package/src/components/tree/index.tsx +764 -0
  219. package/src/components/tree/meta.md +96 -0
  220. package/src/components/tree/stories.tsx +385 -0
  221. package/src/components/tree/utils.ts +269 -0
  222. package/src/components/tree-select/index.tsx +400 -0
  223. package/src/components/tree-select/meta.md +78 -0
  224. package/src/components/tree-select/stories.tsx +234 -0
  225. package/src/components/typography/index.tsx +290 -0
  226. package/src/components/typography/meta.md +151 -0
  227. package/src/components/typography/stories.tsx +151 -0
  228. package/src/components/upload/index.tsx +858 -0
  229. package/src/components/upload/meta.md +122 -0
  230. package/src/components/upload/stories.tsx +261 -0
  231. package/src/components/watermark/index.tsx +152 -0
  232. package/src/components/watermark/meta.md +67 -0
  233. package/src/components/watermark/stories.tsx +59 -0
  234. package/src/hooks/use-mobile.ts +9 -11
  235. package/src/lib/color.ts +243 -0
  236. package/src/{utils/cn.ts → lib/utils.ts} +1 -1
  237. package/src/components/accordion/accordion.meta.md +0 -88
  238. package/src/components/accordion/accordion.stories.tsx +0 -72
  239. package/src/components/accordion/accordion.tsx +0 -154
  240. package/src/components/affix/affix.meta.md +0 -98
  241. package/src/components/affix/affix.stories.tsx +0 -134
  242. package/src/components/affix/affix.tsx +0 -167
  243. package/src/components/alert/alert.meta.md +0 -132
  244. package/src/components/alert/alert.stories.tsx +0 -138
  245. package/src/components/alert/alert.tsx +0 -179
  246. package/src/components/alert-dialog/alert-dialog.meta.md +0 -152
  247. package/src/components/alert-dialog/alert-dialog.stories.tsx +0 -223
  248. package/src/components/alert-dialog/alert-dialog.tsx +0 -183
  249. package/src/components/anchor/anchor.meta.md +0 -92
  250. package/src/components/anchor/anchor.stories.tsx +0 -74
  251. package/src/components/anchor/anchor.tsx +0 -130
  252. package/src/components/app/app.meta.md +0 -91
  253. package/src/components/app/app.stories.tsx +0 -64
  254. package/src/components/app/app.tsx +0 -58
  255. package/src/components/aspect-ratio/aspect-ratio.meta.md +0 -82
  256. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +0 -59
  257. package/src/components/aspect-ratio/aspect-ratio.tsx +0 -22
  258. package/src/components/auto-complete/auto-complete.meta.md +0 -110
  259. package/src/components/auto-complete/auto-complete.stories.tsx +0 -136
  260. package/src/components/auto-complete/auto-complete.tsx +0 -253
  261. package/src/components/avatar/avatar.meta.md +0 -93
  262. package/src/components/avatar/avatar.stories.tsx +0 -98
  263. package/src/components/avatar/avatar.tsx +0 -127
  264. package/src/components/badge/badge.meta.md +0 -120
  265. package/src/components/badge/badge.stories.tsx +0 -153
  266. package/src/components/badge/badge.tsx +0 -204
  267. package/src/components/breadcrumb/breadcrumb.meta.md +0 -127
  268. package/src/components/breadcrumb/breadcrumb.stories.tsx +0 -207
  269. package/src/components/breadcrumb/breadcrumb.tsx +0 -136
  270. package/src/components/button/button.meta.md +0 -335
  271. package/src/components/button/button.stories.tsx +0 -743
  272. package/src/components/button/button.tsx +0 -466
  273. package/src/components/calendar/calendar.meta.md +0 -128
  274. package/src/components/calendar/calendar.stories.tsx +0 -68
  275. package/src/components/calendar/calendar.tsx +0 -172
  276. package/src/components/card/card.meta.md +0 -139
  277. package/src/components/card/card.stories.tsx +0 -151
  278. package/src/components/card/card.tsx +0 -306
  279. package/src/components/carousel/carousel.meta.md +0 -118
  280. package/src/components/carousel/carousel.stories.tsx +0 -89
  281. package/src/components/carousel/carousel.tsx +0 -224
  282. package/src/components/cascader/cascader.meta.md +0 -140
  283. package/src/components/cascader/cascader.stories.tsx +0 -120
  284. package/src/components/cascader/cascader.tsx +0 -548
  285. package/src/components/checkbox/checkbox.meta.md +0 -167
  286. package/src/components/checkbox/checkbox.stories.tsx +0 -288
  287. package/src/components/checkbox/checkbox.tsx +0 -195
  288. package/src/components/collapsible/collapsible.meta.md +0 -88
  289. package/src/components/collapsible/collapsible.stories.tsx +0 -43
  290. package/src/components/collapsible/collapsible.tsx +0 -105
  291. package/src/components/color-picker/color-picker.meta.md +0 -89
  292. package/src/components/color-picker/color-picker.stories.tsx +0 -159
  293. package/src/components/color-picker/color-picker.tsx +0 -171
  294. package/src/components/command/command.meta.md +0 -120
  295. package/src/components/command/command.stories.tsx +0 -59
  296. package/src/components/command/command.tsx +0 -158
  297. package/src/components/context-menu/context-menu.meta.md +0 -93
  298. package/src/components/context-menu/context-menu.stories.tsx +0 -54
  299. package/src/components/context-menu/context-menu.tsx +0 -204
  300. package/src/components/data-table/data-table.meta.md +0 -150
  301. package/src/components/data-table/data-table.stories.tsx +0 -132
  302. package/src/components/data-table/data-table.tsx +0 -185
  303. package/src/components/date-picker/date-picker.meta.md +0 -175
  304. package/src/components/date-picker/date-picker.stories.tsx +0 -108
  305. package/src/components/date-picker/date-picker.tsx +0 -1554
  306. package/src/components/descriptions/descriptions.meta.md +0 -83
  307. package/src/components/descriptions/descriptions.stories.tsx +0 -60
  308. package/src/components/descriptions/descriptions.tsx +0 -137
  309. package/src/components/dialog/dialog.meta.md +0 -168
  310. package/src/components/dialog/dialog.stories.tsx +0 -255
  311. package/src/components/dialog/dialog.tsx +0 -180
  312. package/src/components/dialog/imperative.tsx +0 -252
  313. package/src/components/drawer/drawer.meta.md +0 -95
  314. package/src/components/drawer/drawer.stories.tsx +0 -71
  315. package/src/components/drawer/drawer.tsx +0 -23
  316. package/src/components/dropdown-menu/dropdown-menu.meta.md +0 -171
  317. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -198
  318. package/src/components/dropdown-menu/dropdown-menu.tsx +0 -209
  319. package/src/components/ellipsis/ellipsis.meta.md +0 -87
  320. package/src/components/ellipsis/ellipsis.stories.tsx +0 -72
  321. package/src/components/ellipsis/ellipsis.tsx +0 -153
  322. package/src/components/empty/empty.meta.md +0 -86
  323. package/src/components/empty/empty.stories.tsx +0 -46
  324. package/src/components/empty/empty.tsx +0 -54
  325. package/src/components/field/field.meta.md +0 -154
  326. package/src/components/field/field.stories.tsx +0 -497
  327. package/src/components/field/field.tsx +0 -392
  328. package/src/components/filter-bar/filter-bar.meta.md +0 -92
  329. package/src/components/filter-bar/filter-bar.stories.tsx +0 -1086
  330. package/src/components/filter-bar/filter-bar.tsx +0 -568
  331. package/src/components/flex/flex.meta.md +0 -142
  332. package/src/components/flex/flex.stories.tsx +0 -199
  333. package/src/components/flex/flex.tsx +0 -145
  334. package/src/components/float-button/float-button.meta.md +0 -92
  335. package/src/components/float-button/float-button.stories.tsx +0 -80
  336. package/src/components/float-button/float-button.tsx +0 -143
  337. package/src/components/form/form.meta.md +0 -153
  338. package/src/components/form/form.stories.tsx +0 -472
  339. package/src/components/form/form.tsx +0 -260
  340. package/src/components/grid/grid.meta.md +0 -92
  341. package/src/components/grid/grid.stories.tsx +0 -101
  342. package/src/components/grid/grid.tsx +0 -130
  343. package/src/components/hover-card/hover-card.meta.md +0 -103
  344. package/src/components/hover-card/hover-card.stories.tsx +0 -97
  345. package/src/components/hover-card/hover-card.tsx +0 -67
  346. package/src/components/icon/DEVELOPMENT.md +0 -809
  347. package/src/components/icon/icon.meta.md +0 -170
  348. package/src/components/icon/icon.stories.tsx +0 -344
  349. package/src/components/icon/icon.tsx +0 -248
  350. package/src/components/image/image.meta.md +0 -99
  351. package/src/components/image/image.stories.tsx +0 -55
  352. package/src/components/image/image.tsx +0 -140
  353. package/src/components/input/input.meta.md +0 -115
  354. package/src/components/input/input.stories.tsx +0 -144
  355. package/src/components/input/input.tsx +0 -212
  356. package/src/components/input-group/input-group.meta.md +0 -124
  357. package/src/components/input-group/input-group.stories.tsx +0 -121
  358. package/src/components/input-group/input-group.tsx +0 -143
  359. package/src/components/input-number/input-number.meta.md +0 -148
  360. package/src/components/input-number/input-number.stories.tsx +0 -125
  361. package/src/components/input-number/input-number.tsx +0 -283
  362. package/src/components/input-otp/input-otp.meta.md +0 -106
  363. package/src/components/input-otp/input-otp.stories.tsx +0 -65
  364. package/src/components/input-otp/input-otp.tsx +0 -97
  365. package/src/components/item/item.meta.md +0 -121
  366. package/src/components/item/item.stories.tsx +0 -116
  367. package/src/components/item/item.tsx +0 -172
  368. package/src/components/kbd/kbd.meta.md +0 -94
  369. package/src/components/kbd/kbd.stories.tsx +0 -70
  370. package/src/components/kbd/kbd.tsx +0 -86
  371. package/src/components/label/label.meta.md +0 -99
  372. package/src/components/label/label.stories.tsx +0 -145
  373. package/src/components/label/label.tsx +0 -138
  374. package/src/components/masonry/masonry.meta.md +0 -90
  375. package/src/components/masonry/masonry.stories.tsx +0 -68
  376. package/src/components/masonry/masonry.tsx +0 -60
  377. package/src/components/mentions/mentions.meta.md +0 -119
  378. package/src/components/mentions/mentions.stories.tsx +0 -189
  379. package/src/components/mentions/mentions.tsx +0 -243
  380. package/src/components/menubar/menubar.meta.md +0 -118
  381. package/src/components/menubar/menubar.stories.tsx +0 -141
  382. package/src/components/menubar/menubar.tsx +0 -232
  383. package/src/components/native-select/native-select.meta.md +0 -93
  384. package/src/components/native-select/native-select.stories.tsx +0 -83
  385. package/src/components/native-select/native-select.tsx +0 -54
  386. package/src/components/navigation-menu/navigation-menu.meta.md +0 -118
  387. package/src/components/navigation-menu/navigation-menu.stories.tsx +0 -215
  388. package/src/components/navigation-menu/navigation-menu.tsx +0 -129
  389. package/src/components/notification/notification.meta.md +0 -133
  390. package/src/components/notification/notification.stories.tsx +0 -98
  391. package/src/components/notification/notification.tsx +0 -99
  392. package/src/components/page-header/DEVELOPMENT.md +0 -842
  393. package/src/components/page-header/page-header.meta.md +0 -210
  394. package/src/components/page-header/page-header.stories.tsx +0 -428
  395. package/src/components/page-header/page-header.tsx +0 -284
  396. package/src/components/page-shell/page-shell.meta.md +0 -116
  397. package/src/components/page-shell/page-shell.stories.tsx +0 -149
  398. package/src/components/page-shell/page-shell.tsx +0 -115
  399. package/src/components/pagination/pagination.meta.md +0 -230
  400. package/src/components/pagination/pagination.stories.tsx +0 -284
  401. package/src/components/pagination/pagination.tsx +0 -567
  402. package/src/components/popconfirm/popconfirm.meta.md +0 -114
  403. package/src/components/popconfirm/popconfirm.stories.tsx +0 -75
  404. package/src/components/popconfirm/popconfirm.tsx +0 -134
  405. package/src/components/popover/popover.meta.md +0 -154
  406. package/src/components/popover/popover.stories.tsx +0 -158
  407. package/src/components/popover/popover.tsx +0 -104
  408. package/src/components/progress/progress.meta.md +0 -118
  409. package/src/components/progress/progress.stories.tsx +0 -75
  410. package/src/components/progress/progress.tsx +0 -203
  411. package/src/components/radio-group/radio-group.meta.md +0 -175
  412. package/src/components/radio-group/radio-group.stories.tsx +0 -113
  413. package/src/components/radio-group/radio-group.tsx +0 -209
  414. package/src/components/rate/rate.meta.md +0 -118
  415. package/src/components/rate/rate.stories.tsx +0 -89
  416. package/src/components/rate/rate.tsx +0 -180
  417. package/src/components/resizable/resizable.meta.md +0 -95
  418. package/src/components/resizable/resizable.stories.tsx +0 -104
  419. package/src/components/resizable/resizable.tsx +0 -56
  420. package/src/components/result/result.meta.md +0 -95
  421. package/src/components/result/result.stories.tsx +0 -67
  422. package/src/components/result/result.tsx +0 -100
  423. package/src/components/scroll-area/scroll-area.meta.md +0 -85
  424. package/src/components/scroll-area/scroll-area.stories.tsx +0 -49
  425. package/src/components/scroll-area/scroll-area.tsx +0 -51
  426. package/src/components/segmented/segmented.meta.md +0 -106
  427. package/src/components/segmented/segmented.stories.tsx +0 -130
  428. package/src/components/segmented/segmented.tsx +0 -146
  429. package/src/components/select/select.meta.md +0 -255
  430. package/src/components/select/select.stories.tsx +0 -275
  431. package/src/components/select/select.tsx +0 -735
  432. package/src/components/separator/separator.meta.md +0 -75
  433. package/src/components/separator/separator.stories.tsx +0 -71
  434. package/src/components/separator/separator.tsx +0 -100
  435. package/src/components/sheet/sheet.meta.md +0 -113
  436. package/src/components/sheet/sheet.stories.tsx +0 -188
  437. package/src/components/sheet/sheet.tsx +0 -226
  438. package/src/components/sidebar/sidebar.meta.md +0 -151
  439. package/src/components/sidebar/sidebar.tsx +0 -853
  440. package/src/components/skeleton/skeleton.meta.md +0 -94
  441. package/src/components/skeleton/skeleton.stories.tsx +0 -79
  442. package/src/components/skeleton/skeleton.tsx +0 -144
  443. package/src/components/slider/slider.meta.md +0 -146
  444. package/src/components/slider/slider.stories.tsx +0 -121
  445. package/src/components/slider/slider.tsx +0 -227
  446. package/src/components/sonner/sonner.meta.md +0 -147
  447. package/src/components/sonner/sonner.stories.tsx +0 -164
  448. package/src/components/sonner/sonner.tsx +0 -169
  449. package/src/components/spinner/spinner.meta.md +0 -125
  450. package/src/components/spinner/spinner.stories.tsx +0 -123
  451. package/src/components/spinner/spinner.tsx +0 -166
  452. package/src/components/statistic/statistic.meta.md +0 -104
  453. package/src/components/statistic/statistic.stories.tsx +0 -67
  454. package/src/components/steps/steps.meta.md +0 -116
  455. package/src/components/steps/steps.stories.tsx +0 -115
  456. package/src/components/steps/steps.tsx +0 -173
  457. package/src/components/switch/switch.meta.md +0 -138
  458. package/src/components/switch/switch.stories.tsx +0 -75
  459. package/src/components/switch/switch.tsx +0 -169
  460. package/src/components/table/table.meta.md +0 -106
  461. package/src/components/table/table.stories.tsx +0 -80
  462. package/src/components/table/table.tsx +0 -124
  463. package/src/components/tabs/tabs.meta.md +0 -111
  464. package/src/components/tabs/tabs.stories.tsx +0 -156
  465. package/src/components/tabs/tabs.tsx +0 -190
  466. package/src/components/tag/tag.meta.md +0 -159
  467. package/src/components/tag/tag.stories.tsx +0 -250
  468. package/src/components/tag/tag.tsx +0 -386
  469. package/src/components/textarea/textarea.meta.md +0 -99
  470. package/src/components/textarea/textarea.stories.tsx +0 -89
  471. package/src/components/textarea/textarea.tsx +0 -137
  472. package/src/components/time-picker/time-picker.meta.md +0 -175
  473. package/src/components/time-picker/time-picker.stories.tsx +0 -129
  474. package/src/components/time-picker/time-picker.tsx +0 -946
  475. package/src/components/timeline/timeline.meta.md +0 -110
  476. package/src/components/timeline/timeline.stories.tsx +0 -134
  477. package/src/components/timeline/timeline.tsx +0 -168
  478. package/src/components/toggle/toggle.meta.md +0 -89
  479. package/src/components/toggle/toggle.stories.tsx +0 -66
  480. package/src/components/toggle/toggle.tsx +0 -54
  481. package/src/components/toggle-group/toggle-group.meta.md +0 -91
  482. package/src/components/toggle-group/toggle-group.stories.tsx +0 -83
  483. package/src/components/toggle-group/toggle-group.tsx +0 -78
  484. package/src/components/tooltip/tooltip.meta.md +0 -149
  485. package/src/components/tooltip/tooltip.stories.tsx +0 -108
  486. package/src/components/tooltip/tooltip.tsx +0 -153
  487. package/src/components/tour/tour.meta.md +0 -121
  488. package/src/components/tour/tour.stories.tsx +0 -66
  489. package/src/components/tour/tour.tsx +0 -242
  490. package/src/components/transfer/transfer.meta.md +0 -95
  491. package/src/components/transfer/transfer.stories.tsx +0 -64
  492. package/src/components/transfer/transfer.tsx +0 -258
  493. package/src/components/tree/tree.meta.md +0 -169
  494. package/src/components/tree/tree.stories.tsx +0 -128
  495. package/src/components/tree/tree.tsx +0 -368
  496. package/src/components/tree-select/tree-select.meta.md +0 -151
  497. package/src/components/tree-select/tree-select.stories.tsx +0 -80
  498. package/src/components/tree-select/tree-select.tsx +0 -206
  499. package/src/components/typography/typography.meta.md +0 -149
  500. package/src/components/typography/typography.stories.tsx +0 -116
  501. package/src/components/typography/typography.tsx +0 -260
  502. package/src/components/upload/upload.meta.md +0 -156
  503. package/src/components/upload/upload.stories.tsx +0 -135
  504. package/src/components/upload/upload.tsx +0 -398
  505. package/src/components/watermark/watermark.meta.md +0 -100
  506. package/src/components/watermark/watermark.stories.tsx +0 -170
  507. package/src/components/watermark/watermark.tsx +0 -166
  508. package/src/hooks/use-breakpoint.ts +0 -117
  509. package/src/hooks/use-debounce-callback.ts +0 -52
  510. package/src/stories/theme-tokens.stories.tsx +0 -747
  511. package/src/utils/trigger-input.ts +0 -57
@@ -0,0 +1,1537 @@
1
+ /**
2
+ * 数据表格 DataTable
3
+ *
4
+ * 高阶数据表 — 基于 [@tanstack/react-table](https://tanstack.com/table) 的配置式表格,组合
5
+ * Table primitives + Pagination 实现 排序 / 过滤 / 分页 / 行选择 / 行展开 / 自定义渲染 等核心交互。
6
+ * 主题与视觉对齐本仓 tokens;API 命名采用业界主流(columns / data / accessorKey / header / cell / enableSorting…),
7
+ * 同时保留对 antd / cloud-design 旧字段的向后兼容(dataSource / dataIndex / title / sortable / render / fixed / width)。
8
+ *
9
+ * @when
10
+ * - 需要排序、筛选等交互的数据列表
11
+ * - 批量操作场景(多选行 + 操作按钮)
12
+ * - 大量数据的分页展示
13
+ * - 表格行需要可展开补充信息时
14
+ *
15
+ * 组合结构:DataTable(columns + data)— 内部组合 Table + TableHeader/Body/Row/Cell + Pagination + Popover Filter
16
+ */
17
+ import * as React from 'react';
18
+ import {
19
+ flexRender,
20
+ getCoreRowModel,
21
+ getExpandedRowModel,
22
+ getFilteredRowModel,
23
+ getPaginationRowModel,
24
+ getSortedRowModel,
25
+ useReactTable,
26
+ type CellContext,
27
+ type ColumnDef,
28
+ type ColumnFiltersState,
29
+ type ExpandedState,
30
+ type HeaderContext,
31
+ type RowSelectionState,
32
+ type SortingState,
33
+ type VisibilityState,
34
+ type Row as RTRow,
35
+ type Table as RTTable,
36
+ } from '@tanstack/react-table';
37
+ import { useVirtualizer } from '@tanstack/react-virtual';
38
+ import {
39
+ DndContext,
40
+ PointerSensor,
41
+ closestCenter,
42
+ useSensor,
43
+ useSensors,
44
+ type DragEndEvent,
45
+ } from '@dnd-kit/core';
46
+ import {
47
+ SortableContext,
48
+ useSortable,
49
+ verticalListSortingStrategy,
50
+ } from '@dnd-kit/sortable';
51
+ import { CSS } from '@dnd-kit/utilities';
52
+ import {
53
+ ArrowDown,
54
+ ArrowUp,
55
+ ArrowUpDown,
56
+ ChevronRight,
57
+ Filter as FilterIcon,
58
+ GripVertical,
59
+ } from 'lucide-react';
60
+
61
+ import { cn } from '@/lib/utils';
62
+ import { Button } from '@/components/button';
63
+ import { Checkbox } from '@/components/checkbox';
64
+ import { Pagination } from '@/components/pagination';
65
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/popover';
66
+ import { RadioGroup, RadioGroupItem } from '@/components/radio-group';
67
+ import {
68
+ Table,
69
+ TableBody,
70
+ TableCell,
71
+ TableHead,
72
+ TableHeader,
73
+ TableRow,
74
+ } from '@/components/table';
75
+
76
+ // ─── Types ───────────────────────────────────────────────────────────────────
77
+
78
+ /** 列过滤候选项。 */
79
+ export interface DataTableFilterOption {
80
+ /** 选项展示文案。 */
81
+ label: React.ReactNode;
82
+ /** 选项值(用于匹配 record 字段)。 */
83
+ value: string | number | boolean;
84
+ }
85
+
86
+ /** 列定义;优先使用业界主流字段(accessorKey/header/cell/enableSorting…),保留 cd 旧字段做兼容。 */
87
+ export interface DataTableColumn<T> {
88
+ // ── 主流字段 ────────────────────────────
89
+ /** 列唯一键;若省略则自动用 accessorKey / dataIndex。 */
90
+ key?: string;
91
+ /** 字段访问器(顶级 key)。与 dataIndex 等价。 */
92
+ accessorKey?: keyof T & string;
93
+ /** 表头节点或渲染函数。 */
94
+ header?:
95
+ | React.ReactNode
96
+ | ((ctx: HeaderContext<T, unknown>) => React.ReactNode);
97
+ /** 单元格渲染函数(TanStack 风格)。 */
98
+ cell?: (ctx: CellContext<T, unknown>) => React.ReactNode;
99
+ /** 是否启用排序。 @default false */
100
+ enableSorting?: boolean;
101
+ /** 是否启用列过滤;启用后自动以 includes 匹配 filters 列表。 @default 由 filters 隐式开启 */
102
+ enableColumnFilter?: boolean;
103
+ /** 过滤候选项(启用 filter 时必填)。 */
104
+ filters?: DataTableFilterOption[];
105
+ /** 过滤模式:单选 / 多选。 @default 'multiple' */
106
+ filterMode?: 'single' | 'multiple';
107
+ /** 列宽(数字视为 px)。 */
108
+ size?: number;
109
+ /** 列对齐方式。 @default 'left' */
110
+ align?: 'left' | 'center' | 'right';
111
+ /** 是否可拖拽调整列宽。 @default false */
112
+ resizable?: boolean;
113
+ /** 子列(用于多级表头 / ColumnGroup);传入后本列作为 header group。 */
114
+ children?: DataTableColumn<T>[];
115
+
116
+ // ── 兼容字段(cd / antd) ────────────────
117
+ /** 表头标题(旧字段,等价 `header`)。 */
118
+ title?: React.ReactNode;
119
+ /** 字段名(旧字段,等价 `accessorKey`)。 */
120
+ dataIndex?: keyof T & string;
121
+ /** 是否可排序(旧字段,等价 `enableSorting`)。 */
122
+ sortable?: boolean;
123
+ /** 单元格渲染(旧字段,签名为 `(value, record, index)`)。 */
124
+ render?: (value: unknown, record: T, index: number) => React.ReactNode;
125
+ /** 锁列方向(旧字段,留作 P1 启用)。 */
126
+ fixed?: 'left' | 'right';
127
+ /** 列宽(旧字段,等价 `size`,字符串视作 CSS 宽度)。 */
128
+ width?: number | string;
129
+ }
130
+
131
+ /** 行选择配置(兼容 cd 风格)。 */
132
+ export interface DataTableRowSelection<T> {
133
+ /** 模式:multiple(多选,默认) / single(单选)。 */
134
+ mode?: 'single' | 'multiple';
135
+ /** 受控选中行 key 列表。 */
136
+ selectedRowKeys?: string[];
137
+ /** 默认选中行 key 列表(非受控)。 */
138
+ defaultSelectedRowKeys?: string[];
139
+ /** 选中变化回调。 */
140
+ onChange?: (selectedRowKeys: string[], selectedRows: T[]) => void;
141
+ /** 自定义每行 checkbox / radio 属性。 */
142
+ getProps?: (record: T, index: number) => Record<string, unknown>;
143
+ }
144
+
145
+ /** 行展开配置。 */
146
+ export interface DataTableExpandable<T> {
147
+ /** 展开内容渲染函数。 */
148
+ expandedRowRender: (record: T, index: number) => React.ReactNode;
149
+ /** 行是否可展开;返回 false 则该行不出现展开按钮。 */
150
+ rowExpandable?: (record: T, index: number) => boolean;
151
+ /** 受控展开行 key 列表。 */
152
+ expandedRowKeys?: string[];
153
+ /** 默认展开行 key 列表(非受控)。 */
154
+ defaultExpandedRowKeys?: string[];
155
+ /** 展开 / 收起回调。 */
156
+ onChange?: (expandedRowKeys: string[]) => void;
157
+ }
158
+
159
+ /** 分页配置(false 关闭分页)。 */
160
+ export type DataTablePagination =
161
+ | false
162
+ | {
163
+ /** 当前页(1-based 受控)。 */
164
+ current?: number;
165
+ /** 默认当前页(非受控)。 @default 1 */
166
+ defaultCurrent?: number;
167
+ /** 每页条数(受控)。 */
168
+ pageSize?: number;
169
+ /** 默认每页条数(非受控)。 @default 10 */
170
+ defaultPageSize?: number;
171
+ /** 总条数(默认取 data.length)。 */
172
+ total?: number;
173
+ /** 每页条数候选项。 */
174
+ pageSizeOptions?: number[];
175
+ /** 是否显示 pageSize 选择器。 @default false */
176
+ showSizeChanger?: boolean;
177
+ /** 是否显示总数文本。 @default true */
178
+ showTotal?: boolean;
179
+ /** 翻页回调。 */
180
+ onChange?: (page: number, pageSize: number) => void;
181
+ };
182
+
183
+ /** DataTable 完整 props。 */
184
+ export interface DataTableProps<T>
185
+ extends Omit<React.ComponentProps<'div'>, 'onChange' | 'onDragEnd'> {
186
+ /** 列定义。 */
187
+ columns: DataTableColumn<T>[];
188
+ /** 数据源(主名)。 */
189
+ data?: T[];
190
+ /** 数据源(cd / antd 兼容名,等价 `data`)。 */
191
+ dataSource?: T[];
192
+ /** 行唯一键字段或函数。 @default 'id' */
193
+ rowKey?: keyof T | ((record: T) => string);
194
+ /** 表格尺寸。 @default 'sm' */
195
+ size?: 'sm' | 'md';
196
+ /** 是否显示外边框与单元格分隔线。 @default true */
197
+ bordered?: boolean;
198
+ /** 是否启用斑马纹。 @default false */
199
+ striped?: boolean;
200
+ /** 是否启用行 hover 高亮。 @default true */
201
+ hoverable?: boolean;
202
+ /** 是否启用十字辅助轴。 @default false */
203
+ crossline?: boolean;
204
+ /** 是否吸顶表头。 @default false */
205
+ stickyHeader?: boolean;
206
+ /** 表格容器最大高度(配合 stickyHeader 使用)。 */
207
+ maxBodyHeight?: number | string;
208
+ /** 是否启用列宽拖拽(开关,列级用 column.resizable 覆盖)。 @default false */
209
+ enableColumnResizing?: boolean;
210
+ /** 受控列宽状态。 */
211
+ columnSizing?: Record<string, number>;
212
+ /** 列宽变更回调(拖拽完成时触发)。 */
213
+ onColumnSizingChange?: (sizing: Record<string, number>) => void;
214
+ /** 是否启用树形结构(按 `childrenColumnName` 字段递归渲染子行)。 @default false */
215
+ isTree?: boolean;
216
+ /** 子节点字段名(树形模式下)。 @default 'children' */
217
+ childrenColumnName?: string;
218
+ /** 每层缩进像素(树形模式下)。 @default 16 */
219
+ indentSize?: number;
220
+ /** 是否启用虚拟滚动(大数据量场景)。需同时设置 `maxBodyHeight`。 @default false */
221
+ virtual?: boolean;
222
+ /** 虚拟滚动预估行高(像素)。 @default 40 */
223
+ estimateRowHeight?: number;
224
+ /** 虚拟滚动 overscan 个数。 @default 8 */
225
+ virtualOverscan?: number;
226
+ /** 合并单元格:返回 `{ rowSpan, colSpan }`;返回 0 则跳过该 td。 */
227
+ cellProps?: (
228
+ rowIndex: number,
229
+ colIndex: number,
230
+ record: T,
231
+ ) => { rowSpan?: number; colSpan?: number } | undefined;
232
+ /** 是否启用行拖拽排序。 @default false */
233
+ draggable?: boolean;
234
+ /** 行拖拽结束回调(仅拖拽启用时)。 */
235
+ onDragEnd?: (info: { from: number; to: number; data: T[] }) => void;
236
+ /** 是否加载中。 @default false */
237
+ loading?: boolean;
238
+ /** 自定义加载节点。 */
239
+ loadingNode?: React.ReactNode;
240
+ /** 空数据节点。 @default 居中 Inbox 图标 + “暂无数据” */
241
+ empty?: React.ReactNode;
242
+
243
+ // ── 受控 / 非受控状态 ─────────────────
244
+ /** 受控排序状态(TanStack SortingState)。 */
245
+ sorting?: SortingState;
246
+ /** 排序变化回调。 */
247
+ onSortingChange?: (sorting: SortingState) => void;
248
+ /** 受控列过滤状态(TanStack ColumnFiltersState)。 */
249
+ columnFilters?: ColumnFiltersState;
250
+ /** 列过滤变化回调。 */
251
+ onColumnFiltersChange?: (filters: ColumnFiltersState) => void;
252
+ /** 受控列显隐状态(key 为列 id,value 为 boolean)。 */
253
+ columnVisibility?: VisibilityState;
254
+ /** 列显隐变化回调。 */
255
+ onColumnVisibilityChange?: (visibility: VisibilityState) => void;
256
+ /** 行选择配置;传入开启选择列。 */
257
+ rowSelection?: DataTableRowSelection<T>;
258
+ /** 分页配置;传 false 关闭分页。 */
259
+ pagination?: DataTablePagination;
260
+ /** 行展开配置;传入开启展开列。 */
261
+ expandable?: DataTableExpandable<T>;
262
+ /** 行级 props(cd 兼容)。 */
263
+ onRow?: (
264
+ record: T,
265
+ index: number,
266
+ ) => React.HTMLAttributes<HTMLTableRowElement>;
267
+ /** 整行点击。 */
268
+ onRowClick?: (record: T, index: number, e: React.MouseEvent) => void;
269
+
270
+ /** 类容器 className(透传给最外层 div)。 */
271
+ className?: string;
272
+ /** Table 的 className(内层 table 元素)。 */
273
+ tableClassName?: string;
274
+ }
275
+
276
+ // ─── Helpers ────────────────────────────────────────────────────────────────
277
+
278
+ function getRowKey<T>(
279
+ record: T,
280
+ rowKey: keyof T | ((record: T) => string),
281
+ ): string {
282
+ if (typeof rowKey === 'function') return rowKey(record);
283
+ return String((record as Record<string, unknown>)[rowKey as string]);
284
+ }
285
+
286
+ function alignClass(align?: DataTableColumn<unknown>['align']) {
287
+ if (align === 'center') return 'text-center';
288
+ if (align === 'right') return 'text-right';
289
+ return undefined;
290
+ }
291
+
292
+ function widthStyle(col: DataTableColumn<unknown>): React.CSSProperties {
293
+ const w = col.size ?? col.width;
294
+ if (w === undefined) return {};
295
+ return { width: typeof w === 'number' ? `${w}px` : w };
296
+ }
297
+
298
+ /** 把 DataTableColumn 适配为 TanStack ColumnDef;同时按 fixed 重排(左→中→右)以满足锁列 sticky 计算。 */
299
+ function adaptColumns<T>(
300
+ columns: DataTableColumn<T>[],
301
+ ): ColumnDef<T, unknown>[] {
302
+ const adaptOne = (col: DataTableColumn<T>): ColumnDef<T, unknown> => {
303
+ // 多级表头:header group
304
+ if (col.children && col.children.length > 0) {
305
+ const groupId =
306
+ col.key ??
307
+ (typeof col.header === 'string'
308
+ ? col.header
309
+ : typeof col.title === 'string'
310
+ ? col.title
311
+ : Math.random().toString());
312
+ return {
313
+ id: groupId,
314
+ header: (col.header ?? col.title) as ColumnDef<T, unknown>['header'],
315
+ meta: {
316
+ align: col.align,
317
+ fixed: col.fixed,
318
+ },
319
+ columns: col.children.map(adaptOne),
320
+ };
321
+ }
322
+ const accessorKey = col.accessorKey ?? col.dataIndex;
323
+ const id =
324
+ col.key ??
325
+ (accessorKey as string | undefined) ??
326
+ Math.random().toString();
327
+ const enableSorting = col.enableSorting ?? col.sortable ?? false;
328
+ const enableColumnFilter =
329
+ col.enableColumnFilter ?? (col.filters ? true : false);
330
+ const filterMode = col.filterMode ?? 'multiple';
331
+
332
+ const header =
333
+ col.header ?? col.title ?? (accessorKey as React.ReactNode | undefined);
334
+
335
+ const cell =
336
+ col.cell ??
337
+ ((ctx: CellContext<T, unknown>): React.ReactNode => {
338
+ if (col.render) {
339
+ return col.render(ctx.getValue(), ctx.row.original, ctx.row.index);
340
+ }
341
+ return ctx.getValue() as React.ReactNode;
342
+ });
343
+
344
+ const def: ColumnDef<T, unknown> = {
345
+ id,
346
+ header: header as ColumnDef<T, unknown>['header'],
347
+ cell,
348
+ enableSorting,
349
+ enableColumnFilter,
350
+ enableResizing: col.resizable ?? false,
351
+ meta: {
352
+ align: col.align,
353
+ filters: col.filters,
354
+ filterMode,
355
+ size: col.size ?? col.width,
356
+ fixed: col.fixed,
357
+ resizable: col.resizable ?? false,
358
+ },
359
+ };
360
+
361
+ if (accessorKey) {
362
+ (def as { accessorKey?: string }).accessorKey = accessorKey;
363
+ }
364
+
365
+ if (col.size != null) {
366
+ def.size = col.size;
367
+ }
368
+
369
+ if (enableColumnFilter) {
370
+ def.filterFn = (row, _columnId, filterValue: unknown) => {
371
+ if (filterValue == null) return true;
372
+ const v = row.getValue<unknown>(_columnId);
373
+ if (Array.isArray(filterValue)) {
374
+ if (filterValue.length === 0) return true;
375
+ return filterValue.includes(v as string | number | boolean);
376
+ }
377
+ return v === filterValue;
378
+ };
379
+ }
380
+
381
+ return def;
382
+ };
383
+
384
+ const adapted = columns.map(adaptOne);
385
+
386
+ // 按 fixed 重排:left → middle → right
387
+ const fixedOf = (d: ColumnDef<T, unknown>) =>
388
+ (d.meta as { fixed?: 'left' | 'right' } | undefined)?.fixed;
389
+ const left = adapted.filter((d) => fixedOf(d) === 'left');
390
+ const right = adapted.filter((d) => fixedOf(d) === 'right');
391
+ const mid = adapted.filter((d) => {
392
+ const f = fixedOf(d);
393
+ return f !== 'left' && f !== 'right';
394
+ });
395
+ return [...left, ...mid, ...right];
396
+ }
397
+
398
+ // ─── Filter Popover ─────────────────────────────────────────────────────────
399
+
400
+ interface ColumnFilterPopoverProps {
401
+ options: DataTableFilterOption[];
402
+ mode: 'single' | 'multiple';
403
+ value: unknown;
404
+ onChange: (value: unknown) => void;
405
+ }
406
+
407
+ function ColumnFilterPopover({
408
+ options,
409
+ mode,
410
+ value,
411
+ onChange,
412
+ }: ColumnFilterPopoverProps) {
413
+ const [open, setOpen] = React.useState(false);
414
+ const [draft, setDraft] = React.useState<unknown>(value);
415
+
416
+ React.useEffect(() => {
417
+ if (open) setDraft(value);
418
+ }, [open, value]);
419
+
420
+ const active =
421
+ mode === 'multiple'
422
+ ? Array.isArray(value) && value.length > 0
423
+ : value != null && value !== '';
424
+
425
+ const handleConfirm = () => {
426
+ onChange(draft);
427
+ setOpen(false);
428
+ };
429
+ const handleReset = () => {
430
+ setDraft(mode === 'multiple' ? [] : undefined);
431
+ onChange(undefined);
432
+ setOpen(false);
433
+ };
434
+
435
+ const draftArr = Array.isArray(draft) ? (draft as unknown[]) : [];
436
+ const toggleMulti = (v: unknown, checked: boolean) => {
437
+ setDraft(
438
+ checked ? [...draftArr, v] : draftArr.filter((item) => item !== v),
439
+ );
440
+ };
441
+
442
+ return (
443
+ <Popover open={open} onOpenChange={setOpen}>
444
+ <PopoverTrigger asChild>
445
+ <button
446
+ type="button"
447
+ aria-label="过滤"
448
+ className={cn(
449
+ 'inline-flex size-5 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground',
450
+ active && 'text-primary',
451
+ )}
452
+ onClick={(e) => e.stopPropagation()}
453
+ >
454
+ <FilterIcon className="size-3.5" />
455
+ </button>
456
+ </PopoverTrigger>
457
+ <PopoverContent align="start" className="w-44 gap-2 p-2">
458
+ {mode === 'multiple' ? (
459
+ <div className="flex flex-col gap-1.5 py-1">
460
+ {options.map((opt) => {
461
+ const checked = draftArr.includes(opt.value);
462
+ return (
463
+ <label
464
+ key={String(opt.value)}
465
+ className="flex cursor-pointer items-center gap-2 text-xs"
466
+ >
467
+ <Checkbox
468
+ checked={checked}
469
+ onCheckedChange={(c) => toggleMulti(opt.value, c === true)}
470
+ />
471
+ <span>{opt.label}</span>
472
+ </label>
473
+ );
474
+ })}
475
+ </div>
476
+ ) : (
477
+ <RadioGroup
478
+ value={draft != null ? String(draft) : ''}
479
+ onValueChange={(v) => {
480
+ const matched = options.find((o) => String(o.value) === v);
481
+ setDraft(matched?.value);
482
+ }}
483
+ className="gap-1.5 py-1"
484
+ >
485
+ {options.map((opt) => {
486
+ const id = `filter-${String(opt.value)}`;
487
+ return (
488
+ <label
489
+ key={String(opt.value)}
490
+ htmlFor={id}
491
+ className="flex cursor-pointer items-center gap-2 text-xs"
492
+ >
493
+ <RadioGroupItem id={id} value={String(opt.value)} />
494
+ <span>{opt.label}</span>
495
+ </label>
496
+ );
497
+ })}
498
+ </RadioGroup>
499
+ )}
500
+ <div className="mt-1 flex justify-end gap-2 border-t border-border pt-2">
501
+ <Button variant="ghost" size="sm" onClick={handleReset}>
502
+ 重置
503
+ </Button>
504
+ <Button size="sm" onClick={handleConfirm}>
505
+ 确定
506
+ </Button>
507
+ </div>
508
+ </PopoverContent>
509
+ </Popover>
510
+ );
511
+ }
512
+
513
+ // ─── Drag helpers ───────────────────────────────────────────────────────────
514
+
515
+ interface DragHandleContextValue {
516
+ attributes?: Record<string, unknown>;
517
+ listeners?: Record<string, unknown>;
518
+ }
519
+
520
+ const DragHandleContext = React.createContext<DragHandleContextValue>({});
521
+
522
+ /** 拖拽手柄 cell(在 `__drag__` 列内自动渲染;从 SortableRow 继承 listeners)。 */
523
+ function DragHandleCell() {
524
+ const { attributes, listeners } = React.useContext(DragHandleContext);
525
+ return (
526
+ <button
527
+ type="button"
528
+ aria-label="拖拽排序"
529
+ className="inline-flex size-5 cursor-grab items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground active:cursor-grabbing"
530
+ onClick={(e) => e.stopPropagation()}
531
+ {...(attributes as React.HTMLAttributes<HTMLButtonElement> | undefined)}
532
+ {...(listeners as React.HTMLAttributes<HTMLButtonElement> | undefined)}
533
+ >
534
+ <GripVertical className="size-3.5" />
535
+ </button>
536
+ );
537
+ }
538
+
539
+ interface SortableRowProps extends React.ComponentProps<'tr'> {
540
+ rowId: string;
541
+ }
542
+
543
+ function SortableRow({ rowId, children, style, ...rest }: SortableRowProps) {
544
+ const {
545
+ attributes,
546
+ listeners,
547
+ setNodeRef,
548
+ transform,
549
+ transition,
550
+ isDragging,
551
+ } = useSortable({ id: rowId });
552
+ const finalStyle: React.CSSProperties = {
553
+ ...style,
554
+ transform: CSS.Transform.toString(transform),
555
+ transition,
556
+ opacity: isDragging ? 0.6 : 1,
557
+ position: 'relative',
558
+ zIndex: isDragging ? 2 : undefined,
559
+ };
560
+ return (
561
+ <DragHandleContext.Provider
562
+ value={{
563
+ attributes: attributes as unknown as Record<string, unknown>,
564
+ listeners: listeners as unknown as Record<string, unknown> | undefined,
565
+ }}
566
+ >
567
+ <TableRow
568
+ ref={(node: HTMLTableRowElement | null) => setNodeRef(node)}
569
+ style={finalStyle}
570
+ {...rest}
571
+ >
572
+ {children}
573
+ </TableRow>
574
+ </DragHandleContext.Provider>
575
+ );
576
+ }
577
+
578
+ // ─── EditableCell ───────────────────────────────────────────────────────────
579
+
580
+ export interface EditableCellProps {
581
+ /** 当前值。 */
582
+ value: string | number;
583
+ /** blur / Enter 时若值变化则提交。 */
584
+ onCommit: (next: string | number) => void;
585
+ /** 输入类型。 @default 'text' */
586
+ type?: 'text' | 'number';
587
+ /** placeholder。 */
588
+ placeholder?: string;
589
+ /** 自定义 className。 */
590
+ className?: string;
591
+ }
592
+
593
+ /** 双击切入编辑态的单元格 — 用于 column.cell 中包裹文本字段以快速实现行内编辑。 */
594
+ function EditableCell({
595
+ value,
596
+ onCommit,
597
+ type = 'text',
598
+ placeholder,
599
+ className,
600
+ }: EditableCellProps) {
601
+ const [editing, setEditing] = React.useState(false);
602
+ const [draft, setDraft] = React.useState<string | number>(value);
603
+ React.useEffect(() => {
604
+ setDraft(value);
605
+ }, [value]);
606
+
607
+ if (!editing) {
608
+ return (
609
+ <span
610
+ className={cn(
611
+ 'inline-flex min-h-6 cursor-text items-center rounded-sm px-1 hover:bg-accent/50',
612
+ className,
613
+ )}
614
+ onDoubleClick={(e) => {
615
+ e.stopPropagation();
616
+ setEditing(true);
617
+ }}
618
+ onClick={(e) => e.stopPropagation()}
619
+ >
620
+ {String(value ?? '')}
621
+ </span>
622
+ );
623
+ }
624
+
625
+ return (
626
+ <input
627
+ autoFocus
628
+ type={type}
629
+ value={draft as string | number}
630
+ placeholder={placeholder}
631
+ className={cn(
632
+ 'h-7 w-full rounded-sm border border-border bg-background px-2 text-xs outline-none focus:border-primary',
633
+ className,
634
+ )}
635
+ onChange={(e) =>
636
+ setDraft(type === 'number' ? Number(e.target.value) : e.target.value)
637
+ }
638
+ onBlur={() => {
639
+ setEditing(false);
640
+ if (draft !== value) onCommit(draft);
641
+ }}
642
+ onKeyDown={(e) => {
643
+ if (e.key === 'Enter') (e.target as HTMLInputElement).blur();
644
+ if (e.key === 'Escape') {
645
+ setDraft(value);
646
+ setEditing(false);
647
+ }
648
+ }}
649
+ onClick={(e) => e.stopPropagation()}
650
+ />
651
+ );
652
+ }
653
+
654
+ // ─── DataTable ──────────────────────────────────────────────────────────────
655
+
656
+ function DataTable<T = Record<string, unknown>>(props: DataTableProps<T>) {
657
+ const {
658
+ columns,
659
+ data,
660
+ dataSource,
661
+ rowKey = 'id' as keyof T,
662
+ size = 'sm',
663
+ bordered = false,
664
+ striped = false,
665
+ hoverable = true,
666
+ crossline = false,
667
+ stickyHeader = false,
668
+ maxBodyHeight,
669
+ enableColumnResizing = false,
670
+ columnSizing: columnSizingProp,
671
+ onColumnSizingChange,
672
+ isTree = false,
673
+ childrenColumnName = 'children',
674
+ indentSize = 16,
675
+ virtual = false,
676
+ estimateRowHeight = 40,
677
+ virtualOverscan = 8,
678
+ cellProps,
679
+ draggable = false,
680
+ onDragEnd,
681
+ loading = false,
682
+ loadingNode,
683
+ empty,
684
+ sorting: sortingProp,
685
+ onSortingChange,
686
+ columnFilters: columnFiltersProp,
687
+ onColumnFiltersChange,
688
+ columnVisibility: columnVisibilityProp,
689
+ onColumnVisibilityChange,
690
+ rowSelection: rowSelectionConfig,
691
+ pagination,
692
+ expandable,
693
+ onRow,
694
+ onRowClick,
695
+ className,
696
+ tableClassName,
697
+ ...rest
698
+ } = props;
699
+
700
+ const finalData = (data ?? dataSource ?? []) as T[];
701
+
702
+ // ── Sorting state(受控/非受控)
703
+ const [innerSorting, setInnerSorting] = React.useState<SortingState>([]);
704
+ const sorting = sortingProp ?? innerSorting;
705
+ const handleSortingChange = (updater: React.SetStateAction<SortingState>) => {
706
+ const next = typeof updater === 'function' ? updater(sorting) : updater;
707
+ if (sortingProp === undefined) setInnerSorting(next);
708
+ onSortingChange?.(next);
709
+ };
710
+
711
+ // ── ColumnFilters state(受控/非受控)
712
+ const [innerFilters, setInnerFilters] = React.useState<ColumnFiltersState>(
713
+ [],
714
+ );
715
+ const columnFilters = columnFiltersProp ?? innerFilters;
716
+ const handleFiltersChange = (
717
+ updater: React.SetStateAction<ColumnFiltersState>,
718
+ ) => {
719
+ const next =
720
+ typeof updater === 'function' ? updater(columnFilters) : updater;
721
+ if (columnFiltersProp === undefined) setInnerFilters(next);
722
+ onColumnFiltersChange?.(next);
723
+ };
724
+
725
+ // ── ColumnVisibility state(受控/非受控)
726
+ const [innerVisibility, setInnerVisibility] = React.useState<VisibilityState>(
727
+ {},
728
+ );
729
+ const columnVisibility = columnVisibilityProp ?? innerVisibility;
730
+ const handleVisibilityChange = (
731
+ updater: React.SetStateAction<VisibilityState>,
732
+ ) => {
733
+ const next =
734
+ typeof updater === 'function' ? updater(columnVisibility) : updater;
735
+ if (columnVisibilityProp === undefined) setInnerVisibility(next);
736
+ onColumnVisibilityChange?.(next);
737
+ };
738
+
739
+ // ── ColumnSizing state(受控/非受控)
740
+ const [innerSizing, setInnerSizing] = React.useState<Record<string, number>>(
741
+ {},
742
+ );
743
+ const columnSizing = columnSizingProp ?? innerSizing;
744
+ const handleSizingChange = (
745
+ updater: React.SetStateAction<Record<string, number>>,
746
+ ) => {
747
+ const next =
748
+ typeof updater === 'function' ? updater(columnSizing) : updater;
749
+ if (columnSizingProp === undefined) setInnerSizing(next);
750
+ onColumnSizingChange?.(next);
751
+ };
752
+
753
+ // ── 列 hover 状态(crossline 十字辅助轴)
754
+ const [hoveredColId, setHoveredColId] = React.useState<string | null>(null);
755
+
756
+ // ── RowSelection state
757
+ const selectionMode = rowSelectionConfig?.mode ?? 'multiple';
758
+ const [innerSelection, setInnerSelection] = React.useState<RowSelectionState>(
759
+ () => {
760
+ const init = rowSelectionConfig?.defaultSelectedRowKeys ?? [];
761
+ return Object.fromEntries(
762
+ init.map((k) => [k, true]),
763
+ ) as RowSelectionState;
764
+ },
765
+ );
766
+ const controlledKeys = rowSelectionConfig?.selectedRowKeys;
767
+ const rowSelectionState: RowSelectionState = controlledKeys
768
+ ? (Object.fromEntries(
769
+ controlledKeys.map((k) => [k, true]),
770
+ ) as RowSelectionState)
771
+ : innerSelection;
772
+
773
+ const handleRowSelectionChange = (
774
+ updater: React.SetStateAction<RowSelectionState>,
775
+ ) => {
776
+ let next =
777
+ typeof updater === 'function' ? updater(rowSelectionState) : updater;
778
+ // 单选模式:保留最后一个被勾选的 row id
779
+ if (selectionMode === 'single') {
780
+ const trueKeys = Object.entries(next)
781
+ .filter(([, v]) => v)
782
+ .map(([k]) => k);
783
+ const prevKeys = Object.entries(rowSelectionState)
784
+ .filter(([, v]) => v)
785
+ .map(([k]) => k);
786
+ const newlyAdded = trueKeys.filter((k) => !prevKeys.includes(k));
787
+ const lastKey = newlyAdded[newlyAdded.length - 1] ?? trueKeys[0];
788
+ next = lastKey ? { [lastKey]: true } : {};
789
+ }
790
+ if (controlledKeys === undefined) setInnerSelection(next);
791
+ const keys = Object.entries(next)
792
+ .filter(([, v]) => v)
793
+ .map(([k]) => k);
794
+ const records = finalData.filter((r) =>
795
+ keys.includes(getRowKey(r, rowKey)),
796
+ );
797
+ rowSelectionConfig?.onChange?.(keys, records);
798
+ };
799
+
800
+ // ── Expanded state
801
+ const expandedKeys =
802
+ expandable?.expandedRowKeys ?? expandable?.defaultExpandedRowKeys;
803
+ const [innerExpanded, setInnerExpanded] = React.useState<ExpandedState>(
804
+ () => {
805
+ const init = expandable?.defaultExpandedRowKeys ?? [];
806
+ return Object.fromEntries(init.map((k) => [k, true])) as ExpandedState;
807
+ },
808
+ );
809
+ const expandedState: ExpandedState = expandable?.expandedRowKeys
810
+ ? (Object.fromEntries(
811
+ expandable.expandedRowKeys.map((k) => [k, true]),
812
+ ) as ExpandedState)
813
+ : innerExpanded;
814
+ void expandedKeys; // keep ref for future use
815
+ const handleExpandedChange = (
816
+ updater: React.SetStateAction<ExpandedState>,
817
+ ) => {
818
+ const next =
819
+ typeof updater === 'function' ? updater(expandedState) : updater;
820
+ if (expandable?.expandedRowKeys === undefined) setInnerExpanded(next);
821
+ const keys =
822
+ typeof next === 'object' && next !== null
823
+ ? Object.entries(next)
824
+ .filter(([, v]) => v)
825
+ .map(([k]) => k)
826
+ : [];
827
+ expandable?.onChange?.(keys);
828
+ };
829
+
830
+ // ── Pagination state(仅当未受控时使用 TanStack 内部分页)
831
+ const paginationEnabled = pagination !== false;
832
+ const isPagObject =
833
+ paginationEnabled && pagination && typeof pagination === 'object';
834
+ const pagObj = isPagObject
835
+ ? (pagination as Exclude<DataTablePagination, false>)
836
+ : undefined;
837
+ const [innerPageIndex, setInnerPageIndex] = React.useState(
838
+ (pagObj?.defaultCurrent ?? 1) - 1,
839
+ );
840
+ const [innerPageSize, setInnerPageSize] = React.useState(
841
+ pagObj?.defaultPageSize ?? 10,
842
+ );
843
+ const currentPage =
844
+ pagObj?.current != null ? pagObj.current : innerPageIndex + 1;
845
+ const pageSize = pagObj?.pageSize ?? innerPageSize;
846
+ const totalRecords = pagObj?.total ?? finalData.length;
847
+
848
+ // ── Build columns
849
+ const adaptedColumns = React.useMemo(
850
+ () => adaptColumns<T>(columns),
851
+ [columns],
852
+ );
853
+
854
+ const hasLeftFixed = React.useMemo(
855
+ () =>
856
+ adaptedColumns.some(
857
+ (c) =>
858
+ (c.meta as { fixed?: 'left' | 'right' } | undefined)?.fixed ===
859
+ 'left',
860
+ ),
861
+ [adaptedColumns],
862
+ );
863
+
864
+ const tableColumns = React.useMemo<ColumnDef<T, unknown>[]>(() => {
865
+ const list: ColumnDef<T, unknown>[] = [];
866
+
867
+ // selection column
868
+ if (rowSelectionConfig) {
869
+ list.push({
870
+ id: '__selection__',
871
+ size: 40,
872
+ enableSorting: false,
873
+ enableColumnFilter: false,
874
+ enableResizing: false,
875
+ meta: hasLeftFixed ? { fixed: 'left' } : {},
876
+ header: ({ table }) =>
877
+ selectionMode === 'multiple' ? (
878
+ <Checkbox
879
+ aria-label="全选"
880
+ checked={
881
+ table.getIsAllPageRowsSelected()
882
+ ? true
883
+ : table.getIsSomePageRowsSelected()
884
+ ? 'indeterminate'
885
+ : false
886
+ }
887
+ onCheckedChange={(v) =>
888
+ table.toggleAllPageRowsSelected(v === true)
889
+ }
890
+ />
891
+ ) : null,
892
+ cell: ({ row }) => {
893
+ const extra =
894
+ rowSelectionConfig.getProps?.(row.original, row.index) ?? {};
895
+ if (selectionMode === 'single') {
896
+ return (
897
+ <input
898
+ type="radio"
899
+ aria-label="选择行"
900
+ checked={row.getIsSelected()}
901
+ onChange={() => row.toggleSelected(true)}
902
+ onClick={(e) => e.stopPropagation()}
903
+ className="size-3.5 cursor-pointer accent-primary"
904
+ {...(extra as React.InputHTMLAttributes<HTMLInputElement>)}
905
+ />
906
+ );
907
+ }
908
+ return (
909
+ <Checkbox
910
+ aria-label="选择行"
911
+ checked={row.getIsSelected()}
912
+ onCheckedChange={(v) => row.toggleSelected(v === true)}
913
+ onClick={(e) => e.stopPropagation()}
914
+ {...(extra as Partial<React.ComponentProps<typeof Checkbox>>)}
915
+ />
916
+ );
917
+ },
918
+ });
919
+ }
920
+
921
+ // drag handle column(行拖拽排序时插在最前;锁列存在时跟随 left)
922
+ if (draggable) {
923
+ list.push({
924
+ id: '__drag__',
925
+ size: 32,
926
+ enableSorting: false,
927
+ enableColumnFilter: false,
928
+ enableResizing: false,
929
+ meta: hasLeftFixed ? { fixed: 'left' } : {},
930
+ header: () => null,
931
+ cell: () => <DragHandleCell />,
932
+ });
933
+ }
934
+
935
+ // expand column(树形模式下不另起列,在首列 cell 内联展开按钮)
936
+ if (expandable && !isTree) {
937
+ list.push({
938
+ id: '__expand__',
939
+ size: 40,
940
+ enableSorting: false,
941
+ enableColumnFilter: false,
942
+ enableResizing: false,
943
+ meta: hasLeftFixed ? { fixed: 'left' } : {},
944
+ header: () => null,
945
+ cell: ({ row }) => {
946
+ const can =
947
+ expandable.rowExpandable?.(row.original, row.index) ?? true;
948
+ if (!can) return null;
949
+ const expanded =
950
+ typeof expandedState === 'object' && !!expandedState[row.id];
951
+ return (
952
+ <button
953
+ type="button"
954
+ aria-label={expanded ? '收起' : '展开'}
955
+ className="inline-flex size-5 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
956
+ onClick={(e) => {
957
+ e.stopPropagation();
958
+ handleExpandedChange((prev) => {
959
+ const o =
960
+ typeof prev === 'object' && prev !== null
961
+ ? (prev as Record<string, boolean>)
962
+ : {};
963
+ return { ...o, [row.id]: !o[row.id] } as ExpandedState;
964
+ });
965
+ }}
966
+ >
967
+ <ChevronRight
968
+ className={cn(
969
+ 'size-3.5 transition-transform',
970
+ expanded && 'rotate-90',
971
+ )}
972
+ />
973
+ </button>
974
+ );
975
+ },
976
+ });
977
+ }
978
+
979
+ return [...list, ...adaptedColumns];
980
+ }, [
981
+ adaptedColumns,
982
+ rowSelectionConfig,
983
+ selectionMode,
984
+ expandable,
985
+ expandedState,
986
+ isTree,
987
+ hasLeftFixed,
988
+ draggable,
989
+ ]);
990
+
991
+ // ── Build table
992
+ const table: RTTable<T> = useReactTable({
993
+ data: finalData,
994
+ columns: tableColumns,
995
+ state: {
996
+ sorting,
997
+ columnFilters,
998
+ columnVisibility,
999
+ rowSelection: rowSelectionState,
1000
+ expanded: expandedState,
1001
+ columnSizing,
1002
+ ...(paginationEnabled
1003
+ ? {
1004
+ pagination: {
1005
+ pageIndex: currentPage - 1,
1006
+ pageSize,
1007
+ },
1008
+ }
1009
+ : {}),
1010
+ },
1011
+ onSortingChange: handleSortingChange,
1012
+ onColumnFiltersChange: handleFiltersChange,
1013
+ onColumnVisibilityChange: handleVisibilityChange,
1014
+ onRowSelectionChange: handleRowSelectionChange,
1015
+ onExpandedChange: handleExpandedChange,
1016
+ onColumnSizingChange: handleSizingChange,
1017
+ enableRowSelection: !!rowSelectionConfig,
1018
+ enableMultiRowSelection: selectionMode === 'multiple',
1019
+ enableExpanding: !!expandable || isTree,
1020
+ enableColumnResizing,
1021
+ columnResizeMode: 'onChange',
1022
+ getRowId: (row) => getRowKey(row, rowKey),
1023
+ getCoreRowModel: getCoreRowModel(),
1024
+ getSortedRowModel: getSortedRowModel(),
1025
+ getFilteredRowModel: getFilteredRowModel(),
1026
+ getExpandedRowModel:
1027
+ expandable || isTree ? getExpandedRowModel() : undefined,
1028
+ ...(isTree
1029
+ ? {
1030
+ getSubRows: (row: T) =>
1031
+ (row as Record<string, unknown>)[childrenColumnName] as
1032
+ | T[]
1033
+ | undefined,
1034
+ }
1035
+ : {}),
1036
+ ...(paginationEnabled
1037
+ ? {
1038
+ getPaginationRowModel: getPaginationRowModel(),
1039
+ manualPagination: pagObj?.total != null,
1040
+ pageCount:
1041
+ pagObj?.total != null
1042
+ ? Math.ceil(pagObj.total / pageSize)
1043
+ : undefined,
1044
+ }
1045
+ : {}),
1046
+ });
1047
+
1048
+ const rows = table.getRowModel().rows;
1049
+ const showEmpty = !loading && rows.length === 0;
1050
+ const colCount = tableColumns.length;
1051
+
1052
+ // ── Sticky 偏移计算(锁列)
1053
+ const leafColumns = table.getVisibleLeafColumns();
1054
+ const stickyOffsets = React.useMemo(() => {
1055
+ const left = new Map<string, number>();
1056
+ const right = new Map<string, number>();
1057
+ let leftAcc = 0;
1058
+ for (const col of leafColumns) {
1059
+ const fixed = (col.columnDef.meta as { fixed?: 'left' | 'right' })?.fixed;
1060
+ if (fixed === 'left') {
1061
+ left.set(col.id, leftAcc);
1062
+ leftAcc += col.getSize();
1063
+ }
1064
+ }
1065
+ let rightAcc = 0;
1066
+ for (const col of [...leafColumns].reverse()) {
1067
+ const fixed = (col.columnDef.meta as { fixed?: 'left' | 'right' })?.fixed;
1068
+ if (fixed === 'right') {
1069
+ right.set(col.id, rightAcc);
1070
+ rightAcc += col.getSize();
1071
+ }
1072
+ }
1073
+ return { left, right };
1074
+ }, [leafColumns, columnSizing]);
1075
+
1076
+ // ── 锁列边界:最后一个 left-fixed / 第一个 right-fixed 加 shadow 区分
1077
+ const lastLeftFixedId = React.useMemo(() => {
1078
+ let id: string | undefined;
1079
+ for (const col of leafColumns) {
1080
+ if (
1081
+ (col.columnDef.meta as { fixed?: 'left' | 'right' })?.fixed === 'left'
1082
+ ) {
1083
+ id = col.id;
1084
+ }
1085
+ }
1086
+ return id;
1087
+ }, [leafColumns]);
1088
+ const firstRightFixedId = React.useMemo(() => {
1089
+ for (const col of leafColumns) {
1090
+ if (
1091
+ (col.columnDef.meta as { fixed?: 'left' | 'right' })?.fixed === 'right'
1092
+ ) {
1093
+ return col.id;
1094
+ }
1095
+ }
1096
+ return undefined;
1097
+ }, [leafColumns]);
1098
+
1099
+ // ── 树形/首数据列 id(跳过锁定的选择 / 展开 / 拖拽列,取首个业务列)
1100
+ const firstDataColId = React.useMemo(() => {
1101
+ return leafColumns.find(
1102
+ (c) =>
1103
+ c.id !== '__selection__' &&
1104
+ c.id !== '__expand__' &&
1105
+ c.id !== '__drag__',
1106
+ )?.id;
1107
+ }, [leafColumns]);
1108
+
1109
+ // ── 滚动容器 ref(虚拟滚动需要)
1110
+ const scrollWrapperRef = React.useRef<HTMLDivElement>(null);
1111
+ const getScrollElement = React.useCallback(() => {
1112
+ return (
1113
+ scrollWrapperRef.current?.querySelector<HTMLElement>(
1114
+ '[data-slot="table-container"]',
1115
+ ) ?? null
1116
+ );
1117
+ }, []);
1118
+ const virtualizer = useVirtualizer({
1119
+ count: rows.length,
1120
+ getScrollElement,
1121
+ estimateSize: () => estimateRowHeight,
1122
+ overscan: virtualOverscan,
1123
+ enabled: virtual,
1124
+ });
1125
+ const virtualItems = virtual ? virtualizer.getVirtualItems() : [];
1126
+ const totalSize = virtual ? virtualizer.getTotalSize() : 0;
1127
+ const paddingTop =
1128
+ virtual && virtualItems.length > 0 ? virtualItems[0]!.start : 0;
1129
+ const paddingBottom =
1130
+ virtual && virtualItems.length > 0
1131
+ ? totalSize - virtualItems[virtualItems.length - 1]!.end
1132
+ : 0;
1133
+
1134
+ // ── 拖拽排序 sensors / handler
1135
+ const sensors = useSensors(
1136
+ useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
1137
+ );
1138
+ const rowIds = React.useMemo(() => rows.map((r) => r.id), [rows]);
1139
+ const handleDragEnd = (event: DragEndEvent) => {
1140
+ const { active, over } = event;
1141
+ if (!over || active.id === over.id) return;
1142
+ const from = rowIds.indexOf(String(active.id));
1143
+ const to = rowIds.indexOf(String(over.id));
1144
+ if (from < 0 || to < 0) return;
1145
+ const next = [...finalData];
1146
+ const [moved] = next.splice(from, 1);
1147
+ if (moved !== undefined) next.splice(to, 0, moved);
1148
+ onDragEnd?.({ from, to, data: next });
1149
+ };
1150
+
1151
+ // ── 单行渲染 helper(抽出以复用于虚拟滚动 / 拖拽两条路径)
1152
+ const renderCells = (row: RTRow<T>) => {
1153
+ const visibleCells = row.getVisibleCells();
1154
+ const cells: React.ReactNode[] = [];
1155
+ for (let colIdx = 0; colIdx < visibleCells.length; colIdx += 1) {
1156
+ const cell = visibleCells[colIdx]!;
1157
+ const cellMeta = cell.column.columnDef.meta as
1158
+ | {
1159
+ align?: 'left' | 'center' | 'right';
1160
+ fixed?: 'left' | 'right';
1161
+ }
1162
+ | undefined;
1163
+ const colId = cell.column.id;
1164
+
1165
+ // cellProps 合并(快推内部列)
1166
+ let rowSpan: number | undefined;
1167
+ let colSpan: number | undefined;
1168
+ if (
1169
+ cellProps &&
1170
+ colId !== '__selection__' &&
1171
+ colId !== '__expand__' &&
1172
+ colId !== '__drag__'
1173
+ ) {
1174
+ const cp = cellProps(row.index, colIdx, row.original);
1175
+ if (cp) {
1176
+ rowSpan = cp.rowSpan;
1177
+ colSpan = cp.colSpan;
1178
+ if (rowSpan === 0 || colSpan === 0) continue;
1179
+ }
1180
+ }
1181
+
1182
+ const isFixedLeft = cellMeta?.fixed === 'left';
1183
+ const isFixedRight = cellMeta?.fixed === 'right';
1184
+ const isFixed = isFixedLeft || isFixedRight;
1185
+ const cellStyle: React.CSSProperties = {};
1186
+ if (isFixedLeft) {
1187
+ cellStyle.position = 'sticky';
1188
+ cellStyle.left = `${stickyOffsets.left.get(colId) ?? 0}px`;
1189
+ cellStyle.zIndex = 1;
1190
+ }
1191
+ if (isFixedRight) {
1192
+ cellStyle.position = 'sticky';
1193
+ cellStyle.right = `${stickyOffsets.right.get(colId) ?? 0}px`;
1194
+ cellStyle.zIndex = 1;
1195
+ }
1196
+ if (isFixedLeft && colId === lastLeftFixedId) {
1197
+ cellStyle.boxShadow = 'inset -6px 0 6px -6px var(--color-border)';
1198
+ }
1199
+ if (isFixedRight && colId === firstRightFixedId) {
1200
+ cellStyle.boxShadow = 'inset 6px 0 6px -6px var(--color-border)';
1201
+ }
1202
+ const isFirstDataCol = colId === firstDataColId;
1203
+ const treeIndent = isTree && isFirstDataCol ? row.depth * indentSize : 0;
1204
+ if (treeIndent > 0) {
1205
+ cellStyle.paddingLeft = `${12 + treeIndent}px`;
1206
+ }
1207
+ const isColHover = crossline && hoveredColId === colId;
1208
+ const canTreeExpand = isTree && row.subRows && row.subRows.length > 0;
1209
+
1210
+ cells.push(
1211
+ <TableCell
1212
+ key={cell.id}
1213
+ style={cellStyle}
1214
+ rowSpan={rowSpan}
1215
+ colSpan={colSpan}
1216
+ data-fixed={cellMeta?.fixed}
1217
+ data-col-hover={isColHover ? '' : undefined}
1218
+ className={cn(
1219
+ alignClass(cellMeta?.align),
1220
+ isFixed && 'bg-background',
1221
+ isColHover && 'bg-muted/30',
1222
+ )}
1223
+ onMouseEnter={crossline ? () => setHoveredColId(colId) : undefined}
1224
+ onMouseLeave={crossline ? () => setHoveredColId(null) : undefined}
1225
+ >
1226
+ {isTree && isFirstDataCol ? (
1227
+ <span className="inline-flex items-center gap-1.5">
1228
+ {canTreeExpand ? (
1229
+ <button
1230
+ type="button"
1231
+ aria-label={row.getIsExpanded() ? '收起' : '展开'}
1232
+ className="inline-flex size-5 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
1233
+ onClick={(e) => {
1234
+ e.stopPropagation();
1235
+ row.toggleExpanded();
1236
+ }}
1237
+ >
1238
+ <ChevronRight
1239
+ className={cn(
1240
+ 'size-3.5 transition-transform',
1241
+ row.getIsExpanded() && 'rotate-90',
1242
+ )}
1243
+ />
1244
+ </button>
1245
+ ) : (
1246
+ <span className="inline-block size-5" />
1247
+ )}
1248
+ <span>
1249
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
1250
+ </span>
1251
+ </span>
1252
+ ) : (
1253
+ flexRender(cell.column.columnDef.cell, cell.getContext())
1254
+ )}
1255
+ </TableCell>,
1256
+ );
1257
+ }
1258
+ return cells;
1259
+ };
1260
+
1261
+ const renderRow = (row: RTRow<T>) => {
1262
+ const rowMeta = onRow?.(row.original, row.index) ?? {};
1263
+ const isExpanded =
1264
+ !!expandable &&
1265
+ typeof expandedState === 'object' &&
1266
+ !!expandedState[row.id];
1267
+ const handleClick = (e: React.MouseEvent<HTMLTableRowElement>) => {
1268
+ rowMeta.onClick?.(e);
1269
+ onRowClick?.(row.original, row.index, e);
1270
+ };
1271
+ const dataState = row.getIsSelected() ? 'selected' : undefined;
1272
+ return (
1273
+ <React.Fragment key={row.id}>
1274
+ {draggable ? (
1275
+ <SortableRow
1276
+ rowId={row.id}
1277
+ data-state={dataState}
1278
+ {...rowMeta}
1279
+ onClick={handleClick}
1280
+ >
1281
+ {renderCells(row)}
1282
+ </SortableRow>
1283
+ ) : (
1284
+ <TableRow data-state={dataState} {...rowMeta} onClick={handleClick}>
1285
+ {renderCells(row)}
1286
+ </TableRow>
1287
+ )}
1288
+ {isExpanded ? (
1289
+ <TableRow
1290
+ data-slot="data-table-expanded-row"
1291
+ className="bg-muted/20 hover:bg-muted/20"
1292
+ >
1293
+ <TableCell colSpan={colCount} className="whitespace-normal py-3">
1294
+ {expandable!.expandedRowRender(row.original, row.index)}
1295
+ </TableCell>
1296
+ </TableRow>
1297
+ ) : null}
1298
+ </React.Fragment>
1299
+ );
1300
+ };
1301
+
1302
+ // ── 虚拟模式下仅渲染可见行 + 上下 spacer
1303
+ const visibleRows = virtual
1304
+ ? virtualItems
1305
+ .map((vi) => rows[vi.index])
1306
+ .filter((r): r is RTRow<T> => r !== undefined)
1307
+ : rows;
1308
+
1309
+ // 计算所有列的总宽度,作为 table 的 min-width 防止列被拉伸
1310
+ const totalColumnWidth = React.useMemo(() => {
1311
+ let total = 0;
1312
+ let hasWidth = false;
1313
+ for (const col of columns) {
1314
+ const w = col.size ?? col.width;
1315
+ if (typeof w === 'number') {
1316
+ total += w;
1317
+ hasWidth = true;
1318
+ }
1319
+ }
1320
+ return hasWidth ? total : undefined;
1321
+ }, [columns]);
1322
+
1323
+ const tableEl = (
1324
+ <Table
1325
+ size={size}
1326
+ bordered={bordered}
1327
+ striped={striped}
1328
+ hoverable={hoverable}
1329
+ crossline={crossline}
1330
+ stickyHeader={stickyHeader}
1331
+ maxBodyHeight={maxBodyHeight}
1332
+ loading={loading}
1333
+ loadingNode={loadingNode}
1334
+ empty={showEmpty ? empty ?? undefined : undefined}
1335
+ className={tableClassName}
1336
+ style={
1337
+ totalColumnWidth ? { minWidth: `${totalColumnWidth}px` } : undefined
1338
+ }
1339
+ >
1340
+ <TableHeader>
1341
+ {table.getHeaderGroups().map((headerGroup) => (
1342
+ <TableRow key={headerGroup.id}>
1343
+ {headerGroup.headers.map((header) => {
1344
+ const meta = header.column.columnDef.meta as
1345
+ | {
1346
+ align?: 'left' | 'center' | 'right';
1347
+ filters?: DataTableFilterOption[];
1348
+ filterMode?: 'single' | 'multiple';
1349
+ size?: number | string;
1350
+ fixed?: 'left' | 'right';
1351
+ resizable?: boolean;
1352
+ }
1353
+ | undefined;
1354
+ const canSort = header.column.getCanSort();
1355
+ const sortDir = header.column.getIsSorted();
1356
+ const canFilter =
1357
+ header.column.getCanFilter() && meta?.filters?.length;
1358
+ const canResize =
1359
+ enableColumnResizing &&
1360
+ header.column.getCanResize() &&
1361
+ meta?.resizable !== false;
1362
+ const isFixedLeft = meta?.fixed === 'left';
1363
+ const isFixedRight = meta?.fixed === 'right';
1364
+ const isFixed = isFixedLeft || isFixedRight;
1365
+ const colId = header.column.id;
1366
+ const widthVal =
1367
+ columnSizing[colId] != null
1368
+ ? header.getSize()
1369
+ : meta?.size != null
1370
+ ? typeof meta.size === 'number'
1371
+ ? meta.size
1372
+ : meta.size
1373
+ : isFixed
1374
+ ? header.getSize()
1375
+ : undefined;
1376
+ const finalStyle: React.CSSProperties = {};
1377
+ if (widthVal != null) {
1378
+ finalStyle.width =
1379
+ typeof widthVal === 'number' ? `${widthVal}px` : widthVal;
1380
+ }
1381
+ if (header.colSpan > 1) {
1382
+ // 多级表头:父 group 跨列
1383
+ }
1384
+ if (isFixedLeft) {
1385
+ finalStyle.position = 'sticky';
1386
+ finalStyle.left = `${stickyOffsets.left.get(colId) ?? 0}px`;
1387
+ finalStyle.zIndex = 11;
1388
+ if (colId === lastLeftFixedId) {
1389
+ finalStyle.boxShadow =
1390
+ 'inset -6px 0 6px -6px var(--color-border)';
1391
+ }
1392
+ }
1393
+ if (isFixedRight) {
1394
+ finalStyle.position = 'sticky';
1395
+ finalStyle.right = `${stickyOffsets.right.get(colId) ?? 0}px`;
1396
+ finalStyle.zIndex = 11;
1397
+ if (colId === firstRightFixedId) {
1398
+ finalStyle.boxShadow =
1399
+ 'inset 6px 0 6px -6px var(--color-border)';
1400
+ }
1401
+ }
1402
+ const isColHover = crossline && hoveredColId === colId;
1403
+ return (
1404
+ <TableHead
1405
+ key={header.id}
1406
+ style={finalStyle}
1407
+ colSpan={header.colSpan > 1 ? header.colSpan : undefined}
1408
+ data-fixed={meta?.fixed}
1409
+ data-col-hover={isColHover ? '' : undefined}
1410
+ className={cn(
1411
+ alignClass(meta?.align),
1412
+ canSort && 'cursor-pointer select-none',
1413
+ canResize && 'relative',
1414
+ isFixed && 'bg-panel',
1415
+ isColHover && 'bg-muted/30',
1416
+ header.colSpan > 1 && 'text-center',
1417
+ )}
1418
+ onClick={
1419
+ canSort
1420
+ ? header.column.getToggleSortingHandler()
1421
+ : undefined
1422
+ }
1423
+ onMouseEnter={
1424
+ crossline ? () => setHoveredColId(colId) : undefined
1425
+ }
1426
+ onMouseLeave={
1427
+ crossline ? () => setHoveredColId(null) : undefined
1428
+ }
1429
+ >
1430
+ <span className="inline-flex items-center gap-1.5">
1431
+ {header.isPlaceholder
1432
+ ? null
1433
+ : flexRender(
1434
+ header.column.columnDef.header,
1435
+ header.getContext(),
1436
+ )}
1437
+ {canSort ? (
1438
+ <span className="inline-flex size-4 items-center justify-center text-muted-foreground">
1439
+ {sortDir === 'asc' ? (
1440
+ <ArrowUp className="size-3 text-foreground" />
1441
+ ) : sortDir === 'desc' ? (
1442
+ <ArrowDown className="size-3 text-foreground" />
1443
+ ) : (
1444
+ <ArrowUpDown className="size-3 opacity-60" />
1445
+ )}
1446
+ </span>
1447
+ ) : null}
1448
+ {canFilter ? (
1449
+ <ColumnFilterPopover
1450
+ options={meta!.filters!}
1451
+ mode={meta?.filterMode ?? 'multiple'}
1452
+ value={header.column.getFilterValue()}
1453
+ onChange={(v) => header.column.setFilterValue(v)}
1454
+ />
1455
+ ) : null}
1456
+ </span>
1457
+ {canResize ? (
1458
+ <span
1459
+ role="separator"
1460
+ aria-orientation="vertical"
1461
+ aria-label="拖拽调整列宽"
1462
+ onMouseDown={header.getResizeHandler()}
1463
+ onTouchStart={header.getResizeHandler()}
1464
+ onClick={(e) => e.stopPropagation()}
1465
+ className={cn(
1466
+ 'absolute right-0 top-0 z-10 h-full w-1 cursor-col-resize touch-none select-none bg-transparent transition-colors hover:bg-primary/40',
1467
+ header.column.getIsResizing() && 'bg-primary',
1468
+ )}
1469
+ />
1470
+ ) : null}
1471
+ </TableHead>
1472
+ );
1473
+ })}
1474
+ </TableRow>
1475
+ ))}
1476
+ </TableHeader>
1477
+ <TableBody>
1478
+ {virtual && paddingTop > 0 ? (
1479
+ <tr aria-hidden style={{ height: `${paddingTop}px` }}>
1480
+ <td colSpan={colCount} />
1481
+ </tr>
1482
+ ) : null}
1483
+ {visibleRows.map((row) => renderRow(row))}
1484
+ {virtual && paddingBottom > 0 ? (
1485
+ <tr aria-hidden style={{ height: `${paddingBottom}px` }}>
1486
+ <td colSpan={colCount} />
1487
+ </tr>
1488
+ ) : null}
1489
+ </TableBody>
1490
+ </Table>
1491
+ );
1492
+
1493
+ const tableContent = draggable ? (
1494
+ <DndContext
1495
+ sensors={sensors}
1496
+ collisionDetection={closestCenter}
1497
+ onDragEnd={handleDragEnd}
1498
+ >
1499
+ <SortableContext items={rowIds} strategy={verticalListSortingStrategy}>
1500
+ {tableEl}
1501
+ </SortableContext>
1502
+ </DndContext>
1503
+ ) : (
1504
+ tableEl
1505
+ );
1506
+
1507
+ return (
1508
+ <div
1509
+ ref={scrollWrapperRef}
1510
+ data-slot="data-table"
1511
+ className={cn('flex w-full flex-col gap-3', className)}
1512
+ {...rest}
1513
+ >
1514
+ {tableContent}
1515
+
1516
+ {paginationEnabled && totalRecords > 0 ? (
1517
+ <div className="flex items-center justify-end">
1518
+ <Pagination
1519
+ current={currentPage}
1520
+ total={totalRecords}
1521
+ pageSize={pageSize}
1522
+ pageSizeOptions={pagObj?.pageSizeOptions}
1523
+ showSizeChanger={pagObj?.showSizeChanger}
1524
+ showTotal={pagObj?.showTotal ?? true}
1525
+ onChange={(page, ps) => {
1526
+ if (pagObj?.current === undefined) setInnerPageIndex(page - 1);
1527
+ if (pagObj?.pageSize === undefined) setInnerPageSize(ps);
1528
+ pagObj?.onChange?.(page, ps);
1529
+ }}
1530
+ />
1531
+ </div>
1532
+ ) : null}
1533
+ </div>
1534
+ );
1535
+ }
1536
+
1537
+ export { DataTable, EditableCell };