@teamix-evo/ui 0.3.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 (520) hide show
  1. package/README.md +46 -50
  2. package/manifest.json +420 -647
  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 -462
  273. package/src/components/button/demo/as-child.tsx +0 -24
  274. package/src/components/button/demo/basic.tsx +0 -8
  275. package/src/components/button/demo/block.tsx +0 -16
  276. package/src/components/button/demo/loading.tsx +0 -19
  277. package/src/components/button/demo/shapes.tsx +0 -18
  278. package/src/components/button/demo/sizes.tsx +0 -19
  279. package/src/components/button/demo/variants.tsx +0 -19
  280. package/src/components/button/demo/with-icon.tsx +0 -20
  281. package/src/components/calendar/calendar.meta.md +0 -128
  282. package/src/components/calendar/calendar.stories.tsx +0 -68
  283. package/src/components/calendar/calendar.tsx +0 -172
  284. package/src/components/card/card.meta.md +0 -139
  285. package/src/components/card/card.stories.tsx +0 -151
  286. package/src/components/card/card.tsx +0 -305
  287. package/src/components/carousel/carousel.meta.md +0 -118
  288. package/src/components/carousel/carousel.stories.tsx +0 -89
  289. package/src/components/carousel/carousel.tsx +0 -224
  290. package/src/components/cascader/cascader.meta.md +0 -140
  291. package/src/components/cascader/cascader.stories.tsx +0 -120
  292. package/src/components/cascader/cascader.tsx +0 -541
  293. package/src/components/checkbox/checkbox.meta.md +0 -167
  294. package/src/components/checkbox/checkbox.stories.tsx +0 -288
  295. package/src/components/checkbox/checkbox.tsx +0 -193
  296. package/src/components/collapsible/collapsible.meta.md +0 -88
  297. package/src/components/collapsible/collapsible.stories.tsx +0 -43
  298. package/src/components/collapsible/collapsible.tsx +0 -105
  299. package/src/components/color-picker/color-picker.meta.md +0 -89
  300. package/src/components/color-picker/color-picker.stories.tsx +0 -159
  301. package/src/components/color-picker/color-picker.tsx +0 -171
  302. package/src/components/command/command.meta.md +0 -120
  303. package/src/components/command/command.stories.tsx +0 -59
  304. package/src/components/command/command.tsx +0 -158
  305. package/src/components/context-menu/context-menu.meta.md +0 -93
  306. package/src/components/context-menu/context-menu.stories.tsx +0 -54
  307. package/src/components/context-menu/context-menu.tsx +0 -204
  308. package/src/components/data-table/data-table.meta.md +0 -150
  309. package/src/components/data-table/data-table.stories.tsx +0 -132
  310. package/src/components/data-table/data-table.tsx +0 -185
  311. package/src/components/date-picker/date-picker.meta.md +0 -175
  312. package/src/components/date-picker/date-picker.stories.tsx +0 -108
  313. package/src/components/date-picker/date-picker.tsx +0 -1554
  314. package/src/components/descriptions/descriptions.meta.md +0 -83
  315. package/src/components/descriptions/descriptions.stories.tsx +0 -60
  316. package/src/components/descriptions/descriptions.tsx +0 -137
  317. package/src/components/dialog/dialog.meta.md +0 -168
  318. package/src/components/dialog/dialog.stories.tsx +0 -255
  319. package/src/components/dialog/dialog.tsx +0 -180
  320. package/src/components/dialog/imperative.tsx +0 -252
  321. package/src/components/drawer/drawer.meta.md +0 -95
  322. package/src/components/drawer/drawer.stories.tsx +0 -71
  323. package/src/components/drawer/drawer.tsx +0 -23
  324. package/src/components/dropdown-menu/dropdown-menu.meta.md +0 -171
  325. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -198
  326. package/src/components/dropdown-menu/dropdown-menu.tsx +0 -209
  327. package/src/components/ellipsis/ellipsis.meta.md +0 -87
  328. package/src/components/ellipsis/ellipsis.stories.tsx +0 -72
  329. package/src/components/ellipsis/ellipsis.tsx +0 -153
  330. package/src/components/empty/empty.meta.md +0 -86
  331. package/src/components/empty/empty.stories.tsx +0 -46
  332. package/src/components/empty/empty.tsx +0 -54
  333. package/src/components/field/field.meta.md +0 -154
  334. package/src/components/field/field.stories.tsx +0 -497
  335. package/src/components/field/field.tsx +0 -392
  336. package/src/components/filter-bar/filter-bar.meta.md +0 -92
  337. package/src/components/filter-bar/filter-bar.stories.tsx +0 -1083
  338. package/src/components/filter-bar/filter-bar.tsx +0 -568
  339. package/src/components/flex/flex.meta.md +0 -142
  340. package/src/components/flex/flex.stories.tsx +0 -199
  341. package/src/components/flex/flex.tsx +0 -145
  342. package/src/components/float-button/float-button.meta.md +0 -92
  343. package/src/components/float-button/float-button.stories.tsx +0 -80
  344. package/src/components/float-button/float-button.tsx +0 -143
  345. package/src/components/form/form.meta.md +0 -153
  346. package/src/components/form/form.stories.tsx +0 -469
  347. package/src/components/form/form.tsx +0 -260
  348. package/src/components/grid/grid.meta.md +0 -92
  349. package/src/components/grid/grid.stories.tsx +0 -101
  350. package/src/components/grid/grid.tsx +0 -130
  351. package/src/components/hover-card/hover-card.meta.md +0 -103
  352. package/src/components/hover-card/hover-card.stories.tsx +0 -97
  353. package/src/components/hover-card/hover-card.tsx +0 -67
  354. package/src/components/icon/DEVELOPMENT.md +0 -809
  355. package/src/components/icon/icon.meta.md +0 -170
  356. package/src/components/icon/icon.stories.tsx +0 -344
  357. package/src/components/icon/icon.tsx +0 -248
  358. package/src/components/image/image.meta.md +0 -99
  359. package/src/components/image/image.stories.tsx +0 -55
  360. package/src/components/image/image.tsx +0 -140
  361. package/src/components/input/demo/basic.tsx +0 -12
  362. package/src/components/input/demo/clearable.tsx +0 -21
  363. package/src/components/input/demo/show-count.tsx +0 -18
  364. package/src/components/input/demo/sizes.tsx +0 -15
  365. package/src/components/input/input.meta.md +0 -115
  366. package/src/components/input/input.stories.tsx +0 -144
  367. package/src/components/input/input.tsx +0 -212
  368. package/src/components/input-group/input-group.meta.md +0 -124
  369. package/src/components/input-group/input-group.stories.tsx +0 -121
  370. package/src/components/input-group/input-group.tsx +0 -143
  371. package/src/components/input-number/input-number.meta.md +0 -148
  372. package/src/components/input-number/input-number.stories.tsx +0 -125
  373. package/src/components/input-number/input-number.tsx +0 -283
  374. package/src/components/input-otp/input-otp.meta.md +0 -106
  375. package/src/components/input-otp/input-otp.stories.tsx +0 -65
  376. package/src/components/input-otp/input-otp.tsx +0 -97
  377. package/src/components/item/item.meta.md +0 -121
  378. package/src/components/item/item.stories.tsx +0 -116
  379. package/src/components/item/item.tsx +0 -172
  380. package/src/components/kbd/kbd.meta.md +0 -94
  381. package/src/components/kbd/kbd.stories.tsx +0 -70
  382. package/src/components/kbd/kbd.tsx +0 -86
  383. package/src/components/label/label.meta.md +0 -99
  384. package/src/components/label/label.stories.tsx +0 -145
  385. package/src/components/label/label.tsx +0 -138
  386. package/src/components/masonry/masonry.meta.md +0 -90
  387. package/src/components/masonry/masonry.stories.tsx +0 -68
  388. package/src/components/masonry/masonry.tsx +0 -60
  389. package/src/components/mentions/mentions.meta.md +0 -119
  390. package/src/components/mentions/mentions.stories.tsx +0 -189
  391. package/src/components/mentions/mentions.tsx +0 -243
  392. package/src/components/menubar/menubar.meta.md +0 -118
  393. package/src/components/menubar/menubar.stories.tsx +0 -141
  394. package/src/components/menubar/menubar.tsx +0 -232
  395. package/src/components/native-select/native-select.meta.md +0 -93
  396. package/src/components/native-select/native-select.stories.tsx +0 -83
  397. package/src/components/native-select/native-select.tsx +0 -54
  398. package/src/components/navigation-menu/navigation-menu.meta.md +0 -118
  399. package/src/components/navigation-menu/navigation-menu.stories.tsx +0 -215
  400. package/src/components/navigation-menu/navigation-menu.tsx +0 -129
  401. package/src/components/notification/notification.meta.md +0 -133
  402. package/src/components/notification/notification.stories.tsx +0 -98
  403. package/src/components/notification/notification.tsx +0 -99
  404. package/src/components/page-header/DEVELOPMENT.md +0 -842
  405. package/src/components/page-header/page-header.meta.md +0 -208
  406. package/src/components/page-header/page-header.stories.tsx +0 -421
  407. package/src/components/page-header/page-header.tsx +0 -281
  408. package/src/components/pagination/pagination.meta.md +0 -230
  409. package/src/components/pagination/pagination.stories.tsx +0 -284
  410. package/src/components/pagination/pagination.tsx +0 -577
  411. package/src/components/popconfirm/popconfirm.meta.md +0 -114
  412. package/src/components/popconfirm/popconfirm.stories.tsx +0 -75
  413. package/src/components/popconfirm/popconfirm.tsx +0 -134
  414. package/src/components/popover/popover.meta.md +0 -154
  415. package/src/components/popover/popover.stories.tsx +0 -158
  416. package/src/components/popover/popover.tsx +0 -104
  417. package/src/components/progress/progress.meta.md +0 -118
  418. package/src/components/progress/progress.stories.tsx +0 -75
  419. package/src/components/progress/progress.tsx +0 -203
  420. package/src/components/radio-group/radio-group.meta.md +0 -175
  421. package/src/components/radio-group/radio-group.stories.tsx +0 -113
  422. package/src/components/radio-group/radio-group.tsx +0 -209
  423. package/src/components/rate/rate.meta.md +0 -118
  424. package/src/components/rate/rate.stories.tsx +0 -89
  425. package/src/components/rate/rate.tsx +0 -180
  426. package/src/components/resizable/resizable.meta.md +0 -95
  427. package/src/components/resizable/resizable.stories.tsx +0 -104
  428. package/src/components/resizable/resizable.tsx +0 -56
  429. package/src/components/result/result.meta.md +0 -95
  430. package/src/components/result/result.stories.tsx +0 -67
  431. package/src/components/result/result.tsx +0 -100
  432. package/src/components/scroll-area/scroll-area.meta.md +0 -85
  433. package/src/components/scroll-area/scroll-area.stories.tsx +0 -49
  434. package/src/components/scroll-area/scroll-area.tsx +0 -51
  435. package/src/components/segmented/segmented.meta.md +0 -106
  436. package/src/components/segmented/segmented.stories.tsx +0 -130
  437. package/src/components/segmented/segmented.tsx +0 -146
  438. package/src/components/select/select.meta.md +0 -255
  439. package/src/components/select/select.stories.tsx +0 -275
  440. package/src/components/select/select.tsx +0 -735
  441. package/src/components/separator/separator.meta.md +0 -75
  442. package/src/components/separator/separator.stories.tsx +0 -71
  443. package/src/components/separator/separator.tsx +0 -100
  444. package/src/components/sheet/sheet.meta.md +0 -113
  445. package/src/components/sheet/sheet.stories.tsx +0 -188
  446. package/src/components/sheet/sheet.tsx +0 -226
  447. package/src/components/sidebar/sidebar.meta.md +0 -150
  448. package/src/components/sidebar/sidebar.tsx +0 -824
  449. package/src/components/skeleton/skeleton.meta.md +0 -94
  450. package/src/components/skeleton/skeleton.stories.tsx +0 -79
  451. package/src/components/skeleton/skeleton.tsx +0 -144
  452. package/src/components/slider/slider.meta.md +0 -146
  453. package/src/components/slider/slider.stories.tsx +0 -121
  454. package/src/components/slider/slider.tsx +0 -227
  455. package/src/components/sonner/sonner.meta.md +0 -147
  456. package/src/components/sonner/sonner.stories.tsx +0 -164
  457. package/src/components/sonner/sonner.tsx +0 -169
  458. package/src/components/spinner/spinner.meta.md +0 -125
  459. package/src/components/spinner/spinner.stories.tsx +0 -123
  460. package/src/components/spinner/spinner.tsx +0 -166
  461. package/src/components/statistic/statistic.meta.md +0 -104
  462. package/src/components/statistic/statistic.stories.tsx +0 -67
  463. package/src/components/steps/steps.meta.md +0 -116
  464. package/src/components/steps/steps.stories.tsx +0 -115
  465. package/src/components/steps/steps.tsx +0 -173
  466. package/src/components/switch/switch.meta.md +0 -138
  467. package/src/components/switch/switch.stories.tsx +0 -75
  468. package/src/components/switch/switch.tsx +0 -169
  469. package/src/components/table/table.meta.md +0 -106
  470. package/src/components/table/table.stories.tsx +0 -80
  471. package/src/components/table/table.tsx +0 -122
  472. package/src/components/tabs/tabs.meta.md +0 -111
  473. package/src/components/tabs/tabs.stories.tsx +0 -156
  474. package/src/components/tabs/tabs.tsx +0 -190
  475. package/src/components/tag/tag.meta.md +0 -159
  476. package/src/components/tag/tag.stories.tsx +0 -250
  477. package/src/components/tag/tag.tsx +0 -386
  478. package/src/components/textarea/textarea.meta.md +0 -99
  479. package/src/components/textarea/textarea.stories.tsx +0 -89
  480. package/src/components/textarea/textarea.tsx +0 -137
  481. package/src/components/time-picker/time-picker.meta.md +0 -175
  482. package/src/components/time-picker/time-picker.stories.tsx +0 -129
  483. package/src/components/time-picker/time-picker.tsx +0 -946
  484. package/src/components/timeline/timeline.meta.md +0 -110
  485. package/src/components/timeline/timeline.stories.tsx +0 -134
  486. package/src/components/timeline/timeline.tsx +0 -168
  487. package/src/components/toggle/toggle.meta.md +0 -89
  488. package/src/components/toggle/toggle.stories.tsx +0 -66
  489. package/src/components/toggle/toggle.tsx +0 -54
  490. package/src/components/toggle-group/toggle-group.meta.md +0 -91
  491. package/src/components/toggle-group/toggle-group.stories.tsx +0 -83
  492. package/src/components/toggle-group/toggle-group.tsx +0 -78
  493. package/src/components/tooltip/tooltip.meta.md +0 -149
  494. package/src/components/tooltip/tooltip.stories.tsx +0 -108
  495. package/src/components/tooltip/tooltip.tsx +0 -153
  496. package/src/components/tour/tour.meta.md +0 -121
  497. package/src/components/tour/tour.stories.tsx +0 -66
  498. package/src/components/tour/tour.tsx +0 -242
  499. package/src/components/transfer/transfer.meta.md +0 -95
  500. package/src/components/transfer/transfer.stories.tsx +0 -64
  501. package/src/components/transfer/transfer.tsx +0 -258
  502. package/src/components/tree/tree.meta.md +0 -169
  503. package/src/components/tree/tree.stories.tsx +0 -128
  504. package/src/components/tree/tree.tsx +0 -368
  505. package/src/components/tree-select/tree-select.meta.md +0 -151
  506. package/src/components/tree-select/tree-select.stories.tsx +0 -80
  507. package/src/components/tree-select/tree-select.tsx +0 -206
  508. package/src/components/typography/typography.meta.md +0 -149
  509. package/src/components/typography/typography.stories.tsx +0 -116
  510. package/src/components/typography/typography.tsx +0 -260
  511. package/src/components/upload/upload.meta.md +0 -156
  512. package/src/components/upload/upload.stories.tsx +0 -135
  513. package/src/components/upload/upload.tsx +0 -398
  514. package/src/components/watermark/watermark.meta.md +0 -100
  515. package/src/components/watermark/watermark.stories.tsx +0 -170
  516. package/src/components/watermark/watermark.tsx +0 -166
  517. package/src/hooks/use-breakpoint.ts +0 -117
  518. package/src/hooks/use-debounce-callback.ts +0 -52
  519. package/src/stories/theme-tokens.stories.tsx +0 -747
  520. package/src/utils/trigger-input.ts +0 -53
@@ -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 };