tekivex-ui 2.5.15 → 2.5.16

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 (394) hide show
  1. package/dist/charts.cjs +1 -1
  2. package/dist/charts.js +22 -553
  3. package/dist/chunk-B3ph66Zb.js +1 -0
  4. package/dist/chunk-Bc16rJT-.js +1 -0
  5. package/dist/chunk-BiHAhNbu.js +3 -0
  6. package/dist/chunk-CVRuGUFA.js +1 -0
  7. package/dist/chunk-Dc4_ud5s.js +1 -0
  8. package/dist/chunk-Dj40UFcz.js +1 -0
  9. package/dist/chunk-DjxOP8W4.js +1 -0
  10. package/dist/chunk-DmRB1Blb.js +1 -0
  11. package/dist/chunk-PJ50l0JC.js +1 -0
  12. package/dist/headless.cjs +1 -1
  13. package/dist/headless.js +1 -442
  14. package/dist/i18n.cjs +1 -1
  15. package/dist/i18n.js +1 -733
  16. package/dist/index.cjs +1 -152
  17. package/dist/index.d.ts +0 -1
  18. package/dist/index.js +419 -16786
  19. package/dist/quantum.cjs +1 -89
  20. package/dist/quantum.js +74 -2551
  21. package/dist/realtime.cjs +1 -23
  22. package/dist/realtime.js +1 -804
  23. package/dist/src/a11y/index.d.ts +0 -1
  24. package/dist/src/charts/TkxAreaChart.d.ts +0 -5
  25. package/dist/src/charts/TkxBarChart.d.ts +0 -3
  26. package/dist/src/charts/TkxDonutChart.d.ts +0 -5
  27. package/dist/src/charts/TkxLineChart.d.ts +0 -1
  28. package/dist/src/charts/TkxPieChart.d.ts +0 -3
  29. package/dist/src/charts/TkxRadarChart.d.ts +0 -3
  30. package/dist/src/charts/TkxScatterChart.d.ts +0 -2
  31. package/dist/src/charts/index.d.ts +0 -1
  32. package/dist/src/charts/shared.d.ts +0 -3
  33. package/dist/src/components/TkxAIChatBubble.d.ts +0 -9
  34. package/dist/src/components/TkxAIConfidenceBar.d.ts +0 -6
  35. package/dist/src/components/TkxAIThinking.d.ts +0 -6
  36. package/dist/src/components/TkxAccordion.d.ts +0 -1
  37. package/dist/src/components/TkxAffix.d.ts +0 -1
  38. package/dist/src/components/TkxAlert.d.ts +0 -1
  39. package/dist/src/components/TkxAnchor.d.ts +0 -1
  40. package/dist/src/components/TkxAppBar.d.ts +0 -1
  41. package/dist/src/components/TkxAutocomplete.d.ts +0 -1
  42. package/dist/src/components/TkxAvatar.d.ts +0 -1
  43. package/dist/src/components/TkxBadge.d.ts +0 -1
  44. package/dist/src/components/TkxBottomNav.d.ts +0 -1
  45. package/dist/src/components/TkxBreadcrumb.d.ts +0 -1
  46. package/dist/src/components/TkxButton.d.ts +0 -1
  47. package/dist/src/components/TkxCard.d.ts +0 -1
  48. package/dist/src/components/TkxCarousel.d.ts +0 -1
  49. package/dist/src/components/TkxCascader.d.ts +0 -1
  50. package/dist/src/components/TkxChat.d.ts +0 -1
  51. package/dist/src/components/TkxCheckbox.d.ts +0 -1
  52. package/dist/src/components/TkxClock.d.ts +0 -1
  53. package/dist/src/components/TkxColorPicker.d.ts +0 -1
  54. package/dist/src/components/TkxCommand.d.ts +0 -1
  55. package/dist/src/components/TkxConfigProvider.d.ts +0 -30
  56. package/dist/src/components/TkxDataGrid.d.ts +0 -15
  57. package/dist/src/components/TkxDatePicker.d.ts +0 -1
  58. package/dist/src/components/TkxDivider.d.ts +0 -1
  59. package/dist/src/components/TkxDrawer.d.ts +0 -1
  60. package/dist/src/components/TkxDropdown.d.ts +0 -1
  61. package/dist/src/components/TkxEmpty.d.ts +0 -1
  62. package/dist/src/components/TkxFileUpload.d.ts +0 -1
  63. package/dist/src/components/TkxForm.d.ts +0 -35
  64. package/dist/src/components/TkxIcon.d.ts +0 -1
  65. package/dist/src/components/TkxImage.d.ts +0 -1
  66. package/dist/src/components/TkxInput.d.ts +0 -1
  67. package/dist/src/components/TkxLayout.d.ts +0 -22
  68. package/dist/src/components/TkxList.d.ts +0 -1
  69. package/dist/src/components/TkxLiveFeed.d.ts +0 -1
  70. package/dist/src/components/TkxLiveLog.d.ts +0 -1
  71. package/dist/src/components/TkxLiveMetrics.d.ts +0 -1
  72. package/dist/src/components/TkxLogo.d.ts +0 -1
  73. package/dist/src/components/TkxMarkdown.d.ts +0 -10
  74. package/dist/src/components/TkxMasonry.d.ts +0 -1
  75. package/dist/src/components/TkxMentions.d.ts +0 -1
  76. package/dist/src/components/TkxMenu.d.ts +0 -1
  77. package/dist/src/components/TkxModal.d.ts +0 -1
  78. package/dist/src/components/TkxNumberInput.d.ts +0 -1
  79. package/dist/src/components/TkxOTP.d.ts +0 -1
  80. package/dist/src/components/TkxPagination.d.ts +0 -1
  81. package/dist/src/components/TkxPlayground.d.ts +0 -13
  82. package/dist/src/components/TkxPopover.d.ts +0 -1
  83. package/dist/src/components/TkxProgress.d.ts +0 -1
  84. package/dist/src/components/TkxQRCode.d.ts +0 -1
  85. package/dist/src/components/TkxQuantumForm.d.ts +0 -1
  86. package/dist/src/components/TkxRadio.d.ts +0 -1
  87. package/dist/src/components/TkxRating.d.ts +0 -1
  88. package/dist/src/components/TkxRealTimeChart.d.ts +0 -1
  89. package/dist/src/components/TkxResult.d.ts +0 -1
  90. package/dist/src/components/TkxRichTextDisplay.d.ts +0 -1
  91. package/dist/src/components/TkxSegmented.d.ts +0 -1
  92. package/dist/src/components/TkxSelect.d.ts +0 -3
  93. package/dist/src/components/TkxSkeleton.d.ts +0 -1
  94. package/dist/src/components/TkxSlider.d.ts +0 -1
  95. package/dist/src/components/TkxSnackbar.d.ts +0 -1
  96. package/dist/src/components/TkxSpeedDial.d.ts +0 -1
  97. package/dist/src/components/TkxSpin.d.ts +0 -1
  98. package/dist/src/components/TkxStatistic.d.ts +0 -1
  99. package/dist/src/components/TkxStepper.d.ts +0 -1
  100. package/dist/src/components/TkxTable.d.ts +0 -1
  101. package/dist/src/components/TkxTabs.d.ts +0 -1
  102. package/dist/src/components/TkxTag.d.ts +0 -1
  103. package/dist/src/components/TkxThemeBuilder.d.ts +0 -1
  104. package/dist/src/components/TkxTimeline.d.ts +0 -1
  105. package/dist/src/components/TkxToast.d.ts +0 -1
  106. package/dist/src/components/TkxToggle.d.ts +0 -1
  107. package/dist/src/components/TkxToolbar.d.ts +0 -1
  108. package/dist/src/components/TkxTooltip.d.ts +0 -1
  109. package/dist/src/components/TkxTour.d.ts +0 -1
  110. package/dist/src/components/TkxTransferList.d.ts +0 -1
  111. package/dist/src/components/TkxTreeView.d.ts +0 -1
  112. package/dist/src/components/TkxTypography.d.ts +0 -1
  113. package/dist/src/components/TkxVideoPlayer.d.ts +0 -1
  114. package/dist/src/components/TkxWatermark.d.ts +0 -1
  115. package/dist/src/components/index.d.ts +0 -1
  116. package/dist/src/engine/css.d.ts +0 -1
  117. package/dist/src/engine/index.d.ts +0 -1
  118. package/dist/src/engine/quantum-ai.d.ts +0 -73
  119. package/dist/src/engine/quantum.d.ts +0 -1
  120. package/dist/src/engine/security.d.ts +0 -9
  121. package/dist/src/engine/tkx.d.ts +0 -53
  122. package/dist/src/engine/wcag.d.ts +0 -1
  123. package/dist/src/headless/index.d.ts +0 -1
  124. package/dist/src/headless/useControllable.d.ts +0 -15
  125. package/dist/src/headless/useDebounce.d.ts +0 -12
  126. package/dist/src/headless/useDisclosure.d.ts +0 -13
  127. package/dist/src/headless/useFormState.d.ts +0 -13
  128. package/dist/src/headless/useInfiniteQuery.d.ts +0 -11
  129. package/dist/src/headless/useIntersectionObserver.d.ts +0 -10
  130. package/dist/src/headless/useListSelection.d.ts +0 -9
  131. package/dist/src/headless/useLocalStorage.d.ts +0 -8
  132. package/dist/src/headless/useMediaQuery.d.ts +0 -10
  133. package/dist/src/headless/useRovingTabIndex.d.ts +0 -15
  134. package/dist/src/headless/useSSE.d.ts +0 -9
  135. package/dist/src/headless/useThrottle.d.ts +0 -7
  136. package/dist/src/headless/useWebSocket.d.ts +0 -9
  137. package/dist/src/hooks/index.d.ts +0 -1
  138. package/dist/src/i18n/I18nProvider.d.ts +0 -1
  139. package/dist/src/i18n/index.d.ts +0 -1
  140. package/dist/src/quantum/index.d.ts +0 -1
  141. package/dist/src/realtime/index.d.ts +0 -1
  142. package/dist/src/themes/index.d.ts +0 -1
  143. package/package.json +5 -3
  144. package/dist/TkxForm-BaO4wvXt.js +0 -320
  145. package/dist/TkxForm-M6ttomry.cjs +0 -12
  146. package/dist/chunk-Bmb41Sf3.cjs +0 -1
  147. package/dist/hooks-6pRsPOyG.js +0 -58
  148. package/dist/hooks-mivgRljH.cjs +0 -1
  149. package/dist/index.d.ts.map +0 -1
  150. package/dist/src/a11y/index.d.ts.map +0 -1
  151. package/dist/src/charts/TkxAreaChart.d.ts.map +0 -1
  152. package/dist/src/charts/TkxBarChart.d.ts.map +0 -1
  153. package/dist/src/charts/TkxDonutChart.d.ts.map +0 -1
  154. package/dist/src/charts/TkxLineChart.d.ts.map +0 -1
  155. package/dist/src/charts/TkxPieChart.d.ts.map +0 -1
  156. package/dist/src/charts/TkxRadarChart.d.ts.map +0 -1
  157. package/dist/src/charts/TkxScatterChart.d.ts.map +0 -1
  158. package/dist/src/charts/index.d.ts.map +0 -1
  159. package/dist/src/charts/shared.d.ts.map +0 -1
  160. package/dist/src/components/TkxAIChatBubble.d.ts.map +0 -1
  161. package/dist/src/components/TkxAIConfidenceBar.d.ts.map +0 -1
  162. package/dist/src/components/TkxAIThinking.d.ts.map +0 -1
  163. package/dist/src/components/TkxAccordion.d.ts.map +0 -1
  164. package/dist/src/components/TkxAffix.d.ts.map +0 -1
  165. package/dist/src/components/TkxAlert.d.ts.map +0 -1
  166. package/dist/src/components/TkxAnchor.d.ts.map +0 -1
  167. package/dist/src/components/TkxAppBar.d.ts.map +0 -1
  168. package/dist/src/components/TkxAutocomplete.d.ts.map +0 -1
  169. package/dist/src/components/TkxAvatar.d.ts.map +0 -1
  170. package/dist/src/components/TkxBadge.d.ts.map +0 -1
  171. package/dist/src/components/TkxBottomNav.d.ts.map +0 -1
  172. package/dist/src/components/TkxBreadcrumb.d.ts.map +0 -1
  173. package/dist/src/components/TkxButton.d.ts.map +0 -1
  174. package/dist/src/components/TkxCard.d.ts.map +0 -1
  175. package/dist/src/components/TkxCarousel.d.ts.map +0 -1
  176. package/dist/src/components/TkxCascader.d.ts.map +0 -1
  177. package/dist/src/components/TkxChat.d.ts.map +0 -1
  178. package/dist/src/components/TkxCheckbox.d.ts.map +0 -1
  179. package/dist/src/components/TkxClock.d.ts.map +0 -1
  180. package/dist/src/components/TkxColorPicker.d.ts.map +0 -1
  181. package/dist/src/components/TkxCommand.d.ts.map +0 -1
  182. package/dist/src/components/TkxConfigProvider.d.ts.map +0 -1
  183. package/dist/src/components/TkxDataGrid.d.ts.map +0 -1
  184. package/dist/src/components/TkxDatePicker.d.ts.map +0 -1
  185. package/dist/src/components/TkxDivider.d.ts.map +0 -1
  186. package/dist/src/components/TkxDrawer.d.ts.map +0 -1
  187. package/dist/src/components/TkxDropdown.d.ts.map +0 -1
  188. package/dist/src/components/TkxEmpty.d.ts.map +0 -1
  189. package/dist/src/components/TkxFileUpload.d.ts.map +0 -1
  190. package/dist/src/components/TkxForm.d.ts.map +0 -1
  191. package/dist/src/components/TkxIcon.d.ts.map +0 -1
  192. package/dist/src/components/TkxImage.d.ts.map +0 -1
  193. package/dist/src/components/TkxInput.d.ts.map +0 -1
  194. package/dist/src/components/TkxLayout.d.ts.map +0 -1
  195. package/dist/src/components/TkxList.d.ts.map +0 -1
  196. package/dist/src/components/TkxLiveFeed.d.ts.map +0 -1
  197. package/dist/src/components/TkxLiveLog.d.ts.map +0 -1
  198. package/dist/src/components/TkxLiveMetrics.d.ts.map +0 -1
  199. package/dist/src/components/TkxLogo.d.ts.map +0 -1
  200. package/dist/src/components/TkxMarkdown.d.ts.map +0 -1
  201. package/dist/src/components/TkxMasonry.d.ts.map +0 -1
  202. package/dist/src/components/TkxMentions.d.ts.map +0 -1
  203. package/dist/src/components/TkxMenu.d.ts.map +0 -1
  204. package/dist/src/components/TkxModal.d.ts.map +0 -1
  205. package/dist/src/components/TkxNumberInput.d.ts.map +0 -1
  206. package/dist/src/components/TkxOTP.d.ts.map +0 -1
  207. package/dist/src/components/TkxPagination.d.ts.map +0 -1
  208. package/dist/src/components/TkxPlayground.d.ts.map +0 -1
  209. package/dist/src/components/TkxPopover.d.ts.map +0 -1
  210. package/dist/src/components/TkxProgress.d.ts.map +0 -1
  211. package/dist/src/components/TkxQRCode.d.ts.map +0 -1
  212. package/dist/src/components/TkxQuantumForm.d.ts.map +0 -1
  213. package/dist/src/components/TkxRadio.d.ts.map +0 -1
  214. package/dist/src/components/TkxRating.d.ts.map +0 -1
  215. package/dist/src/components/TkxRealTimeChart.d.ts.map +0 -1
  216. package/dist/src/components/TkxResult.d.ts.map +0 -1
  217. package/dist/src/components/TkxRichTextDisplay.d.ts.map +0 -1
  218. package/dist/src/components/TkxSegmented.d.ts.map +0 -1
  219. package/dist/src/components/TkxSelect.d.ts.map +0 -1
  220. package/dist/src/components/TkxSkeleton.d.ts.map +0 -1
  221. package/dist/src/components/TkxSlider.d.ts.map +0 -1
  222. package/dist/src/components/TkxSnackbar.d.ts.map +0 -1
  223. package/dist/src/components/TkxSpeedDial.d.ts.map +0 -1
  224. package/dist/src/components/TkxSpin.d.ts.map +0 -1
  225. package/dist/src/components/TkxStatistic.d.ts.map +0 -1
  226. package/dist/src/components/TkxStepper.d.ts.map +0 -1
  227. package/dist/src/components/TkxTable.d.ts.map +0 -1
  228. package/dist/src/components/TkxTabs.d.ts.map +0 -1
  229. package/dist/src/components/TkxTag.d.ts.map +0 -1
  230. package/dist/src/components/TkxThemeBuilder.d.ts.map +0 -1
  231. package/dist/src/components/TkxTimeline.d.ts.map +0 -1
  232. package/dist/src/components/TkxToast.d.ts.map +0 -1
  233. package/dist/src/components/TkxToggle.d.ts.map +0 -1
  234. package/dist/src/components/TkxToolbar.d.ts.map +0 -1
  235. package/dist/src/components/TkxTooltip.d.ts.map +0 -1
  236. package/dist/src/components/TkxTour.d.ts.map +0 -1
  237. package/dist/src/components/TkxTransferList.d.ts.map +0 -1
  238. package/dist/src/components/TkxTreeView.d.ts.map +0 -1
  239. package/dist/src/components/TkxTypography.d.ts.map +0 -1
  240. package/dist/src/components/TkxVideoPlayer.d.ts.map +0 -1
  241. package/dist/src/components/TkxWatermark.d.ts.map +0 -1
  242. package/dist/src/components/index.d.ts.map +0 -1
  243. package/dist/src/engine/css.d.ts.map +0 -1
  244. package/dist/src/engine/index.d.ts.map +0 -1
  245. package/dist/src/engine/quantum-ai.d.ts.map +0 -1
  246. package/dist/src/engine/quantum.d.ts.map +0 -1
  247. package/dist/src/engine/security.d.ts.map +0 -1
  248. package/dist/src/engine/tkx.d.ts.map +0 -1
  249. package/dist/src/engine/wcag.d.ts.map +0 -1
  250. package/dist/src/headless/index.d.ts.map +0 -1
  251. package/dist/src/headless/useControllable.d.ts.map +0 -1
  252. package/dist/src/headless/useDebounce.d.ts.map +0 -1
  253. package/dist/src/headless/useDisclosure.d.ts.map +0 -1
  254. package/dist/src/headless/useFormState.d.ts.map +0 -1
  255. package/dist/src/headless/useInfiniteQuery.d.ts.map +0 -1
  256. package/dist/src/headless/useIntersectionObserver.d.ts.map +0 -1
  257. package/dist/src/headless/useListSelection.d.ts.map +0 -1
  258. package/dist/src/headless/useLocalStorage.d.ts.map +0 -1
  259. package/dist/src/headless/useMediaQuery.d.ts.map +0 -1
  260. package/dist/src/headless/useRovingTabIndex.d.ts.map +0 -1
  261. package/dist/src/headless/useSSE.d.ts.map +0 -1
  262. package/dist/src/headless/useThrottle.d.ts.map +0 -1
  263. package/dist/src/headless/useWebSocket.d.ts.map +0 -1
  264. package/dist/src/hooks/index.d.ts.map +0 -1
  265. package/dist/src/i18n/I18nProvider.d.ts.map +0 -1
  266. package/dist/src/i18n/index.d.ts.map +0 -1
  267. package/dist/src/quantum/index.d.ts.map +0 -1
  268. package/dist/src/realtime/index.d.ts.map +0 -1
  269. package/dist/src/themes/index.d.ts.map +0 -1
  270. package/dist/themes-BbJanqiC.js +0 -498
  271. package/dist/themes-CMq2vWXj.cjs +0 -2
  272. package/dist/tkx-BoP5V5zy.cjs +0 -4
  273. package/dist/tkx-mOKMzvUK.js +0 -1174
  274. package/src/a11y/index.tsx +0 -87
  275. package/src/charts/TkxAreaChart.tsx +0 -121
  276. package/src/charts/TkxBarChart.tsx +0 -117
  277. package/src/charts/TkxDonutChart.tsx +0 -164
  278. package/src/charts/TkxLineChart.tsx +0 -137
  279. package/src/charts/TkxPieChart.tsx +0 -103
  280. package/src/charts/TkxRadarChart.tsx +0 -92
  281. package/src/charts/TkxScatterChart.tsx +0 -104
  282. package/src/charts/index.ts +0 -22
  283. package/src/charts/shared.ts +0 -47
  284. package/src/components/TkxAIChatBubble.tsx +0 -256
  285. package/src/components/TkxAIConfidenceBar.tsx +0 -133
  286. package/src/components/TkxAIThinking.tsx +0 -193
  287. package/src/components/TkxAccordion.tsx +0 -590
  288. package/src/components/TkxAffix.tsx +0 -140
  289. package/src/components/TkxAlert.tsx +0 -94
  290. package/src/components/TkxAnchor.tsx +0 -195
  291. package/src/components/TkxAppBar.tsx +0 -262
  292. package/src/components/TkxAutocomplete.tsx +0 -455
  293. package/src/components/TkxAvatar.tsx +0 -103
  294. package/src/components/TkxBadge.tsx +0 -75
  295. package/src/components/TkxBottomNav.tsx +0 -241
  296. package/src/components/TkxBreadcrumb.tsx +0 -324
  297. package/src/components/TkxButton.tsx +0 -155
  298. package/src/components/TkxCard.tsx +0 -96
  299. package/src/components/TkxCarousel.tsx +0 -554
  300. package/src/components/TkxCascader.tsx +0 -278
  301. package/src/components/TkxChat.tsx +0 -356
  302. package/src/components/TkxCheckbox.tsx +0 -220
  303. package/src/components/TkxClock.tsx +0 -603
  304. package/src/components/TkxColorPicker.tsx +0 -635
  305. package/src/components/TkxCommand.tsx +0 -646
  306. package/src/components/TkxConfigProvider.tsx +0 -460
  307. package/src/components/TkxDataGrid.tsx +0 -941
  308. package/src/components/TkxDatePicker.tsx +0 -1625
  309. package/src/components/TkxDivider.tsx +0 -51
  310. package/src/components/TkxDrawer.tsx +0 -236
  311. package/src/components/TkxDropdown.tsx +0 -804
  312. package/src/components/TkxEmpty.tsx +0 -221
  313. package/src/components/TkxFileUpload.tsx +0 -373
  314. package/src/components/TkxForm.tsx +0 -683
  315. package/src/components/TkxIcon.tsx +0 -685
  316. package/src/components/TkxImage.tsx +0 -279
  317. package/src/components/TkxInput.tsx +0 -103
  318. package/src/components/TkxLayout.tsx +0 -649
  319. package/src/components/TkxList.tsx +0 -244
  320. package/src/components/TkxLiveFeed.tsx +0 -331
  321. package/src/components/TkxLiveLog.tsx +0 -472
  322. package/src/components/TkxLiveMetrics.tsx +0 -315
  323. package/src/components/TkxLogo.tsx +0 -299
  324. package/src/components/TkxMarkdown.tsx +0 -855
  325. package/src/components/TkxMasonry.tsx +0 -178
  326. package/src/components/TkxMentions.tsx +0 -212
  327. package/src/components/TkxMenu.tsx +0 -1044
  328. package/src/components/TkxModal.tsx +0 -122
  329. package/src/components/TkxNumberInput.tsx +0 -410
  330. package/src/components/TkxOTP.tsx +0 -297
  331. package/src/components/TkxPagination.tsx +0 -390
  332. package/src/components/TkxPlayground.tsx +0 -845
  333. package/src/components/TkxPopover.tsx +0 -297
  334. package/src/components/TkxProgress.tsx +0 -114
  335. package/src/components/TkxQRCode.tsx +0 -176
  336. package/src/components/TkxQuantumForm.tsx +0 -594
  337. package/src/components/TkxRadio.tsx +0 -286
  338. package/src/components/TkxRating.tsx +0 -288
  339. package/src/components/TkxRealTimeChart.tsx +0 -383
  340. package/src/components/TkxResult.tsx +0 -130
  341. package/src/components/TkxRichTextDisplay.tsx +0 -396
  342. package/src/components/TkxSegmented.tsx +0 -171
  343. package/src/components/TkxSelect.tsx +0 -1130
  344. package/src/components/TkxSkeleton.tsx +0 -107
  345. package/src/components/TkxSlider.tsx +0 -495
  346. package/src/components/TkxSnackbar.tsx +0 -316
  347. package/src/components/TkxSpeedDial.tsx +0 -397
  348. package/src/components/TkxSpin.tsx +0 -263
  349. package/src/components/TkxStatistic.tsx +0 -263
  350. package/src/components/TkxStepper.tsx +0 -487
  351. package/src/components/TkxTable.tsx +0 -424
  352. package/src/components/TkxTabs.tsx +0 -126
  353. package/src/components/TkxTag.tsx +0 -321
  354. package/src/components/TkxThemeBuilder.tsx +0 -687
  355. package/src/components/TkxTimeline.tsx +0 -305
  356. package/src/components/TkxToast.tsx +0 -287
  357. package/src/components/TkxToggle.tsx +0 -95
  358. package/src/components/TkxToolbar.tsx +0 -262
  359. package/src/components/TkxTooltip.tsx +0 -85
  360. package/src/components/TkxTour.tsx +0 -241
  361. package/src/components/TkxTransferList.tsx +0 -489
  362. package/src/components/TkxTreeView.tsx +0 -423
  363. package/src/components/TkxTypography.tsx +0 -267
  364. package/src/components/TkxVideoPlayer.tsx +0 -1015
  365. package/src/components/TkxWatermark.tsx +0 -145
  366. package/src/components/index.ts +0 -80
  367. package/src/engine/css.ts +0 -126
  368. package/src/engine/index.ts +0 -12
  369. package/src/engine/quantum-ai.ts +0 -1045
  370. package/src/engine/quantum.ts +0 -207
  371. package/src/engine/security.ts +0 -232
  372. package/src/engine/tkx.ts +0 -1084
  373. package/src/engine/wcag.ts +0 -276
  374. package/src/headless/index.ts +0 -52
  375. package/src/headless/useControllable.ts +0 -42
  376. package/src/headless/useDebounce.ts +0 -37
  377. package/src/headless/useDisclosure.ts +0 -30
  378. package/src/headless/useFormState.ts +0 -113
  379. package/src/headless/useInfiniteQuery.ts +0 -155
  380. package/src/headless/useIntersectionObserver.ts +0 -53
  381. package/src/headless/useListSelection.ts +0 -103
  382. package/src/headless/useLocalStorage.ts +0 -48
  383. package/src/headless/useMediaQuery.ts +0 -39
  384. package/src/headless/useRovingTabIndex.ts +0 -86
  385. package/src/headless/useSSE.ts +0 -167
  386. package/src/headless/useThrottle.ts +0 -48
  387. package/src/headless/useWebSocket.ts +0 -231
  388. package/src/hooks/index.ts +0 -122
  389. package/src/i18n/I18nProvider.tsx +0 -36
  390. package/src/i18n/index.ts +0 -877
  391. package/src/quantum/index.ts +0 -39
  392. package/src/realtime/index.ts +0 -21
  393. package/src/styles/global.css +0 -167
  394. package/src/themes/index.ts +0 -294
@@ -1,941 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- useState,
5
- useCallback,
6
- useRef,
7
- useMemo,
8
- useId,
9
- useEffect,
10
- type ReactNode,
11
- type CSSProperties,
12
- type MouseEvent as ReactMouseEvent,
13
- type ChangeEvent,
14
- } from 'react';
15
- import { useTheme } from '../themes';
16
- import { sanitizeString } from '../engine/security';
17
- import { useReducedMotion } from '../hooks';
18
- import { TkxSkeleton } from './TkxSkeleton';
19
- import { tkx } from '../engine/tkx';
20
-
21
- // ── Types ─────────────────────────────────────────────────────────────────────
22
-
23
- export interface DataGridColumn<T = any> {
24
- key: string;
25
- header: string;
26
- width?: number | string;
27
- sortable?: boolean;
28
- filterable?: boolean;
29
- resizable?: boolean;
30
- renderCell?: (value: any, row: T) => ReactNode;
31
- renderHeader?: (col: DataGridColumn<T>) => ReactNode;
32
- align?: 'left' | 'center' | 'right';
33
- pinned?: 'left' | 'right';
34
- }
35
-
36
- export interface TkxDataGridProps<T = any> {
37
- columns: DataGridColumn<T>[];
38
- data: T[];
39
- rowKey: string | ((row: T) => string);
40
- selectable?: boolean;
41
- selectedRows?: string[];
42
- onSelectionChange?: (ids: string[]) => void;
43
- /** Enable column sorting. When true, clicks sort data client-side unless onSort is also provided. */
44
- sortable?: boolean;
45
- /** External sort handler. When provided, raw data is NOT sorted internally — you must pass pre-sorted data. */
46
- onSort?: (key: string, direction: 'asc' | 'desc') => void;
47
- loading?: boolean;
48
- emptyMessage?: string;
49
- stickyHeader?: boolean;
50
- striped?: boolean;
51
- bordered?: boolean;
52
- compact?: boolean;
53
- maxHeight?: number | string;
54
- onRowClick?: (row: T) => void;
55
- /** Enable virtual scrolling. Defaults to auto (enabled when data ≥ 50 rows and maxHeight is set). */
56
- virtualScroll?: boolean;
57
- /** Row height in pixels for virtual scrolling calculations. Default: 40 */
58
- rowHeight?: number;
59
- /** Show column filter inputs below the header row. */
60
- showFilters?: boolean;
61
- /** Controlled filter values keyed by column key. */
62
- filterValues?: Record<string, string>;
63
- /** Called when a column filter changes. */
64
- onFilterChange?: (key: string, value: string) => void;
65
- /** Rows per page for built-in pagination. Set to 0 to disable. Default: 0 (disabled). */
66
- pageSize?: number;
67
- /** Show a CSV export button in the toolbar. */
68
- showExport?: boolean;
69
- /** File name for CSV export (without .csv extension). Default: "export". */
70
- exportFileName?: string;
71
- /** Called when the user scrolls near the bottom. Use to fetch more data. */
72
- onLoadMore?: () => void | Promise<void>;
73
- /** Whether more data is available to load. When false, sentinel is hidden. */
74
- hasMore?: boolean;
75
- /** Show a loading skeleton at the bottom while fetching more data. */
76
- loadingMore?: boolean;
77
- /** How many pixels from the bottom to trigger onLoadMore. Default: 200 */
78
- loadMoreThreshold?: number;
79
- }
80
-
81
- // ── Sort icon ─────────────────────────────────────────────────────────────────
82
-
83
- function SortIcon({ direction }: { direction: 'asc' | 'desc' | null }) {
84
- return (
85
- <svg
86
- width="12"
87
- height="12"
88
- viewBox="0 0 24 24"
89
- fill="currentColor"
90
- aria-hidden="true"
91
- className={tkx('shrink-0 ml-1')}
92
- >
93
- {direction === 'asc' ? (
94
- <path d="M7 14l5-5 5 5H7z" />
95
- ) : direction === 'desc' ? (
96
- <path d="M7 10l5 5 5-5H7z" />
97
- ) : (
98
- <path d="M7 10l5 5 5-5H7zM7 14l5-5 5 5H7z" opacity="0.35" />
99
- )}
100
- </svg>
101
- );
102
- }
103
-
104
- // ── Checkbox icon ─────────────────────────────────────────────────────────────
105
-
106
- function CheckboxIcon({ checked, indeterminate }: { checked: boolean; indeterminate?: boolean }) {
107
- const theme = useTheme();
108
- return (
109
- <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
110
- <rect
111
- x="1" y="1" width="14" height="14" rx="2"
112
- fill={checked || indeterminate ? theme.primary : 'transparent'}
113
- stroke={checked || indeterminate ? theme.primary : theme.border}
114
- strokeWidth="1.5"
115
- />
116
- {indeterminate && (
117
- <line x1="4" y1="8" x2="12" y2="8" stroke={theme.bg} strokeWidth="2" />
118
- )}
119
- {checked && !indeterminate && (
120
- <path d="M4.5 8L7 10.5L11.5 5.5" fill="none" stroke={theme.bg} strokeWidth="1.8"
121
- strokeLinecap="round" strokeLinejoin="round" />
122
- )}
123
- </svg>
124
- );
125
- }
126
-
127
- // ── Helpers ──────────────────────────────────────────────────────────────────
128
-
129
- function getRowId<T>(row: T, rowKey: string | ((row: T) => string)): string {
130
- if (typeof rowKey === 'function') return rowKey(row);
131
- return String((row as Record<string, unknown>)[rowKey]);
132
- }
133
-
134
- function getCellValue<T>(row: T, key: string): unknown {
135
- return (row as Record<string, unknown>)[key];
136
- }
137
-
138
- function compareValues(a: unknown, b: unknown): number {
139
- if (a == null && b == null) return 0;
140
- if (a == null) return 1;
141
- if (b == null) return -1;
142
- if (typeof a === 'number' && typeof b === 'number') return a - b;
143
- return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
144
- }
145
-
146
- function escapeCSV(value: unknown): string {
147
- const str = String(value ?? '');
148
- if (str.includes(',') || str.includes('"') || str.includes('\n')) {
149
- return `"${str.replace(/"/g, '""')}"`;
150
- }
151
- return str;
152
- }
153
-
154
- // ── Loading skeleton ────────────────────────────────────────────────────────
155
-
156
- function LoadingOverlay({ colCount }: { colCount: number }) {
157
- const theme = useTheme();
158
- return (
159
- <tbody>
160
- {Array.from({ length: 5 }, (_, r) => (
161
- <tr key={r}>
162
- {Array.from({ length: colCount }, (_, c) => (
163
- <td key={c} className={tkx('px-3 py-2')}
164
- style={{ borderBottom: `1px solid ${theme.border}` }}>
165
- <TkxSkeleton height={16} />
166
- </td>
167
- ))}
168
- </tr>
169
- ))}
170
- </tbody>
171
- );
172
- }
173
-
174
- // ── Toolbar ──────────────────────────────────────────────────────────────────
175
-
176
- function GridToolbar({
177
- label,
178
- showExport,
179
- onExport,
180
- totalRows,
181
- selectedCount,
182
- theme,
183
- }: {
184
- label: string;
185
- showExport: boolean;
186
- onExport: () => void;
187
- totalRows: number;
188
- selectedCount: number;
189
- theme: ReturnType<typeof useTheme>;
190
- }) {
191
- if (!showExport && selectedCount === 0) return null;
192
- return (
193
- <div
194
- style={{
195
- display: 'flex',
196
- alignItems: 'center',
197
- justifyContent: 'space-between',
198
- padding: '8px 12px',
199
- borderBottom: `1px solid ${theme.border}`,
200
- backgroundColor: theme.surface,
201
- gap: 8,
202
- }}
203
- >
204
- <span style={{ fontSize: 13, color: theme.textMuted }}>
205
- {selectedCount > 0
206
- ? `${selectedCount} of ${totalRows} row${totalRows !== 1 ? 's' : ''} selected`
207
- : `${totalRows} row${totalRows !== 1 ? 's' : ''}`}
208
- </span>
209
- {showExport && (
210
- <button
211
- onClick={onExport}
212
- aria-label={`Export ${label} as CSV`}
213
- style={{
214
- display: 'inline-flex',
215
- alignItems: 'center',
216
- gap: 6,
217
- padding: '5px 12px',
218
- fontSize: 13,
219
- fontWeight: 500,
220
- borderRadius: 6,
221
- border: `1px solid ${theme.border}`,
222
- backgroundColor: 'transparent',
223
- color: theme.text,
224
- cursor: 'pointer',
225
- }}
226
- >
227
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
228
- strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
229
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
230
- <polyline points="7 10 12 15 17 10" />
231
- <line x1="12" y1="15" x2="12" y2="3" />
232
- </svg>
233
- Export CSV
234
- </button>
235
- )}
236
- </div>
237
- );
238
- }
239
-
240
- // ── Pagination Bar ────────────────────────────────────────────────────────────
241
-
242
- function PaginationBar({
243
- page,
244
- totalPages,
245
- pageSize,
246
- totalRows,
247
- onPageChange,
248
- theme,
249
- }: {
250
- page: number;
251
- totalPages: number;
252
- pageSize: number;
253
- totalRows: number;
254
- onPageChange: (p: number) => void;
255
- theme: ReturnType<typeof useTheme>;
256
- }) {
257
- const start = (page - 1) * pageSize + 1;
258
- const end = Math.min(page * pageSize, totalRows);
259
- const btnBase: CSSProperties = {
260
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
261
- width: 32, height: 32, borderRadius: 6, border: `1px solid ${theme.border}`,
262
- backgroundColor: 'transparent', color: theme.text, cursor: 'pointer',
263
- fontSize: 13, fontWeight: 500,
264
- };
265
- return (
266
- <div
267
- style={{
268
- display: 'flex', alignItems: 'center', justifyContent: 'space-between',
269
- padding: '8px 12px', borderTop: `1px solid ${theme.border}`,
270
- backgroundColor: theme.surface, gap: 8, flexWrap: 'wrap',
271
- }}
272
- role="navigation"
273
- aria-label="Pagination"
274
- >
275
- <span style={{ fontSize: 13, color: theme.textMuted }}>
276
- {totalRows === 0 ? '0 rows' : `${start}–${end} of ${totalRows}`}
277
- </span>
278
- <div style={{ display: 'flex', gap: 4 }}>
279
- <button
280
- onClick={() => onPageChange(1)}
281
- disabled={page === 1}
282
- aria-label="First page"
283
- style={{ ...btnBase, opacity: page === 1 ? 0.4 : 1 }}
284
- >
285
- «
286
- </button>
287
- <button
288
- onClick={() => onPageChange(page - 1)}
289
- disabled={page === 1}
290
- aria-label="Previous page"
291
- style={{ ...btnBase, opacity: page === 1 ? 0.4 : 1 }}
292
- >
293
-
294
- </button>
295
- {Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
296
- let p = i + 1;
297
- if (totalPages > 5) {
298
- const half = Math.floor(5 / 2);
299
- const start2 = Math.max(1, Math.min(page - half, totalPages - 4));
300
- p = start2 + i;
301
- }
302
- return (
303
- <button
304
- key={p}
305
- onClick={() => onPageChange(p)}
306
- aria-label={`Page ${p}`}
307
- aria-current={p === page ? 'page' : undefined}
308
- style={{
309
- ...btnBase,
310
- backgroundColor: p === page ? theme.primary : 'transparent',
311
- color: p === page ? theme.bg : theme.text,
312
- borderColor: p === page ? theme.primary : theme.border,
313
- }}
314
- >
315
- {p}
316
- </button>
317
- );
318
- })}
319
- <button
320
- onClick={() => onPageChange(page + 1)}
321
- disabled={page === totalPages}
322
- aria-label="Next page"
323
- style={{ ...btnBase, opacity: page === totalPages ? 0.4 : 1 }}
324
- >
325
-
326
- </button>
327
- <button
328
- onClick={() => onPageChange(totalPages)}
329
- disabled={page === totalPages}
330
- aria-label="Last page"
331
- style={{ ...btnBase, opacity: page === totalPages ? 0.4 : 1 }}
332
- >
333
- »
334
- </button>
335
- </div>
336
- </div>
337
- );
338
- }
339
-
340
- // ── Component ─────────────────────────────────────────────────────────────────
341
-
342
- export function TkxDataGrid<T = any>({
343
- columns,
344
- data,
345
- rowKey,
346
- selectable = false,
347
- selectedRows = [],
348
- onSelectionChange,
349
- sortable = false,
350
- onSort,
351
- loading = false,
352
- emptyMessage = 'No data to display',
353
- stickyHeader = false,
354
- striped = false,
355
- bordered = false,
356
- compact = false,
357
- maxHeight,
358
- onRowClick,
359
- virtualScroll,
360
- rowHeight = 40,
361
- showFilters = false,
362
- filterValues: externalFilterValues,
363
- onFilterChange,
364
- pageSize = 0,
365
- showExport = false,
366
- exportFileName = 'export',
367
- onLoadMore,
368
- hasMore,
369
- loadingMore = false,
370
- loadMoreThreshold = 200,
371
- }: TkxDataGridProps<T>) {
372
- const theme = useTheme();
373
- const reduced = useReducedMotion();
374
- const gridId = useId();
375
-
376
- // ── Internal sort state ─────────────────────────────────────────────────
377
- const [sortKey, setSortKey] = useState<string | null>(null);
378
- const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
379
-
380
- const handleSort = useCallback(
381
- (key: string) => {
382
- const nextDir: 'asc' | 'desc' = sortKey === key && sortDir === 'asc' ? 'desc' : 'asc';
383
- setSortKey(key);
384
- setSortDir(nextDir);
385
- onSort?.(key, nextDir);
386
- },
387
- [sortKey, sortDir, onSort],
388
- );
389
-
390
- // ── Client-side sort (when no external onSort handler) ──────────────────
391
- const sortedData = useMemo<T[]>(() => {
392
- // If consumer handles sorting externally, trust the data order
393
- if (onSort || !sortKey) return data;
394
- return [...data].sort((a, b) => {
395
- const aVal = getCellValue(a, sortKey);
396
- const bVal = getCellValue(b, sortKey);
397
- const cmp = compareValues(aVal, bVal);
398
- return sortDir === 'asc' ? cmp : -cmp;
399
- });
400
- }, [data, sortKey, sortDir, onSort]);
401
-
402
- // ── Internal filter state ───────────────────────────────────────────────
403
- const [internalFilters, setInternalFilters] = useState<Record<string, string>>({});
404
- const filters = externalFilterValues ?? internalFilters;
405
-
406
- const handleFilterChange = useCallback(
407
- (key: string, value: string) => {
408
- if (onFilterChange) {
409
- onFilterChange(key, value);
410
- } else {
411
- setInternalFilters(prev => ({ ...prev, [key]: value }));
412
- }
413
- },
414
- [onFilterChange],
415
- );
416
-
417
- // ── Client-side filter ──────────────────────────────────────────────────
418
- const filteredData = useMemo<T[]>(() => {
419
- const activeFilters = Object.entries(filters).filter(([, v]) => v.trim() !== '');
420
- if (activeFilters.length === 0) return sortedData;
421
- return sortedData.filter(row =>
422
- activeFilters.every(([key, filterVal]) => {
423
- const cell = getCellValue(row, key);
424
- return String(cell ?? '').toLowerCase().includes(filterVal.toLowerCase());
425
- }),
426
- );
427
- }, [sortedData, filters]);
428
-
429
- // ── Pagination ──────────────────────────────────────────────────────────
430
- const [page, setPage] = useState(1);
431
- const isPaginated = pageSize > 0;
432
- const totalRows = filteredData.length;
433
- const totalPages = isPaginated ? Math.max(1, Math.ceil(totalRows / pageSize)) : 1;
434
-
435
- // Reset to page 1 when filters/sort change
436
- useEffect(() => { setPage(1); }, [filters, sortKey, sortDir]);
437
-
438
- const pagedData = useMemo<T[]>(() => {
439
- if (!isPaginated) return filteredData;
440
- return filteredData.slice((page - 1) * pageSize, page * pageSize);
441
- }, [filteredData, isPaginated, page, pageSize]);
442
-
443
- // ── Virtual scroll ─────────────────────────────────────────────────────
444
- const OVERSCAN = 10;
445
- const isVirtual =
446
- virtualScroll !== undefined
447
- ? virtualScroll
448
- : !isPaginated && maxHeight !== undefined && pagedData.length >= 50;
449
-
450
- const scrollContainerRef = useRef<HTMLDivElement>(null);
451
- const [scrollTop, setScrollTop] = useState(0);
452
- const [containerHeight, setContainerHeight] = useState(0);
453
-
454
- const handleScroll = useCallback(() => {
455
- if (scrollContainerRef.current) setScrollTop(scrollContainerRef.current.scrollTop);
456
- }, []);
457
-
458
- useEffect(() => {
459
- if (!isVirtual || !scrollContainerRef.current) return;
460
- const el = scrollContainerRef.current;
461
- setContainerHeight(el.clientHeight);
462
- const ro = new ResizeObserver(() => setContainerHeight(el.clientHeight));
463
- ro.observe(el);
464
- return () => ro.disconnect();
465
- }, [isVirtual]);
466
-
467
- const totalHeight = pagedData.length * rowHeight;
468
- const startIndex = isVirtual
469
- ? Math.max(0, Math.floor(scrollTop / rowHeight) - OVERSCAN)
470
- : 0;
471
- const endIndex = isVirtual
472
- ? Math.min(pagedData.length, Math.ceil((scrollTop + containerHeight) / rowHeight) + OVERSCAN)
473
- : pagedData.length;
474
- const visibleData = isVirtual ? pagedData.slice(startIndex, endIndex) : pagedData;
475
- const offsetY = startIndex * rowHeight;
476
-
477
- // ── Infinite scroll ─────────────────────────────────────────────────────
478
- const sentinelRef = useRef<HTMLDivElement>(null);
479
- const loadingRef = useRef<boolean>(false);
480
-
481
- // Reset the debounce flag whenever data.length changes (new data arrived)
482
- useEffect(() => {
483
- loadingRef.current = false;
484
- }, [data.length]);
485
-
486
- useEffect(() => {
487
- if (!onLoadMore) return;
488
- const sentinel = sentinelRef.current;
489
- if (!sentinel) return;
490
-
491
- const observer = new IntersectionObserver(
492
- (entries) => {
493
- const entry = entries[0];
494
- if (entry.isIntersecting && !loadingRef.current && hasMore !== false) {
495
- loadingRef.current = true;
496
- onLoadMore();
497
- }
498
- },
499
- {
500
- root: scrollContainerRef.current,
501
- rootMargin: `0px 0px ${loadMoreThreshold}px 0px`,
502
- threshold: 0,
503
- },
504
- );
505
-
506
- observer.observe(sentinel);
507
- return () => observer.disconnect();
508
- // eslint-disable-next-line react-hooks/exhaustive-deps
509
- }, [onLoadMore, hasMore, loadMoreThreshold]);
510
-
511
- // ── Selection logic ─────────────────────────────────────────────────────
512
- const selectedSet = useMemo(() => new Set(selectedRows), [selectedRows]);
513
- const allIds = useMemo(() => filteredData.map(row => getRowId(row, rowKey)), [filteredData, rowKey]);
514
- const allSelected = allIds.length > 0 && allIds.every(id => selectedSet.has(id));
515
- const someSelected = allIds.some(id => selectedSet.has(id));
516
-
517
- const toggleAll = useCallback(() => {
518
- onSelectionChange?.(allSelected ? [] : allIds);
519
- }, [allSelected, allIds, onSelectionChange]);
520
-
521
- const toggleRow = useCallback((id: string) => {
522
- const next = selectedSet.has(id)
523
- ? selectedRows.filter(r => r !== id)
524
- : [...selectedRows, id];
525
- onSelectionChange?.(next);
526
- }, [selectedSet, selectedRows, onSelectionChange]);
527
-
528
- // ── Column resize ─────────────────────────────────────────────────────
529
- const [colWidths, setColWidths] = useState<Record<string, number>>({});
530
- const resizeRef = useRef<{ key: string; startX: number; startWidth: number } | null>(null);
531
-
532
- const handleResizeStart = useCallback(
533
- (key: string, e: ReactMouseEvent) => {
534
- e.preventDefault();
535
- e.stopPropagation();
536
- const th = (e.target as HTMLElement).closest('th');
537
- if (!th) return;
538
- const startWidth = colWidths[key] || th.getBoundingClientRect().width;
539
- resizeRef.current = { key, startX: e.clientX, startWidth };
540
-
541
- const handleMouseMove = (ev: globalThis.MouseEvent) => {
542
- if (!resizeRef.current) return;
543
- const newWidth = Math.max(50, resizeRef.current.startWidth + ev.clientX - resizeRef.current.startX);
544
- setColWidths(prev => ({ ...prev, [resizeRef.current!.key]: newWidth }));
545
- };
546
- const handleMouseUp = () => {
547
- resizeRef.current = null;
548
- document.removeEventListener('mousemove', handleMouseMove);
549
- document.removeEventListener('mouseup', handleMouseUp);
550
- };
551
- document.addEventListener('mousemove', handleMouseMove);
552
- document.addEventListener('mouseup', handleMouseUp);
553
- },
554
- [colWidths],
555
- );
556
-
557
- // ── CSV Export ──────────────────────────────────────────────────────────
558
- const handleExport = useCallback(() => {
559
- const header = columns.map(c => escapeCSV(c.header)).join(',');
560
- const rows = filteredData.map(row =>
561
- columns.map(col => {
562
- const raw = getCellValue(row, col.key);
563
- // Skip custom renderers for CSV — export raw values
564
- return escapeCSV(raw);
565
- }).join(','),
566
- );
567
- const csv = [header, ...rows].join('\n');
568
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
569
- const url = URL.createObjectURL(blob);
570
- const a = document.createElement('a');
571
- a.href = url;
572
- a.download = `${exportFileName}.csv`;
573
- a.click();
574
- URL.revokeObjectURL(url);
575
- }, [columns, filteredData, exportFileName]);
576
-
577
- // ── Cell sizing ─────────────────────────────────────────────────────────
578
- const py = compact ? '4px' : '8px';
579
- const px = compact ? '8px' : '12px';
580
-
581
- const getColStyle = (col: DataGridColumn<T>): CSSProperties => {
582
- const w = colWidths[col.key] || col.width;
583
- return {
584
- width: w != null ? (typeof w === 'number' ? w : w) : undefined,
585
- minWidth: w != null ? (typeof w === 'number' ? w : w) : 80,
586
- textAlign: col.align ?? 'left',
587
- padding: `${py} ${px}`,
588
- };
589
- };
590
-
591
- const borderRight = bordered ? `1px solid ${theme.border}` : 'none';
592
- const safeEmpty = sanitizeString(emptyMessage);
593
- const totalCols = columns.length + (selectable ? 1 : 0);
594
-
595
- const hasFilterableColumns = columns.some(c => c.filterable);
596
-
597
- return (
598
- <div
599
- role="grid"
600
- aria-label="Data grid"
601
- aria-rowcount={totalRows}
602
- id={gridId}
603
- className={tkx('font-sans rounded-lg overflow-hidden')}
604
- style={{ border: `1px solid ${theme.border}`, backgroundColor: theme.bg }}
605
- >
606
- {/* ── Toolbar ───────────────────────────────────────────────────── */}
607
- <GridToolbar
608
- label="data grid"
609
- showExport={showExport}
610
- onExport={handleExport}
611
- totalRows={totalRows}
612
- selectedCount={selectedRows.length}
613
- theme={theme}
614
- />
615
-
616
- <div
617
- ref={scrollContainerRef}
618
- onScroll={isVirtual ? handleScroll : undefined}
619
- style={{
620
- maxHeight: maxHeight ?? 'none',
621
- overflowX: 'auto',
622
- overflowY: maxHeight ? 'auto' : 'visible',
623
- }}
624
- >
625
- <table
626
- className={tkx('w-full')}
627
- style={{ borderCollapse: 'separate', borderSpacing: 0, tableLayout: 'auto' }}
628
- >
629
- {/* ── Header ─────────────────────────────────────────────── */}
630
- <thead>
631
- <tr role="row">
632
- {/* Select-all */}
633
- {selectable && (
634
- <th
635
- scope="col"
636
- role="columnheader"
637
- style={{
638
- position: stickyHeader ? 'sticky' : 'static',
639
- top: 0, zIndex: 2,
640
- backgroundColor: theme.surface,
641
- borderBottom: `2px solid ${theme.border}`,
642
- borderRight,
643
- padding: `${py} ${px}`,
644
- width: 40,
645
- textAlign: 'center',
646
- }}
647
- >
648
- <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', margin: '0 auto', width: 16 }}>
649
- <input
650
- type="checkbox"
651
- checked={allSelected}
652
- ref={el => { if (el) el.indeterminate = someSelected && !allSelected; }}
653
- onChange={toggleAll}
654
- aria-label={allSelected ? 'Deselect all rows' : 'Select all rows'}
655
- style={{ position: 'absolute', opacity: 0, width: 16, height: 16, cursor: 'pointer', margin: 0 }}
656
- />
657
- <CheckboxIcon checked={allSelected} indeterminate={someSelected && !allSelected} />
658
- </label>
659
- </th>
660
- )}
661
-
662
- {/* Column headers */}
663
- {columns.map(col => {
664
- const isSortable = (col.sortable ?? sortable) && col.sortable !== false;
665
- const isSorted = sortKey === col.key;
666
- const safeHeader = sanitizeString(col.header);
667
-
668
- return (
669
- <th
670
- key={col.key}
671
- scope="col"
672
- role="columnheader"
673
- aria-sort={isSorted ? (sortDir === 'asc' ? 'ascending' : 'descending') : (isSortable ? 'none' : undefined)}
674
- className={tkx('text-xs font-semibold uppercase tracking-wider')}
675
- style={{
676
- ...getColStyle(col),
677
- position: stickyHeader ? 'sticky' : 'static',
678
- top: 0, zIndex: 2,
679
- backgroundColor: theme.surface,
680
- color: theme.textMuted,
681
- borderBottom: `2px solid ${theme.border}`,
682
- borderRight,
683
- cursor: isSortable ? 'pointer' : 'default',
684
- userSelect: 'none',
685
- whiteSpace: 'nowrap',
686
- transition: reduced ? 'none' : 'background-color 150ms ease',
687
- }}
688
- onClick={isSortable ? () => handleSort(col.key) : undefined}
689
- onKeyDown={isSortable ? e => {
690
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleSort(col.key); }
691
- } : undefined}
692
- tabIndex={isSortable ? 0 : undefined}
693
- >
694
- <div className={tkx('flex items-center gap-1')} style={{ position: 'relative' }}>
695
- {col.renderHeader ? col.renderHeader(col) : safeHeader}
696
- {isSortable && <SortIcon direction={isSorted ? sortDir : null} />}
697
-
698
- {/* Resize handle */}
699
- {col.resizable && (
700
- <div
701
- role="separator"
702
- aria-orientation="vertical"
703
- aria-label={`Resize column ${safeHeader}`}
704
- className={tkx('cursor-col-resize shrink-0')}
705
- style={{
706
- position: 'absolute', right: 0, top: 0, bottom: 0,
707
- width: 4, backgroundColor: 'transparent',
708
- }}
709
- onMouseDown={e => handleResizeStart(col.key, e)}
710
- />
711
- )}
712
- </div>
713
- </th>
714
- );
715
- })}
716
- </tr>
717
-
718
- {/* ── Filter row ──────────────────────────────────────── */}
719
- {(showFilters || hasFilterableColumns) && (
720
- <tr role="row" aria-label="Column filters">
721
- {selectable && (
722
- <th
723
- scope="col"
724
- style={{
725
- backgroundColor: theme.surfaceAlt,
726
- borderBottom: `1px solid ${theme.border}`,
727
- borderRight,
728
- padding: `4px ${px}`,
729
- width: 40,
730
- }}
731
- />
732
- )}
733
- {columns.map(col => (
734
- <th
735
- key={col.key}
736
- scope="col"
737
- style={{
738
- backgroundColor: theme.surfaceAlt,
739
- borderBottom: `1px solid ${theme.border}`,
740
- borderRight,
741
- padding: `4px ${px}`,
742
- }}
743
- >
744
- {(col.filterable || showFilters) && (
745
- <input
746
- type="text"
747
- value={filters[col.key] ?? ''}
748
- onChange={(e: ChangeEvent<HTMLInputElement>) =>
749
- handleFilterChange(col.key, e.target.value)
750
- }
751
- placeholder={`Filter ${col.header}…`}
752
- aria-label={`Filter by ${col.header}`}
753
- style={{
754
- width: '100%',
755
- padding: '3px 8px',
756
- fontSize: 12,
757
- borderRadius: 4,
758
- border: `1px solid ${theme.border}`,
759
- backgroundColor: theme.bg,
760
- color: theme.text,
761
- outline: 'none',
762
- }}
763
- />
764
- )}
765
- </th>
766
- ))}
767
- </tr>
768
- )}
769
- </thead>
770
-
771
- {/* ── Loading ───────────────────────────────────────────── */}
772
- {loading && <LoadingOverlay colCount={totalCols} />}
773
-
774
- {/* ── Body ──────────────────────────────────────────────── */}
775
- {!loading && (
776
- <tbody>
777
- {pagedData.length === 0 ? (
778
- <tr>
779
- <td colSpan={totalCols} className={tkx('text-center py-10')}
780
- style={{ color: theme.textMuted }}>
781
- {safeEmpty}
782
- </td>
783
- </tr>
784
- ) : (
785
- <>
786
- {/* Top spacer for virtual scrolling */}
787
- {isVirtual && offsetY > 0 && (
788
- <tr aria-hidden="true">
789
- <td colSpan={totalCols} style={{ height: offsetY, padding: 0, border: 'none' }} />
790
- </tr>
791
- )}
792
-
793
- {visibleData.map((row, i) => {
794
- const rowIndex = startIndex + i;
795
- const id = getRowId(row, rowKey);
796
- const isSelected = selectedSet.has(id);
797
- const isStriped = striped && rowIndex % 2 === 1;
798
-
799
- let rowBg = 'transparent';
800
- if (isSelected) rowBg = `${theme.primary}22`;
801
- else if (isStriped) rowBg = theme.surfaceAlt;
802
-
803
- return (
804
- <tr
805
- key={id}
806
- role="row"
807
- aria-rowindex={rowIndex + 1}
808
- aria-selected={selectable ? isSelected : undefined}
809
- onClick={onRowClick ? () => onRowClick(row) : undefined}
810
- onKeyDown={onRowClick ? e => {
811
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onRowClick(row); }
812
- } : undefined}
813
- tabIndex={onRowClick ? 0 : undefined}
814
- className={tkx(onRowClick ? 'cursor-pointer' : '')}
815
- style={{
816
- backgroundColor: rowBg,
817
- transition: reduced ? 'none' : 'background-color 120ms ease',
818
- ...(isVirtual ? { height: rowHeight, boxSizing: 'border-box' } : {}),
819
- }}
820
- onMouseEnter={onRowClick ? e => {
821
- (e.currentTarget as HTMLElement).style.backgroundColor =
822
- isSelected ? `${theme.primary}30` : theme.surfaceAlt;
823
- } : undefined}
824
- onMouseLeave={onRowClick ? e => {
825
- (e.currentTarget as HTMLElement).style.backgroundColor = rowBg;
826
- } : undefined}
827
- >
828
- {/* Selection checkbox */}
829
- {selectable && (
830
- <td
831
- role="gridcell"
832
- style={{
833
- borderBottom: `1px solid ${theme.border}`,
834
- borderRight,
835
- padding: `${py} ${px}`,
836
- textAlign: 'center',
837
- width: 40,
838
- }}
839
- >
840
- <label
841
- style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', margin: '0 auto', width: 16 }}
842
- onClick={e => e.stopPropagation()}
843
- >
844
- <input
845
- type="checkbox"
846
- checked={isSelected}
847
- onChange={() => toggleRow(id)}
848
- aria-label={isSelected ? `Deselect row ${id}` : `Select row ${id}`}
849
- style={{ position: 'absolute', opacity: 0, width: 16, height: 16, cursor: 'pointer', margin: 0 }}
850
- />
851
- <CheckboxIcon checked={isSelected} />
852
- </label>
853
- </td>
854
- )}
855
-
856
- {/* Data cells */}
857
- {columns.map(col => {
858
- const value = getCellValue(row, col.key);
859
- const cellContent = col.renderCell
860
- ? col.renderCell(value, row)
861
- : typeof value === 'string'
862
- ? sanitizeString(value)
863
- : String(value ?? '');
864
-
865
- return (
866
- <td
867
- key={col.key}
868
- role="gridcell"
869
- className={tkx('text-sm')}
870
- style={{
871
- ...getColStyle(col),
872
- borderBottom: `1px solid ${theme.border}`,
873
- borderRight,
874
- color: theme.text,
875
- overflow: 'hidden',
876
- textOverflow: 'ellipsis',
877
- whiteSpace: 'nowrap',
878
- }}
879
- >
880
- {cellContent}
881
- </td>
882
- );
883
- })}
884
- </tr>
885
- );
886
- })}
887
-
888
- {/* Bottom spacer for virtual scrolling */}
889
- {isVirtual && endIndex < pagedData.length && (
890
- <tr aria-hidden="true">
891
- <td colSpan={totalCols}
892
- style={{ height: (pagedData.length - endIndex) * rowHeight, padding: 0, border: 'none' }} />
893
- </tr>
894
- )}
895
- </>
896
- )}
897
- </tbody>
898
- )}
899
- </table>
900
-
901
- {/* ── Infinite scroll sentinel ─────────────────────────────── */}
902
- {(hasMore || loadingMore) && (
903
- <div ref={sentinelRef} style={{ height: 1, width: '100%' }}>
904
- {loadingMore && (
905
- <table style={{ width: '100%', borderCollapse: 'collapse' }}>
906
- <tbody>
907
- {Array.from({ length: 3 }, (_, i) => (
908
- <tr key={i}>
909
- {columns.map((col) => (
910
- <td key={col.key} style={{ padding: '10px 16px' }}>
911
- <TkxSkeleton variant="text" height="16px" />
912
- </td>
913
- ))}
914
- </tr>
915
- ))}
916
- </tbody>
917
- </table>
918
- )}
919
- </div>
920
- )}
921
- {!hasMore && onLoadMore && (
922
- <div style={{ padding: '16px', textAlign: 'center', fontSize: 13, color: theme.textMuted }}>
923
- ✓ All rows loaded
924
- </div>
925
- )}
926
- </div>
927
-
928
- {/* ── Pagination ────────────────────────────────────────────────── */}
929
- {isPaginated && !loading && (
930
- <PaginationBar
931
- page={page}
932
- totalPages={totalPages}
933
- pageSize={pageSize}
934
- totalRows={totalRows}
935
- onPageChange={setPage}
936
- theme={theme}
937
- />
938
- )}
939
- </div>
940
- );
941
- }