@comitor/ui 2.1.0 → 3.0.1

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 (405) hide show
  1. package/README.md +63 -24
  2. package/dist/chart/chart.d.ts +6 -1
  3. package/dist/chart/chart.js +3 -1
  4. package/dist/chart/chart.js.map +1 -1
  5. package/dist/chart/comitor-chart.d.ts +4 -1
  6. package/dist/chart/comitor-chart.js +3 -1
  7. package/dist/chart/comitor-chart.js.map +1 -1
  8. package/dist/components/composite/avatar-group.d.ts +9 -6
  9. package/dist/components/composite/avatar-group.js +6 -2
  10. package/dist/components/composite/avatar-group.js.map +1 -1
  11. package/dist/components/composite/combobox.d.ts +11 -11
  12. package/dist/components/composite/combobox.js +25 -11
  13. package/dist/components/composite/combobox.js.map +1 -1
  14. package/dist/components/composite/command-palette-constants.d.ts +15 -5
  15. package/dist/components/composite/command-palette-constants.js +3 -2
  16. package/dist/components/composite/command-palette-constants.js.map +1 -1
  17. package/dist/components/composite/command-palette.d.ts +2 -2
  18. package/dist/components/composite/command-palette.js +8 -5
  19. package/dist/components/composite/command-palette.js.map +1 -1
  20. package/dist/components/composite/confirm-dialog.d.ts +8 -8
  21. package/dist/components/composite/confirm-dialog.js +8 -3
  22. package/dist/components/composite/confirm-dialog.js.map +1 -1
  23. package/dist/components/composite/copy-button.d.ts +7 -3
  24. package/dist/components/composite/copy-button.js +7 -3
  25. package/dist/components/composite/copy-button.js.map +1 -1
  26. package/dist/components/composite/data-table-constants.d.ts +7 -3
  27. package/dist/components/composite/data-table-constants.js +2 -9
  28. package/dist/components/composite/data-table-constants.js.map +1 -1
  29. package/dist/components/composite/data-table.js +3 -3
  30. package/dist/components/composite/data-table.js.map +1 -1
  31. package/dist/components/composite/date-picker-constants.d.ts +32 -9
  32. package/dist/components/composite/date-picker-constants.js +16 -11
  33. package/dist/components/composite/date-picker-constants.js.map +1 -1
  34. package/dist/components/composite/date-picker.d.ts +19 -10
  35. package/dist/components/composite/date-picker.js +33 -9
  36. package/dist/components/composite/date-picker.js.map +1 -1
  37. package/dist/components/composite/filter-chips.d.ts +4 -3
  38. package/dist/components/composite/filter-chips.js +6 -2
  39. package/dist/components/composite/filter-chips.js.map +1 -1
  40. package/dist/components/composite/inline-edit.d.ts +14 -3
  41. package/dist/components/composite/inline-edit.js +8 -4
  42. package/dist/components/composite/inline-edit.js.map +1 -1
  43. package/dist/components/composite/relative-time.d.ts +4 -4
  44. package/dist/components/composite/relative-time.js +4 -2
  45. package/dist/components/composite/relative-time.js.map +1 -1
  46. package/dist/components/composite/route-tabs-nav.d.ts +9 -0
  47. package/dist/components/composite/route-tabs-nav.js +11 -0
  48. package/dist/components/composite/route-tabs-nav.js.map +1 -0
  49. package/dist/components/composite/route-tabs.d.ts +4 -1
  50. package/dist/components/composite/route-tabs.js +3 -2
  51. package/dist/components/composite/route-tabs.js.map +1 -1
  52. package/dist/components/composite/search-filter-bar-constants.d.ts +18 -8
  53. package/dist/components/composite/search-filter-bar-constants.js +2 -14
  54. package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
  55. package/dist/components/composite/search-filter-bar.d.ts +5 -2
  56. package/dist/components/composite/search-filter-bar.js +2 -2
  57. package/dist/components/composite/search-filter-bar.js.map +1 -1
  58. package/dist/components/composite/stat-card.d.ts +1 -1
  59. package/dist/components/composite/table-pagination-constants.d.ts +16 -11
  60. package/dist/components/composite/table-pagination-constants.js +2 -12
  61. package/dist/components/composite/table-pagination-constants.js.map +1 -1
  62. package/dist/components/composite/table-pagination.d.ts +6 -3
  63. package/dist/components/composite/table-pagination.js +3 -5
  64. package/dist/components/composite/table-pagination.js.map +1 -1
  65. package/dist/components/composite/toast.d.ts +5 -2
  66. package/dist/components/composite/toast.js +3 -1
  67. package/dist/components/composite/toast.js.map +1 -1
  68. package/dist/components/ui/action-bar-constants.d.ts +8 -4
  69. package/dist/components/ui/action-bar-constants.js +2 -5
  70. package/dist/components/ui/action-bar-constants.js.map +1 -1
  71. package/dist/components/ui/action-bar.d.ts +5 -4
  72. package/dist/components/ui/action-bar.js +47 -3
  73. package/dist/components/ui/action-bar.js.map +1 -1
  74. package/dist/components/ui/alert.d.ts +1 -1
  75. package/dist/components/ui/badge-overflow.d.ts +2 -2
  76. package/dist/components/ui/badge-overflow.js +4 -2
  77. package/dist/components/ui/badge-overflow.js.map +1 -1
  78. package/dist/components/ui/badge.d.ts +1 -1
  79. package/dist/components/ui/banner.d.ts +6 -5
  80. package/dist/components/ui/banner.js +3 -1
  81. package/dist/components/ui/banner.js.map +1 -1
  82. package/dist/components/ui/breadcrumb.d.ts +11 -4
  83. package/dist/components/ui/breadcrumb.js +7 -2
  84. package/dist/components/ui/breadcrumb.js.map +1 -1
  85. package/dist/components/ui/button.d.ts +2 -2
  86. package/dist/components/ui/calendar.d.ts +12 -5
  87. package/dist/components/ui/calendar.js +38 -3
  88. package/dist/components/ui/calendar.js.map +1 -1
  89. package/dist/components/ui/carousel.d.ts +4 -3
  90. package/dist/components/ui/carousel.js +11 -4
  91. package/dist/components/ui/carousel.js.map +1 -1
  92. package/dist/components/ui/circular-progress.js +10 -2
  93. package/dist/components/ui/circular-progress.js.map +1 -1
  94. package/dist/components/ui/command.d.ts +5 -4
  95. package/dist/components/ui/command.js +6 -2
  96. package/dist/components/ui/command.js.map +1 -1
  97. package/dist/components/ui/cropper-constants.d.ts +8 -3
  98. package/dist/components/ui/cropper-constants.js +2 -6
  99. package/dist/components/ui/cropper-constants.js.map +1 -1
  100. package/dist/components/ui/cropper.d.ts +2 -1
  101. package/dist/components/ui/cropper.js +2 -2
  102. package/dist/components/ui/cropper.js.map +1 -1
  103. package/dist/components/ui/dialog.d.ts +2 -2
  104. package/dist/components/ui/dialog.js +4 -1
  105. package/dist/components/ui/dialog.js.map +1 -1
  106. package/dist/components/ui/item.d.ts +2 -2
  107. package/dist/components/ui/kanban-constants.d.ts +7 -1
  108. package/dist/components/ui/kanban-constants.js +2 -17
  109. package/dist/components/ui/kanban-constants.js.map +1 -1
  110. package/dist/components/ui/kanban.d.ts +2 -2
  111. package/dist/components/ui/kanban.js +11 -5
  112. package/dist/components/ui/kanban.js.map +1 -1
  113. package/dist/components/ui/key-value-constants.d.ts +7 -3
  114. package/dist/components/ui/key-value-constants.js +2 -7
  115. package/dist/components/ui/key-value-constants.js.map +1 -1
  116. package/dist/components/ui/key-value.d.ts +5 -5
  117. package/dist/components/ui/key-value.js +72 -78
  118. package/dist/components/ui/key-value.js.map +1 -1
  119. package/dist/components/ui/mask-input-constants.d.ts +2 -3
  120. package/dist/components/ui/mask-input-constants.js +36 -77
  121. package/dist/components/ui/mask-input-constants.js.map +1 -1
  122. package/dist/components/ui/mask-input.d.ts +7 -4
  123. package/dist/components/ui/mask-input.js +22 -22
  124. package/dist/components/ui/mask-input.js.map +1 -1
  125. package/dist/components/ui/mention.d.ts +3 -2
  126. package/dist/components/ui/mention.js +4 -6
  127. package/dist/components/ui/mention.js.map +1 -1
  128. package/dist/components/ui/pagination.d.ts +13 -9
  129. package/dist/components/ui/pagination.js +15 -6
  130. package/dist/components/ui/pagination.js.map +1 -1
  131. package/dist/components/ui/phone-input-constants.d.ts +17 -6
  132. package/dist/components/ui/phone-input-constants.js +2 -5
  133. package/dist/components/ui/phone-input-constants.js.map +1 -1
  134. package/dist/components/ui/phone-input.d.ts +18 -11
  135. package/dist/components/ui/phone-input.js +14 -12
  136. package/dist/components/ui/phone-input.js.map +1 -1
  137. package/dist/components/ui/progress.d.ts +2 -2
  138. package/dist/components/ui/rating.d.ts +2 -1
  139. package/dist/components/ui/rating.js +8 -4
  140. package/dist/components/ui/rating.js.map +1 -1
  141. package/dist/components/ui/scroll-spy.js +3 -1
  142. package/dist/components/ui/scroll-spy.js.map +1 -1
  143. package/dist/components/ui/sheet.d.ts +2 -2
  144. package/dist/components/ui/sheet.js +10 -1
  145. package/dist/components/ui/sheet.js.map +1 -1
  146. package/dist/components/ui/slider.d.ts +1 -1
  147. package/dist/components/ui/slider.js +15 -1
  148. package/dist/components/ui/slider.js.map +1 -1
  149. package/dist/components/ui/sonner.js +2 -0
  150. package/dist/components/ui/sonner.js.map +1 -1
  151. package/dist/components/ui/spinner.js +4 -1
  152. package/dist/components/ui/spinner.js.map +1 -1
  153. package/dist/components/ui/stepper-constants.d.ts +7 -2
  154. package/dist/components/ui/stepper-constants.js +2 -5
  155. package/dist/components/ui/stepper-constants.js.map +1 -1
  156. package/dist/components/ui/stepper.d.ts +2 -1
  157. package/dist/components/ui/stepper.js +2 -2
  158. package/dist/components/ui/stepper.js.map +1 -1
  159. package/dist/components/ui/tags-input.d.ts +3 -2
  160. package/dist/components/ui/tags-input.js +4 -2
  161. package/dist/components/ui/tags-input.js.map +1 -1
  162. package/dist/components/ui/time-picker-constants.d.ts +12 -9
  163. package/dist/components/ui/time-picker-constants.js +2 -10
  164. package/dist/components/ui/time-picker-constants.js.map +1 -1
  165. package/dist/components/ui/time-picker.d.ts +11 -6
  166. package/dist/components/ui/time-picker.js +5 -7
  167. package/dist/components/ui/time-picker.js.map +1 -1
  168. package/dist/components/ui/toggle-group.js +12 -1
  169. package/dist/components/ui/toggle-group.js.map +1 -1
  170. package/dist/components/ui/toggle.d.ts +1 -1
  171. package/dist/components/ui/tour-constants.d.ts +7 -2
  172. package/dist/components/ui/tour-constants.js +2 -8
  173. package/dist/components/ui/tour-constants.js.map +1 -1
  174. package/dist/components/ui/tour.d.ts +3 -2
  175. package/dist/components/ui/tour.js +5 -4
  176. package/dist/components/ui/tour.js.map +1 -1
  177. package/dist/data-grid/data-grid-cell-variants.js +4 -4
  178. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  179. package/dist/data-grid/data-grid-column-header.js +1 -1
  180. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  181. package/dist/data-grid/data-grid-filter-menu.d.ts +5 -4
  182. package/dist/data-grid/data-grid-filter-menu.js +13 -8
  183. package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
  184. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +4 -4
  185. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
  186. package/dist/data-grid/data-grid-locale.d.ts +58 -6
  187. package/dist/data-grid/data-grid-locale.js +41 -9
  188. package/dist/data-grid/data-grid-locale.js.map +1 -1
  189. package/dist/data-grid/data-grid-row-height-menu.d.ts +4 -3
  190. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
  191. package/dist/data-grid/data-grid-sort-menu.d.ts +4 -4
  192. package/dist/data-grid/data-grid-sort-menu.js +1 -1
  193. package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
  194. package/dist/data-grid/data-grid-view-menu.d.ts +4 -4
  195. package/dist/data-grid/data-grid-view-menu.js.map +1 -1
  196. package/dist/data-grid/data-grid.d.ts +4 -3
  197. package/dist/data-grid/data-grid.js +2 -3
  198. package/dist/data-grid/data-grid.js.map +1 -1
  199. package/dist/data-grid/index.d.ts +1 -0
  200. package/dist/data-grid/index.js.map +1 -1
  201. package/dist/data-grid/messages-en.d.ts +23 -4
  202. package/dist/data-grid/messages-en.js +20 -0
  203. package/dist/data-grid/messages-en.js.map +1 -1
  204. package/dist/data-grid/messages-vi.d.ts +3 -1
  205. package/dist/data-grid/messages-vi.js +14 -0
  206. package/dist/data-grid/messages-vi.js.map +1 -1
  207. package/dist/data-grid/messages.d.ts +29 -3
  208. package/dist/data-grid/messages.js.map +1 -1
  209. package/dist/data-grid/sortable.js +16 -19
  210. package/dist/data-grid/sortable.js.map +1 -1
  211. package/dist/data-grid/types.d.ts +1 -1
  212. package/dist/data-grid/use-data-grid-undo-redo.d.ts +5 -4
  213. package/dist/data-grid/use-data-grid-undo-redo.js +2 -2
  214. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
  215. package/dist/data-grid/use-data-grid.d.ts +23 -8
  216. package/dist/data-grid/use-data-grid.js +8 -6
  217. package/dist/data-grid/use-data-grid.js.map +1 -1
  218. package/dist/data-grid/utils.d.ts +24 -4
  219. package/dist/data-grid/utils.js +15 -5
  220. package/dist/data-grid/utils.js.map +1 -1
  221. package/dist/i18n/locale-provider.d.ts +127 -0
  222. package/dist/i18n/locale-provider.js +47 -0
  223. package/dist/i18n/locale-provider.js.map +1 -0
  224. package/dist/i18n/locale.d.ts +27 -0
  225. package/dist/i18n/locale.js +10 -0
  226. package/dist/i18n/locale.js.map +1 -0
  227. package/dist/i18n/messages.d.ts +116 -0
  228. package/dist/i18n/messages.js +149 -0
  229. package/dist/i18n/messages.js.map +1 -0
  230. package/dist/i18n/namespaces/action-bar.d.ts +7 -0
  231. package/dist/i18n/namespaces/action-bar.js +15 -0
  232. package/dist/i18n/namespaces/action-bar.js.map +1 -0
  233. package/dist/i18n/namespaces/avatar-group.d.ts +14 -0
  234. package/dist/i18n/namespaces/avatar-group.js +13 -0
  235. package/dist/i18n/namespaces/avatar-group.js.map +1 -0
  236. package/dist/i18n/namespaces/badge-overflow.d.ts +12 -0
  237. package/dist/i18n/namespaces/badge-overflow.js +11 -0
  238. package/dist/i18n/namespaces/badge-overflow.js.map +1 -0
  239. package/dist/i18n/namespaces/banner.d.ts +9 -0
  240. package/dist/i18n/namespaces/banner.js +7 -0
  241. package/dist/i18n/namespaces/banner.js.map +1 -0
  242. package/dist/i18n/namespaces/breadcrumb.d.ts +11 -0
  243. package/dist/i18n/namespaces/breadcrumb.js +13 -0
  244. package/dist/i18n/namespaces/breadcrumb.js.map +1 -0
  245. package/dist/i18n/namespaces/calendar.d.ts +40 -0
  246. package/dist/i18n/namespaces/calendar.js +25 -0
  247. package/dist/i18n/namespaces/calendar.js.map +1 -0
  248. package/dist/i18n/namespaces/carousel.d.ts +15 -0
  249. package/dist/i18n/namespaces/carousel.js +17 -0
  250. package/dist/i18n/namespaces/carousel.js.map +1 -0
  251. package/dist/i18n/namespaces/combobox.d.ts +21 -0
  252. package/dist/i18n/namespaces/combobox.js +23 -0
  253. package/dist/i18n/namespaces/combobox.js.map +1 -0
  254. package/dist/i18n/namespaces/command-palette.d.ts +20 -0
  255. package/dist/i18n/namespaces/command-palette.js +19 -0
  256. package/dist/i18n/namespaces/command-palette.js.map +1 -0
  257. package/dist/i18n/namespaces/command.d.ts +11 -0
  258. package/dist/i18n/namespaces/command.js +13 -0
  259. package/dist/i18n/namespaces/command.js.map +1 -0
  260. package/dist/i18n/namespaces/confirm-dialog.d.ts +13 -0
  261. package/dist/i18n/namespaces/confirm-dialog.js +15 -0
  262. package/dist/i18n/namespaces/confirm-dialog.js.map +1 -0
  263. package/dist/i18n/namespaces/contrast.d.ts +7 -0
  264. package/dist/i18n/namespaces/contrast.js +13 -0
  265. package/dist/i18n/namespaces/contrast.js.map +1 -0
  266. package/dist/i18n/namespaces/copy-button.d.ts +11 -0
  267. package/dist/i18n/namespaces/copy-button.js +13 -0
  268. package/dist/i18n/namespaces/copy-button.js.map +1 -0
  269. package/dist/i18n/namespaces/cropper.d.ts +7 -0
  270. package/dist/i18n/namespaces/cropper.js +17 -0
  271. package/dist/i18n/namespaces/cropper.js.map +1 -0
  272. package/dist/i18n/namespaces/data-table.d.ts +7 -0
  273. package/dist/i18n/namespaces/data-table.js +23 -0
  274. package/dist/i18n/namespaces/data-table.js.map +1 -0
  275. package/dist/i18n/namespaces/date-picker.d.ts +42 -0
  276. package/dist/i18n/namespaces/date-picker.js +35 -0
  277. package/dist/i18n/namespaces/date-picker.js.map +1 -0
  278. package/dist/i18n/namespaces/dialog.d.ts +9 -0
  279. package/dist/i18n/namespaces/dialog.js +7 -0
  280. package/dist/i18n/namespaces/dialog.js.map +1 -0
  281. package/dist/i18n/namespaces/display-axis.d.ts +25 -0
  282. package/dist/i18n/namespaces/display-axis.js +21 -0
  283. package/dist/i18n/namespaces/display-axis.js.map +1 -0
  284. package/dist/i18n/namespaces/file-upload.d.ts +11 -0
  285. package/dist/i18n/namespaces/file-upload.js +19 -0
  286. package/dist/i18n/namespaces/file-upload.js.map +1 -0
  287. package/dist/i18n/namespaces/filter-chips.d.ts +11 -0
  288. package/dist/i18n/namespaces/filter-chips.js +13 -0
  289. package/dist/i18n/namespaces/filter-chips.js.map +1 -0
  290. package/dist/i18n/namespaces/image-upload-field.d.ts +12 -0
  291. package/dist/i18n/namespaces/image-upload-field.js +23 -0
  292. package/dist/i18n/namespaces/image-upload-field.js.map +1 -0
  293. package/dist/i18n/namespaces/inline-edit.d.ts +15 -0
  294. package/dist/i18n/namespaces/inline-edit.js +17 -0
  295. package/dist/i18n/namespaces/inline-edit.js.map +1 -0
  296. package/dist/i18n/namespaces/kanban.d.ts +11 -0
  297. package/dist/i18n/namespaces/kanban.js +39 -0
  298. package/dist/i18n/namespaces/kanban.js.map +1 -0
  299. package/dist/i18n/namespaces/key-value.d.ts +10 -0
  300. package/dist/i18n/namespaces/key-value.js +19 -0
  301. package/dist/i18n/namespaces/key-value.js.map +1 -0
  302. package/dist/i18n/namespaces/mention.d.ts +9 -0
  303. package/dist/i18n/namespaces/mention.js +7 -0
  304. package/dist/i18n/namespaces/mention.js.map +1 -0
  305. package/dist/i18n/namespaces/pagination.d.ts +19 -0
  306. package/dist/i18n/namespaces/pagination.js +21 -0
  307. package/dist/i18n/namespaces/pagination.js.map +1 -0
  308. package/dist/i18n/namespaces/phone-input.d.ts +10 -0
  309. package/dist/i18n/namespaces/phone-input.js +17 -0
  310. package/dist/i18n/namespaces/phone-input.js.map +1 -0
  311. package/dist/i18n/namespaces/rating.d.ts +12 -0
  312. package/dist/i18n/namespaces/rating.js +12 -0
  313. package/dist/i18n/namespaces/rating.js.map +1 -0
  314. package/dist/i18n/namespaces/route-tabs.d.ts +9 -0
  315. package/dist/i18n/namespaces/route-tabs.js +7 -0
  316. package/dist/i18n/namespaces/route-tabs.js.map +1 -0
  317. package/dist/i18n/namespaces/scroll-spy.d.ts +9 -0
  318. package/dist/i18n/namespaces/scroll-spy.js +7 -0
  319. package/dist/i18n/namespaces/scroll-spy.js.map +1 -0
  320. package/dist/i18n/namespaces/search-filter-bar.d.ts +15 -0
  321. package/dist/i18n/namespaces/search-filter-bar.js +32 -0
  322. package/dist/i18n/namespaces/search-filter-bar.js.map +1 -0
  323. package/dist/i18n/namespaces/sheet.d.ts +9 -0
  324. package/dist/i18n/namespaces/sheet.js +7 -0
  325. package/dist/i18n/namespaces/sheet.js.map +1 -0
  326. package/dist/i18n/namespaces/shell.d.ts +21 -0
  327. package/dist/i18n/namespaces/shell.js +75 -0
  328. package/dist/i18n/namespaces/shell.js.map +1 -0
  329. package/dist/i18n/namespaces/spinner.d.ts +9 -0
  330. package/dist/i18n/namespaces/spinner.js +7 -0
  331. package/dist/i18n/namespaces/spinner.js.map +1 -0
  332. package/dist/i18n/namespaces/stepper.d.ts +7 -0
  333. package/dist/i18n/namespaces/stepper.js +15 -0
  334. package/dist/i18n/namespaces/stepper.js.map +1 -0
  335. package/dist/i18n/namespaces/table-pagination.d.ts +7 -0
  336. package/dist/i18n/namespaces/table-pagination.js +29 -0
  337. package/dist/i18n/namespaces/table-pagination.js.map +1 -0
  338. package/dist/i18n/namespaces/tags-input.d.ts +12 -0
  339. package/dist/i18n/namespaces/tags-input.js +11 -0
  340. package/dist/i18n/namespaces/tags-input.js.map +1 -0
  341. package/dist/i18n/namespaces/time-picker.d.ts +15 -0
  342. package/dist/i18n/namespaces/time-picker.js +25 -0
  343. package/dist/i18n/namespaces/time-picker.js.map +1 -0
  344. package/dist/i18n/namespaces/toast.d.ts +16 -0
  345. package/dist/i18n/namespaces/toast.js +7 -0
  346. package/dist/i18n/namespaces/toast.js.map +1 -0
  347. package/dist/i18n/namespaces/tour.d.ts +7 -0
  348. package/dist/i18n/namespaces/tour.js +21 -0
  349. package/dist/i18n/namespaces/tour.js.map +1 -0
  350. package/dist/index.d.ts +32 -0
  351. package/dist/index.js +24 -1
  352. package/dist/index.js.map +1 -1
  353. package/dist/lib/toast-language.d.ts +8 -0
  354. package/dist/lib/toast-language.js +19 -0
  355. package/dist/lib/toast-language.js.map +1 -0
  356. package/dist/shell/app-header.js.map +1 -1
  357. package/dist/shell/app-shell.d.ts +20 -2
  358. package/dist/shell/app-shell.js +4 -1
  359. package/dist/shell/app-shell.js.map +1 -1
  360. package/dist/shell/contrast-constants.d.ts +9 -1
  361. package/dist/shell/contrast-constants.js +2 -4
  362. package/dist/shell/contrast-constants.js.map +1 -1
  363. package/dist/shell/contrast-toggle.d.ts +4 -1
  364. package/dist/shell/contrast-toggle.js +2 -2
  365. package/dist/shell/contrast-toggle.js.map +1 -1
  366. package/dist/shell/display-axis-constants.d.ts +2 -1
  367. package/dist/shell/display-axis-constants.js.map +1 -1
  368. package/dist/shell/display-axis-controls.d.ts +15 -6
  369. package/dist/shell/display-axis-controls.js +11 -9
  370. package/dist/shell/display-axis-controls.js.map +1 -1
  371. package/dist/shell/display-axis-provider.d.ts +1 -1
  372. package/dist/shell/mobile-menu.js.map +1 -1
  373. package/dist/shell/notifications-button.d.ts +1 -1
  374. package/dist/shell/notifications-button.js +3 -2
  375. package/dist/shell/notifications-button.js.map +1 -1
  376. package/dist/shell/shell-context.d.ts +4 -1
  377. package/dist/shell/shell-context.js +2 -4
  378. package/dist/shell/shell-context.js.map +1 -1
  379. package/dist/shell/sidebar.d.ts +1 -1
  380. package/dist/shell/sidebar.js.map +1 -1
  381. package/dist/shell/theme-toggle.d.ts +6 -3
  382. package/dist/shell/theme-toggle.js +2 -2
  383. package/dist/shell/theme-toggle.js.map +1 -1
  384. package/dist/shell/types.d.ts +14 -8
  385. package/dist/shell/types.js +2 -35
  386. package/dist/shell/types.js.map +1 -1
  387. package/dist/shell/user-menu.d.ts +6 -6
  388. package/dist/shell/user-menu.js.map +1 -1
  389. package/dist/shell/utils.d.ts +1 -1
  390. package/dist/shell/utils.js.map +1 -1
  391. package/dist/shell/workspace-switcher.js +1 -1
  392. package/dist/shell/workspace-switcher.js.map +1 -1
  393. package/dist/uploader/file-upload-constants.d.ts +13 -0
  394. package/dist/uploader/file-upload-constants.js +4 -16
  395. package/dist/uploader/file-upload-constants.js.map +1 -1
  396. package/dist/uploader/file-upload.d.ts +4 -1
  397. package/dist/uploader/file-upload.js +2 -2
  398. package/dist/uploader/file-upload.js.map +1 -1
  399. package/dist/uploader/image-upload-field.d.ts +5 -1
  400. package/dist/uploader/image-upload-field.js +2 -2
  401. package/dist/uploader/image-upload-field.js.map +1 -1
  402. package/dist/uploader/use-file-upload.d.ts +4 -0
  403. package/dist/uploader/use-file-upload.js +6 -2
  404. package/dist/uploader/use-file-upload.js.map +1 -1
  405. package/package.json +4 -3
@@ -2,12 +2,13 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Check, Copy } from "lucide-react";
4
4
  import { useEffect, useRef, useState } from "react";
5
+ import { useComitorMessages } from "../../i18n/locale-provider.js";
5
6
  import { cn } from "../../lib/cn.js";
6
7
  import { Button } from "../ui/button.js";
7
8
  function CopyButton({
8
9
  value,
9
- label = "Sao ch\xE9p",
10
- copiedLabel = "\u0110\xE3 ch\xE9p",
10
+ label: labelProp,
11
+ copiedLabel: copiedLabelProp,
11
12
  resetAfterMs = 2e3,
12
13
  onCopied,
13
14
  onCopyError,
@@ -16,6 +17,9 @@ function CopyButton({
16
17
  className,
17
18
  ...props
18
19
  }) {
20
+ const t = useComitorMessages("copyButton");
21
+ const label = labelProp === void 0 ? t.label : labelProp;
22
+ const copiedLabel = copiedLabelProp ?? t.copiedLabel;
19
23
  const [copied, setCopied] = useState(false);
20
24
  const timerRef = useRef(null);
21
25
  useEffect(
@@ -63,7 +67,7 @@ function CopyButton({
63
67
  variant,
64
68
  size,
65
69
  onClick: () => void copy(),
66
- "aria-label": label ?? (copied ? copiedLabel : "Sao ch\xE9p"),
70
+ "aria-label": label ?? (copied ? copiedLabel : t.label),
67
71
  className: cn("gap-1.5", copied && "text-success-ink", className),
68
72
  ...props,
69
73
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { Check, Copy } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button, type ButtonProps } from \"../ui/button.js\";\n\nexport interface CopyButtonProps extends Omit<ButtonProps, \"children\" | \"onCopy\"> {\n /** Nội dung được chép vào clipboard */\n value: string;\n /** Nhãn khi chưa chép (mặc định \"Sao chép\"). `null` = chỉ icon. */\n label?: string | null;\n /** Nhãn sau khi chép thành công */\n copiedLabel?: string;\n /** Thời gian giữ trạng thái \"đã chép\" (ms) */\n resetAfterMs?: number;\n onCopied?: (value: string) => void;\n onCopyError?: (error: unknown) => void;\n}\n\n/**\n * Chép giá trị vào clipboard kèm phản hồi thị giác.\n *\n * Có đường lui khi `navigator.clipboard` không dùng được (trang http không phải localhost,\n * WebView cũ): tạo textarea ẩn rồi `execCommand(\"copy\")`. Thiếu đường lui này thì nút im lặng\n * không làm gì — người dùng tưởng đã chép và dán ra nội dung cũ.\n */\nexport function CopyButton({\n value,\n label = \"Sao chép\",\n copiedLabel = \"Đã chép\",\n resetAfterMs = 2000,\n onCopied,\n onCopyError,\n variant = \"ghost\",\n size = \"sm\",\n className,\n ...props\n}: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(\n () => () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n },\n []\n );\n\n const writeFallback = (text: string): boolean => {\n try {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n const ok = document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n return ok;\n } catch {\n return false;\n }\n };\n\n const copy = async () => {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(value);\n } else if (!writeFallback(value)) {\n throw new Error(\"Trình duyệt không cho phép ghi clipboard\");\n }\n\n setCopied(true);\n onCopied?.(value);\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setCopied(false), resetAfterMs);\n } catch (error) {\n onCopyError?.(error);\n }\n };\n\n const Icon = copied ? Check : Copy;\n\n return (\n <Button\n type=\"button\"\n variant={variant}\n size={size}\n onClick={() => void copy()}\n aria-label={label ?? (copied ? copiedLabel : \"Sao chép\")}\n // `text-success-ink`: nhãn \"Đã chép\" là CHỮ trên nền trang → AA 4,5:1. Token tô nền\n // `--success` chỉ 3,13:1 ở light ✗ (dark 10,85:1). Ink: 6,52:1 (light) · 10,85:1 (dark) ✓.\n className={cn(\"gap-1.5\", copied && \"text-success-ink\", className)}\n {...props}\n >\n <Icon aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n {label !== null && <span>{copied ? copiedLabel : label}</span>}\n <span aria-live=\"polite\" className=\"sr-only\">\n {copied ? copiedLabel : \"\"}\n </span>\n </Button>\n );\n}\n"],"mappings":";AAsFI,SAWE,KAXF;AApFJ,SAAS,OAAO,YAAY;AAC5B,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,UAAU;AACnB,SAAS,cAAgC;AAsBlC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,WAAW,OAA6C,IAAI;AAElE;AAAA,IACE,MAAM,MAAM;AACV,UAAI,SAAS,QAAS,cAAa,SAAS,OAAO;AAAA,IACrD;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,gBAAgB,CAAC,SAA0B;AAC/C,QAAI;AACF,YAAM,WAAW,SAAS,cAAc,UAAU;AAClD,eAAS,QAAQ;AACjB,eAAS,aAAa,YAAY,EAAE;AACpC,eAAS,MAAM,WAAW;AAC1B,eAAS,MAAM,UAAU;AACzB,eAAS,KAAK,YAAY,QAAQ;AAClC,eAAS,OAAO;AAChB,YAAM,KAAK,SAAS,YAAY,MAAM;AACtC,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,YAAY;AACvB,QAAI;AACF,UAAI,UAAU,WAAW,WAAW;AAClC,cAAM,UAAU,UAAU,UAAU,KAAK;AAAA,MAC3C,WAAW,CAAC,cAAc,KAAK,GAAG;AAChC,cAAM,IAAI,MAAM,wDAA0C;AAAA,MAC5D;AAEA,gBAAU,IAAI;AACd,iBAAW,KAAK;AAChB,UAAI,SAAS,QAAS,cAAa,SAAS,OAAO;AACnD,eAAS,UAAU,WAAW,MAAM,UAAU,KAAK,GAAG,YAAY;AAAA,IACpE,SAAS,OAAO;AACd,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,QAAQ;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,KAAK;AAAA,MACzB,cAAY,UAAU,SAAS,cAAc;AAAA,MAG7C,WAAW,GAAG,WAAW,UAAU,oBAAoB,SAAS;AAAA,MAC/D,GAAG;AAAA,MAEJ;AAAA,4BAAC,QAAK,eAAY,QAAO,WAAU,qBAAoB;AAAA,QACtD,UAAU,QAAQ,oBAAC,UAAM,mBAAS,cAAc,OAAM;AAAA,QACvD,oBAAC,UAAK,aAAU,UAAS,WAAU,WAChC,mBAAS,cAAc,IAC1B;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { Check, Copy } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button, type ButtonProps } from \"../ui/button.js\";\n\nexport interface CopyButtonProps extends Omit<ButtonProps, \"children\" | \"onCopy\"> {\n /** Nội dung được chép vào clipboard */\n value: string;\n /**\n * Nhãn khi chưa chép. Không truyền (`undefined`) thì lấy khoá `label` của namespace `copyButton`\n * (`ComitorLocaleProvider`, mặc định tiếng Anh). `null` = chỉ icon — khi đó khoá `label` vẫn là\n * `aria-label` của nút.\n */\n label?: string | null;\n /** Nhãn sau khi chép thành công (khoá `copiedLabel` của namespace `copyButton`) */\n copiedLabel?: string;\n /** Thời gian giữ trạng thái \"đã chép\" (ms) */\n resetAfterMs?: number;\n onCopied?: (value: string) => void;\n onCopyError?: (error: unknown) => void;\n}\n\n/**\n * Chép giá trị vào clipboard kèm phản hồi thị giác.\n *\n * Có đường lui khi `navigator.clipboard` không dùng được (trang http không phải localhost,\n * WebView cũ): tạo textarea ẩn rồi `execCommand(\"copy\")`. Thiếu đường lui này thì nút im lặng\n * không làm gì — người dùng tưởng đã chép và dán ra nội dung cũ.\n */\nexport function CopyButton({\n value,\n label: labelProp,\n copiedLabel: copiedLabelProp,\n resetAfterMs = 2000,\n onCopied,\n onCopyError,\n variant = \"ghost\",\n size = \"sm\",\n className,\n ...props\n}: CopyButtonProps) {\n const t = useComitorMessages(\"copyButton\");\n // `=== undefined` chứ KHÔNG `??`: `null` là giá trị có nghĩa (chỉ icon), `??` sẽ nuốt nó.\n const label = labelProp === undefined ? t.label : labelProp;\n const copiedLabel = copiedLabelProp ?? t.copiedLabel;\n const [copied, setCopied] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(\n () => () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n },\n []\n );\n\n const writeFallback = (text: string): boolean => {\n try {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n const ok = document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n return ok;\n } catch {\n return false;\n }\n };\n\n const copy = async () => {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(value);\n } else if (!writeFallback(value)) {\n throw new Error(\"Trình duyệt không cho phép ghi clipboard\");\n }\n\n setCopied(true);\n onCopied?.(value);\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setCopied(false), resetAfterMs);\n } catch (error) {\n onCopyError?.(error);\n }\n };\n\n const Icon = copied ? Check : Copy;\n\n return (\n <Button\n type=\"button\"\n variant={variant}\n size={size}\n onClick={() => void copy()}\n aria-label={label ?? (copied ? copiedLabel : t.label)}\n // `text-success-ink`: nhãn `copiedLabel` là CHỮ trên nền trang → AA 4,5:1. Token tô nền\n // `--success` chỉ 3,13:1 ở light ✗ (dark 10,85:1). Ink: 6,52:1 (light) · 10,85:1 (dark) ✓.\n className={cn(\"gap-1.5\", copied && \"text-success-ink\", className)}\n {...props}\n >\n <Icon aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n {label !== null && <span>{copied ? copiedLabel : label}</span>}\n <span aria-live=\"polite\" className=\"sr-only\">\n {copied ? copiedLabel : \"\"}\n </span>\n </Button>\n );\n}\n"],"mappings":";AA+FI,SAWE,KAXF;AA7FJ,SAAS,OAAO,YAAY;AAC5B,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,0BAA0B;AACnC,SAAS,UAAU;AACnB,SAAS,cAAgC;AA0BlC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP,aAAa;AAAA,EACb,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,IAAI,mBAAmB,YAAY;AAEzC,QAAM,QAAQ,cAAc,SAAY,EAAE,QAAQ;AAClD,QAAM,cAAc,mBAAmB,EAAE;AACzC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,WAAW,OAA6C,IAAI;AAElE;AAAA,IACE,MAAM,MAAM;AACV,UAAI,SAAS,QAAS,cAAa,SAAS,OAAO;AAAA,IACrD;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,gBAAgB,CAAC,SAA0B;AAC/C,QAAI;AACF,YAAM,WAAW,SAAS,cAAc,UAAU;AAClD,eAAS,QAAQ;AACjB,eAAS,aAAa,YAAY,EAAE;AACpC,eAAS,MAAM,WAAW;AAC1B,eAAS,MAAM,UAAU;AACzB,eAAS,KAAK,YAAY,QAAQ;AAClC,eAAS,OAAO;AAChB,YAAM,KAAK,SAAS,YAAY,MAAM;AACtC,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,YAAY;AACvB,QAAI;AACF,UAAI,UAAU,WAAW,WAAW;AAClC,cAAM,UAAU,UAAU,UAAU,KAAK;AAAA,MAC3C,WAAW,CAAC,cAAc,KAAK,GAAG;AAChC,cAAM,IAAI,MAAM,wDAA0C;AAAA,MAC5D;AAEA,gBAAU,IAAI;AACd,iBAAW,KAAK;AAChB,UAAI,SAAS,QAAS,cAAa,SAAS,OAAO;AACnD,eAAS,UAAU,WAAW,MAAM,UAAU,KAAK,GAAG,YAAY;AAAA,IACpE,SAAS,OAAO;AACd,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,QAAQ;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,KAAK;AAAA,MACzB,cAAY,UAAU,SAAS,cAAc,EAAE;AAAA,MAG/C,WAAW,GAAG,WAAW,UAAU,oBAAoB,SAAS;AAAA,MAC/D,GAAG;AAAA,MAEJ;AAAA,4BAAC,QAAK,eAAY,QAAO,WAAU,qBAAoB;AAAA,QACtD,UAAU,QAAQ,oBAAC,UAAM,mBAAS,cAAc,OAAM;AAAA,QACvD,oBAAC,UAAK,aAAU,UAAS,WAAU,WAChC,mBAAS,cAAc,IAC1B;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -12,8 +12,8 @@
12
12
  */
13
13
  /**
14
14
  * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và
15
- * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop
16
- * `labels`. Mặc định là tiếng Việt.
15
+ * `DataTableColumnToggle`. Theo `ComitorLocaleProvider` (mặc định tiếng Anh, bảng tiếng Việt dựng
16
+ * sẵn); override từng khoá qua prop `labels`.
17
17
  *
18
18
  * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi
19
19
  * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.
@@ -34,7 +34,11 @@ interface DataTableLabels {
34
34
  /** Nhãn nút + tiêu đề menu ẩn/hiện cột */
35
35
  columnToggle: string;
36
36
  }
37
- /** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */
37
+ /**
38
+ * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:
39
+ * `comitorMessagesVi.dataTable`. Component không đọc thẳng hằng này — nó đọc bảng của
40
+ * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
41
+ */
38
42
  declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
39
43
 
40
44
  export { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels };
@@ -1,12 +1,5 @@
1
- const DEFAULT_DATA_TABLE_LABELS = {
2
- selectAllRows: "Ch\u1ECDn t\u1EA5t c\u1EA3 d\xF2ng \u0111ang hi\u1EC3n th\u1ECB",
3
- deselectAllRows: "B\u1ECF ch\u1ECDn t\u1EA5t c\u1EA3",
4
- selectRow: "Ch\u1ECDn d\xF2ng",
5
- deselectRow: "B\u1ECF ch\u1ECDn d\xF2ng",
6
- emptyTitle: "Ch\u01B0a c\xF3 d\u1EEF li\u1EC7u",
7
- emptyDescription: "Danh s\xE1ch hi\u1EC7n \u0111ang tr\u1ED1ng.",
8
- columnToggle: "C\u1ED9t hi\u1EC3n th\u1ECB"
9
- };
1
+ import { dataTableEn } from "../../i18n/namespaces/data-table.js";
2
+ const DEFAULT_DATA_TABLE_LABELS = { ...dataTableEn };
10
3
  export {
11
4
  DEFAULT_DATA_TABLE_LABELS
12
5
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/data-table-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `DataTable` / `DataTableColumnToggle`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, danh sách chọn) là thứ trang server hay đọc nhất — spread để override\n * một khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `data-table.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\n/**\n * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và\n * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop\n * `labels`. Mặc định là tiếng Việt.\n *\n * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi\n * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.\n */\nexport interface DataTableLabels {\n /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */\n selectAllRows: string;\n /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */\n deselectAllRows: string;\n /** aria-label ô chọn của một dòng khi dòng chưa được chọn */\n selectRow: string;\n /** aria-label ô chọn của một dòng khi dòng đã được chọn */\n deselectRow: string;\n /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */\n emptyTitle: string;\n /** Mô tả trạng thái rỗng mặc định */\n emptyDescription: string;\n /** Nhãn nút + tiêu đề menu ẩn/hiện cột */\n columnToggle: string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n selectAllRows: \"Chọn tất cả dòng đang hiển thị\",\n deselectAllRows: \"Bỏ chọn tất cả\",\n selectRow: \"Chọn dòng\",\n deselectRow: \"Bỏ chọn dòng\",\n emptyTitle: \"Chưa có dữ liệu\",\n emptyDescription: \"Danh sách hiện đang trống.\",\n columnToggle: \"Cột hiển thị\"\n};\n"],"mappings":"AAuCO,MAAM,4BAA6C;AAAA,EACxD,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,cAAc;AAChB;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/data-table-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `DataTable` / `DataTableColumnToggle`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, danh sách chọn) là thứ trang server hay đọc nhất — spread để override\n * một khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `data-table.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\nimport { dataTableEn } from \"../../i18n/namespaces/data-table.js\";\n\n/**\n * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và\n * `DataTableColumnToggle`. Theo `ComitorLocaleProvider` (mặc định tiếng Anh, bảng tiếng Việt dựng\n * sẵn); override từng khoá qua prop `labels`.\n *\n * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi\n * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.\n */\nexport interface DataTableLabels {\n /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */\n selectAllRows: string;\n /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */\n deselectAllRows: string;\n /** aria-label ô chọn của một dòng khi dòng chưa được chọn */\n selectRow: string;\n /** aria-label ô chọn của một dòng khi dòng đã được chọn */\n deselectRow: string;\n /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */\n emptyTitle: string;\n /** Mô tả trạng thái rỗng mặc định */\n emptyDescription: string;\n /** Nhãn nút + tiêu đề menu ẩn/hiện cột */\n columnToggle: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.dataTable`. Component không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\n/*\n * `{ ...dataTableEn }` chứ không `= dataTableEn`: cổng `verify:directives` chỉ nhận ra HẰNG DỮ LIỆU\n * khi khởi tạo là một literal (object/mảng/chuỗi…). Gán thẳng một định danh thì nó không còn biết\n * đây là dữ liệu, và thôi canh chuyện hằng này lọt vào file \"use client\" — đo được ở 3.0.0: số\n * hằng được canh tụt từ 58 xuống 47 cho tới khi đổi lại. Cùng khuôn ở mọi `DEFAULT_*_LABELS`.\n */\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = { ...dataTableEn };\n"],"mappings":"AAaA,SAAS,mBAAmB;AAsCrB,MAAM,4BAA6C,EAAE,GAAG,YAAY;","names":[]}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { ArrowDown, ArrowUp, ChevronsUpDown, Inbox, SlidersHorizontal } from "lucide-react";
4
+ import { useComitorLabels } from "../../i18n/locale-provider.js";
4
5
  import { cn } from "../../lib/cn.js";
5
6
  import { Button } from "../ui/button.js";
6
7
  import { Checkbox } from "../ui/checkbox.js";
@@ -14,7 +15,6 @@ import {
14
15
  } from "../ui/dropdown-menu.js";
15
16
  import { Skeleton } from "../ui/skeleton.js";
16
17
  import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
17
- import { DEFAULT_DATA_TABLE_LABELS } from "./data-table-constants.js";
18
18
  import { EmptyState } from "./empty-state.js";
19
19
  const alignClasses = {
20
20
  left: "text-left",
@@ -69,7 +69,7 @@ function DataTable({
69
69
  "aria-label": ariaLabel,
70
70
  "aria-labelledby": ariaLabelledby
71
71
  }) {
72
- const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };
72
+ const labels = useComitorLabels("dataTable", labelsProp);
73
73
  const shownColumns = visibleColumnIds ? columns.filter((column) => visibleColumnIds.includes(column.id)) : columns;
74
74
  const pinnedStyles = getPinnedOffsets(shownColumns, selectable);
75
75
  const colCount = shownColumns.length + (selectable ? 1 : 0);
@@ -298,7 +298,7 @@ function DataTableColumnToggle({
298
298
  labels: labelsProp,
299
299
  className
300
300
  }) {
301
- const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };
301
+ const labels = useComitorLabels("dataTable", labelsProp);
302
302
  const text = label ?? labels.columnToggle;
303
303
  const hideable = columns.filter((column) => column.hideable !== false);
304
304
  const toggle = (columnId) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowDown, ArrowUp, ChevronsUpDown, Inbox, SlidersHorizontal } from \"lucide-react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Checkbox } from \"../ui/checkbox.js\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../ui/dropdown-menu.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport { TableBody, TableCell, TableHead, TableHeader, TableRow } from \"../ui/table.js\";\nimport { type DataTableLabels, DEFAULT_DATA_TABLE_LABELS } from \"./data-table-constants.js\";\nimport { EmptyState } from \"./empty-state.js\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type DataTableAlign = \"left\" | \"center\" | \"right\";\n\nexport interface DataTableColumn<TRow> {\n id: string;\n /** Nội dung ô tiêu đề */\n header: ReactNode;\n /** Nhãn chữ dùng trong menu ẩn/hiện cột — bắt buộc khi `header` không phải chuỗi */\n label?: string;\n /** Render ô dữ liệu — nhận cả chỉ số dòng để đánh số thứ tự */\n cell: (row: TRow, rowIndex: number) => ReactNode;\n sortable?: boolean;\n align?: DataTableAlign;\n /**\n * Bề rộng cột tính bằng px. BẮT BUỘC với cột ghim (`pinned`) vì offset `left/right` của\n * sticky được tính cộng dồn từ giá trị này — thiếu width thì các cột ghim chồng lên nhau.\n */\n width?: number;\n minWidth?: number;\n /** Ghim cột khi cuộn ngang (bảng nhiều cột) */\n pinned?: \"left\" | \"right\";\n /** Cho phép người dùng ẩn cột này (mặc định true; cột định danh nên đặt false) */\n hideable?: boolean;\n headerClassName?: string;\n cellClassName?: string;\n}\n\nexport interface DataTableProps<TRow> {\n columns: DataTableColumn<TRow>[];\n rows: TRow[];\n /** Khoá ổn định của dòng — dùng cho `key` React và cho tập chọn */\n getRowId: (row: TRow, index: number) => string;\n\n /** Đang tải → hiện `loadingRowCount` dòng khung xương thay vì bảng trống */\n loading?: boolean;\n loadingRowCount?: number;\n /** Nội dung khi không có dòng nào (mặc định EmptyState theo `labels.emptyTitle`) */\n empty?: ReactNode;\n\n /** Sắp xếp — controlled; `null` = không sắp xếp. App tự sắp/tự gọi API. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Chọn dòng bằng checkbox — controlled */\n selectable?: boolean;\n selectedIds?: string[];\n onSelectedIdsChange?: (ids: string[]) => void;\n /** Khoá chọn với một số dòng (vd bản ghi đã khoá) */\n isRowSelectable?: (row: TRow, index: number) => boolean;\n\n /**\n * Mã các cột đang hiện — controlled. Bỏ trống = hiện tất cả. Đổi bố cục đi qua\n * `DataTableColumnToggle`, app tự lưu theo người dùng (server, localStorage…).\n *\n * ⚠ Đây là một **TẬP HỢP**, không phải thứ tự: thứ tự cột luôn lấy từ mảng `columns`, thứ tự\n * bên trong mảng này bị bỏ qua. Tên có hậu tố `Ids` chính là để nói đúng thứ nó chứa —\n * 0.1.0 đặt `visibleColumns` khiến người đọc tưởng là mảng cột (`DataTableColumn[]`) hoặc\n * tưởng sắp lại mảng thì cột đổi chỗ.\n *\n * Cố ý KHÔNG dùng `Record<string, boolean>`: kiểu đó có trạng thái thứ ba — **khoá vắng\n * mặt**. App thêm cột mới ở bản sau thì bố cục người dùng đã lưu không có khoá đó, mỗi nơi\n * tự đoán hiện/ẩn → lỗi im lặng. Một mảng mã cột thì `JSON.stringify` là lưu được.\n *\n * Package cố ý KHÔNG tự ghi localStorage: ở SaaS nhiều workspace, bố cục bảng phải theo\n * cặp (người dùng × workspace) và thường cần đồng bộ giữa các máy — chuyện của app.\n */\n visibleColumnIds?: string[];\n\n /**\n * Bấm vào dòng (vd mở chi tiết bản ghi). Có prop này thì dòng vào chuỗi Tab và nhận\n * Enter/Space — mở chi tiết KHÔNG được là thao tác chỉ-chuột.\n */\n onRowClick?: (row: TRow, index: number) => void;\n rowClassName?: (row: TRow, index: number) => string | undefined;\n /**\n * Dòng đang được làm nổi (vd bản ghi vừa mở ở panel bên).\n *\n * Đánh dấu bằng `outline` chứ không bằng nền — xem lý do đầy đủ ở chỗ áp class. Tóm tắt: nền\n * có alpha sẽ làm ô cột GHIM trong mờ, và nền class trần thì thua sọc của `striped` về độ đặc\n * tả nên mất hẳn ở hàng chẵn. `outline` không dính cả hai.\n *\n * ⚠ Vì thế app KHÔNG cần tự vá bằng `rowClassName` nữa — dùng thẳng prop này.\n */\n isRowActive?: (row: TRow, index: number) => boolean;\n\n /** Tiêu đề dính đỉnh vùng cuộn (mặc định true) */\n stickyHeader?: boolean;\n /**\n * Tô nền xen kẽ cho hàng chẵn — mẫu DataTable ở mục **Patterns** của comitor-ds dùng\n * `<Table striped>`, còn `DataTable` trước đây không có đường bật.\n *\n * Mặc định `false`: bảng dài và dày cột thì sọc giúp dò hàng, nhưng bảng ngắn thì nó chỉ thêm\n * nhiễu. Đây là quyết định của từng trang, không phải mặc định của gói.\n *\n * ⚠ Sọc PHẢI là màu ĐỤC (`--table-row-stripe`), không phải `bg-muted/30` như bản trước.\n *\n * Vì sao: sọc khai bằng biến thể tuỳ ý ở cấp `<table>` nên chọn tử sinh ra là\n * `.[…] tbody tr:nth-child(even)` — độ đặc tả (0,2,2), trong khi `bg-card` mà `<TableRow>` tự\n * mang chỉ là một class (0,1,0). Sọc THẮNG và THAY THẾ `bg-card` chứ không chồng lên nó, nên\n * hàng chẵn còn alpha 0,3 mà không có lớp đục nào bên dưới. Ô cột ghim lấy `bg-inherit` từ hàng\n * → cũng trong mờ → cuộn ngang là chữ của các cột bên dưới lộ xuyên qua cột ghim. Đây đúng là\n * lỗi người dùng nhìn thấy, và nó chỉ xuất hiện ở hàng CHẴN nên rất dễ tưởng là \"hàng đầu ổn\".\n *\n * (Chú thích cũ ở đây giải thích rằng \"nền trạng thái luôn thắng vì ghi sau trong chuỗi lớp\" —\n * SAI, và đó chính là lý do lỗi trên lọt qua: thứ tự trong chuỗi class không quyết định gì, độ\n * đặc tả mới quyết định. Nền của hàng ĐANG MỞ `bg-app-accent/5` là (0,1,0) nên nó cũng thua\n * sọc — nhưng hàng đang mở vốn được đánh dấu bằng `outline`, không bằng nền, nên chỗ đó không\n * lộ ra. Đừng lấy nền làm dấu hiệu hàng ở bảng có sọc.)\n */\n striped?: boolean;\n /** Bề rộng tối thiểu của bảng — bật cuộn ngang khi màn hình hẹp */\n minWidth?: number | string;\n /**\n * Ghi đè mật độ cho RIÊNG bảng này, bất kể trục mật độ toàn app đang ở mức nào.\n *\n * Bỏ trống = theo `<html data-density>`. Đặt `\"compact\"` thì bảng tự đặt lại `--row-py` và\n * `--table-head-h` ngay trên thẻ `<table>`, nên tiêu đề và thân đi cùng nhau — bản trước chỉ\n * gắn class vào ô THÂN nên `compact` cho hàng tiêu đề 40px đứng trên hàng thân ~26px.\n */\n density?: \"comfortable\" | \"compact\";\n /** Ghi đè chuỗi hiển thị/a11y (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n /** Nhãn của bảng cho trình đọc màn hình (bắt buộc khi trang có nhiều bảng) */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n className?: string;\n /** className của khung cuộn bọc ngoài */\n containerClassName?: string;\n}\n\nconst alignClasses: Record<DataTableAlign, string> = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\"\n};\n\n/** Offset sticky cộng dồn cho cột ghim trái/phải. */\nfunction getPinnedOffsets<TRow>(columns: DataTableColumn<TRow>[], selectable: boolean): Map<string, CSSProperties> {\n const styles = new Map<string, CSSProperties>();\n // Cột checkbox (nếu có) luôn ghim trái và chiếm 44px — cột ghim đầu tiên phải bắt đầu sau nó.\n let left = selectable ? 44 : 0;\n for (const column of columns) {\n if (column.pinned !== \"left\") continue;\n styles.set(column.id, { position: \"sticky\", left, zIndex: 2 });\n left += column.width ?? 0;\n }\n\n let right = 0;\n for (const column of [...columns].reverse()) {\n if (column.pinned !== \"right\") continue;\n styles.set(column.id, { position: \"sticky\", right, zIndex: 2 });\n right += column.width ?? 0;\n }\n\n return styles;\n}\n\nfunction nextSort(current: DataTableSort | null | undefined, columnId: string): DataTableSort | null {\n // asc → desc → bỏ sắp xếp. Vòng 3 trạng thái để người dùng quay lại thứ tự gốc của server\n // mà không phải tải lại trang.\n if (!current || current.columnId !== columnId) return { columnId, direction: \"asc\" };\n if (current.direction === \"asc\") return { columnId, direction: \"desc\" };\n return null;\n}\n\n/**\n * Bảng dữ liệu chuẩn của Comitor: tiêu đề dính, sắp xếp, chọn dòng, ghim cột, ẩn/hiện cột,\n * trạng thái tải và trạng thái rỗng.\n *\n * Mọi trạng thái đều **controlled** — package không giữ dữ liệu, không gọi API, không tự phân\n * trang. Nhờ vậy dùng được cả với dữ liệu client (mảng trong bộ nhớ) lẫn server-side\n * pagination mà không phải viết hai component.\n */\nexport function DataTable<TRow>({\n columns,\n rows,\n getRowId,\n loading = false,\n loadingRowCount = 5,\n empty,\n sort,\n onSortChange,\n selectable = false,\n selectedIds,\n onSelectedIdsChange,\n isRowSelectable,\n visibleColumnIds,\n onRowClick,\n rowClassName,\n isRowActive,\n stickyHeader = true,\n striped = false,\n minWidth,\n density = \"comfortable\",\n labels: labelsProp,\n className,\n containerClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby\n}: DataTableProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n // Thứ tự luôn theo `columns` — `visibleColumnIds` chỉ trả lời \"cột nào được hiện\".\n const shownColumns = visibleColumnIds ? columns.filter((column) => visibleColumnIds.includes(column.id)) : columns;\n const pinnedStyles = getPinnedOffsets(shownColumns, selectable);\n const colCount = shownColumns.length + (selectable ? 1 : 0);\n\n const selected = selectedIds ?? [];\n // Một lượt duyệt: vừa lọc dòng chọn được vừa lấy id — tránh indexOf() lồng trong map (O(n²))\n // và tránh sai khi hai dòng cùng tham chiếu object.\n const selectableIds: string[] = [];\n rows.forEach((row, index) => {\n if (isRowSelectable?.(row, index) ?? true) selectableIds.push(getRowId(row, index));\n });\n const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selected.includes(id));\n const someSelected = !allSelected && selectableIds.some((id) => selected.includes(id));\n\n const toggleAll = () => {\n if (!onSelectedIdsChange) return;\n if (allSelected) {\n onSelectedIdsChange(selected.filter((id) => !selectableIds.includes(id)));\n return;\n }\n onSelectedIdsChange([...new Set([...selected, ...selectableIds])]);\n };\n\n const toggleRow = (id: string) => {\n if (!onSelectedIdsChange) return;\n onSelectedIdsChange(selected.includes(id) ? selected.filter((item) => item !== id) : [...selected, id]);\n };\n\n /*\n * `density` nay là GHI ĐÈ CỤC BỘ của trục mật độ toàn cục, không phải một bảng lớp riêng.\n *\n * Bản trước là `density === \"compact\" ? \"px-3 py-1.5\" : \"px-4 py-3\"` gắn tay vào bốn ô THÂN —\n * ba khuyết tật cùng lúc: (1) nó không bao giờ tới `<TableHead>`, nên `compact` cho hàng tiêu\n * đề 40px đứng trên hàng thân ~26px; (2) nó trộn hai trục, co cả chiều NGANG (px-4 → px-3)\n * trong khi \"gọn\" chỉ nên là chuyện chiều dọc; (3) bảng nào dùng `<Table>` trần thì không có\n * mật độ nào cả.\n *\n * Đặt biến ở chính thẻ `<table>` thì cả ba biến mất: `TableHead`/`TableCell` đã đọc\n * `--table-head-h`/`--row-py` nên tiêu đề và thân đi cùng nhau, và một bảng vẫn gọn được ngay\n * cả khi toàn app đang ở mức thoải mái (hoặc ngược lại).\n */\n const densityVars =\n density === \"compact\" ? ({ \"--row-py\": \"0.375rem\", \"--table-head-h\": \"2rem\" } as CSSProperties) : undefined;\n\n return (\n <div\n data-slot=\"data-table-container\"\n className={cn(\"relative w-full overflow-auto rounded-xl border border-border bg-card\", containerClassName)}\n >\n <table\n data-slot=\"data-table\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-busy={loading || undefined}\n className={cn(\n \"w-full caption-bottom text-sm\",\n // Cùng một chuỗi lớp với `<Table striped>` của Tier 1 — đổi một chỗ thì đổi cả hai.\n striped &&\n \"[&_tbody_tr:nth-child(even)]:bg-table-row-stripe [&_tbody_tr:nth-child(even):hover]:bg-table-row-hover\",\n className\n )}\n style={\n minWidth != null || densityVars ? { ...densityVars, ...(minWidth != null ? { minWidth } : null) } : undefined\n }\n >\n <TableHeader className={cn(stickyHeader && \"sticky top-0 z-10\")}>\n {/* Nền ĐẶC đặt trên <tr>, không phải <thead>: ô ghim dùng `bg-inherit` mà\n background-color kế thừa từ CHA TRỰC TIẾP (<tr>). Để nền ở <thead> thì <tr> vẫn\n trong suốt → cột ghim nhìn xuyên thấy dữ liệu cuộn phía dưới. */}\n <TableRow className=\"bg-muted hover:bg-muted\">\n {selectable && (\n <TableHead\n className=\"w-11 bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 3, width: 44, minWidth: 44 }}\n >\n <Checkbox\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={toggleAll}\n disabled={selectableIds.length === 0}\n aria-label={allSelected ? labels.deselectAllRows : labels.selectAllRows}\n />\n </TableHead>\n )}\n\n {shownColumns.map((column) => {\n const isSorted = sort?.columnId === column.id;\n const SortIcon = isSorted ? (sort?.direction === \"asc\" ? ArrowUp : ArrowDown) : ChevronsUpDown;\n const pinnedStyle = pinnedStyles.get(column.id);\n\n return (\n <TableHead\n key={column.id}\n scope=\"col\"\n aria-sort={isSorted ? (sort?.direction === \"asc\" ? \"ascending\" : \"descending\") : undefined}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.headerClassName\n )}\n style={{\n ...pinnedStyle,\n width: column.width,\n minWidth: column.minWidth ?? column.width\n }}\n >\n {column.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, column.id))}\n className={cn(\n \"-mx-1 inline-flex items-center gap-1 rounded-sm px-1 py-0.5 uppercase transition-colors hover:text-foreground\",\n column.align === \"right\" && \"flex-row-reverse\",\n isSorted && \"text-foreground\"\n )}\n >\n {column.header}\n {/* KHÔNG làm mờ icon chưa sắp xếp: nút này còn bấm được nên `opacity-*`\n không được miễn trừ ngưỡng 3:1 của WCAG 1.4.11 cho icon mang nghĩa.\n `opacity-50` cho ra 1,98:1 (light) / 2,23:1 (dark) trên nền `bg-muted`\n của hàng tiêu đề ✗; để nguyên `text-muted-foreground` là 4,89 / 4,86:1 ✓.\n Trạng thái ĐANG sắp xếp vẫn phân biệt được: nút đổi sang\n `text-foreground` và icon đổi hình (ArrowUp/ArrowDown ⇄ ChevronsUpDown). */}\n <SortIcon aria-hidden=\"true\" className=\"size-3.5\" />\n </button>\n ) : (\n column.header\n )}\n </TableHead>\n );\n })}\n </TableRow>\n </TableHeader>\n\n <TableBody>\n {loading &&\n Array.from({ length: loadingRowCount }).map((_, rowIndex) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: dòng khung xương không có danh tính dữ liệu\n <TableRow key={`skeleton-${rowIndex}`} className=\"hover:bg-transparent\">\n {selectable && (\n <TableCell>\n <Skeleton className=\"size-4 rounded-sm\" />\n </TableCell>\n )}\n {shownColumns.map((column) => (\n <TableCell key={column.id}>\n <Skeleton className=\"h-4 w-full max-w-[12rem]\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n\n {!loading && rows.length === 0 && (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"p-0\">\n {empty ?? <EmptyState icon={Inbox} title={labels.emptyTitle} description={labels.emptyDescription} />}\n </TableCell>\n </TableRow>\n )}\n\n {!loading &&\n rows.map((row, rowIndex) => {\n const id = getRowId(row, rowIndex);\n const isSelected = selected.includes(id);\n const canSelect = isRowSelectable?.(row, rowIndex) ?? true;\n const active = isRowActive?.(row, rowIndex) ?? false;\n\n return (\n <TableRow\n key={id}\n data-state={isSelected ? \"selected\" : undefined}\n aria-selected={selectable ? isSelected : undefined}\n onClick={onRowClick ? () => onRowClick(row, rowIndex) : undefined}\n // Dòng bấm được phải dùng được bằng BÀN PHÍM: đưa <tr> vào chuỗi Tab và tự\n // xử lý Enter/Space. Cố ý KHÔNG đặt role=\"button\" — role đó đè role=\"row\"\n // ngầm, trình đọc màn hình sẽ mất cấu trúc bảng (số dòng/cột, tiêu đề cột);\n // dòng vẫn là dòng, chỉ thêm khả năng kích hoạt.\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (event) => {\n // Chỉ nhận phím khi CHÍNH <tr> đang focus: Enter/Space bấm trên\n // control bên trong ô (checkbox, nút, link) đã có nghĩa riêng —\n // nổi bọt lên đây sẽ mở chi tiết ngoài ý muốn.\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n // Space mặc định cuộn trang, Enter có thể submit form bao ngoài.\n event.preventDefault();\n onRowClick(row, rowIndex);\n }\n : undefined\n }\n className={cn(\n // bg-card trên <tr> để ô ghim (`bg-inherit`) có màu đặc mà vẫn đổi theo\n // trạng thái chọn/hover của chính dòng đó.\n \"bg-card\",\n // Viền focus vẽ bằng `outline` chứ không `ring`: <tr> nằm trong bảng\n // border-collapse, box-shadow không được vẽ. Offset ÂM để viền nằm trong\n // lòng dòng, không bị khung cuộn cắt mất ở mép trái/phải.\n // Màu dùng `foreground` chứ không `ring` (gold #E8B824 chỉ ~1,9:1 trên nền\n // card — dưới 3:1 của WCAG 1.4.11 cho thành phần phi-văn-bản).\n onRowClick &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-foreground focus-visible:[outline-offset:-2px]\",\n /*\n * Dòng ĐANG MỞ đánh dấu bằng `outline`, KHÔNG bằng nền — hai lý do, cả hai\n * đều là lỗi thật của bản trước (`bg-app-accent/5`):\n *\n * 1. Lớp nền đó CÓ ALPHA. Ô cột ghim lấy nền bằng `bg-inherit` từ hàng, nên\n * nó thừa hưởng luôn alpha và trở nên trong mờ — cuộn ngang là chữ của\n * các cột bên dưới lộ xuyên qua cột ghim.\n * 2. Nó là một class trần, độ đặc tả (0,1,0). Sọc của `striped` khai ở cấp\n * `<table>` nên chọn tử ra (0,2,2) và THẮNG — tức ở bảng có sọc, dòng\n * đang mở không hiện gì ở hàng CHẴN.\n *\n * Và vì sao vẽ trên TỪNG Ô (`[&>td]`) chứ không phải `outline` trên `<tr>`:\n * ô cột ghim mang `z-index: 2` nên nó nằm ở lớp cao hơn và ĐÈ LÊN viền của\n * hàng — dấu bị cắt cụt đúng ở vùng cột ghim, kể cả khi chưa cuộn ngang. Đã\n * thử và chụp lại. Bóng INSET vẽ trong lòng từng ô, tức vẽ CÙNG lớp với ô đó,\n * nên cột ghim cũng có dấu; và vì là bóng chứ không phải viền nên nó không\n * cộng vào chiều cao — mở một dòng không làm bảng nhảy 4px.\n *\n * Không đụng nhóm `bg-*` nên miễn nhiễm cả hai lỗi trên: cột ghim giữ nền\n * đục, và sọc không có gì để tranh.\n *\n * Màu `--app-accent-ink` (bậc đi lên nền trang): 6,14:1 trên `--card` ở light\n * · 9,61:1 ở dark — trên ngưỡng 3:1 của WCAG 1.4.11. KHÔNG dùng\n * `--app-accent` trần: gold #E8B824 chỉ 1,86:1, đúng cái bẫy \"token tô NỀN\n * đem vẽ DẤU\".\n *\n * ⚠ Trong giá trị tuỳ ý phải viết `var(--app-accent-ink)` — TOKEN THÔ — chứ\n * KHÔNG phải `var(--color-app-accent-ink)`. `@theme inline` khai\n * `--color-app-accent-ink: var(--app-accent-ink)`, nghĩa là utility sinh ra\n * nhúng thẳng `var(--app-accent-ink)` nên nó KẾ THỪA được accent mà app đặt\n * trên một thẻ bọc; còn biến `--color-*` thì vẫn nằm ở `:root` với giá trị đã\n * hoá giải theo gốc. Đo tại chính ô: `--app-accent-ink` = #1A6668 (teal của\n * app) nhưng `--color-app-accent-ink` = #7A5800 (gold mặc định) — viết nhầm\n * là dấu ra sai màu app mà không có gì báo.\n */\n active &&\n \"[&>td]:shadow-[inset_0_2px_0_var(--app-accent-ink),inset_0_-2px_0_var(--app-accent-ink)]\",\n rowClassName?.(row, rowIndex)\n )}\n >\n {selectable && (\n <TableCell\n className=\"bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 1, width: 44, minWidth: 44 }}\n >\n {/* Chặn nổi bọt: bấm checkbox không được kích hoạt onRowClick (mở chi tiết) */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: lớp bọc chỉ chặn sự kiện, tương tác nằm ở Checkbox bên trong */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: phím bấm đã do Checkbox xử lý */}\n <span onClick={(event) => event.stopPropagation()}>\n <Checkbox\n checked={isSelected}\n disabled={!canSelect}\n onCheckedChange={() => toggleRow(id)}\n aria-label={isSelected ? labels.deselectRow : labels.selectRow}\n />\n </span>\n </TableCell>\n )}\n\n {shownColumns.map((column) => {\n const pinnedStyle = pinnedStyles.get(column.id);\n return (\n <TableCell\n key={column.id}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.cellClassName\n )}\n style={{ ...pinnedStyle, width: column.width, minWidth: column.minWidth ?? column.width }}\n >\n {column.cell(row, rowIndex)}\n </TableCell>\n );\n })}\n </TableRow>\n );\n })}\n </TableBody>\n </table>\n </div>\n );\n}\n\nexport interface DataTableColumnToggleProps<TRow> {\n columns: DataTableColumn<TRow>[];\n /** Mã các cột đang hiện — cùng nguồn với `DataTable.visibleColumnIds` (tập hợp, không phải thứ tự) */\n visibleColumnIds: string[];\n onVisibleColumnIdsChange: (visibleColumnIds: string[]) => void;\n /** Nhãn nút + tiêu đề menu; không truyền thì lấy `labels.columnToggle` */\n label?: string;\n /** Ghi đè chuỗi hiển thị (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n className?: string;\n}\n\n/**\n * Menu ẩn/hiện cột đi kèm `DataTable` (đặt trên thanh công cụ của trang danh sách).\n *\n * Không cho ẩn hết: luôn giữ lại ít nhất một cột, nếu không bảng thành khối trắng vô nghĩa\n * và người dùng không có đường quay lại.\n */\nexport function DataTableColumnToggle<TRow>({\n columns,\n visibleColumnIds,\n onVisibleColumnIdsChange,\n label,\n labels: labelsProp,\n className\n}: DataTableColumnToggleProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n const text = label ?? labels.columnToggle;\n const hideable = columns.filter((column) => column.hideable !== false);\n\n const toggle = (columnId: string) => {\n // Bật lại một cột thì dựng lại danh sách theo thứ tự `columns` — giữ cho giá trị app lưu\n // luôn cùng một hình dạng, dù `DataTable` đằng nào cũng bỏ qua thứ tự bên trong.\n const next = visibleColumnIds.includes(columnId)\n ? visibleColumnIds.filter((id) => id !== columnId)\n : columns.filter((column) => visibleColumnIds.includes(column.id) || column.id === columnId).map((c) => c.id);\n if (next.length === 0) return;\n onVisibleColumnIdsChange(next);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className={className} leftIcon={<SlidersHorizontal className=\"size-4\" />}>\n {text}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n <DropdownMenuLabel>{text}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {hideable.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={visibleColumnIds.includes(column.id)}\n onCheckedChange={() => toggle(column.id)}\n onSelect={(event) => event.preventDefault()}\n >\n {column.label ?? (typeof column.header === \"string\" ? column.header : column.id)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowDown, ArrowUp, ChevronsUpDown, Inbox, SlidersHorizontal } from \"lucide-react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { useComitorLabels } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Checkbox } from \"../ui/checkbox.js\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../ui/dropdown-menu.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport { TableBody, TableCell, TableHead, TableHeader, TableRow } from \"../ui/table.js\";\nimport type { DataTableLabels } from \"./data-table-constants.js\";\nimport { EmptyState } from \"./empty-state.js\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type DataTableAlign = \"left\" | \"center\" | \"right\";\n\nexport interface DataTableColumn<TRow> {\n id: string;\n /** Nội dung ô tiêu đề */\n header: ReactNode;\n /** Nhãn chữ dùng trong menu ẩn/hiện cột — bắt buộc khi `header` không phải chuỗi */\n label?: string;\n /** Render ô dữ liệu — nhận cả chỉ số dòng để đánh số thứ tự */\n cell: (row: TRow, rowIndex: number) => ReactNode;\n sortable?: boolean;\n align?: DataTableAlign;\n /**\n * Bề rộng cột tính bằng px. BẮT BUỘC với cột ghim (`pinned`) vì offset `left/right` của\n * sticky được tính cộng dồn từ giá trị này — thiếu width thì các cột ghim chồng lên nhau.\n */\n width?: number;\n minWidth?: number;\n /** Ghim cột khi cuộn ngang (bảng nhiều cột) */\n pinned?: \"left\" | \"right\";\n /** Cho phép người dùng ẩn cột này (mặc định true; cột định danh nên đặt false) */\n hideable?: boolean;\n headerClassName?: string;\n cellClassName?: string;\n}\n\nexport interface DataTableProps<TRow> {\n columns: DataTableColumn<TRow>[];\n rows: TRow[];\n /** Khoá ổn định của dòng — dùng cho `key` React và cho tập chọn */\n getRowId: (row: TRow, index: number) => string;\n\n /** Đang tải → hiện `loadingRowCount` dòng khung xương thay vì bảng trống */\n loading?: boolean;\n loadingRowCount?: number;\n /** Nội dung khi không có dòng nào (mặc định EmptyState theo `labels.emptyTitle`) */\n empty?: ReactNode;\n\n /** Sắp xếp — controlled; `null` = không sắp xếp. App tự sắp/tự gọi API. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Chọn dòng bằng checkbox — controlled */\n selectable?: boolean;\n selectedIds?: string[];\n onSelectedIdsChange?: (ids: string[]) => void;\n /** Khoá chọn với một số dòng (vd bản ghi đã khoá) */\n isRowSelectable?: (row: TRow, index: number) => boolean;\n\n /**\n * Mã các cột đang hiện — controlled. Bỏ trống = hiện tất cả. Đổi bố cục đi qua\n * `DataTableColumnToggle`, app tự lưu theo người dùng (server, localStorage…).\n *\n * ⚠ Đây là một **TẬP HỢP**, không phải thứ tự: thứ tự cột luôn lấy từ mảng `columns`, thứ tự\n * bên trong mảng này bị bỏ qua. Tên có hậu tố `Ids` chính là để nói đúng thứ nó chứa —\n * 0.1.0 đặt `visibleColumns` khiến người đọc tưởng là mảng cột (`DataTableColumn[]`) hoặc\n * tưởng sắp lại mảng thì cột đổi chỗ.\n *\n * Cố ý KHÔNG dùng `Record<string, boolean>`: kiểu đó có trạng thái thứ ba — **khoá vắng\n * mặt**. App thêm cột mới ở bản sau thì bố cục người dùng đã lưu không có khoá đó, mỗi nơi\n * tự đoán hiện/ẩn → lỗi im lặng. Một mảng mã cột thì `JSON.stringify` là lưu được.\n *\n * Package cố ý KHÔNG tự ghi localStorage: ở SaaS nhiều workspace, bố cục bảng phải theo\n * cặp (người dùng × workspace) và thường cần đồng bộ giữa các máy — chuyện của app.\n */\n visibleColumnIds?: string[];\n\n /**\n * Bấm vào dòng (vd mở chi tiết bản ghi). Có prop này thì dòng vào chuỗi Tab và nhận\n * Enter/Space — mở chi tiết KHÔNG được là thao tác chỉ-chuột.\n */\n onRowClick?: (row: TRow, index: number) => void;\n rowClassName?: (row: TRow, index: number) => string | undefined;\n /**\n * Dòng đang được làm nổi (vd bản ghi vừa mở ở panel bên).\n *\n * Đánh dấu bằng `outline` chứ không bằng nền — xem lý do đầy đủ ở chỗ áp class. Tóm tắt: nền\n * có alpha sẽ làm ô cột GHIM trong mờ, và nền class trần thì thua sọc của `striped` về độ đặc\n * tả nên mất hẳn ở hàng chẵn. `outline` không dính cả hai.\n *\n * ⚠ Vì thế app KHÔNG cần tự vá bằng `rowClassName` nữa — dùng thẳng prop này.\n */\n isRowActive?: (row: TRow, index: number) => boolean;\n\n /** Tiêu đề dính đỉnh vùng cuộn (mặc định true) */\n stickyHeader?: boolean;\n /**\n * Tô nền xen kẽ cho hàng chẵn — mẫu DataTable ở mục **Patterns** của comitor-ds dùng\n * `<Table striped>`, còn `DataTable` trước đây không có đường bật.\n *\n * Mặc định `false`: bảng dài và dày cột thì sọc giúp dò hàng, nhưng bảng ngắn thì nó chỉ thêm\n * nhiễu. Đây là quyết định của từng trang, không phải mặc định của gói.\n *\n * ⚠ Sọc PHẢI là màu ĐỤC (`--table-row-stripe`), không phải `bg-muted/30` như bản trước.\n *\n * Vì sao: sọc khai bằng biến thể tuỳ ý ở cấp `<table>` nên chọn tử sinh ra là\n * `.[…] tbody tr:nth-child(even)` — độ đặc tả (0,2,2), trong khi `bg-card` mà `<TableRow>` tự\n * mang chỉ là một class (0,1,0). Sọc THẮNG và THAY THẾ `bg-card` chứ không chồng lên nó, nên\n * hàng chẵn còn alpha 0,3 mà không có lớp đục nào bên dưới. Ô cột ghim lấy `bg-inherit` từ hàng\n * → cũng trong mờ → cuộn ngang là chữ của các cột bên dưới lộ xuyên qua cột ghim. Đây đúng là\n * lỗi người dùng nhìn thấy, và nó chỉ xuất hiện ở hàng CHẴN nên rất dễ tưởng là \"hàng đầu ổn\".\n *\n * (Chú thích cũ ở đây giải thích rằng \"nền trạng thái luôn thắng vì ghi sau trong chuỗi lớp\" —\n * SAI, và đó chính là lý do lỗi trên lọt qua: thứ tự trong chuỗi class không quyết định gì, độ\n * đặc tả mới quyết định. Nền của hàng ĐANG MỞ `bg-app-accent/5` là (0,1,0) nên nó cũng thua\n * sọc — nhưng hàng đang mở vốn được đánh dấu bằng `outline`, không bằng nền, nên chỗ đó không\n * lộ ra. Đừng lấy nền làm dấu hiệu hàng ở bảng có sọc.)\n */\n striped?: boolean;\n /** Bề rộng tối thiểu của bảng — bật cuộn ngang khi màn hình hẹp */\n minWidth?: number | string;\n /**\n * Ghi đè mật độ cho RIÊNG bảng này, bất kể trục mật độ toàn app đang ở mức nào.\n *\n * Bỏ trống = theo `<html data-density>`. Đặt `\"compact\"` thì bảng tự đặt lại `--row-py` và\n * `--table-head-h` ngay trên thẻ `<table>`, nên tiêu đề và thân đi cùng nhau — bản trước chỉ\n * gắn class vào ô THÂN nên `compact` cho hàng tiêu đề 40px đứng trên hàng thân ~26px.\n */\n density?: \"comfortable\" | \"compact\";\n /** Ghi đè chuỗi hiển thị/a11y (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n /** Nhãn của bảng cho trình đọc màn hình (bắt buộc khi trang có nhiều bảng) */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n className?: string;\n /** className của khung cuộn bọc ngoài */\n containerClassName?: string;\n}\n\nconst alignClasses: Record<DataTableAlign, string> = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\"\n};\n\n/** Offset sticky cộng dồn cho cột ghim trái/phải. */\nfunction getPinnedOffsets<TRow>(columns: DataTableColumn<TRow>[], selectable: boolean): Map<string, CSSProperties> {\n const styles = new Map<string, CSSProperties>();\n // Cột checkbox (nếu có) luôn ghim trái và chiếm 44px — cột ghim đầu tiên phải bắt đầu sau nó.\n let left = selectable ? 44 : 0;\n for (const column of columns) {\n if (column.pinned !== \"left\") continue;\n styles.set(column.id, { position: \"sticky\", left, zIndex: 2 });\n left += column.width ?? 0;\n }\n\n let right = 0;\n for (const column of [...columns].reverse()) {\n if (column.pinned !== \"right\") continue;\n styles.set(column.id, { position: \"sticky\", right, zIndex: 2 });\n right += column.width ?? 0;\n }\n\n return styles;\n}\n\nfunction nextSort(current: DataTableSort | null | undefined, columnId: string): DataTableSort | null {\n // asc → desc → bỏ sắp xếp. Vòng 3 trạng thái để người dùng quay lại thứ tự gốc của server\n // mà không phải tải lại trang.\n if (!current || current.columnId !== columnId) return { columnId, direction: \"asc\" };\n if (current.direction === \"asc\") return { columnId, direction: \"desc\" };\n return null;\n}\n\n/**\n * Bảng dữ liệu chuẩn của Comitor: tiêu đề dính, sắp xếp, chọn dòng, ghim cột, ẩn/hiện cột,\n * trạng thái tải và trạng thái rỗng.\n *\n * Mọi trạng thái đều **controlled** — package không giữ dữ liệu, không gọi API, không tự phân\n * trang. Nhờ vậy dùng được cả với dữ liệu client (mảng trong bộ nhớ) lẫn server-side\n * pagination mà không phải viết hai component.\n */\nexport function DataTable<TRow>({\n columns,\n rows,\n getRowId,\n loading = false,\n loadingRowCount = 5,\n empty,\n sort,\n onSortChange,\n selectable = false,\n selectedIds,\n onSelectedIdsChange,\n isRowSelectable,\n visibleColumnIds,\n onRowClick,\n rowClassName,\n isRowActive,\n stickyHeader = true,\n striped = false,\n minWidth,\n density = \"comfortable\",\n labels: labelsProp,\n className,\n containerClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby\n}: DataTableProps<TRow>) {\n const labels = useComitorLabels(\"dataTable\", labelsProp);\n // Thứ tự luôn theo `columns` — `visibleColumnIds` chỉ trả lời \"cột nào được hiện\".\n const shownColumns = visibleColumnIds ? columns.filter((column) => visibleColumnIds.includes(column.id)) : columns;\n const pinnedStyles = getPinnedOffsets(shownColumns, selectable);\n const colCount = shownColumns.length + (selectable ? 1 : 0);\n\n const selected = selectedIds ?? [];\n // Một lượt duyệt: vừa lọc dòng chọn được vừa lấy id — tránh indexOf() lồng trong map (O(n²))\n // và tránh sai khi hai dòng cùng tham chiếu object.\n const selectableIds: string[] = [];\n rows.forEach((row, index) => {\n if (isRowSelectable?.(row, index) ?? true) selectableIds.push(getRowId(row, index));\n });\n const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selected.includes(id));\n const someSelected = !allSelected && selectableIds.some((id) => selected.includes(id));\n\n const toggleAll = () => {\n if (!onSelectedIdsChange) return;\n if (allSelected) {\n onSelectedIdsChange(selected.filter((id) => !selectableIds.includes(id)));\n return;\n }\n onSelectedIdsChange([...new Set([...selected, ...selectableIds])]);\n };\n\n const toggleRow = (id: string) => {\n if (!onSelectedIdsChange) return;\n onSelectedIdsChange(selected.includes(id) ? selected.filter((item) => item !== id) : [...selected, id]);\n };\n\n /*\n * `density` nay là GHI ĐÈ CỤC BỘ của trục mật độ toàn cục, không phải một bảng lớp riêng.\n *\n * Bản trước là `density === \"compact\" ? \"px-3 py-1.5\" : \"px-4 py-3\"` gắn tay vào bốn ô THÂN —\n * ba khuyết tật cùng lúc: (1) nó không bao giờ tới `<TableHead>`, nên `compact` cho hàng tiêu\n * đề 40px đứng trên hàng thân ~26px; (2) nó trộn hai trục, co cả chiều NGANG (px-4 → px-3)\n * trong khi \"gọn\" chỉ nên là chuyện chiều dọc; (3) bảng nào dùng `<Table>` trần thì không có\n * mật độ nào cả.\n *\n * Đặt biến ở chính thẻ `<table>` thì cả ba biến mất: `TableHead`/`TableCell` đã đọc\n * `--table-head-h`/`--row-py` nên tiêu đề và thân đi cùng nhau, và một bảng vẫn gọn được ngay\n * cả khi toàn app đang ở mức thoải mái (hoặc ngược lại).\n */\n const densityVars =\n density === \"compact\" ? ({ \"--row-py\": \"0.375rem\", \"--table-head-h\": \"2rem\" } as CSSProperties) : undefined;\n\n return (\n <div\n data-slot=\"data-table-container\"\n className={cn(\"relative w-full overflow-auto rounded-xl border border-border bg-card\", containerClassName)}\n >\n <table\n data-slot=\"data-table\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-busy={loading || undefined}\n className={cn(\n \"w-full caption-bottom text-sm\",\n // Cùng một chuỗi lớp với `<Table striped>` của Tier 1 — đổi một chỗ thì đổi cả hai.\n striped &&\n \"[&_tbody_tr:nth-child(even)]:bg-table-row-stripe [&_tbody_tr:nth-child(even):hover]:bg-table-row-hover\",\n className\n )}\n style={\n minWidth != null || densityVars ? { ...densityVars, ...(minWidth != null ? { minWidth } : null) } : undefined\n }\n >\n <TableHeader className={cn(stickyHeader && \"sticky top-0 z-10\")}>\n {/* Nền ĐẶC đặt trên <tr>, không phải <thead>: ô ghim dùng `bg-inherit` mà\n background-color kế thừa từ CHA TRỰC TIẾP (<tr>). Để nền ở <thead> thì <tr> vẫn\n trong suốt → cột ghim nhìn xuyên thấy dữ liệu cuộn phía dưới. */}\n <TableRow className=\"bg-muted hover:bg-muted\">\n {selectable && (\n <TableHead\n className=\"w-11 bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 3, width: 44, minWidth: 44 }}\n >\n <Checkbox\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={toggleAll}\n disabled={selectableIds.length === 0}\n aria-label={allSelected ? labels.deselectAllRows : labels.selectAllRows}\n />\n </TableHead>\n )}\n\n {shownColumns.map((column) => {\n const isSorted = sort?.columnId === column.id;\n const SortIcon = isSorted ? (sort?.direction === \"asc\" ? ArrowUp : ArrowDown) : ChevronsUpDown;\n const pinnedStyle = pinnedStyles.get(column.id);\n\n return (\n <TableHead\n key={column.id}\n scope=\"col\"\n aria-sort={isSorted ? (sort?.direction === \"asc\" ? \"ascending\" : \"descending\") : undefined}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.headerClassName\n )}\n style={{\n ...pinnedStyle,\n width: column.width,\n minWidth: column.minWidth ?? column.width\n }}\n >\n {column.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, column.id))}\n className={cn(\n \"-mx-1 inline-flex items-center gap-1 rounded-sm px-1 py-0.5 uppercase transition-colors hover:text-foreground\",\n column.align === \"right\" && \"flex-row-reverse\",\n isSorted && \"text-foreground\"\n )}\n >\n {column.header}\n {/* KHÔNG làm mờ icon chưa sắp xếp: nút này còn bấm được nên `opacity-*`\n không được miễn trừ ngưỡng 3:1 của WCAG 1.4.11 cho icon mang nghĩa.\n `opacity-50` cho ra 1,98:1 (light) / 2,23:1 (dark) trên nền `bg-muted`\n của hàng tiêu đề ✗; để nguyên `text-muted-foreground` là 4,89 / 4,86:1 ✓.\n Trạng thái ĐANG sắp xếp vẫn phân biệt được: nút đổi sang\n `text-foreground` và icon đổi hình (ArrowUp/ArrowDown ⇄ ChevronsUpDown). */}\n <SortIcon aria-hidden=\"true\" className=\"size-3.5\" />\n </button>\n ) : (\n column.header\n )}\n </TableHead>\n );\n })}\n </TableRow>\n </TableHeader>\n\n <TableBody>\n {loading &&\n Array.from({ length: loadingRowCount }).map((_, rowIndex) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: dòng khung xương không có danh tính dữ liệu\n <TableRow key={`skeleton-${rowIndex}`} className=\"hover:bg-transparent\">\n {selectable && (\n <TableCell>\n <Skeleton className=\"size-4 rounded-sm\" />\n </TableCell>\n )}\n {shownColumns.map((column) => (\n <TableCell key={column.id}>\n <Skeleton className=\"h-4 w-full max-w-[12rem]\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n\n {!loading && rows.length === 0 && (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"p-0\">\n {empty ?? <EmptyState icon={Inbox} title={labels.emptyTitle} description={labels.emptyDescription} />}\n </TableCell>\n </TableRow>\n )}\n\n {!loading &&\n rows.map((row, rowIndex) => {\n const id = getRowId(row, rowIndex);\n const isSelected = selected.includes(id);\n const canSelect = isRowSelectable?.(row, rowIndex) ?? true;\n const active = isRowActive?.(row, rowIndex) ?? false;\n\n return (\n <TableRow\n key={id}\n data-state={isSelected ? \"selected\" : undefined}\n aria-selected={selectable ? isSelected : undefined}\n onClick={onRowClick ? () => onRowClick(row, rowIndex) : undefined}\n // Dòng bấm được phải dùng được bằng BÀN PHÍM: đưa <tr> vào chuỗi Tab và tự\n // xử lý Enter/Space. Cố ý KHÔNG đặt role=\"button\" — role đó đè role=\"row\"\n // ngầm, trình đọc màn hình sẽ mất cấu trúc bảng (số dòng/cột, tiêu đề cột);\n // dòng vẫn là dòng, chỉ thêm khả năng kích hoạt.\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (event) => {\n // Chỉ nhận phím khi CHÍNH <tr> đang focus: Enter/Space bấm trên\n // control bên trong ô (checkbox, nút, link) đã có nghĩa riêng —\n // nổi bọt lên đây sẽ mở chi tiết ngoài ý muốn.\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n // Space mặc định cuộn trang, Enter có thể submit form bao ngoài.\n event.preventDefault();\n onRowClick(row, rowIndex);\n }\n : undefined\n }\n className={cn(\n // bg-card trên <tr> để ô ghim (`bg-inherit`) có màu đặc mà vẫn đổi theo\n // trạng thái chọn/hover của chính dòng đó.\n \"bg-card\",\n // Viền focus vẽ bằng `outline` chứ không `ring`: <tr> nằm trong bảng\n // border-collapse, box-shadow không được vẽ. Offset ÂM để viền nằm trong\n // lòng dòng, không bị khung cuộn cắt mất ở mép trái/phải.\n // Màu dùng `foreground` chứ không `ring` (gold #E8B824 chỉ ~1,9:1 trên nền\n // card — dưới 3:1 của WCAG 1.4.11 cho thành phần phi-văn-bản).\n onRowClick &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-foreground focus-visible:[outline-offset:-2px]\",\n /*\n * Dòng ĐANG MỞ đánh dấu bằng `outline`, KHÔNG bằng nền — hai lý do, cả hai\n * đều là lỗi thật của bản trước (`bg-app-accent/5`):\n *\n * 1. Lớp nền đó CÓ ALPHA. Ô cột ghim lấy nền bằng `bg-inherit` từ hàng, nên\n * nó thừa hưởng luôn alpha và trở nên trong mờ — cuộn ngang là chữ của\n * các cột bên dưới lộ xuyên qua cột ghim.\n * 2. Nó là một class trần, độ đặc tả (0,1,0). Sọc của `striped` khai ở cấp\n * `<table>` nên chọn tử ra (0,2,2) và THẮNG — tức ở bảng có sọc, dòng\n * đang mở không hiện gì ở hàng CHẴN.\n *\n * Và vì sao vẽ trên TỪNG Ô (`[&>td]`) chứ không phải `outline` trên `<tr>`:\n * ô cột ghim mang `z-index: 2` nên nó nằm ở lớp cao hơn và ĐÈ LÊN viền của\n * hàng — dấu bị cắt cụt đúng ở vùng cột ghim, kể cả khi chưa cuộn ngang. Đã\n * thử và chụp lại. Bóng INSET vẽ trong lòng từng ô, tức vẽ CÙNG lớp với ô đó,\n * nên cột ghim cũng có dấu; và vì là bóng chứ không phải viền nên nó không\n * cộng vào chiều cao — mở một dòng không làm bảng nhảy 4px.\n *\n * Không đụng nhóm `bg-*` nên miễn nhiễm cả hai lỗi trên: cột ghim giữ nền\n * đục, và sọc không có gì để tranh.\n *\n * Màu `--app-accent-ink` (bậc đi lên nền trang): 6,14:1 trên `--card` ở light\n * · 9,61:1 ở dark — trên ngưỡng 3:1 của WCAG 1.4.11. KHÔNG dùng\n * `--app-accent` trần: gold #E8B824 chỉ 1,86:1, đúng cái bẫy \"token tô NỀN\n * đem vẽ DẤU\".\n *\n * ⚠ Trong giá trị tuỳ ý phải viết `var(--app-accent-ink)` — TOKEN THÔ — chứ\n * KHÔNG phải `var(--color-app-accent-ink)`. `@theme inline` khai\n * `--color-app-accent-ink: var(--app-accent-ink)`, nghĩa là utility sinh ra\n * nhúng thẳng `var(--app-accent-ink)` nên nó KẾ THỪA được accent mà app đặt\n * trên một thẻ bọc; còn biến `--color-*` thì vẫn nằm ở `:root` với giá trị đã\n * hoá giải theo gốc. Đo tại chính ô: `--app-accent-ink` = #1A6668 (teal của\n * app) nhưng `--color-app-accent-ink` = #7A5800 (gold mặc định) — viết nhầm\n * là dấu ra sai màu app mà không có gì báo.\n */\n active &&\n \"[&>td]:shadow-[inset_0_2px_0_var(--app-accent-ink),inset_0_-2px_0_var(--app-accent-ink)]\",\n rowClassName?.(row, rowIndex)\n )}\n >\n {selectable && (\n <TableCell\n className=\"bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 1, width: 44, minWidth: 44 }}\n >\n {/* Chặn nổi bọt: bấm checkbox không được kích hoạt onRowClick (mở chi tiết) */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: lớp bọc chỉ chặn sự kiện, tương tác nằm ở Checkbox bên trong */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: phím bấm đã do Checkbox xử lý */}\n <span onClick={(event) => event.stopPropagation()}>\n <Checkbox\n checked={isSelected}\n disabled={!canSelect}\n onCheckedChange={() => toggleRow(id)}\n aria-label={isSelected ? labels.deselectRow : labels.selectRow}\n />\n </span>\n </TableCell>\n )}\n\n {shownColumns.map((column) => {\n const pinnedStyle = pinnedStyles.get(column.id);\n return (\n <TableCell\n key={column.id}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.cellClassName\n )}\n style={{ ...pinnedStyle, width: column.width, minWidth: column.minWidth ?? column.width }}\n >\n {column.cell(row, rowIndex)}\n </TableCell>\n );\n })}\n </TableRow>\n );\n })}\n </TableBody>\n </table>\n </div>\n );\n}\n\nexport interface DataTableColumnToggleProps<TRow> {\n columns: DataTableColumn<TRow>[];\n /** Mã các cột đang hiện — cùng nguồn với `DataTable.visibleColumnIds` (tập hợp, không phải thứ tự) */\n visibleColumnIds: string[];\n onVisibleColumnIdsChange: (visibleColumnIds: string[]) => void;\n /** Nhãn nút + tiêu đề menu; không truyền thì lấy `labels.columnToggle` */\n label?: string;\n /** Ghi đè chuỗi hiển thị (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n className?: string;\n}\n\n/**\n * Menu ẩn/hiện cột đi kèm `DataTable` (đặt trên thanh công cụ của trang danh sách).\n *\n * Không cho ẩn hết: luôn giữ lại ít nhất một cột, nếu không bảng thành khối trắng vô nghĩa\n * và người dùng không có đường quay lại.\n */\nexport function DataTableColumnToggle<TRow>({\n columns,\n visibleColumnIds,\n onVisibleColumnIdsChange,\n label,\n labels: labelsProp,\n className\n}: DataTableColumnToggleProps<TRow>) {\n const labels = useComitorLabels(\"dataTable\", labelsProp);\n const text = label ?? labels.columnToggle;\n const hideable = columns.filter((column) => column.hideable !== false);\n\n const toggle = (columnId: string) => {\n // Bật lại một cột thì dựng lại danh sách theo thứ tự `columns` — giữ cho giá trị app lưu\n // luôn cùng một hình dạng, dù `DataTable` đằng nào cũng bỏ qua thứ tự bên trong.\n const next = visibleColumnIds.includes(columnId)\n ? visibleColumnIds.filter((id) => id !== columnId)\n : columns.filter((column) => visibleColumnIds.includes(column.id) || column.id === columnId).map((c) => c.id);\n if (next.length === 0) return;\n onVisibleColumnIdsChange(next);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className={className} leftIcon={<SlidersHorizontal className=\"size-4\" />}>\n {text}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n <DropdownMenuLabel>{text}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {hideable.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={visibleColumnIds.includes(column.id)}\n onCheckedChange={() => toggle(column.id)}\n onSelect={(event) => event.preventDefault()}\n >\n {column.label ?? (typeof column.header === \"string\" ? column.header : column.id)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
@@ -18,8 +18,9 @@ import { DateRange } from 'react-day-picker';
18
18
  * Định dạng ngày của Việt Nam.
19
19
  *
20
20
  * ⚠ KHÔNG dùng nó làm định dạng mặc định nữa — `DatePicker`/`DateRangePicker` suy định dạng từ
21
- * `locale` bằng `dateFormatForLocale()` bên dưới, nên một app tiếng Anh ra `MM/dd/yyyy` thay vì
22
- * ngày Việt Nam. Hằng này giữ lại vì nó là API công khai và vẫn đúng khi app cần ghim cứng ngày
21
+ * `locale` bằng `dateFormatForLocale()` bên dưới (không truyền `locale` thì lấy locale date-fns
22
+ * của `ComitorLocaleProvider`), nên một app tiếng Anh ra `MM/dd/yyyy` thay vì ngày Việt Nam.
23
+ * Hằng này giữ lại vì nó là API công khai và vẫn đúng khi app cần ghim cứng ngày
23
24
  * kiểu Việt Nam ở một chỗ cụ thể (hoá đơn in ra, tệp xuất cho cơ quan thuế…).
24
25
  */
25
26
  declare const VI_DATE_FORMAT = "dd/MM/yyyy";
@@ -38,12 +39,13 @@ declare function dateFormatForLocale(locale: Locale): string;
38
39
  /**
39
40
  * Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `DatePicker` — không hiện thành chữ trên giao diện.
40
41
  *
41
- * Vì sao vẫn phải override được: chúng là chuỗi tiếng Việt cứng nằm trong `sr-only`, nên một app
42
- * tiếng Anh trông thì đúng nhưng ĐỌC ra vẫn là tiếng Việt — lỗi không ai thấy trên ảnh chụp và
43
- * cũng không có gì báo đỏ.
42
+ * Theo `ComitorLocaleProvider` (namespace `datePicker`, mặc định tiếng Anh); đè từng khoá qua prop
43
+ * `labels`. Chúng nằm trong `sr-only`, nên sai ngôn ngữ ở đây là lỗi không ảnh chụp nào thấy: giao
44
+ * diện nhìn thì đúng mà ĐỌC ra lại là thứ tiếng khác.
44
45
  *
45
- * ⚠ `locale` KHÔNG dịch mấy nhãn này, và không thể: locale của date-fns chỉ mang cách viết ngày
46
- * tháng, không mang chuỗi giao diện. Đổi ngôn ngữ app thì phải truyền CẢ `locale` lẫn `labels`.
46
+ * ⚠ Prop `locale` (date-fns) KHÔNG dịch mấy nhãn này, và không thể: locale của date-fns chỉ mang
47
+ * cách viết ngày tháng, không mang chuỗi giao diện. Provider thì lo CẢ HAI — chỉ khi đè riêng một ô
48
+ * bằng prop `locale` mới phải nhớ truyền kèm `labels`.
47
49
  */
48
50
  interface DatePickerLabels {
49
51
  /** Nút xoá ngày đang chọn (`DatePicker`) */
@@ -53,21 +55,42 @@ interface DatePickerLabels {
53
55
  /** Nút mở lịch (`DatePicker`; `DateRangePicker` mở lịch bằng chính ô của nó nên không dùng khoá này) */
54
56
  openCalendar: string;
55
57
  }
58
+ /**
59
+ * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:
60
+ * `comitorMessagesVi.datePicker`. Component không đọc thẳng hằng này — nó đọc bảng của
61
+ * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
62
+ *
63
+ * Nhặt đúng ba khoá chứ không trỏ thẳng `datePickerEn`: bảng đó còn mang `rangePlaceholder` và
64
+ * `presets`, không thuộc hình dạng `DatePickerLabels`.
65
+ */
56
66
  declare const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels;
57
67
  interface DatePreset {
58
68
  id: string;
69
+ /** Hiện NGUYÊN VĂN — preset app tự dựng không bao giờ bị gói dịch lại */
59
70
  label: string;
60
71
  /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */
61
72
  getValue: () => Date;
62
73
  }
63
74
  interface DateRangePreset {
64
75
  id: string;
76
+ /** Hiện NGUYÊN VĂN — preset app tự dựng không bao giờ bị gói dịch lại */
65
77
  label: string;
66
78
  getValue: () => DateRange;
67
79
  }
68
- /** Preset mặc định cho ô chọn một ngày. */
80
+ /**
81
+ * Preset mặc định cho ô chọn một ngày. `DatePicker` KHÔNG tự bật chúng (`presets` mặc định `false`)
82
+ * — truyền `presets={DEFAULT_DATE_PRESETS}` để hiện.
83
+ *
84
+ * `label` ở đây là tiếng Anh (từ `3.0.0`), nhưng component nhận ra chính các object này và hiện
85
+ * nhãn theo `ComitorLocaleProvider` (`datePicker.presets`) — kể cả khi app lọc bớt mảng. Muốn
86
+ * nhãn khác thì dựng preset mới (`{ ...preset, label }`): object mới hiện đúng `label` của nó.
87
+ */
69
88
  declare const DEFAULT_DATE_PRESETS: DatePreset[];
70
- /** Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách). */
89
+ /**
90
+ * Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách) — `DateRangePicker` dùng khi
91
+ * app không truyền `presets`. Nhãn: như `DEFAULT_DATE_PRESETS` (tiếng Anh ở hằng, theo provider
92
+ * khi hiện).
93
+ */
71
94
  declare const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[];
72
95
 
73
96
  export { DEFAULT_DATE_PICKER_LABELS, DEFAULT_DATE_PRESETS, DEFAULT_DATE_RANGE_PRESETS, type DatePickerLabels, type DatePreset, type DateRangePreset, VI_DATE_FORMAT, dateFormatForLocale };
@@ -1,38 +1,43 @@
1
1
  import { endOfMonth, startOfDay, startOfMonth, subDays, subMonths } from "date-fns";
2
+ import { datePickerEn } from "../../i18n/namespaces/date-picker.js";
2
3
  const VI_DATE_FORMAT = "dd/MM/yyyy";
3
4
  function dateFormatForLocale(locale) {
4
5
  return locale.formatLong.date({ width: "short" }).replace(/y+/g, (run) => run.length === 1 ? "yyyy" : run);
5
6
  }
6
7
  const DEFAULT_DATE_PICKER_LABELS = {
7
- clear: "X\xF3a ng\xE0y",
8
- clearRange: "X\xF3a kho\u1EA3ng ng\xE0y",
9
- openCalendar: "M\u1EDF l\u1ECBch"
8
+ clear: datePickerEn.clear,
9
+ clearRange: datePickerEn.clearRange,
10
+ openCalendar: datePickerEn.openCalendar
10
11
  };
11
12
  const DEFAULT_DATE_PRESETS = [
12
- { id: "today", label: "H\xF4m nay", getValue: () => startOfDay(/* @__PURE__ */ new Date()) },
13
- { id: "yesterday", label: "H\xF4m qua", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 1)) },
14
- { id: "week-ago", label: "7 ng\xE0y tr\u01B0\u1EDBc", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 7)) }
13
+ { id: "today", label: datePickerEn.presets.today, getValue: () => startOfDay(/* @__PURE__ */ new Date()) },
14
+ { id: "yesterday", label: datePickerEn.presets.yesterday, getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 1)) },
15
+ { id: "week-ago", label: datePickerEn.presets.weekAgo, getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 7)) }
15
16
  ];
16
17
  const DEFAULT_DATE_RANGE_PRESETS = [
17
- { id: "today", label: "H\xF4m nay", getValue: () => ({ from: startOfDay(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) }) },
18
+ {
19
+ id: "today",
20
+ label: datePickerEn.presets.today,
21
+ getValue: () => ({ from: startOfDay(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) })
22
+ },
18
23
  {
19
24
  id: "last-7",
20
- label: "7 ng\xE0y qua",
25
+ label: datePickerEn.presets.last7Days,
21
26
  getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 6)), to: startOfDay(/* @__PURE__ */ new Date()) })
22
27
  },
23
28
  {
24
29
  id: "last-30",
25
- label: "30 ng\xE0y qua",
30
+ label: datePickerEn.presets.last30Days,
26
31
  getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 29)), to: startOfDay(/* @__PURE__ */ new Date()) })
27
32
  },
28
33
  {
29
34
  id: "this-month",
30
- label: "Th\xE1ng n\xE0y",
35
+ label: datePickerEn.presets.thisMonth,
31
36
  getValue: () => ({ from: startOfMonth(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) })
32
37
  },
33
38
  {
34
39
  id: "last-month",
35
- label: "Th\xE1ng tr\u01B0\u1EDBc",
40
+ label: datePickerEn.presets.lastMonth,
36
41
  getValue: () => {
37
42
  const previous = subMonths(/* @__PURE__ */ new Date(), 1);
38
43
  return { from: startOfMonth(previous), to: endOfMonth(previous) };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/date-picker-constants.ts"],"sourcesContent":["/**\n * Định dạng ngày và preset mặc định của `DatePicker` / `DateRangePicker`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, sentinel) là thứ trang server hay đọc nhất — spread để override một\n * khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `date-picker.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\nimport { endOfMonth, type Locale, startOfDay, startOfMonth, subDays, subMonths } from \"date-fns\";\nimport type { DateRange } from \"react-day-picker\";\n\n/**\n * Định dạng ngày của Việt Nam.\n *\n * ⚠ KHÔNG dùng nó làm định dạng mặc định nữa — `DatePicker`/`DateRangePicker` suy định dạng từ\n * `locale` bằng `dateFormatForLocale()` bên dưới, nên một app tiếng Anh ra `MM/dd/yyyy` thay vì\n * ngày Việt Nam. Hằng này giữ lại vì nó là API công khai và vẫn đúng khi app cần ghim cứng ngày\n * kiểu Việt Nam ở một chỗ cụ thể (hoá đơn in ra, tệp xuất cho cơ quan thuế…).\n */\nexport const VI_DATE_FORMAT = \"dd/MM/yyyy\";\n\n/**\n * Định dạng ngày SỐ của một `locale` date-fns — dùng cho cả `format`, `parse` và chuỗi gợi ý\n * trong ô nhập, nên ba thứ đó không bao giờ lệch nhau.\n *\n * `formatLong.date({ width: \"short\" })` là nguồn: `vi` cho `dd/MM/y`, `en-US` cho `MM/dd/yyyy`,\n * `ja` cho `y/MM/dd`. Một chữ `y` đơn được nong thành `yyyy` — không phải để đổi kết quả (`y` in\n * ra \"2026\" y hệt) mà vì chuỗi này CÒN LÀM PLACEHOLDER: gợi ý \"dd/MM/y\" dạy người dùng gõ sai.\n *\n * Nhờ vậy `vi` cho ra đúng `dd/MM/yyyy` như hằng bên trên — đổi sang cách suy này không làm đổi\n * một pixel nào ở các app đang chạy tiếng Việt.\n */\nexport function dateFormatForLocale(locale: Locale): string {\n return locale.formatLong.date({ width: \"short\" }).replace(/y+/g, (run) => (run.length === 1 ? \"yyyy\" : run));\n}\n\n/**\n * Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `DatePicker` — không hiện thành chữ trên giao diện.\n *\n * Vì sao vẫn phải override được: chúng là chuỗi tiếng Việt cứng nằm trong `sr-only`, nên một app\n * tiếng Anh trông thì đúng nhưng ĐỌC ra vẫn là tiếng Việt — lỗi không ai thấy trên ảnh chụp và\n * cũng không có gì báo đỏ.\n *\n * ⚠ `locale` KHÔNG dịch mấy nhãn này, và không thể: locale của date-fns chỉ mang cách viết ngày\n * tháng, không mang chuỗi giao diện. Đổi ngôn ngữ app thì phải truyền CẢ `locale` lẫn `labels`.\n */\nexport interface DatePickerLabels {\n /** Nút xoá ngày đang chọn (`DatePicker`) */\n clear: string;\n /** Nút xoá khoảng ngày đang chọn (`DateRangePicker`) */\n clearRange: string;\n /** Nút mở lịch (`DatePicker`; `DateRangePicker` mở lịch bằng chính ô của nó nên không dùng khoá này) */\n openCalendar: string;\n}\n\nexport const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels = {\n clear: \"Xóa ngày\",\n clearRange: \"Xóa khoảng ngày\",\n openCalendar: \"Mở lịch\"\n};\n\nexport interface DatePreset {\n id: string;\n label: string;\n /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */\n getValue: () => Date;\n}\n\nexport interface DateRangePreset {\n id: string;\n label: string;\n getValue: () => DateRange;\n}\n\n/** Preset mặc định cho ô chọn một ngày. */\nexport const DEFAULT_DATE_PRESETS: DatePreset[] = [\n { id: \"today\", label: \"Hôm nay\", getValue: () => startOfDay(new Date()) },\n { id: \"yesterday\", label: \"Hôm qua\", getValue: () => startOfDay(subDays(new Date(), 1)) },\n { id: \"week-ago\", label: \"7 ngày trước\", getValue: () => startOfDay(subDays(new Date(), 7)) }\n];\n\n/** Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách). */\nexport const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[] = [\n { id: \"today\", label: \"Hôm nay\", getValue: () => ({ from: startOfDay(new Date()), to: startOfDay(new Date()) }) },\n {\n id: \"last-7\",\n label: \"7 ngày qua\",\n getValue: () => ({ from: startOfDay(subDays(new Date(), 6)), to: startOfDay(new Date()) })\n },\n {\n id: \"last-30\",\n label: \"30 ngày qua\",\n getValue: () => ({ from: startOfDay(subDays(new Date(), 29)), to: startOfDay(new Date()) })\n },\n {\n id: \"this-month\",\n label: \"Tháng này\",\n getValue: () => ({ from: startOfMonth(new Date()), to: startOfDay(new Date()) })\n },\n {\n id: \"last-month\",\n label: \"Tháng trước\",\n getValue: () => {\n const previous = subMonths(new Date(), 1);\n return { from: startOfMonth(previous), to: endOfMonth(previous) };\n }\n }\n];\n"],"mappings":"AAYA,SAAS,YAAyB,YAAY,cAAc,SAAS,iBAAiB;AAW/E,MAAM,iBAAiB;AAavB,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,QAAQ,CAAC,EAAE,QAAQ,OAAO,CAAC,QAAS,IAAI,WAAW,IAAI,SAAS,GAAI;AAC7G;AAqBO,MAAM,6BAA+C;AAAA,EAC1D,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,cAAc;AAChB;AAgBO,MAAM,uBAAqC;AAAA,EAChD,EAAE,IAAI,SAAS,OAAO,cAAW,UAAU,MAAM,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EACxE,EAAE,IAAI,aAAa,OAAO,cAAW,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAAA,EACxF,EAAE,IAAI,YAAY,OAAO,6BAAgB,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAC9F;AAGO,MAAM,6BAAgD;AAAA,EAC3D,EAAE,IAAI,SAAS,OAAO,cAAW,UAAU,OAAO,EAAE,MAAM,WAAW,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE,GAAG;AAAA,EAChH;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC1F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC3F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,aAAa,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAChF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,YAAM,WAAW,UAAU,oBAAI,KAAK,GAAG,CAAC;AACxC,aAAO,EAAE,MAAM,aAAa,QAAQ,GAAG,IAAI,WAAW,QAAQ,EAAE;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/date-picker-constants.ts"],"sourcesContent":["/**\n * Định dạng ngày và preset mặc định của `DatePicker` / `DateRangePicker`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, sentinel) là thứ trang server hay đọc nhất — spread để override một\n * khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `date-picker.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\nimport { endOfMonth, type Locale, startOfDay, startOfMonth, subDays, subMonths } from \"date-fns\";\nimport type { DateRange } from \"react-day-picker\";\nimport { datePickerEn } from \"../../i18n/namespaces/date-picker.js\";\n\n/**\n * Định dạng ngày của Việt Nam.\n *\n * ⚠ KHÔNG dùng nó làm định dạng mặc định nữa — `DatePicker`/`DateRangePicker` suy định dạng từ\n * `locale` bằng `dateFormatForLocale()` bên dưới (không truyền `locale` thì lấy locale date-fns\n * của `ComitorLocaleProvider`), nên một app tiếng Anh ra `MM/dd/yyyy` thay vì ngày Việt Nam.\n * Hằng này giữ lại vì nó là API công khai và vẫn đúng khi app cần ghim cứng ngày\n * kiểu Việt Nam ở một chỗ cụ thể (hoá đơn in ra, tệp xuất cho cơ quan thuế…).\n */\nexport const VI_DATE_FORMAT = \"dd/MM/yyyy\";\n\n/**\n * Định dạng ngày SỐ của một `locale` date-fns — dùng cho cả `format`, `parse` và chuỗi gợi ý\n * trong ô nhập, nên ba thứ đó không bao giờ lệch nhau.\n *\n * `formatLong.date({ width: \"short\" })` là nguồn: `vi` cho `dd/MM/y`, `en-US` cho `MM/dd/yyyy`,\n * `ja` cho `y/MM/dd`. Một chữ `y` đơn được nong thành `yyyy` — không phải để đổi kết quả (`y` in\n * ra \"2026\" y hệt) mà vì chuỗi này CÒN LÀM PLACEHOLDER: gợi ý \"dd/MM/y\" dạy người dùng gõ sai.\n *\n * Nhờ vậy `vi` cho ra đúng `dd/MM/yyyy` như hằng bên trên — đổi sang cách suy này không làm đổi\n * một pixel nào ở các app đang chạy tiếng Việt.\n */\nexport function dateFormatForLocale(locale: Locale): string {\n return locale.formatLong.date({ width: \"short\" }).replace(/y+/g, (run) => (run.length === 1 ? \"yyyy\" : run));\n}\n\n/**\n * Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `DatePicker` — không hiện thành chữ trên giao diện.\n *\n * Theo `ComitorLocaleProvider` (namespace `datePicker`, mặc định tiếng Anh); đè từng khoá qua prop\n * `labels`. Chúng nằm trong `sr-only`, nên sai ngôn ngữ ở đây là lỗi không ảnh chụp nào thấy: giao\n * diện nhìn thì đúng mà ĐỌC ra lại là thứ tiếng khác.\n *\n * ⚠ Prop `locale` (date-fns) KHÔNG dịch mấy nhãn này, và không thể: locale của date-fns chỉ mang\n * cách viết ngày tháng, không mang chuỗi giao diện. Provider thì lo CẢ HAI — chỉ khi đè riêng một ô\n * bằng prop `locale` mới phải nhớ truyền kèm `labels`.\n */\nexport interface DatePickerLabels {\n /** Nút xoá ngày đang chọn (`DatePicker`) */\n clear: string;\n /** Nút xoá khoảng ngày đang chọn (`DateRangePicker`) */\n clearRange: string;\n /** Nút mở lịch (`DatePicker`; `DateRangePicker` mở lịch bằng chính ô của nó nên không dùng khoá này) */\n openCalendar: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.datePicker`. Component không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n *\n * Nhặt đúng ba khoá chứ không trỏ thẳng `datePickerEn`: bảng đó còn mang `rangePlaceholder` và\n * `presets`, không thuộc hình dạng `DatePickerLabels`.\n */\nexport const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels = {\n clear: datePickerEn.clear,\n clearRange: datePickerEn.clearRange,\n openCalendar: datePickerEn.openCalendar\n};\n\nexport interface DatePreset {\n id: string;\n /** Hiện NGUYÊN VĂN — preset app tự dựng không bao giờ bị gói dịch lại */\n label: string;\n /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */\n getValue: () => Date;\n}\n\nexport interface DateRangePreset {\n id: string;\n /** Hiện NGUYÊN VĂN — preset app tự dựng không bao giờ bị gói dịch lại */\n label: string;\n getValue: () => DateRange;\n}\n\n/**\n * Preset mặc định cho ô chọn một ngày. `DatePicker` KHÔNG tự bật chúng (`presets` mặc định `false`)\n * — truyền `presets={DEFAULT_DATE_PRESETS}` để hiện.\n *\n * `label` ở đây là tiếng Anh (từ `3.0.0`), nhưng component nhận ra chính các object này và hiện\n * nhãn theo `ComitorLocaleProvider` (`datePicker.presets`) — kể cả khi app lọc bớt mảng. Muốn\n * nhãn khác thì dựng preset mới (`{ ...preset, label }`): object mới hiện đúng `label` của nó.\n */\nexport const DEFAULT_DATE_PRESETS: DatePreset[] = [\n { id: \"today\", label: datePickerEn.presets.today, getValue: () => startOfDay(new Date()) },\n { id: \"yesterday\", label: datePickerEn.presets.yesterday, getValue: () => startOfDay(subDays(new Date(), 1)) },\n { id: \"week-ago\", label: datePickerEn.presets.weekAgo, getValue: () => startOfDay(subDays(new Date(), 7)) }\n];\n\n/**\n * Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách) — `DateRangePicker` dùng khi\n * app không truyền `presets`. Nhãn: như `DEFAULT_DATE_PRESETS` (tiếng Anh ở hằng, theo provider\n * khi hiện).\n */\nexport const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[] = [\n {\n id: \"today\",\n label: datePickerEn.presets.today,\n getValue: () => ({ from: startOfDay(new Date()), to: startOfDay(new Date()) })\n },\n {\n id: \"last-7\",\n label: datePickerEn.presets.last7Days,\n getValue: () => ({ from: startOfDay(subDays(new Date(), 6)), to: startOfDay(new Date()) })\n },\n {\n id: \"last-30\",\n label: datePickerEn.presets.last30Days,\n getValue: () => ({ from: startOfDay(subDays(new Date(), 29)), to: startOfDay(new Date()) })\n },\n {\n id: \"this-month\",\n label: datePickerEn.presets.thisMonth,\n getValue: () => ({ from: startOfMonth(new Date()), to: startOfDay(new Date()) })\n },\n {\n id: \"last-month\",\n label: datePickerEn.presets.lastMonth,\n getValue: () => {\n const previous = subMonths(new Date(), 1);\n return { from: startOfMonth(previous), to: endOfMonth(previous) };\n }\n }\n];\n"],"mappings":"AAYA,SAAS,YAAyB,YAAY,cAAc,SAAS,iBAAiB;AAEtF,SAAS,oBAAoB;AAWtB,MAAM,iBAAiB;AAavB,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,QAAQ,CAAC,EAAE,QAAQ,OAAO,CAAC,QAAS,IAAI,WAAW,IAAI,SAAS,GAAI;AAC7G;AA8BO,MAAM,6BAA+C;AAAA,EAC1D,OAAO,aAAa;AAAA,EACpB,YAAY,aAAa;AAAA,EACzB,cAAc,aAAa;AAC7B;AAyBO,MAAM,uBAAqC;AAAA,EAChD,EAAE,IAAI,SAAS,OAAO,aAAa,QAAQ,OAAO,UAAU,MAAM,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EACzF,EAAE,IAAI,aAAa,OAAO,aAAa,QAAQ,WAAW,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAAA,EAC7G,EAAE,IAAI,YAAY,OAAO,aAAa,QAAQ,SAAS,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAC5G;AAOO,MAAM,6BAAgD;AAAA,EAC3D;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ;AAAA,IAC5B,UAAU,OAAO,EAAE,MAAM,WAAW,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC9E;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ;AAAA,IAC5B,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC1F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ;AAAA,IAC5B,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC3F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ;AAAA,IAC5B,UAAU,OAAO,EAAE,MAAM,aAAa,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAChF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ;AAAA,IAC5B,UAAU,MAAM;AACd,YAAM,WAAW,UAAU,oBAAI,KAAK,GAAG,CAAC;AACxC,aAAO,EAAE,MAAM,aAAa,QAAQ,GAAG,IAAI,WAAW,QAAQ,EAAE;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
@@ -14,14 +14,17 @@ interface DatePickerProps {
14
14
  maxDate?: Date;
15
15
  /** Hiện nút xoá khi đã có giá trị (mặc định true) */
16
16
  clearable?: boolean;
17
- /** Cột preset bên trái lịch (mặc định ẩn) */
17
+ /**
18
+ * Cột preset bên trái lịch (mặc định ẩn). `DEFAULT_DATE_PRESETS` hiện nhãn theo
19
+ * `ComitorLocaleProvider`; preset app tự dựng hiện nguyên `label`.
20
+ */
18
21
  presets?: DatePreset[] | false;
19
22
  /**
20
- * Ngôn ngữ của date-fns (mặc định `vi`) — quyết định tên thứ/tháng trong lịch VÀ thứ tự
21
- * ngày/tháng của ô nhập, qua `dateFormatForLocale()`.
23
+ * Ngôn ngữ của date-fns — quyết định tên thứ/tháng trong lịch VÀ thứ tự ngày/tháng của ô nhập
24
+ * (gõ, hiển thị lẫn placeholder), qua `dateFormatForLocale()`.
22
25
  *
23
- * App đa ngôn ngữ phải truyền: để mặc định thì giao diện tiếng Anh vẫn hiện lịch tiếng Việt và
24
- * bắt người dùng gõ `dd/MM/yyyy` — hai lỗi không có gì báo đỏ.
26
+ * Không truyền thì theo `ComitorLocaleProvider` (`vi` → `dd/MM/yyyy`, còn lại `enUS` →
27
+ * `MM/dd/yyyy`). Trước `3.0.0` mặc định cứng là `vi`, nên app tiếng Anh phải nhớ truyền.
25
28
  */
26
29
  locale?: Locale;
27
30
  /**
@@ -29,6 +32,7 @@ interface DatePickerProps {
29
32
  * luôn là thứ đúng. Chỉ dùng khi một màn hình phải ghim cứng một định dạng bất kể ngôn ngữ.
30
33
  */
31
34
  dateFormat?: string;
35
+ /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */
32
36
  labels?: Partial<DatePickerLabels>;
33
37
  className?: string;
34
38
  id?: string;
@@ -39,30 +43,35 @@ interface DatePickerProps {
39
43
  "aria-required"?: true;
40
44
  }
41
45
  /**
42
- * Ô chọn ngày: gõ tay `dd/MM/yyyy` HOẶC bấm lịch.
46
+ * Ô chọn ngày: gõ tay theo định dạng của `locale` (`dd/MM/yyyy`, `MM/dd/yyyy`…) HOẶC bấm lịch.
43
47
  *
44
48
  * Vì sao vẫn giữ ô gõ tay thay vì chỉ có nút mở lịch: người nhập liệu chuyên nghiệp (kế toán,
45
49
  * nhân sự) gõ nhanh hơn bấm nhiều lần, và ngày sinh năm 1975 mà bắt lật lịch 50 năm thì không
46
50
  * ai chịu nổi. Chuỗi gõ dở KHÔNG bị ghi đè trong lúc gõ; sai định dạng thì trả về giá trị cũ
47
51
  * khi rời ô, không âm thầm nuốt dữ liệu.
48
52
  */
49
- declare function DatePicker({ value, onValueChange, placeholder, disabled, minDate, maxDate, clearable, presets, locale, dateFormat, labels, className, id, name, ...aria }: DatePickerProps): React.JSX.Element;
53
+ declare function DatePicker({ value, onValueChange, placeholder, disabled, minDate, maxDate, clearable, presets, locale: localeProp, dateFormat, labels, className, id, name, ...aria }: DatePickerProps): React.JSX.Element;
50
54
  interface DateRangePickerProps {
51
55
  value?: DateRange | null;
52
56
  onValueChange: (value: DateRange | null) => void;
57
+ /** Chữ khi chưa chọn — không truyền thì theo khoá `rangePlaceholder` của namespace `datePicker` */
53
58
  placeholder?: string;
54
59
  disabled?: boolean;
55
60
  minDate?: Date;
56
61
  maxDate?: Date;
57
62
  clearable?: boolean;
58
- /** Cột preset bên trái (mặc định DEFAULT_DATE_RANGE_PRESETS; `false` để tắt) */
63
+ /**
64
+ * Cột preset bên trái (mặc định `DEFAULT_DATE_RANGE_PRESETS`, nhãn theo `ComitorLocaleProvider`;
65
+ * `false` để tắt). Preset app tự dựng hiện nguyên `label`.
66
+ */
59
67
  presets?: DateRangePreset[] | false;
60
68
  /** Số tháng hiện cùng lúc (mặc định 2 trên màn rộng) */
61
69
  numberOfMonths?: number;
62
- /** Ngôn ngữ date-fns (mặc định `vi`) — xem `DatePickerProps.locale`. */
70
+ /** Ngôn ngữ date-fns (mặc định theo `ComitorLocaleProvider`) — xem `DatePickerProps.locale`. */
63
71
  locale?: Locale;
64
72
  /** Ghi đè định dạng ngày; bỏ trống thì suy từ `locale`. */
65
73
  dateFormat?: string;
74
+ /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */
66
75
  labels?: Partial<DatePickerLabels>;
67
76
  className?: string;
68
77
  id?: string;
@@ -76,6 +85,6 @@ interface DateRangePickerProps {
76
85
  * Preset đứng trước lịch vì 9/10 lần người dùng muốn "7 ngày qua" chứ không phải một khoảng
77
86
  * tuỳ ý — bắt họ bấm hai lần trên lịch mỗi lần xem báo cáo là thuế lặp lại vô ích.
78
87
  */
79
- declare function DateRangePicker({ value, onValueChange, placeholder, disabled, minDate, maxDate, clearable, presets, numberOfMonths, locale, dateFormat, labels, className, id, ...aria }: DateRangePickerProps): React.JSX.Element;
88
+ declare function DateRangePicker({ value, onValueChange, placeholder: placeholderProp, disabled, minDate, maxDate, clearable, presets, numberOfMonths, locale: localeProp, dateFormat, labels, className, id, ...aria }: DateRangePickerProps): React.JSX.Element;
80
89
 
81
90
  export { DatePicker, type DatePickerProps, DateRangePicker, type DateRangePickerProps };