@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
package/dist/index.js CHANGED
@@ -121,25 +121,48 @@ export * from "./components/ui/toggle-group.js";
121
121
  export * from "./components/ui/tooltip.js";
122
122
  export * from "./components/ui/tour.js";
123
123
  export * from "./components/ui/tour-constants.js";
124
+ import { DEFAULT_COMITOR_LOCALE, getComitorLanguage } from "./i18n/locale.js";
125
+ import {
126
+ ComitorLocaleProvider,
127
+ useComitorLocale,
128
+ useComitorMessages
129
+ } from "./i18n/locale-provider.js";
130
+ import {
131
+ COMITOR_MESSAGES,
132
+ comitorMessagesEn,
133
+ comitorMessagesVi,
134
+ getComitorMessages,
135
+ mergeComitorMessages
136
+ } from "./i18n/messages.js";
124
137
  import { AVATAR_TONE_NAMES } from "./lib/avatar-tone.js";
125
138
  export * from "./lib/cn.js";
126
139
  export * from "./tokens.js";
127
140
  export {
128
141
  AVATAR_TONE_NAMES,
142
+ COMITOR_MESSAGES,
143
+ ComitorLocaleProvider,
144
+ DEFAULT_COMITOR_LOCALE,
129
145
  IconAvatar,
130
146
  LetterAvatar,
131
147
  MASK_PATTERNS,
132
148
  applyCurrencyMask,
133
149
  applyMask,
134
150
  applyPercentageMask,
151
+ comitorMessagesEn,
152
+ comitorMessagesVi,
135
153
  fromUnmaskedIndex,
136
154
  getAvatarToneClasses,
155
+ getComitorLanguage,
156
+ getComitorMessages,
137
157
  getUnmaskedValue,
138
158
  getVietnameseInitials,
139
159
  matchesSearch,
160
+ mergeComitorMessages,
140
161
  normalizeVi,
141
162
  removeVietnameseTones,
142
163
  toUnmaskedIndex,
143
- truncate
164
+ truncate,
165
+ useComitorLocale,
166
+ useComitorMessages
144
167
  };
145
168
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n/*\n * ⚠ Các file `*-constants` bên dưới — ở CẢ Tier 2 lẫn Tier 1 (từ `2.1.0`: `action-bar`, `cropper`,\n * `kanban`, `key-value`, `mask-input`, `phone-input`, `stepper`, `time-picker`, `tour`) — KHÔNG phải\n * chỗ để gom code cho gọn — chúng là RANH GIỚI SERVER/CLIENT. Component chủ (`command-palette.tsx`,\n * `data-table.tsx`, `kanban.tsx`…) đều mở đầu bằng `\"use client\"`,\n * mà mọi export của một module client đến tay Server Component dưới dạng client-reference proxy:\n * đọc ra `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/command-palette-constants.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-field-constants.js\";\nexport * from \"./components/composite/form-section.js\";\nexport { IconAvatar, type IconAvatarIcon, type IconAvatarProps } from \"./components/composite/icon-avatar.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport type { LetterAvatarProps, LetterAvatarSize } from \"./components/composite/letter-avatar.js\";\n// Xuất CHỌN LỌC (không `export *`): file này còn `AVATAR_TONES` nhập từ `lib/avatar-tone.js`\n// và các helper nội bộ khác. Liệt kê tường minh thì thêm helper riêng tư vào file KHÔNG tự động\n// biến nó thành API công khai — hàng rào mà `export *` không dựng được.\nexport { getAvatarToneClasses, LetterAvatar } from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/route-tabs.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/search-filter-bar-constants.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\nexport * from \"./components/composite/table-pagination-constants.js\";\n// Xuất CHỌN LỌC vì cùng lý do với `letter-avatar` ở trên: `text-utils.ts` là chỗ dễ bị thêm\n// helper nội bộ nhất của Tier 2 (0.1.0 từng rò `hashString`/`pickByString` ra API công khai\n// đúng theo đường này). Năm hàm dưới đây là hợp đồng, phần còn lại của file thì không.\nexport {\n getVietnameseInitials,\n matchesSearch,\n normalizeVi,\n removeVietnameseTones,\n truncate\n} from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/action-bar.js\";\nexport * from \"./components/ui/action-bar-constants.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/badge-overflow.js\";\nexport * from \"./components/ui/banner.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/circular-progress.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/cropper.js\";\nexport * from \"./components/ui/cropper-constants.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kanban.js\";\nexport * from \"./components/ui/kanban-constants.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/key-value.js\";\nexport * from \"./components/ui/key-value-constants.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/mask-input.js\";\n/*\n * Xuất CHỌN LỌC: `mask-input-constants.ts` giữ cả bộ máy thuần của MaskInput (tách khỏi file\n * \"use client\" để hằng `MASK_PATTERNS` không thành proxy ở Server Component), gồm những helper nội\n * bộ như `DEFAULT_CURRENCY` / `REGEX_CACHE`. Chỉ đúng các tên thượng nguồn Dice UI xuất là API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n fromUnmaskedIndex,\n getUnmaskedValue,\n MASK_PATTERNS,\n type MaskPattern,\n toUnmaskedIndex\n} from \"./components/ui/mask-input-constants.js\";\nexport * from \"./components/ui/mention.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/phone-input.js\";\nexport * from \"./components/ui/phone-input-constants.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/rating.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/responsive-dialog.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/scroll-spy.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/stepper.js\";\nexport * from \"./components/ui/stepper-constants.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\n/* Bảng lớp của Tabs xuất THẲNG từ module thuần — `RouteTabs` và `Tabs` dùng chung nó, và hằng\n dữ liệu đi vòng qua file \"use client\" sẽ thành proxy ở Server Component (CHANGELOG 0.2.2 §2). */\nexport * from \"./components/ui/tabs-variants.js\";\nexport * from \"./components/ui/tags-input.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/time-picker.js\";\nexport * from \"./components/ui/time-picker-constants.js\";\nexport * from \"./components/ui/timeline.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\nexport * from \"./components/ui/tour.js\";\nexport * from \"./components/ui/tour-constants.js\";\n/*\n * `IconAvatar` — em ruột của `LetterAvatar`, dùng chung bảng tone. Xuất kèm `AVATAR_TONE_NAMES`\n * và kiểu `AvatarToneName` vì prop `tone` chỉ có nghĩa khi chỗ gọi liệt kê được tập chọn (màn\n * quản trị cần dựng một `<Select>` từ đúng tập đó).\n *\n * ⚠ KHÔNG xuất `AVATAR_TONES` lẫn `getAvatarToneIndex` ra entry này: bảng thô và hàm băm vẫn là\n * nội bộ, đúng như `lib/avatar-tone.ts` dặn — xuất chúng là mời app dựng lại phép chọn tone của\n * riêng nó, tức cùng một tên ra hai màu.\n */\nexport { AVATAR_TONE_NAMES, type AvatarToneName } from \"./lib/avatar-tone.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAsCA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAA6D;AACtE,cAAc;AACd,cAAc;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAMd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAUd,SAAS,yBAA8C;AAGvD,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n/*\n * ⚠ Các file `*-constants` bên dưới — ở CẢ Tier 2 lẫn Tier 1 (từ `2.1.0`: `action-bar`, `cropper`,\n * `kanban`, `key-value`, `mask-input`, `phone-input`, `stepper`, `time-picker`, `tour`) — KHÔNG phải\n * chỗ để gom code cho gọn — chúng là RANH GIỚI SERVER/CLIENT. Component chủ (`command-palette.tsx`,\n * `data-table.tsx`, `kanban.tsx`…) đều mở đầu bằng `\"use client\"`,\n * mà mọi export của một module client đến tay Server Component dưới dạng client-reference proxy:\n * đọc ra `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/command-palette-constants.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-field-constants.js\";\nexport * from \"./components/composite/form-section.js\";\nexport { IconAvatar, type IconAvatarIcon, type IconAvatarProps } from \"./components/composite/icon-avatar.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport type { LetterAvatarProps, LetterAvatarSize } from \"./components/composite/letter-avatar.js\";\n// Xuất CHỌN LỌC (không `export *`): file này còn `AVATAR_TONES` nhập từ `lib/avatar-tone.js`\n// và các helper nội bộ khác. Liệt kê tường minh thì thêm helper riêng tư vào file KHÔNG tự động\n// biến nó thành API công khai — hàng rào mà `export *` không dựng được.\nexport { getAvatarToneClasses, LetterAvatar } from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/route-tabs.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/search-filter-bar-constants.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\nexport * from \"./components/composite/table-pagination-constants.js\";\n// Xuất CHỌN LỌC vì cùng lý do với `letter-avatar` ở trên: `text-utils.ts` là chỗ dễ bị thêm\n// helper nội bộ nhất của Tier 2 (0.1.0 từng rò `hashString`/`pickByString` ra API công khai\n// đúng theo đường này). Năm hàm dưới đây là hợp đồng, phần còn lại của file thì không.\nexport {\n getVietnameseInitials,\n matchesSearch,\n normalizeVi,\n removeVietnameseTones,\n truncate\n} from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/action-bar.js\";\nexport * from \"./components/ui/action-bar-constants.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/badge-overflow.js\";\nexport * from \"./components/ui/banner.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/circular-progress.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/cropper.js\";\nexport * from \"./components/ui/cropper-constants.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kanban.js\";\nexport * from \"./components/ui/kanban-constants.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/key-value.js\";\nexport * from \"./components/ui/key-value-constants.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/mask-input.js\";\n/*\n * Xuất CHỌN LỌC: `mask-input-constants.ts` giữ cả bộ máy thuần của MaskInput (tách khỏi file\n * \"use client\" để hằng `MASK_PATTERNS` không thành proxy ở Server Component), gồm những helper nội\n * bộ như `DEFAULT_CURRENCY` / `REGEX_CACHE`. Chỉ đúng các tên thượng nguồn Dice UI xuất là API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n fromUnmaskedIndex,\n getUnmaskedValue,\n MASK_PATTERNS,\n type MaskPattern,\n toUnmaskedIndex\n} from \"./components/ui/mask-input-constants.js\";\nexport * from \"./components/ui/mention.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/phone-input.js\";\nexport * from \"./components/ui/phone-input-constants.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/rating.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/responsive-dialog.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/scroll-spy.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/stepper.js\";\nexport * from \"./components/ui/stepper-constants.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\n/* Bảng lớp của Tabs xuất THẲNG từ module thuần — `RouteTabs` và `Tabs` dùng chung nó, và hằng\n dữ liệu đi vòng qua file \"use client\" sẽ thành proxy ở Server Component (CHANGELOG 0.2.2 §2). */\nexport * from \"./components/ui/tabs-variants.js\";\nexport * from \"./components/ui/tags-input.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/time-picker.js\";\nexport * from \"./components/ui/time-picker-constants.js\";\nexport * from \"./components/ui/timeline.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\nexport * from \"./components/ui/tour.js\";\nexport * from \"./components/ui/tour-constants.js\";\n// ─── Ngôn ngữ (từ 3.0.0) — một provider cho CẢ gói, mặc định tiếng Anh ───\n/*\n * Xuất CHỌN LỌC: `locale-provider.tsx` còn `useComitorLabels` — hook nội bộ các component dùng để\n * trộn `labels` lên bảng chữ, không phải API. Bảng chữ và hàm thuần xuất THẲNG từ module thuần\n * (`messages.ts`, `locale.ts`) chứ không vòng qua file \"use client\" — cùng lý do với các file\n * `*-constants` bên dưới.\n */\nexport { type ComitorLanguage, DEFAULT_COMITOR_LOCALE, getComitorLanguage } from \"./i18n/locale.js\";\nexport {\n ComitorLocaleProvider,\n type ComitorLocaleProviderProps,\n useComitorLocale,\n useComitorMessages\n} from \"./i18n/locale-provider.js\";\nexport {\n COMITOR_MESSAGES,\n type ComitorMessages,\n type ComitorMessagesOverride,\n comitorMessagesEn,\n comitorMessagesVi,\n getComitorMessages,\n mergeComitorMessages\n} from \"./i18n/messages.js\";\n/*\n * `IconAvatar` — em ruột của `LetterAvatar`, dùng chung bảng tone. Xuất kèm `AVATAR_TONE_NAMES`\n * và kiểu `AvatarToneName` vì prop `tone` chỉ có nghĩa khi chỗ gọi liệt kê được tập chọn (màn\n * quản trị cần dựng một `<Select>` từ đúng tập đó).\n *\n * ⚠ KHÔNG xuất `AVATAR_TONES` lẫn `getAvatarToneIndex` ra entry này: bảng thô và hàm băm vẫn là\n * nội bộ, đúng như `lib/avatar-tone.ts` dặn — xuất chúng là mời app dựng lại phép chọn tone của\n * riêng nó, tức cùng một tên ra hai màu.\n */\nexport { AVATAR_TONE_NAMES, type AvatarToneName } from \"./lib/avatar-tone.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAsCA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAA6D;AACtE,cAAc;AACd,cAAc;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAMd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAQd,SAA+B,wBAAwB,0BAA0B;AACjF;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,SAAS,yBAA8C;AAGvD,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,8 @@
1
+ import { ToastMessages } from '../i18n/namespaces/toast.js';
2
+
3
+ /** Bảng chữ `toast` của toaster đang gắn trên trang (tiếng Anh khi chưa có toaster nào). */
4
+ declare function getActiveToastMessages(): ToastMessages;
5
+ /** Gọi trong MỌI toaster: ghi bảng chữ của provider gần nhất cho `toastPromise` đọc. */
6
+ declare function useToastLanguageBridge(): void;
7
+
8
+ export { getActiveToastMessages, useToastLanguageBridge };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { useComitorMessages } from "../i18n/locale-provider.js";
3
+ import { toastEn } from "../i18n/namespaces/toast.js";
4
+ import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
5
+ let activeToastMessages = toastEn;
6
+ function getActiveToastMessages() {
7
+ return activeToastMessages;
8
+ }
9
+ function useToastLanguageBridge() {
10
+ const toastMessages = useComitorMessages("toast");
11
+ useIsomorphicLayoutEffect(() => {
12
+ activeToastMessages = toastMessages;
13
+ }, [toastMessages]);
14
+ }
15
+ export {
16
+ getActiveToastMessages,
17
+ useToastLanguageBridge
18
+ };
19
+ //# sourceMappingURL=toast-language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/toast-language.ts"],"sourcesContent":["\"use client\";\n\nimport { useComitorMessages } from \"../i18n/locale-provider.js\";\nimport { toastEn } from \"../i18n/namespaces/toast.js\";\nimport { useIsomorphicLayoutEffect } from \"./use-isomorphic-layout-effect.js\";\n\n/**\n * Cầu NGÔN NGỮ cho `toastPromise` — nội bộ, không nằm trong barrel nào.\n *\n * `toastPromise` là hàm thường gọi từ event handler: ngoài lượt render, không gọi được hook, nên\n * không đọc được `ComitorLocaleProvider`. Toaster thì LUÔN nằm trong cây React (đặt một lần ở gốc,\n * bên trong provider): nó ghi bảng chữ hiện hành vào đây mỗi khi ngôn ngữ đổi, và `toastPromise`\n * đọc từ đây. Không có toaster thì cũng không có toast nào hiện ra, nên chữ rơi về tiếng Anh\n * cũng không ai thấy.\n *\n * ⚠ Vì sao tách khỏi `composite/toast.tsx` (3.0.1): ở `3.0.0` biến này nằm trong file đó và chỉ\n * `Toaster` của entry `.` ghi vào — `SonnerToaster` của `/shell` thì không, nên app dùng shell\n * (đúng cách tài liệu khuyên cho app Next) có `toastPromise` nói tiếng Anh giữa màn hình tiếng Việt\n * dù đã đặt provider. Hai toaster nay gọi cùng `useToastLanguageBridge()`. Không để hook ấy ở\n * `toast.tsx`: file đó được barrel `export *`, và hook sẽ thành API công khai ngoài ý muốn.\n */\nlet activeToastMessages = toastEn;\n\n/** Bảng chữ `toast` của toaster đang gắn trên trang (tiếng Anh khi chưa có toaster nào). */\nexport function getActiveToastMessages() {\n return activeToastMessages;\n}\n\n/** Gọi trong MỌI toaster: ghi bảng chữ của provider gần nhất cho `toastPromise` đọc. */\nexport function useToastLanguageBridge() {\n const toastMessages = useComitorMessages(\"toast\");\n useIsomorphicLayoutEffect(() => {\n activeToastMessages = toastMessages;\n }, [toastMessages]);\n}\n"],"mappings":";AAEA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,iCAAiC;AAiB1C,IAAI,sBAAsB;AAGnB,SAAS,yBAAyB;AACvC,SAAO;AACT;AAGO,SAAS,yBAAyB;AACvC,QAAM,gBAAgB,mBAAmB,OAAO;AAChD,4BAA0B,MAAM;AAC9B,0BAAsB;AAAA,EACxB,GAAG,CAAC,aAAa,CAAC;AACpB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/*\n ── VÙNG GIỮA: ô tìm dạng PILL, và chỉ nó ────────────────────────────\n Từ `lg` trở lên vùng này giữ ô tìm 256px và nằm chính giữa header. Dưới `lg` ô pill bị\n `hidden` nên vùng này RỖNG và co về 0 — lúc đó hai vùng `flex-1` hai bên chia đôi phần\n còn lại, và nút icon tìm kiếm (nằm ở vùng PHẢI) được căn phải cùng các icon khác.\n ⚠ Nút icon KHÔNG được để ở đây: nó sẽ bị căn giữa header, tức một icon lẻ trôi giữa\n khoảng trống trong khi mọi icon khác nằm sát phải.\n */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n //\n // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó\n // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh\n // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai\n // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px\n // trước khi vá: ba nút header tụt còn 16px bề ngang.\n // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó\n // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // chuột vào, dưới xa mức 3:1 WCAG 1.4.11 đòi cho viền control. `gold-300` lại là\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n {/* `shrink-0`: cụm nút KHÔNG được co. Thiếu nó thì flexbox bóp chúng thay vì bóp ô tìm —\n ở 768px (thanh bên vừa hiện mà vùng nội dung mới 512px) chúng tụt còn 16px bề ngang. */}\n <div className=\"flex shrink-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {/*\n Dưới `lg`: ô tìm thu thành icon và đứng ở ĐÂY, cùng hàng với các icon khác bên phải —\n không phải ở vùng giữa. `searchSlot` của app thì không có bản icon nên nó ở lại vùng\n giữa và tự lo phần thu hẹp.\n */}\n {showSearch && !searchSlot ? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n ) : null}\n\n {/*\n `hidden md:flex`: nút danh sách ứng dụng chỉ ở desktop. Dưới `md`, `MobileMenu` đã\n render `<AppLauncher inline variant=\"list\">` ngay trong menu hamburger — để cả hai là\n hai lối vào cho cùng một danh sách, và trên màn hình hẹp thì mỗi ô 36px của header đều\n đáng giá. Cùng mốc `md` với nút đổi giao diện ngay dưới.\n ⚠ App nào tắt `showMobileMenu`/`showAppLauncher` của `MobileMenu` thì phải xét lại đây,\n nếu không màn hình hẹp sẽ mất hẳn lối vào danh sách ứng dụng.\n */}\n {showAppLauncher ? <AppLauncher className=\"hidden md:flex\" /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {/*\n `hidden md:flex`: nút đổi giao diện chỉ ở desktop. Dưới `md` lối vào là submenu \"Giao\n diện\" trong `UserMenu` (mặc định `showThemeSubmenu=\"mobile\"`) — hai bên dùng CHUNG mốc\n `md` nên ở mọi bề rộng có đúng MỘT lối vào, không trùng và không hụt.\n ⚠ Đổi mốc ở một bên thì phải đổi ở bên kia, nếu không sẽ có dải bề rộng mất hẳn nút đổi\n giao diện.\n */}\n {showThemeToggle ? <ThemeToggle labels={labels} className=\"hidden md:flex\" /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":";AAsGU,SAsIA,UAtIA,KAkBE,YAlBF;AApGV,SAAS,MAAM,WAAW,cAAc;AAIxC,SAAS,gBAAgB;AACzB,SAAS,WAAW;AACpB,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,gBAAoC;AA+CtC,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf;AACF,GAAmB;AACjB,QAAM,EAAE,mBAAmB,uBAAuB,eAAe,kBAAkB,QAAQ,KAAK,IAAI,SAAS;AAC7G,QAAM,QAAQ,SAAS;AACvB,QAAM,SAAS,QAAQ,WAAM;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAGA;AAAA,6BAAC,SAAI,WAAU,0CAEb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,kBAAkB,IAAI;AAAA,cACrC,cAAY,OAAO;AAAA,cACnB,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,UAC3B;AAAA,UAEC,oBACC,qBAAC,WACC;AAAA,gCAAC,kBAAe,SAAO,MACrB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,cAAY,mBAAmB,OAAO,gBAAgB,OAAO;AAAA,gBAC7D,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,gBACF;AAAA,gBAEA,8BAAC,aAAU,WAAU,UAAS;AAAA;AAAA,YAChC,GACF;AAAA,YACA,qBAAC,kBAAe,MAAK,UAClB;AAAA,iCAAmB,OAAO,gBAAgB,OAAO;AAAA,cAAgB;AAAA,cAAI;AAAA,cAAO;AAAA,eAC/E;AAAA,aACF,IACE;AAAA,UAEH,OAAO,oBAAC,SAAI,WAAU,YAAY,gBAAK,IAAS;AAAA,UAGjD,oBAAC,SAAI,WAAU,6BACZ,yBAAe,QAAQ,oBAAC,QAAG,WAAU,kDAAkD,iBAAM,IAAQ,OACxG;AAAA,WACF;AAAA,QAUA,oBAAC,SAAI,WAAU,6CACZ,uBACI,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,sBAAsB,IAAI;AAAA,YACzC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAYT;AAAA,cACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAUA;AAAA,YACF;AAAA,YAEA;AAAA,kCAAC,UAAO,WAAU,qBAAoB;AAAA,cACtC,oBAAC,UAAK,WAAU,6BAA6B,iBAAO,mBAAkB;AAAA,cACtE,qBAAC,OAAK;AAAA;AAAA,gBAAO;AAAA,iBAAC;AAAA;AAAA;AAAA,QAChB,IAEF,MACN;AAAA,QAKA,qBAAC,SAAI,WAAU,uDACZ;AAAA;AAAA,UAOA,cAAc,CAAC,aACd;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,sBAAsB,IAAI;AAAA,cACzC,cAAY,OAAO;AAAA,cACnB,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,UAAO,WAAU,UAAS;AAAA;AAAA,UAC7B,IACE;AAAA,UAUH,kBAAkB,oBAAC,eAAY,WAAU,kBAAiB,IAAK;AAAA,UAE/D,sBACE,sBAAsB,SAAY,OACjC;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,OAAO,OAAO;AAAA,cACd,SAAS;AAAA;AAAA,UACX;AAAA,UAUH,kBAAkB,oBAAC,eAAY,QAAgB,WAAU,kBAAiB,IAAK;AAAA,UAE/E,gBAAgB,OACf,iCACE;AAAA,gCAAC,UAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAC7E,oBAAC,YAAU,GAAG,eAAe;AAAA,aAC/B,IACE;AAAA,WACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/*\n ── VÙNG GIỮA: ô tìm dạng PILL, và chỉ nó ────────────────────────────\n Từ `lg` trở lên vùng này giữ ô tìm 256px và nằm chính giữa header. Dưới `lg` ô pill bị\n `hidden` nên vùng này RỖNG và co về 0 — lúc đó hai vùng `flex-1` hai bên chia đôi phần\n còn lại, và nút icon tìm kiếm (nằm ở vùng PHẢI) được căn phải cùng các icon khác.\n ⚠ Nút icon KHÔNG được để ở đây: nó sẽ bị căn giữa header, tức một icon lẻ trôi giữa\n khoảng trống trong khi mọi icon khác nằm sát phải.\n */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n //\n // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó\n // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh\n // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai\n // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px\n // trước khi vá: ba nút header tụt còn 16px bề ngang.\n // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó\n // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // chuột vào, dưới xa mức 3:1 WCAG 1.4.11 đòi cho viền control. `gold-300` lại là\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n {/* `shrink-0`: cụm nút KHÔNG được co. Thiếu nó thì flexbox bóp chúng thay vì bóp ô tìm —\n ở 768px (thanh bên vừa hiện mà vùng nội dung mới 512px) chúng tụt còn 16px bề ngang. */}\n <div className=\"flex shrink-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {/*\n Dưới `lg`: ô tìm thu thành icon và đứng ở ĐÂY, cùng hàng với các icon khác bên phải —\n không phải ở vùng giữa. `searchSlot` của app thì không có bản icon nên nó ở lại vùng\n giữa và tự lo phần thu hẹp.\n */}\n {showSearch && !searchSlot ? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n ) : null}\n\n {/*\n `hidden md:flex`: nút danh sách ứng dụng chỉ ở desktop. Dưới `md`, `MobileMenu` đã\n render `<AppLauncher inline variant=\"list\">` ngay trong menu hamburger — để cả hai là\n hai lối vào cho cùng một danh sách, và trên màn hình hẹp thì mỗi ô 36px của header đều\n đáng giá. Cùng mốc `md` với nút đổi giao diện ngay dưới.\n ⚠ App nào tắt `showMobileMenu`/`showAppLauncher` của `MobileMenu` thì phải xét lại đây,\n nếu không màn hình hẹp sẽ mất hẳn lối vào danh sách ứng dụng.\n */}\n {showAppLauncher ? <AppLauncher className=\"hidden md:flex\" /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {/*\n `hidden md:flex`: nút đổi giao diện chỉ ở desktop. Dưới `md` lối vào là submenu đổi giao\n diện trong `UserMenu` (mặc định `showThemeSubmenu=\"mobile\"`) — hai bên dùng CHUNG mốc\n `md` nên ở mọi bề rộng có đúng MỘT lối vào, không trùng và không hụt.\n ⚠ Đổi mốc ở một bên thì phải đổi ở bên kia, nếu không sẽ có dải bề rộng mất hẳn nút đổi\n giao diện.\n */}\n {showThemeToggle ? <ThemeToggle labels={labels} className=\"hidden md:flex\" /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":";AAsGU,SAsIA,UAtIA,KAkBE,YAlBF;AApGV,SAAS,MAAM,WAAW,cAAc;AAIxC,SAAS,gBAAgB;AACzB,SAAS,WAAW;AACpB,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,gBAAoC;AA+CtC,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf;AACF,GAAmB;AACjB,QAAM,EAAE,mBAAmB,uBAAuB,eAAe,kBAAkB,QAAQ,KAAK,IAAI,SAAS;AAC7G,QAAM,QAAQ,SAAS;AACvB,QAAM,SAAS,QAAQ,WAAM;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAGA;AAAA,6BAAC,SAAI,WAAU,0CAEb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,kBAAkB,IAAI;AAAA,cACrC,cAAY,OAAO;AAAA,cACnB,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,UAC3B;AAAA,UAEC,oBACC,qBAAC,WACC;AAAA,gCAAC,kBAAe,SAAO,MACrB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,cAAY,mBAAmB,OAAO,gBAAgB,OAAO;AAAA,gBAC7D,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,gBACF;AAAA,gBAEA,8BAAC,aAAU,WAAU,UAAS;AAAA;AAAA,YAChC,GACF;AAAA,YACA,qBAAC,kBAAe,MAAK,UAClB;AAAA,iCAAmB,OAAO,gBAAgB,OAAO;AAAA,cAAgB;AAAA,cAAI;AAAA,cAAO;AAAA,eAC/E;AAAA,aACF,IACE;AAAA,UAEH,OAAO,oBAAC,SAAI,WAAU,YAAY,gBAAK,IAAS;AAAA,UAGjD,oBAAC,SAAI,WAAU,6BACZ,yBAAe,QAAQ,oBAAC,QAAG,WAAU,kDAAkD,iBAAM,IAAQ,OACxG;AAAA,WACF;AAAA,QAUA,oBAAC,SAAI,WAAU,6CACZ,uBACI,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,sBAAsB,IAAI;AAAA,YACzC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAYT;AAAA,cACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAUA;AAAA,YACF;AAAA,YAEA;AAAA,kCAAC,UAAO,WAAU,qBAAoB;AAAA,cACtC,oBAAC,UAAK,WAAU,6BAA6B,iBAAO,mBAAkB;AAAA,cACtE,qBAAC,OAAK;AAAA;AAAA,gBAAO;AAAA,iBAAC;AAAA;AAAA;AAAA,QAChB,IAEF,MACN;AAAA,QAKA,qBAAC,SAAI,WAAU,uDACZ;AAAA;AAAA,UAOA,cAAc,CAAC,aACd;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,sBAAsB,IAAI;AAAA,cACzC,cAAY,OAAO;AAAA,cACnB,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,UAAO,WAAU,UAAS;AAAA;AAAA,UAC7B,IACE;AAAA,UAUH,kBAAkB,oBAAC,eAAY,WAAU,kBAAiB,IAAK;AAAA,UAE/D,sBACE,sBAAsB,SAAY,OACjC;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,OAAO,OAAO;AAAA,cACd,SAAS;AAAA;AAAA,UACX;AAAA,UAUH,kBAAkB,oBAAC,eAAY,QAAgB,WAAU,kBAAiB,IAAK;AAAA,UAE/E,gBAAgB,OACf,iCACE;AAAA,gCAAC,UAAK,WAAU,2CAA0C,eAAY,QAAO;AAAA,YAC7E,oBAAC,YAAU,GAAG,eAAe;AAAA,aAC/B,IACE;AAAA,WACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -29,6 +29,23 @@ interface AppShellProps extends ShellProviderProps {
29
29
  * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.
30
30
  */
31
31
  commandPaletteSlot?: ReactNode;
32
+ /**
33
+ * Ngôn ngữ của cả cây shell (mã BCP 47: `"vi"`, `"en"`, `"en-GB"`…) — TIỆN ÍCH, không phải chỗ
34
+ * khuyên dùng.
35
+ *
36
+ * Chỗ đúng là `<ComitorLocaleProvider locale={…}>` ở ROOT layout: nó phủ cả những trang KHÔNG
37
+ * nằm trong shell (đăng nhập, trang lỗi, trang công khai) và mọi component của gói đọc theo nó.
38
+ * Prop này chỉ dành cho app không có root layout riêng để đặt provider.
39
+ *
40
+ * Có truyền → `AppShell` bọc cây của nó trong `ComitorLocaleProvider locale={locale}`. Bỏ trống →
41
+ * không bọc gì, mọi thứ đọc provider gần nhất bên ngoài (không có thì tiếng Anh).
42
+ *
43
+ * ⚠ Provider dựng ở đây là provider MỚI: nó KHÔNG kế thừa `messages` (ghi đè chữ) hay
44
+ * `dateFnsLocale` của provider bên ngoài. Cần những thứ đó thì đặt provider ở root layout.
45
+ * ⚠ Đổi qua lại giữa có và không truyền prop này đổi hình dạng cây — cả shell dựng lại từ đầu
46
+ * (mất trạng thái). Giữ nó luôn có hoặc luôn vắng.
47
+ */
48
+ locale?: string;
32
49
  className?: string;
33
50
  contentClassName?: string;
34
51
  }
@@ -36,11 +53,12 @@ interface AppShellProps extends ShellProviderProps {
36
53
  * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.
37
54
  *
38
55
  * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes
39
- * cần nằm trên cùng để tránh nháy theme lúc hydrate.
56
+ * cần nằm trên cùng để tránh nháy theme lúc hydrate. `<ComitorLocaleProvider>` cũng nên ở đó
57
+ * (ngôn ngữ của cả gói, kể cả trang ngoài shell); prop `locale` chỉ là lối tắt.
40
58
  *
41
59
  * Có `"use client"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi
42
60
  * render `<AppShell>` phải là client component.
43
61
  */
44
- declare function AppShell({ layout, showSidebar, headerProps, sidebarProps, mobileMenuProps, commandPaletteSlot, className, contentClassName, children, ...providerProps }: AppShellProps): React.JSX.Element;
62
+ declare function AppShell({ layout, showSidebar, headerProps, sidebarProps, mobileMenuProps, commandPaletteSlot, locale, className, contentClassName, children, ...providerProps }: AppShellProps): React.JSX.Element;
45
63
 
46
64
  export { AppShell, type AppShellLayout, type AppShellProps };
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { ComitorLocaleProvider } from "../i18n/locale-provider.js";
3
4
  import { cn } from "../lib/cn.js";
4
5
  import { AppHeader } from "./app-header.js";
5
6
  import { MobileMenu } from "./mobile-menu.js";
@@ -14,12 +15,13 @@ function AppShell({
14
15
  sidebarProps,
15
16
  mobileMenuProps,
16
17
  commandPaletteSlot,
18
+ locale,
17
19
  className,
18
20
  contentClassName,
19
21
  children,
20
22
  ...providerProps
21
23
  }) {
22
- return /* @__PURE__ */ jsx(ShellProvider, { ...providerProps, children: /* @__PURE__ */ jsx(
24
+ const shell = /* @__PURE__ */ jsx(ShellProvider, { ...providerProps, children: /* @__PURE__ */ jsx(
23
25
  AppShellFrame,
24
26
  {
25
27
  layout,
@@ -33,6 +35,7 @@ function AppShell({
33
35
  children
34
36
  }
35
37
  ) });
38
+ return locale === void 0 ? shell : /* @__PURE__ */ jsx(ComitorLocaleProvider, { locale, children: shell });
36
39
  }
37
40
  function AppShellFrame({
38
41
  layout = "sidebar-first",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppHeader, type AppHeaderProps } from \"./app-header.js\";\nimport { MobileMenu, type MobileMenuProps } from \"./mobile-menu.js\";\nimport { ShellProvider, type ShellProviderProps, useShell } from \"./shell-context.js\";\nimport { Sidebar, type SidebarProps } from \"./sidebar.js\";\nimport { appAccentStyle } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport type AppShellLayout = \"sidebar-first\" | \"header-first\";\n\nexport interface AppShellProps extends ShellProviderProps {\n /**\n * \"sidebar-first\" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó\n * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).\n * \"header-first\" — header full width trên cùng, sidebar nằm dưới\n * (WorkspaceSwitcher chuyển lên header).\n */\n layout?: AppShellLayout;\n /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */\n showSidebar?: boolean;\n headerProps?: AppHeaderProps;\n sidebarProps?: SidebarProps;\n mobileMenuProps?: MobileMenuProps;\n /**\n * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),\n * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.\n */\n commandPaletteSlot?: ReactNode;\n className?: string;\n contentClassName?: string;\n}\n\n/**\n * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.\n *\n * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes\n * cần nằm trên cùng để tránh nháy theme lúc hydrate.\n *\n * Có `\"use client\"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi\n * render `<AppShell>` phải là client component.\n */\nexport function AppShell({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children,\n ...providerProps\n}: AppShellProps) {\n return (\n <ShellProvider {...providerProps}>\n <AppShellFrame\n layout={layout}\n showSidebar={showSidebar}\n headerProps={headerProps}\n sidebarProps={sidebarProps}\n mobileMenuProps={mobileMenuProps}\n commandPaletteSlot={commandPaletteSlot}\n className={className}\n contentClassName={contentClassName}\n >\n {children}\n </AppShellFrame>\n </ShellProvider>\n );\n}\n\ntype AppShellFrameProps = Pick<\n AppShellProps,\n | \"layout\"\n | \"showSidebar\"\n | \"headerProps\"\n | \"sidebarProps\"\n | \"mobileMenuProps\"\n | \"commandPaletteSlot\"\n | \"className\"\n | \"contentClassName\"\n> & { children: ReactNode };\n\n/**\n * Phần khung thật sự — tách khỏi `AppShell` vì cần `useShell()` (phải ở TRONG provider)\n * để gán `--app-accent` của app đang mở cho cả cây.\n */\nfunction AppShellFrame({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children\n}: AppShellFrameProps) {\n const { currentApp } = useShell();\n const headerFirst = layout === \"header-first\";\n\n // Ở header-first, bộ chuyển workspace chuyển lên header — nếu để cả hai chỗ\n // thì người dùng thấy hai nút giống hệt nhau.\n // Spread TRƯỚC rồi mới đặt giá trị mặc định, nếu không `{...headerProps}` sẽ ghi đè\n // bằng `undefined` và giá trị mặc định tính ở trên thành công cốc.\n const header = (\n <AppHeader\n {...headerProps}\n showSidebarToggle={headerProps?.showSidebarToggle ?? headerFirst}\n logo={headerProps?.logo ?? (headerFirst ? <WorkspaceSwitcher className=\"w-56 shrink-0\" /> : undefined)}\n />\n );\n\n const sidebar = showSidebar ? (\n <Sidebar\n {...sidebarProps}\n showWorkspaceSwitcher={sidebarProps?.showWorkspaceSwitcher ?? !headerFirst}\n showCollapseButton={sidebarProps?.showCollapseButton ?? !headerFirst}\n />\n ) : null;\n\n /*\n * ─ VÌ SAO <main> PHẢI CÓ `relative` ─\n *\n * `overflow-y-auto` KHÔNG cắt phần tử `position: absolute` nếu chính nó không được định vị:\n * containing block của những phần tử đó nhảy lên tới `body`/ICB, nên chúng nằm ngoài tầm cắt\n * của `main` và **kéo dài vùng cuộn của cả TÀI LIỆU**. Khung shell là `h-dvh overflow-hidden`\n * nên phần kéo dài đó không có gì để vẽ: người dùng cuộn xuống và thấy một khoảng TRẮNG lớn\n * dưới đáy trang.\n *\n * Không phải lỗi lý thuyết — hai nguồn sinh ra nó có mặt trong chính gói này:\n * • `sr-only` của Tailwind là `position: absolute` không có `top/left`, nên nó đứng ở vị trí\n * tĩnh của mình. Trong `TablePagination`, bốn nhãn ẩn (\"Trang đầu/trước/sau/cuối\") nằm\n * trong `<Button>` — mà `Button` không `relative`.\n * • Radix để `<input>`/`<select>` gốc ẩn bằng `position: absolute` (RadioGroup, Select).\n *\n * Đo trên trang `/cong-viec` trước khi vá: `documentElement.scrollHeight` = 1531 trong khi\n * `clientHeight` = 900 → 631px trắng. Trang `/bieu-mau`: 1792 / 900 → 892px. Trang ngắn hoặc\n * trang có các phần tử đó nằm TRÊN nếp gấp thì không lộ ra — đó là lý do lỗi trông như chỉ dính\n * vài trang.\n *\n * `relative` đưa containing block về đúng `main`, và `overflow-y-auto` cắt được như mong đợi.\n * ⚠ Đừng đổi sang `contain: paint`: nó cũng chữa được nhưng kéo theo containment về sơn/bố cục\n * mà không ai cần ở đây.\n */\n const main = (\n <main className={cn(\"relative flex-1 overflow-y-auto bg-background\", contentClassName)}>{children}</main>\n );\n\n return (\n <div\n style={appAccentStyle(currentApp)}\n className={cn(\n \"flex h-dvh w-full overflow-hidden bg-background\",\n headerFirst ? \"flex-col\" : \"flex-row\",\n className\n )}\n >\n {headerFirst ? (\n <>\n {header}\n <div className=\"flex min-h-0 flex-1\">\n {sidebar}\n {main}\n </div>\n </>\n ) : (\n <>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n {main}\n </div>\n </>\n )}\n\n <MobileMenu {...mobileMenuProps} />\n {commandPaletteSlot}\n </div>\n );\n}\n"],"mappings":";AA0DM,SAwGE,UAxGF,KA0GI,YA1GJ;AAvDN,SAAS,UAAU;AACnB,SAAS,iBAAsC;AAC/C,SAAS,kBAAwC;AACjD,SAAS,eAAwC,gBAAgB;AACjE,SAAS,eAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAmC3B,SAAS,SAAS;AAAA,EACvB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,SACE,oBAAC,iBAAe,GAAG,eACjB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH,GACF;AAEJ;AAkBA,SAAS,cAAc;AAAA,EACrB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,QAAM,cAAc,WAAW;AAM/B,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,mBAAmB,aAAa,qBAAqB;AAAA,MACrD,MAAM,aAAa,SAAS,cAAc,oBAAC,qBAAkB,WAAU,iBAAgB,IAAK;AAAA;AAAA,EAC9F;AAGF,QAAM,UAAU,cACd;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,uBAAuB,cAAc,yBAAyB,CAAC;AAAA,MAC/D,oBAAoB,cAAc,sBAAsB,CAAC;AAAA;AAAA,EAC3D,IACE;AA0BJ,QAAM,OACJ,oBAAC,UAAK,WAAW,GAAG,iDAAiD,gBAAgB,GAAI,UAAS;AAGpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,eAAe,UAAU;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B;AAAA,MACF;AAAA,MAEC;AAAA,sBACC,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF,IAEA,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,gCACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF;AAAA,QAGF,oBAAC,cAAY,GAAG,iBAAiB;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ComitorLocaleProvider } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppHeader, type AppHeaderProps } from \"./app-header.js\";\nimport { MobileMenu, type MobileMenuProps } from \"./mobile-menu.js\";\nimport { ShellProvider, type ShellProviderProps, useShell } from \"./shell-context.js\";\nimport { Sidebar, type SidebarProps } from \"./sidebar.js\";\nimport { appAccentStyle } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport type AppShellLayout = \"sidebar-first\" | \"header-first\";\n\nexport interface AppShellProps extends ShellProviderProps {\n /**\n * \"sidebar-first\" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó\n * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).\n * \"header-first\" — header full width trên cùng, sidebar nằm dưới\n * (WorkspaceSwitcher chuyển lên header).\n */\n layout?: AppShellLayout;\n /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */\n showSidebar?: boolean;\n headerProps?: AppHeaderProps;\n sidebarProps?: SidebarProps;\n mobileMenuProps?: MobileMenuProps;\n /**\n * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),\n * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.\n */\n commandPaletteSlot?: ReactNode;\n /**\n * Ngôn ngữ của cả cây shell (mã BCP 47: `\"vi\"`, `\"en\"`, `\"en-GB\"`…) — TIỆN ÍCH, không phải chỗ\n * khuyên dùng.\n *\n * Chỗ đúng là `<ComitorLocaleProvider locale={…}>` ở ROOT layout: nó phủ cả những trang KHÔNG\n * nằm trong shell (đăng nhập, trang lỗi, trang công khai) và mọi component của gói đọc theo nó.\n * Prop này chỉ dành cho app không có root layout riêng để đặt provider.\n *\n * Có truyền → `AppShell` bọc cây của nó trong `ComitorLocaleProvider locale={locale}`. Bỏ trống →\n * không bọc gì, mọi thứ đọc provider gần nhất bên ngoài (không có thì tiếng Anh).\n *\n * ⚠ Provider dựng ở đây là provider MỚI: nó KHÔNG kế thừa `messages` (ghi đè chữ) hay\n * `dateFnsLocale` của provider bên ngoài. Cần những thứ đó thì đặt provider ở root layout.\n * ⚠ Đổi qua lại giữa có và không truyền prop này đổi hình dạng cây — cả shell dựng lại từ đầu\n * (mất trạng thái). Giữ nó luôn có hoặc luôn vắng.\n */\n locale?: string;\n className?: string;\n contentClassName?: string;\n}\n\n/**\n * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.\n *\n * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes\n * cần nằm trên cùng để tránh nháy theme lúc hydrate. `<ComitorLocaleProvider>` cũng nên ở đó\n * (ngôn ngữ của cả gói, kể cả trang ngoài shell); prop `locale` chỉ là lối tắt.\n *\n * Có `\"use client\"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi\n * render `<AppShell>` phải là client component.\n */\nexport function AppShell({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n locale,\n className,\n contentClassName,\n children,\n ...providerProps\n}: AppShellProps) {\n const shell = (\n <ShellProvider {...providerProps}>\n <AppShellFrame\n layout={layout}\n showSidebar={showSidebar}\n headerProps={headerProps}\n sidebarProps={sidebarProps}\n mobileMenuProps={mobileMenuProps}\n commandPaletteSlot={commandPaletteSlot}\n className={className}\n contentClassName={contentClassName}\n >\n {children}\n </AppShellFrame>\n </ShellProvider>\n );\n\n // Provider ở NGOÀI `ShellProvider`: chính `ShellProvider` đọc namespace `shell` để dựng `labels`.\n return locale === undefined ? shell : <ComitorLocaleProvider locale={locale}>{shell}</ComitorLocaleProvider>;\n}\n\ntype AppShellFrameProps = Pick<\n AppShellProps,\n | \"layout\"\n | \"showSidebar\"\n | \"headerProps\"\n | \"sidebarProps\"\n | \"mobileMenuProps\"\n | \"commandPaletteSlot\"\n | \"className\"\n | \"contentClassName\"\n> & { children: ReactNode };\n\n/**\n * Phần khung thật sự — tách khỏi `AppShell` vì cần `useShell()` (phải ở TRONG provider)\n * để gán `--app-accent` của app đang mở cho cả cây.\n */\nfunction AppShellFrame({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children\n}: AppShellFrameProps) {\n const { currentApp } = useShell();\n const headerFirst = layout === \"header-first\";\n\n // Ở header-first, bộ chuyển workspace chuyển lên header — nếu để cả hai chỗ\n // thì người dùng thấy hai nút giống hệt nhau.\n // Spread TRƯỚC rồi mới đặt giá trị mặc định, nếu không `{...headerProps}` sẽ ghi đè\n // bằng `undefined` và giá trị mặc định tính ở trên thành công cốc.\n const header = (\n <AppHeader\n {...headerProps}\n showSidebarToggle={headerProps?.showSidebarToggle ?? headerFirst}\n logo={headerProps?.logo ?? (headerFirst ? <WorkspaceSwitcher className=\"w-56 shrink-0\" /> : undefined)}\n />\n );\n\n const sidebar = showSidebar ? (\n <Sidebar\n {...sidebarProps}\n showWorkspaceSwitcher={sidebarProps?.showWorkspaceSwitcher ?? !headerFirst}\n showCollapseButton={sidebarProps?.showCollapseButton ?? !headerFirst}\n />\n ) : null;\n\n /*\n * ─ VÌ SAO <main> PHẢI CÓ `relative` ─\n *\n * `overflow-y-auto` KHÔNG cắt phần tử `position: absolute` nếu chính nó không được định vị:\n * containing block của những phần tử đó nhảy lên tới `body`/ICB, nên chúng nằm ngoài tầm cắt\n * của `main` và **kéo dài vùng cuộn của cả TÀI LIỆU**. Khung shell là `h-dvh overflow-hidden`\n * nên phần kéo dài đó không có gì để vẽ: người dùng cuộn xuống và thấy một khoảng TRẮNG lớn\n * dưới đáy trang.\n *\n * Không phải lỗi lý thuyết — hai nguồn sinh ra nó có mặt trong chính gói này:\n * • `sr-only` của Tailwind là `position: absolute` không có `top/left`, nên nó đứng ở vị trí\n * tĩnh của mình. Trong `TablePagination`, bốn nhãn ẩn (\"Trang đầu/trước/sau/cuối\") nằm\n * trong `<Button>` — mà `Button` không `relative`.\n * • Radix để `<input>`/`<select>` gốc ẩn bằng `position: absolute` (RadioGroup, Select).\n *\n * Đo trên trang `/cong-viec` trước khi vá: `documentElement.scrollHeight` = 1531 trong khi\n * `clientHeight` = 900 → 631px trắng. Trang `/bieu-mau`: 1792 / 900 → 892px. Trang ngắn hoặc\n * trang có các phần tử đó nằm TRÊN nếp gấp thì không lộ ra — đó là lý do lỗi trông như chỉ dính\n * vài trang.\n *\n * `relative` đưa containing block về đúng `main`, và `overflow-y-auto` cắt được như mong đợi.\n * ⚠ Đừng đổi sang `contain: paint`: nó cũng chữa được nhưng kéo theo containment về sơn/bố cục\n * mà không ai cần ở đây.\n */\n const main = (\n <main className={cn(\"relative flex-1 overflow-y-auto bg-background\", contentClassName)}>{children}</main>\n );\n\n return (\n <div\n style={appAccentStyle(currentApp)}\n className={cn(\n \"flex h-dvh w-full overflow-hidden bg-background\",\n headerFirst ? \"flex-col\" : \"flex-row\",\n className\n )}\n >\n {headerFirst ? (\n <>\n {header}\n <div className=\"flex min-h-0 flex-1\">\n {sidebar}\n {main}\n </div>\n </>\n ) : (\n <>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n {main}\n </div>\n </>\n )}\n\n <MobileMenu {...mobileMenuProps} />\n {commandPaletteSlot}\n </div>\n );\n}\n"],"mappings":";AA8EM,SA2GE,UA3GF,KA6GI,YA7GJ;AA3EN,SAAS,6BAA6B;AACtC,SAAS,UAAU;AACnB,SAAS,iBAAsC;AAC/C,SAAS,kBAAwC;AACjD,SAAS,eAAwC,gBAAgB;AACjE,SAAS,eAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAqD3B,SAAS,SAAS;AAAA,EACvB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,QACJ,oBAAC,iBAAe,GAAG,eACjB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH,GACF;AAIF,SAAO,WAAW,SAAY,QAAQ,oBAAC,yBAAsB,QAAiB,iBAAM;AACtF;AAkBA,SAAS,cAAc;AAAA,EACrB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,QAAM,cAAc,WAAW;AAM/B,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,mBAAmB,aAAa,qBAAqB;AAAA,MACrD,MAAM,aAAa,SAAS,cAAc,oBAAC,qBAAkB,WAAU,iBAAgB,IAAK;AAAA;AAAA,EAC9F;AAGF,QAAM,UAAU,cACd;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,uBAAuB,cAAc,yBAAyB,CAAC;AAAA,MAC/D,oBAAoB,cAAc,sBAAsB,CAAC;AAAA;AAAA,EAC3D,IACE;AA0BJ,QAAM,OACJ,oBAAC,UAAK,WAAW,GAAG,iDAAiD,gBAAgB,GAAI,UAAS;AAGpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,eAAe,UAAU;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B;AAAA,MACF;AAAA,MAEC;AAAA,sBACC,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF,IAEA,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,gCACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF;AAAA,QAGF,oBAAC,cAAY,GAAG,iBAAiB;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -17,13 +17,21 @@ declare const CONTRAST_MODES: readonly ContrastMode[];
17
17
  declare const CONTRAST_ATTRIBUTE = "data-contrast";
18
18
  /** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */
19
19
  declare const CONTRAST_STORAGE_KEY = "comitor-contrast";
20
- /** Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell. */
20
+ /**
21
+ * Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell.
22
+ * Theo `ComitorLocaleProvider` (namespace `contrast`); override từng khoá qua prop `labels`.
23
+ */
21
24
  interface ContrastToggleLabels {
22
25
  /** Nhãn chính, đứng cạnh nút gạt. */
23
26
  label: string;
24
27
  /** Dòng mô tả bên dưới nhãn. */
25
28
  description: string;
26
29
  }
30
+ /**
31
+ * 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:
32
+ * `comitorMessagesVi.contrast`. `ContrastToggle` không đọc thẳng hằng này — nó đọc bảng của
33
+ * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
34
+ */
27
35
  declare const DEFAULT_CONTRAST_TOGGLE_LABELS: ContrastToggleLabels;
28
36
  declare const isContrastMode: (value: unknown) => value is ContrastMode;
29
37
 
@@ -1,10 +1,8 @@
1
+ import { contrastEn } from "../i18n/namespaces/contrast.js";
1
2
  const CONTRAST_MODES = ["normal", "high"];
2
3
  const CONTRAST_ATTRIBUTE = "data-contrast";
3
4
  const CONTRAST_STORAGE_KEY = "comitor-contrast";
4
- const DEFAULT_CONTRAST_TOGGLE_LABELS = {
5
- label: "T\u01B0\u01A1ng ph\u1EA3n cao",
6
- description: "\u0110\u1EADm m\xE0u vi\u1EC1n, c\xF4ng t\u1EAFc v\xE0 ch\u1EEF ph\u1EE5. H\u1EEFu \xEDch tr\xEAn m\xE0n h\xECnh \u0111\u1ED9 s\xE1ng th\u1EA5p ho\u1EB7c khi d\xF9ng ngo\xE0i tr\u1EDDi."
7
- };
5
+ const DEFAULT_CONTRAST_TOGGLE_LABELS = { ...contrastEn };
8
6
  const isContrastMode = (value) => typeof value === "string" && CONTRAST_MODES.includes(value);
9
7
  export {
10
8
  CONTRAST_ATTRIBUTE,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/contrast-constants.ts"],"sourcesContent":["/**\n * Hằng và kiểu của trục TƯƠNG PHẢN — module THUẦN, cố ý KHÔNG có `\"use client\"`.\n *\n * Ranh giới RSC cắt theo FILE: mọi export của một module `\"use client\"` khi đi tới Server\n * Component đều thành client-reference proxy, nên `CONTRAST_STORAGE_KEY` sẽ ra `undefined` mà\n * KHÔNG throw và KHÔNG warning (xem CHANGELOG 0.2.2 §2). Root layout là Server Component và nó\n * cần đúng khoá này để render `<ContrastScript />`, nên hằng phải sống ở đây.\n *\n * ⚠ Barrel `shell/index.ts` phải xuất thẳng từ file này, KHÔNG vòng qua contrast-provider.tsx —\n * đi qua một file client là ranh giới biến hằng lại thành proxy (cổng `verify:directives` phép\n * kiểm 3 canh đúng chuyện này).\n */\n\n/** Hai bảng màu của hệ sinh thái. `normal` = comitor-ds (bản duyệt), `high` = bảng đã đo WCAG. */\nexport type ContrastMode = \"normal\" | \"high\";\n\nexport const CONTRAST_MODES: readonly ContrastMode[] = [\"normal\", \"high\"];\n\n/** Thuộc tính trên `<html>` mà styles.css nhìn vào: `[data-contrast=\"high\"]`. */\nexport const CONTRAST_ATTRIBUTE = \"data-contrast\";\n\n/** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */\nexport const CONTRAST_STORAGE_KEY = \"comitor-contrast\";\n\n/** Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell. */\nexport interface ContrastToggleLabels {\n /** Nhãn chính, đứng cạnh nút gạt. */\n label: string;\n /** Dòng mô tả bên dưới nhãn. */\n description: string;\n}\n\nexport const DEFAULT_CONTRAST_TOGGLE_LABELS: ContrastToggleLabels = {\n label: \"Tương phản cao\",\n description: \"Đậm màu viền, công tắc và chữ phụ. Hữu ích trên màn hình độ sáng thấp hoặc khi dùng ngoài trời.\"\n};\n\nexport const isContrastMode = (value: unknown): value is ContrastMode =>\n typeof value === \"string\" && (CONTRAST_MODES as readonly string[]).includes(value);\n"],"mappings":"AAgBO,MAAM,iBAA0C,CAAC,UAAU,MAAM;AAGjE,MAAM,qBAAqB;AAG3B,MAAM,uBAAuB;AAU7B,MAAM,iCAAuD;AAAA,EAClE,OAAO;AAAA,EACP,aAAa;AACf;AAEO,MAAM,iBAAiB,CAAC,UAC7B,OAAO,UAAU,YAAa,eAAqC,SAAS,KAAK;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/contrast-constants.ts"],"sourcesContent":["/**\n * Hằng và kiểu của trục TƯƠNG PHẢN — module THUẦN, cố ý KHÔNG có `\"use client\"`.\n *\n * Ranh giới RSC cắt theo FILE: mọi export của một module `\"use client\"` khi đi tới Server\n * Component đều thành client-reference proxy, nên `CONTRAST_STORAGE_KEY` sẽ ra `undefined` mà\n * KHÔNG throw và KHÔNG warning (xem CHANGELOG 0.2.2 §2). Root layout là Server Component và nó\n * cần đúng khoá này để render `<ContrastScript />`, nên hằng phải sống ở đây.\n *\n * ⚠ Barrel `shell/index.ts` phải xuất thẳng từ file này, KHÔNG vòng qua contrast-provider.tsx —\n * đi qua một file client là ranh giới biến hằng lại thành proxy (cổng `verify:directives` phép\n * kiểm 3 canh đúng chuyện này).\n */\n\nimport { contrastEn } from \"../i18n/namespaces/contrast.js\";\n\n/** Hai bảng màu của hệ sinh thái. `normal` = comitor-ds (bản duyệt), `high` = bảng đã đo WCAG. */\nexport type ContrastMode = \"normal\" | \"high\";\n\nexport const CONTRAST_MODES: readonly ContrastMode[] = [\"normal\", \"high\"];\n\n/** Thuộc tính trên `<html>` mà styles.css nhìn vào: `[data-contrast=\"high\"]`. */\nexport const CONTRAST_ATTRIBUTE = \"data-contrast\";\n\n/** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */\nexport const CONTRAST_STORAGE_KEY = \"comitor-contrast\";\n\n/**\n * Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell.\n * Theo `ComitorLocaleProvider` (namespace `contrast`); override từng khoá qua prop `labels`.\n */\nexport interface ContrastToggleLabels {\n /** Nhãn chính, đứng cạnh nút gạt. */\n label: string;\n /** Dòng mô tả bên dưới nhãn. */\n description: 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.contrast`. `ContrastToggle` 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 */\nexport const DEFAULT_CONTRAST_TOGGLE_LABELS: ContrastToggleLabels = { ...contrastEn };\n\nexport const isContrastMode = (value: unknown): value is ContrastMode =>\n typeof value === \"string\" && (CONTRAST_MODES as readonly string[]).includes(value);\n"],"mappings":"AAaA,SAAS,kBAAkB;AAKpB,MAAM,iBAA0C,CAAC,UAAU,MAAM;AAGjE,MAAM,qBAAqB;AAG3B,MAAM,uBAAuB;AAkB7B,MAAM,iCAAuD,EAAE,GAAG,WAAW;AAE7E,MAAM,iBAAiB,CAAC,UAC7B,OAAO,UAAU,YAAa,eAAqC,SAAS,KAAK;","names":[]}
@@ -3,7 +3,10 @@ import { ContrastToggleLabels, ContrastMode } from './contrast-constants.js';
3
3
 
4
4
  interface ContrastToggleProps {
5
5
  className?: string;
6
- /** Override chuỗi hiển thị (mặc định tiếng Việt). */
6
+ /**
7
+ * Override chuỗi hiển thị. Không truyền thì theo namespace `contrast` của
8
+ * `ComitorLocaleProvider` (không có provider → tiếng Anh).
9
+ */
7
10
  labels?: Partial<ContrastToggleLabels>;
8
11
  /** Gọi sau khi người dùng đổi — để app hiện toast hoặc ghi log. */
9
12
  onContrastChange?: (mode: ContrastMode) => void;
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { Switch } from "../components/ui/switch.js";
4
+ import { useComitorLabels } from "../i18n/locale-provider.js";
4
5
  import { cn } from "../lib/cn.js";
5
- import { DEFAULT_CONTRAST_TOGGLE_LABELS } from "./contrast-constants.js";
6
6
  import { useContrast } from "./contrast-provider.js";
7
7
  function ContrastToggle({ className, labels: labelsProp, onContrastChange }) {
8
8
  const { contrast, setContrast, mounted } = useContrast();
9
- const labels = { ...DEFAULT_CONTRAST_TOGGLE_LABELS, ...labelsProp };
9
+ const labels = useComitorLabels("contrast", labelsProp);
10
10
  return /* @__PURE__ */ jsx(
11
11
  Switch,
12
12
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/contrast-toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { Switch } from \"../components/ui/switch.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { type ContrastMode, type ContrastToggleLabels, DEFAULT_CONTRAST_TOGGLE_LABELS } from \"./contrast-constants.js\";\nimport { useContrast } from \"./contrast-provider.js\";\n\nexport interface ContrastToggleProps {\n className?: string;\n /** Override chuỗi hiển thị (mặc định tiếng Việt). */\n labels?: Partial<ContrastToggleLabels>;\n /** Gọi sau khi người dùng đổi — để app hiện toast hoặc ghi log. */\n onContrastChange?: (mode: ContrastMode) => void;\n}\n\n/**\n * Nút gạt chọn BẢNG MÀU (bản duyệt comitor-ds ⇄ bản tương phản cao). Trục này độc lập với\n * sáng/tối: hai bảng màu đều có bản light và bản dark riêng.\n *\n * Đặt ở trang cài đặt của app, cạnh `<ThemeToggle>`. Phải nằm trong `<ContrastProvider>`.\n *\n * Chống lệch hydration bằng cờ `mounted` của provider — cùng lối với `ThemeToggle`: lựa chọn thật\n * chỉ đọc được ở client (localStorage), render thẳng trạng thái nút ngay lần đầu sẽ lệch với HTML\n * do server sinh ra. Trước khi mount thì nút hiện ở trạng thái mặc định và bị khoá, để layout\n * không nhảy và người dùng không bấm trúng một trạng thái sắp bị thay.\n */\nexport function ContrastToggle({ className, labels: labelsProp, onContrastChange }: ContrastToggleProps) {\n const { contrast, setContrast, mounted } = useContrast();\n const labels: ContrastToggleLabels = { ...DEFAULT_CONTRAST_TOGGLE_LABELS, ...labelsProp };\n\n return (\n <Switch\n className={cn(className)}\n checked={contrast === \"high\"}\n disabled={!mounted}\n label={labels.label}\n description={labels.description}\n onCheckedChange={(checked) => {\n const next: ContrastMode = checked ? \"high\" : \"normal\";\n setContrast(next);\n onContrastChange?.(next);\n }}\n />\n );\n}\n"],"mappings":";AA+BI;AA7BJ,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAuD,sCAAsC;AAC7F,SAAS,mBAAmB;AAqBrB,SAAS,eAAe,EAAE,WAAW,QAAQ,YAAY,iBAAiB,GAAwB;AACvG,QAAM,EAAE,UAAU,aAAa,QAAQ,IAAI,YAAY;AACvD,QAAM,SAA+B,EAAE,GAAG,gCAAgC,GAAG,WAAW;AAExF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,SAAS;AAAA,MACvB,SAAS,aAAa;AAAA,MACtB,UAAU,CAAC;AAAA,MACX,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,iBAAiB,CAAC,YAAY;AAC5B,cAAM,OAAqB,UAAU,SAAS;AAC9C,oBAAY,IAAI;AAChB,2BAAmB,IAAI;AAAA,MACzB;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/contrast-toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { Switch } from \"../components/ui/switch.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ContrastMode, ContrastToggleLabels } from \"./contrast-constants.js\";\nimport { useContrast } from \"./contrast-provider.js\";\n\nexport interface ContrastToggleProps {\n className?: string;\n /**\n * Override chuỗi hiển thị. Không truyền thì theo namespace `contrast` của\n * `ComitorLocaleProvider` (không có provider → tiếng Anh).\n */\n labels?: Partial<ContrastToggleLabels>;\n /** Gọi sau khi người dùng đổi — để app hiện toast hoặc ghi log. */\n onContrastChange?: (mode: ContrastMode) => void;\n}\n\n/**\n * Nút gạt chọn BẢNG MÀU (bản duyệt comitor-ds ⇄ bản tương phản cao). Trục này độc lập với\n * sáng/tối: hai bảng màu đều có bản light và bản dark riêng.\n *\n * Đặt ở trang cài đặt của app, cạnh `<ThemeToggle>`. Phải nằm trong `<ContrastProvider>`.\n *\n * Chống lệch hydration bằng cờ `mounted` của provider — cùng lối với `ThemeToggle`: lựa chọn thật\n * chỉ đọc được ở client (localStorage), render thẳng trạng thái nút ngay lần đầu sẽ lệch với HTML\n * do server sinh ra. Trước khi mount thì nút hiện ở trạng thái mặc định và bị khoá, để layout\n * không nhảy và người dùng không bấm trúng một trạng thái sắp bị thay.\n */\nexport function ContrastToggle({ className, labels: labelsProp, onContrastChange }: ContrastToggleProps) {\n const { contrast, setContrast, mounted } = useContrast();\n const labels: ContrastToggleLabels = useComitorLabels(\"contrast\", labelsProp);\n\n return (\n <Switch\n className={cn(className)}\n checked={contrast === \"high\"}\n disabled={!mounted}\n label={labels.label}\n description={labels.description}\n onCheckedChange={(checked) => {\n const next: ContrastMode = checked ? \"high\" : \"normal\";\n setContrast(next);\n onContrastChange?.(next);\n }}\n />\n );\n}\n"],"mappings":";AAmCI;AAjCJ,SAAS,cAAc;AACvB,SAAS,wBAAwB;AACjC,SAAS,UAAU;AAEnB,SAAS,mBAAmB;AAwBrB,SAAS,eAAe,EAAE,WAAW,QAAQ,YAAY,iBAAiB,GAAwB;AACvG,QAAM,EAAE,UAAU,aAAa,QAAQ,IAAI,YAAY;AACvD,QAAM,SAA+B,iBAAiB,YAAY,UAAU;AAE5E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,SAAS;AAAA,MACvB,SAAS,aAAa;AAAA,MACtB,UAAU,CAAC;AAAA,MACX,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,iBAAiB,CAAC,YAAY;AAC5B,cAAM,OAAqB,UAAU,SAAS;AAC9C,oBAAY,IAAI;AAChB,2BAAmB,IAAI;AAAA,MACzB;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -23,7 +23,8 @@ declare function isDensityMode(value: unknown): value is DensityMode;
23
23
  *
24
24
  * Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào
25
25
  * `[data-font-size="…"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,
26
- * không theo luật nhãn. Nhãn tiếng Việt do app truyền vào control.
26
+ * không theo luật nhãn. Nhãn hiển thị của từng mức nằm ở namespace `displayAxis` (theo
27
+ * `ComitorLocaleProvider`) và prop `optionLabels` của `FontSizeControl`.
27
28
  */
28
29
  declare const FONT_SIZE_MODES: readonly ["sm", "md", "lg"];
29
30
  type FontSizeMode = (typeof FONT_SIZE_MODES)[number];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/display-axis-constants.ts"],"sourcesContent":["/**\n * Hằng của HAI trục hiển thị thêm: MẬT ĐỘ và CỠ CHỮ.\n *\n * ⚠ File này KHÔNG có `\"use client\"`, và đó là điều kiện bắt buộc chứ không phải tuỳ chọn.\n * Root layout là Server Component nhưng vẫn cần đọc tên thuộc tính / khoá lưu (vd để render sẵn\n * giá trị lên `<html>`). Mọi export của một module `\"use client\"` khi đi tới Server Component sẽ\n * biến thành client-reference proxy và đọc ra `undefined` — KHÔNG ném lỗi, không cảnh báo. Đúng\n * cái bẫy mà `contrast-constants.ts` được tách ra để tránh (CHANGELOG 0.2.2 §2), và cổng\n * `verify:directives` canh đúng chuyện này.\n *\n * Vì vậy: hằng ở đây, provider ở file khác, và `shell/index.ts` re-export THẲNG từ file này\n * chứ không đi vòng qua provider.\n */\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 3 — MẬT ĐỘ (bố cục gọn)\n ──────────────────────────────────────────────────────────────────────────── */\n\n/** `comfortable` là mặc định và KHÔNG ghi thuộc tính ra DOM — xem ghi chú ở cuối file. */\nexport const DENSITY_MODES = [\"comfortable\", \"compact\"] as const;\nexport type DensityMode = (typeof DENSITY_MODES)[number];\n\nexport const DENSITY_ATTRIBUTE = \"data-density\";\nexport const DENSITY_STORAGE_KEY = \"comitor-density\";\nexport const DEFAULT_DENSITY: DensityMode = \"comfortable\";\n\nexport function isDensityMode(value: unknown): value is DensityMode {\n return typeof value === \"string\" && (DENSITY_MODES as readonly string[]).includes(value);\n}\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 4 — CỠ CHỮ\n ──────────────────────────────────────────────────────────────────────────── */\n\n/**\n * `md` là mặc định và KHÔNG ghi thuộc tính ra DOM.\n *\n * Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào\n * `[data-font-size=\"…\"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,\n * không theo luật nhãn. Nhãn tiếng Việt do app truyền vào control.\n */\nexport const FONT_SIZE_MODES = [\"sm\", \"md\", \"lg\"] as const;\nexport type FontSizeMode = (typeof FONT_SIZE_MODES)[number];\n\nexport const FONT_SIZE_ATTRIBUTE = \"data-font-size\";\nexport const FONT_SIZE_STORAGE_KEY = \"comitor-font-size\";\nexport const DEFAULT_FONT_SIZE: FontSizeMode = \"md\";\n\nexport function isFontSizeMode(value: unknown): value is FontSizeMode {\n return typeof value === \"string\" && (FONT_SIZE_MODES as readonly string[]).includes(value);\n}\n\n/**\n * ─ VÌ SAO GIÁ TRỊ MẶC ĐỊNH KHÔNG GHI THUỘC TÍNH ─\n *\n * Cả hai trục đều GỠ thuộc tính khi ở mức mặc định thay vì ghi `data-density=\"comfortable\"`.\n * Cùng lối với `ContrastProvider`, và ở trục cỡ chữ thì nó còn là chuyện TRỢ NĂNG:\n *\n * Mức `md` không được phép là `font-size: 100%` viết ra DOM, càng không được là `16px`. Người\n * dùng có thể đã tăng cỡ chữ mặc định của trình duyệt; khai một cỡ tuyệt đối ở `<html>` là XOÁ\n * lựa chọn đó — tức tự tay vi phạm WCAG 1.4.4 ngay bên trong một tính năng trợ năng. Không ghi gì\n * ở mức mặc định thì cỡ gốc của trình duyệt được giữ nguyên, còn `sm`/`lg` là PHẦN TRĂM của chính\n * cỡ đó (87,5% / 112,5%) nên vẫn tôn trọng nó.\n *\n * ⚠ Không cổng nào bắt được lỗi này: `check-contrast.mjs` chỉ có ngưỡng 4,5 và 3, không có logic\n * cỡ chữ nào cả.\n */\n"],"mappings":"AAmBO,MAAM,gBAAgB,CAAC,eAAe,SAAS;AAG/C,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,kBAA+B;AAErC,SAAS,cAAc,OAAsC;AAClE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;AAaO,MAAM,kBAAkB,CAAC,MAAM,MAAM,IAAI;AAGzC,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,oBAAkC;AAExC,SAAS,eAAe,OAAuC;AACpE,SAAO,OAAO,UAAU,YAAa,gBAAsC,SAAS,KAAK;AAC3F;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/display-axis-constants.ts"],"sourcesContent":["/**\n * Hằng của HAI trục hiển thị thêm: MẬT ĐỘ và CỠ CHỮ.\n *\n * ⚠ File này KHÔNG có `\"use client\"`, và đó là điều kiện bắt buộc chứ không phải tuỳ chọn.\n * Root layout là Server Component nhưng vẫn cần đọc tên thuộc tính / khoá lưu (vd để render sẵn\n * giá trị lên `<html>`). Mọi export của một module `\"use client\"` khi đi tới Server Component sẽ\n * biến thành client-reference proxy và đọc ra `undefined` — KHÔNG ném lỗi, không cảnh báo. Đúng\n * cái bẫy mà `contrast-constants.ts` được tách ra để tránh (CHANGELOG 0.2.2 §2), và cổng\n * `verify:directives` canh đúng chuyện này.\n *\n * Vì vậy: hằng ở đây, provider ở file khác, và `shell/index.ts` re-export THẲNG từ file này\n * chứ không đi vòng qua provider.\n */\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 3 — MẬT ĐỘ (bố cục gọn)\n ──────────────────────────────────────────────────────────────────────────── */\n\n/** `comfortable` là mặc định và KHÔNG ghi thuộc tính ra DOM — xem ghi chú ở cuối file. */\nexport const DENSITY_MODES = [\"comfortable\", \"compact\"] as const;\nexport type DensityMode = (typeof DENSITY_MODES)[number];\n\nexport const DENSITY_ATTRIBUTE = \"data-density\";\nexport const DENSITY_STORAGE_KEY = \"comitor-density\";\nexport const DEFAULT_DENSITY: DensityMode = \"comfortable\";\n\nexport function isDensityMode(value: unknown): value is DensityMode {\n return typeof value === \"string\" && (DENSITY_MODES as readonly string[]).includes(value);\n}\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 4 — CỠ CHỮ\n ──────────────────────────────────────────────────────────────────────────── */\n\n/**\n * `md` là mặc định và KHÔNG ghi thuộc tính ra DOM.\n *\n * Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào\n * `[data-font-size=\"…\"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,\n * không theo luật nhãn. Nhãn hiển thị của từng mức nằm ở namespace `displayAxis` (theo\n * `ComitorLocaleProvider`) và prop `optionLabels` của `FontSizeControl`.\n */\nexport const FONT_SIZE_MODES = [\"sm\", \"md\", \"lg\"] as const;\nexport type FontSizeMode = (typeof FONT_SIZE_MODES)[number];\n\nexport const FONT_SIZE_ATTRIBUTE = \"data-font-size\";\nexport const FONT_SIZE_STORAGE_KEY = \"comitor-font-size\";\nexport const DEFAULT_FONT_SIZE: FontSizeMode = \"md\";\n\nexport function isFontSizeMode(value: unknown): value is FontSizeMode {\n return typeof value === \"string\" && (FONT_SIZE_MODES as readonly string[]).includes(value);\n}\n\n/**\n * ─ VÌ SAO GIÁ TRỊ MẶC ĐỊNH KHÔNG GHI THUỘC TÍNH ─\n *\n * Cả hai trục đều GỠ thuộc tính khi ở mức mặc định thay vì ghi `data-density=\"comfortable\"`.\n * Cùng lối với `ContrastProvider`, và ở trục cỡ chữ thì nó còn là chuyện TRỢ NĂNG:\n *\n * Mức `md` không được phép là `font-size: 100%` viết ra DOM, càng không được là `16px`. Người\n * dùng có thể đã tăng cỡ chữ mặc định của trình duyệt; khai một cỡ tuyệt đối ở `<html>` là XOÁ\n * lựa chọn đó — tức tự tay vi phạm WCAG 1.4.4 ngay bên trong một tính năng trợ năng. Không ghi gì\n * ở mức mặc định thì cỡ gốc của trình duyệt được giữ nguyên, còn `sm`/`lg` là PHẦN TRĂM của chính\n * cỡ đó (87,5% / 112,5%) nên vẫn tôn trọng nó.\n *\n * ⚠ Không cổng nào bắt được lỗi này: `check-contrast.mjs` chỉ có ngưỡng 4,5 và 3, không có logic\n * cỡ chữ nào cả.\n */\n"],"mappings":"AAmBO,MAAM,gBAAgB,CAAC,eAAe,SAAS;AAG/C,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,kBAA+B;AAErC,SAAS,cAAc,OAAsC;AAClE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;AAcO,MAAM,kBAAkB,CAAC,MAAM,MAAM,IAAI;AAGzC,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,oBAAkC;AAExC,SAAS,eAAe,OAAuC;AACpE,SAAO,OAAO,UAAU,YAAa,gBAAsC,SAAS,KAAK;AAC3F;","names":[]}
@@ -13,9 +13,15 @@ import { DensityMode, FontSizeMode } from './display-axis-constants.js';
13
13
  *
14
14
  * (DOM thật thì không nháy: script chống nháy của provider đã đặt thuộc tính lên `<html>` trước
15
15
  * lần sơn đầu tiên. Chỉ CONTROL là phải chờ.)
16
+ *
17
+ * ─ CHỮ ─
18
+ * Mọi chuỗi mặc định theo namespace `displayAxis` của `ComitorLocaleProvider` (không có provider
19
+ * → tiếng Anh). Prop `label`/`description`/`optionLabels` truyền vào vẫn thắng.
16
20
  */
17
21
  interface DensityToggleProps {
22
+ /** Nhãn nút gạt (khoá `densityLabel` của namespace `displayAxis`). */
18
23
  label?: string;
24
+ /** Dòng mô tả dưới nhãn (khoá `densityDescription`). */
19
25
  description?: React.ReactNode;
20
26
  className?: string;
21
27
  /**
@@ -40,11 +46,14 @@ interface DensityToggleProps {
40
46
  * thanh "Có thay đổi chưa lưu" sáng lên cho một thay đổi đã có hiệu lực, và nút "Hoàn tác" trong
41
47
  * toast sẽ lùi bản nháp mà KHÔNG lùi được trục — giao diện tự mâu thuẫn với chính nó.
42
48
  */
43
- declare function DensityToggle({ label, description, className, onDensityChange }: DensityToggleProps): React.JSX.Element;
49
+ declare function DensityToggle({ label: labelProp, description: descriptionProp, className, onDensityChange }: DensityToggleProps): React.JSX.Element;
44
50
  interface FontSizeControlProps {
45
- /** Nhãn của cả nhóm, đọc lên cho trình đọc màn hình. */
51
+ /** Nhãn của cả nhóm, đọc lên cho trình đọc màn hình (khoá `fontSizeLabel` của `displayAxis`). */
46
52
  label?: string;
47
- /** Nhãn từng mức — THỨ TỰ theo `FONT_SIZE_MODES` (`sm`, `md`, `lg`). */
53
+ /**
54
+ * Nhãn từng mức — THỨ TỰ theo `FONT_SIZE_MODES` (`sm`, `md`, `lg`). Không truyền thì lấy ba khoá
55
+ * `fontSizeSm`/`fontSizeMd`/`fontSizeLg` của namespace `displayAxis`.
56
+ */
48
57
  optionLabels?: Record<FontSizeMode, string>;
49
58
  className?: string;
50
59
  /** Gọi SAU khi người dùng đổi — xem giải thích ở `DensityToggleProps.onDensityChange`. */
@@ -58,9 +67,9 @@ interface FontSizeControlProps {
58
67
  * phím tới được bằng Tab) thay vì roving focus của Radix ToggleGroup — nhóm chỉ có ba mục nên
59
68
  * roving focus không đáng cái giá thêm một phụ thuộc.
60
69
  *
61
- * Cỡ chữ của CHÍNH các nút này cố định `text-sm`: nếu chúng phóng theo lựa chọn thì bấm "Lớn" sẽ
62
- * làm cả hàng nút nhảy chỗ ngay dưới ngón tay.
70
+ * Cỡ chữ của CHÍNH các nút này cố định `text-sm`: nếu chúng phóng theo lựa chọn thì bấm mức `lg`
71
+ * sẽ làm cả hàng nút nhảy chỗ ngay dưới ngón tay.
63
72
  */
64
- declare function FontSizeControl({ label, optionLabels, className, onFontSizeChange }: FontSizeControlProps): React.JSX.Element;
73
+ declare function FontSizeControl({ label: labelProp, optionLabels: optionLabelsProp, className, onFontSizeChange }: FontSizeControlProps): React.JSX.Element;
65
74
 
66
75
  export { DensityToggle, type DensityToggleProps, FontSizeControl, type FontSizeControlProps };
@@ -1,15 +1,19 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { Switch } from "../components/ui/switch.js";
4
+ import { useComitorMessages } from "../i18n/locale-provider.js";
4
5
  import { cn } from "../lib/cn.js";
5
6
  import { FONT_SIZE_MODES } from "./display-axis-constants.js";
6
7
  import { useDensity, useFontSize } from "./display-axis-provider.js";
7
8
  function DensityToggle({
8
- label = "B\u1ED1 c\u1EE5c g\u1ECDn",
9
- description = "Gi\u1EA3m kho\u1EA3ng c\xE1ch d\u1ECDc gi\u1EEFa c\xE1c d\xF2ng trong b\u1EA3ng, danh s\xE1ch v\xE0 menu.",
9
+ label: labelProp,
10
+ description: descriptionProp,
10
11
  className,
11
12
  onDensityChange
12
13
  }) {
14
+ const t = useComitorMessages("displayAxis");
15
+ const label = labelProp ?? t.densityLabel;
16
+ const description = descriptionProp ?? t.densityDescription;
13
17
  const { density, setDensity, mounted } = useDensity();
14
18
  return /* @__PURE__ */ jsx(
15
19
  Switch,
@@ -27,17 +31,15 @@ function DensityToggle({
27
31
  }
28
32
  );
29
33
  }
30
- const DEFAULT_FONT_SIZE_LABELS = {
31
- sm: "Nh\u1ECF",
32
- md: "V\u1EEBa",
33
- lg: "L\u1EDBn"
34
- };
35
34
  function FontSizeControl({
36
- label = "C\u1EE1 ch\u1EEF",
37
- optionLabels = DEFAULT_FONT_SIZE_LABELS,
35
+ label: labelProp,
36
+ optionLabels: optionLabelsProp,
38
37
  className,
39
38
  onFontSizeChange
40
39
  }) {
40
+ const t = useComitorMessages("displayAxis");
41
+ const label = labelProp ?? t.fontSizeLabel;
42
+ const optionLabels = optionLabelsProp ?? { sm: t.fontSizeSm, md: t.fontSizeMd, lg: t.fontSizeLg };
41
43
  const { fontSize, setFontSize, mounted } = useFontSize();
42
44
  return /* @__PURE__ */ jsx(
43
45
  "div",