@eifi1/ui-kit 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +89 -19
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +74 -19
  63. package/dist/components/dropdown.js +72 -22
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +21 -7
  75. package/dist/components/file-dropzone.js +28 -7
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +21 -2
  105. package/dist/components/number-input.js +2 -1
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +25 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +5 -3
  176. package/dist/components/user-avatar.js +4 -2
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +14 -1
  270. package/dist/theme/palette-presets.js +231 -3
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +66 -13
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/currency-select.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from \"./ui\";\nimport { cn } from \"../lib/cn\";\nimport { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from \"./dropdown\";\n\nexport interface CurrencyOption {\n code: string;\n name: string;\n symbol: string;\n /** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */\n country: string;\n}\n\nexport const CURRENCIES: CurrencyOption[] = [\n { code: \"EUR\", name: \"Euro\", symbol: \"€\", country: \"eu\" },\n { code: \"USD\", name: \"US Dollar\", symbol: \"$\", country: \"us\" },\n { code: \"GBP\", name: \"British Pound\", symbol: \"£\", country: \"gb\" },\n { code: \"CHF\", name: \"Swiss Franc\", symbol: \"CHF\", country: \"ch\" },\n { code: \"JPY\", name: \"Japanese Yen\", symbol: \"¥\", country: \"jp\" },\n { code: \"CAD\", name: \"Canadian Dollar\", symbol: \"CA$\", country: \"ca\" },\n { code: \"AUD\", name: \"Australian Dollar\", symbol: \"A$\", country: \"au\" },\n { code: \"NZD\", name: \"New Zealand Dollar\", symbol: \"NZ$\", country: \"nz\" },\n { code: \"SEK\", name: \"Swedish Krona\", symbol: \"kr\", country: \"se\" },\n { code: \"NOK\", name: \"Norwegian Krone\", symbol: \"kr\", country: \"no\" },\n { code: \"DKK\", name: \"Danish Krone\", symbol: \"kr\", country: \"dk\" },\n { code: \"PLN\", name: \"Polish Zloty\", symbol: \"zł\", country: \"pl\" },\n { code: \"CZK\", name: \"Czech Koruna\", symbol: \"Kč\", country: \"cz\" },\n { code: \"HUF\", name: \"Hungarian Forint\", symbol: \"Ft\", country: \"hu\" },\n { code: \"RON\", name: \"Romanian Leu\", symbol: \"lei\", country: \"ro\" },\n { code: \"BGN\", name: \"Bulgarian Lev\", symbol: \"лв\", country: \"bg\" },\n { code: \"TRY\", name: \"Turkish Lira\", symbol: \"₺\", country: \"tr\" },\n { code: \"RUB\", name: \"Russian Ruble\", symbol: \"₽\", country: \"ru\" },\n { code: \"CNY\", name: \"Chinese Yuan\", symbol: \"¥\", country: \"cn\" },\n { code: \"HKD\", name: \"Hong Kong Dollar\", symbol: \"HK$\", country: \"hk\" },\n { code: \"SGD\", name: \"Singapore Dollar\", symbol: \"S$\", country: \"sg\" },\n { code: \"INR\", name: \"Indian Rupee\", symbol: \"₹\", country: \"in\" },\n { code: \"BRL\", name: \"Brazilian Real\", symbol: \"R$\", country: \"br\" },\n { code: \"MXN\", name: \"Mexican Peso\", symbol: \"MX$\", country: \"mx\" },\n { code: \"ZAR\", name: \"South African Rand\", symbol: \"R\", country: \"za\" },\n { code: \"ILS\", name: \"Israeli Shekel\", symbol: \"₪\", country: \"il\" },\n { code: \"AED\", name: \"UAE Dirham\", symbol: \"د.إ\", country: \"ae\" },\n];\n\nexport function CurrencyFlag({ country, className }: { country: string; className?: string }) {\n return (\n <span\n aria-hidden\n className={cn(\"fi inline-block w-5 h-[15px] rounded-sm shadow-[0_0_0_1px_rgba(0,0,0,0.08)] shrink-0\", `fi-${country}`, className)}\n />\n );\n}\n\nconst CURRENCY_BY_CODE = new Map(CURRENCIES.map((c) => [c.code, c]));\n\nexport function getCurrency(code: string | null | undefined): CurrencyOption | undefined {\n return code ? CURRENCY_BY_CODE.get(code.toUpperCase()) : undefined;\n}\n\ninterface CurrencySelectProps {\n value: string;\n onChange: (code: string) => void;\n placeholder?: string;\n className?: string;\n label?: ReactNode;\n /**\n * Restrict the pickable currencies to these ISO codes (e.g. only the\n * currencies actually present in a budget). Order is preserved. Defaults to\n * the full {@link CURRENCIES} list.\n */\n options?: string[];\n /** Required and unanswered — {@link FIELD_INVALID}, the same rose border the\n * native `Select` has worn since feedback #235. Every field-styled control in\n * this package carries it now, so a form can mark any of its fields rather than\n * only the one that happened to have it first. */\n invalid?: boolean;\n}\n\nexport function CurrencySelect({ value, onChange, placeholder, className, label, options, invalid }: CurrencySelectProps) {\n const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();\n\n const selected = getCurrency(value);\n const pool = useMemo(() => {\n if (!options) return CURRENCIES;\n // Keep the caller's order; drop codes we don't have a definition for.\n return options.map((code) => getCurrency(code)).filter((c): c is CurrencyOption => !!c);\n }, [options]);\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return pool;\n return pool.filter(\n (c) =>\n c.code.toLowerCase().includes(q) ||\n c.name.toLowerCase().includes(q) ||\n c.symbol.toLowerCase().includes(q),\n );\n }, [query, pool]);\n\n return (\n <div ref={wrapperRef} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n aria-invalid={invalid || undefined}\n className={cn(FIELD_TRIGGER, \"pr-9\", label !== undefined && FIELD_FLOATING_PAD, invalid && FIELD_INVALID)}\n >\n {/* The trigger stays COMPACT — flag + code only — so it never clips in a\n narrow field; the full names live in the (wider) popup (feedback\n #308). */}\n <span className={cn(\"flex items-center gap-2 min-w-0\", selected ? \"text-slate-900 dark:text-slate-100\" : \"text-slate-400 dark:text-slate-500\")}>\n {selected ? (\n <>\n <CurrencyFlag country={selected.country} />\n <span className=\"font-medium\">{selected.code}</span>\n </>\n ) : (\n placeholder ?? \"Currency\"\n )}\n </span>\n <FieldChevron />\n </button>\n {open && (\n <DropdownPanel\n // At least as wide as the trigger, but grows to fit the full currency\n // names and caps so it never runs off-screen. RIGHT-aligned so it grows\n // leftward instead of pushing the page width when there's content to the\n // right of the field (feedback #308).\n className=\"right-0 min-w-full w-max max-w-[min(20rem,calc(100vw-2rem))]\"\n empty={filtered.length === 0}\n header={\n <DropdownSearchHeader\n query={query}\n onQueryChange={setQuery}\n inputRef={inputRef}\n placeholder={placeholder ?? \"Search currency\"}\n />\n }\n >\n {filtered.map((c) => {\n const active = c.code === selected?.code;\n return (\n <li key={c.code}>\n <button\n type=\"button\"\n onClick={() => {\n onChange(c.code);\n setOpen(false);\n }}\n className={cn(\n \"flex w-full items-center justify-between gap-2 px-3 py-2 text-sm text-left hover:bg-slate-50 dark:hover:bg-slate-800\",\n active && \"bg-slate-100 dark:bg-slate-800\",\n )}\n >\n <span className=\"flex items-center gap-2 min-w-0\">\n <CurrencyFlag country={c.country} />\n <span className=\"font-mono text-slate-500 dark:text-slate-400 w-8 shrink-0\">{c.symbol}</span>\n <span className=\"font-medium text-slate-900 dark:text-slate-100\">{c.code}</span>\n <span className=\"text-slate-500 dark:text-slate-400 truncate\">{c.name}</span>\n </span>\n {active && <Check className=\"size-4 text-slate-700 dark:text-slate-200 shrink-0\" />}\n </button>\n </li>\n );\n })}\n </DropdownPanel>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/currency-select.tsx"],"sourcesContent":["import { useMemo, useId } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from \"./ui\";\nimport { cn } from \"../lib/cn\";\nimport { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from \"./dropdown\";\nimport { DEFAULT_CURRENCY_LABELS, useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\n\nexport interface CurrencyOption {\n code: string;\n name: string;\n symbol: string;\n /** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */\n country: string;\n}\n\nexport const CURRENCIES: CurrencyOption[] = [\n { code: \"EUR\", name: \"Euro\", symbol: \"€\", country: \"eu\" },\n { code: \"USD\", name: \"US Dollar\", symbol: \"$\", country: \"us\" },\n { code: \"GBP\", name: \"British Pound\", symbol: \"£\", country: \"gb\" },\n { code: \"CHF\", name: \"Swiss Franc\", symbol: \"CHF\", country: \"ch\" },\n { code: \"JPY\", name: \"Japanese Yen\", symbol: \"¥\", country: \"jp\" },\n { code: \"CAD\", name: \"Canadian Dollar\", symbol: \"CA$\", country: \"ca\" },\n { code: \"AUD\", name: \"Australian Dollar\", symbol: \"A$\", country: \"au\" },\n { code: \"NZD\", name: \"New Zealand Dollar\", symbol: \"NZ$\", country: \"nz\" },\n { code: \"SEK\", name: \"Swedish Krona\", symbol: \"kr\", country: \"se\" },\n { code: \"NOK\", name: \"Norwegian Krone\", symbol: \"kr\", country: \"no\" },\n { code: \"DKK\", name: \"Danish Krone\", symbol: \"kr\", country: \"dk\" },\n { code: \"PLN\", name: \"Polish Zloty\", symbol: \"zł\", country: \"pl\" },\n { code: \"CZK\", name: \"Czech Koruna\", symbol: \"Kč\", country: \"cz\" },\n { code: \"HUF\", name: \"Hungarian Forint\", symbol: \"Ft\", country: \"hu\" },\n { code: \"RON\", name: \"Romanian Leu\", symbol: \"lei\", country: \"ro\" },\n { code: \"BGN\", name: \"Bulgarian Lev\", symbol: \"лв\", country: \"bg\" },\n { code: \"TRY\", name: \"Turkish Lira\", symbol: \"₺\", country: \"tr\" },\n { code: \"RUB\", name: \"Russian Ruble\", symbol: \"₽\", country: \"ru\" },\n { code: \"CNY\", name: \"Chinese Yuan\", symbol: \"¥\", country: \"cn\" },\n { code: \"HKD\", name: \"Hong Kong Dollar\", symbol: \"HK$\", country: \"hk\" },\n { code: \"SGD\", name: \"Singapore Dollar\", symbol: \"S$\", country: \"sg\" },\n { code: \"INR\", name: \"Indian Rupee\", symbol: \"₹\", country: \"in\" },\n { code: \"BRL\", name: \"Brazilian Real\", symbol: \"R$\", country: \"br\" },\n { code: \"MXN\", name: \"Mexican Peso\", symbol: \"MX$\", country: \"mx\" },\n { code: \"ZAR\", name: \"South African Rand\", symbol: \"R\", country: \"za\" },\n { code: \"ILS\", name: \"Israeli Shekel\", symbol: \"₪\", country: \"il\" },\n { code: \"AED\", name: \"UAE Dirham\", symbol: \"د.إ\", country: \"ae\" },\n];\n\nexport interface CurrencyFlagProps extends ComponentPropsWithoutRef<\"span\"> {\n /** ISO 3166-1 alpha-2, lowercase — the class flag-icons keys its sprite by. */\n country: string;\n}\n\nexport function CurrencyFlag({ country, className, ...rest }: CurrencyFlagProps) {\n return (\n <span\n {...rest}\n // Stays `aria-hidden` whatever a caller passes: the flag is a picture of the\n // code printed next to it, and a reader that met both would hear the currency\n // twice.\n aria-hidden\n className={cn(\"fi inline-block w-5 h-[15px] rounded-sm shadow-[0_0_0_1px_rgba(0,0,0,0.08)] shrink-0\", `fi-${country}`, className)}\n />\n );\n}\n\nconst CURRENCY_BY_CODE = new Map(CURRENCIES.map((c) => [c.code, c]));\n\nexport function getCurrency(code: string | null | undefined): CurrencyOption | undefined {\n return code ? CURRENCY_BY_CODE.get(code.toUpperCase()) : undefined;\n}\n\n/** The name to SHOW for a currency: the caller's translation if they have one for\n * that code; else, when a `locale` is known, the name `Intl.DisplayNames` has for\n * it in that locale; else the English name {@link CURRENCIES} carries. Shared with\n * AmountInput, which renders the same list from the same data.\n *\n * `Intl` only when a locale was actually GIVEN (by prop or `<UiKitProvider>`): an\n * app that never set one keeps the shipped English names it has always shown,\n * rather than whatever language the test runner or the browser happens to be in. */\nexport function currencyName(\n c: CurrencyOption,\n names?: Record<string, string>,\n locale?: string,\n): string {\n const own = names?.[c.code];\n if (own !== undefined) return own;\n if (locale) {\n try {\n const shown = new Intl.DisplayNames([locale], { type: \"currency\" }).of(c.code);\n if (shown && shown !== c.code) return shown;\n } catch {\n // A malformed tag, or a runtime without `DisplayNames`: the shipped name.\n }\n }\n return c.name;\n}\n\n/** `onChange` is the kit's — \"a currency was picked\", carrying the code — rather\n * than the div's form event. */\nexport interface CurrencySelectProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n value: string;\n onChange: (code: string) => void;\n placeholder?: string;\n label?: ReactNode;\n /**\n * Restrict the pickable currencies to these ISO codes (e.g. only the\n * currencies actually present in a budget). Order is preserved. Defaults to\n * the full {@link CURRENCIES} list.\n */\n options?: string[];\n /** Required and unanswered — {@link FIELD_INVALID}, the same rose border the\n * native `Select` has worn since feedback #235. Every field-styled control in\n * this package carries it now, so a form can mark any of its fields rather than\n * only the one that happened to have it first. */\n invalid?: boolean;\n /** Every user-facing string this control owns, shaped like `AmountInput`'s\n * `labels` so the two currency pickers in the kit are configured the same way.\n *\n * `search` exists because `placeholder` used to do BOTH jobs: it named the empty\n * field and, for want of anything else, it was also handed to the search box —\n * so a caller who wrote \"Currency\" got \"Currency\" as the prompt for typing a\n * filter, and a caller who wanted to name the filter had to rename the field.\n * `placeholder` still wins for the trigger, so nothing moves for the callers\n * that set it; what changes is that the search box no longer borrows it. */\n labels?: {\n /** The empty trigger, when `placeholder` is not given. */\n currency?: string;\n /** The filter box inside the open panel. */\n search?: string;\n };\n /**\n * Translated currency names, keyed by ISO code — `{ CHF: \"Schweizer Franken\" }`.\n *\n * {@link CURRENCIES} is data the package ships, so its 27 names were the one\n * string in the row a consumer could not translate: the code and the symbol are\n * language-neutral, and the name beside them stayed English in a German UI. This\n * is a sparse OVERRIDE rather than a replacement list, so an app translates the\n * eight currencies it actually uses and keeps the rest; forking `CURRENCIES` to\n * get there would also fork the flags and symbols, which nobody wants to maintain.\n *\n * The search matches what is SHOWN — type \"Franken\" and you find CHF — because a\n * filter that only matches the hidden English name is worse than no filter.\n */\n currencyNames?: Record<string, string>;\n}\n\nexport function CurrencySelect({\n value,\n onChange,\n placeholder,\n className,\n label,\n options,\n invalid,\n labels,\n currencyNames,\n \"aria-label\": ariaLabel,\n ...rest\n}: CurrencySelectProps) {\n const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();\n // `labels` > `currency` from the provider > English.\n const text = useKitLabels(\"currency\", DEFAULT_CURRENCY_LABELS, labels);\n const locale = useKitLocale();\n\n const selected = getCurrency(value);\n const pool = useMemo(() => {\n if (!options) return CURRENCIES;\n // Keep the caller's order; drop codes we don't have a definition for.\n return options.map((code) => getCurrency(code)).filter((c): c is CurrencyOption => !!c);\n }, [options]);\n const filtered = useMemo(() => {\n const q = query.trim().toLocaleLowerCase(locale);\n if (!q) return pool;\n return pool.filter(\n (c) =>\n c.code.toLowerCase().includes(q) ||\n currencyName(c, currencyNames, locale).toLocaleLowerCase(locale).includes(q) ||\n c.symbol.toLowerCase().includes(q),\n );\n }, [query, pool, currencyNames, locale]);\n\n const listboxId = `${useId()}-listbox`;\n\n return (\n // The wrapper takes `rest`; the trigger takes the name. This field's label is a\n // floating <span> rather than a <label for>, so a caller who wants the control to\n // announce more than the code inside it has nowhere else to put it.\n <div {...rest} ref={wrapperRef} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n type=\"button\"\n aria-label={ariaLabel}\n onClick={() => setOpen((v) => !v)}\n // The implicit `button` role supports neither `aria-expanded` nor\n // `aria-invalid`, so this trigger previously wore a red border and announced\n // nothing about being invalid or about there being a list behind it. The role\n // that DESCRIBES it is the one that also supports the attributes — the same\n // treatment its siblings (MultiSelect, the entity comboboxes) now carry, so a\n // form row of pickers reads the same way throughout.\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-controls={listboxId}\n aria-expanded={open}\n aria-invalid={invalid || undefined}\n className={cn(FIELD_TRIGGER, \"pr-9\", label !== undefined && FIELD_FLOATING_PAD, invalid && FIELD_INVALID)}\n >\n {/* The trigger stays COMPACT — flag + code only — so it never clips in a\n narrow field; the full names live in the (wider) popup (feedback\n #308). */}\n <span className={cn(\"flex items-center gap-2 min-w-0\", selected ? \"text-[var(--text-primary)]\" : \"text-[var(--text-placeholder)]\")}>\n {selected ? (\n <>\n <CurrencyFlag country={selected.country} />\n <span className=\"font-medium\">{selected.code}</span>\n </>\n ) : (\n placeholder ?? text.currency\n )}\n </span>\n <FieldChevron />\n </button>\n {open && (\n <DropdownPanel\n // At least as wide as the trigger, but grows to fit the full currency\n // names and caps so it never runs off-screen. RIGHT-aligned so it grows\n // leftward instead of pushing the page width when there's content to the\n // right of the field (feedback #308).\n className=\"right-0 min-w-full w-max max-w-[min(20rem,calc(100vw-2rem))]\"\n empty={filtered.length === 0}\n listProps={{ id: listboxId }}\n header={\n <DropdownSearchHeader\n query={query}\n onQueryChange={setQuery}\n inputRef={inputRef}\n placeholder={text.search}\n />\n }\n >\n {filtered.map((c) => {\n const active = c.code === selected?.code;\n return (\n <li key={c.code}>\n <button\n type=\"button\"\n onClick={() => {\n onChange(c.code);\n setOpen(false);\n }}\n className={cn(\n \"flex w-full items-center justify-between gap-2 px-3 py-2 text-sm text-left hover:bg-[var(--bg-hover)]\",\n active && \"bg-[var(--bg-active)]\",\n )}\n >\n <span className=\"flex items-center gap-2 min-w-0\">\n <CurrencyFlag country={c.country} />\n <span className=\"font-mono text-[var(--text-muted)] w-8 shrink-0\">{c.symbol}</span>\n <span className=\"font-medium text-[var(--text-primary)]\">{c.code}</span>\n <span className=\"text-[var(--text-muted)] truncate\">{currencyName(c, currencyNames, locale)}</span>\n </span>\n {active && <Check className=\"size-4 text-[var(--text-secondary)] shrink-0\" />}\n </button>\n </li>\n );\n })}\n </DropdownPanel>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { a as DataTableColumn, c as FilterValue } from '../data-table-filters-aXyf0Xub.js';
2
+ import { a as DataTableColumn, c as FilterValue } from '../data-table-filters-DM1auUfH.js';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
4
  import './data-table-sort.js';
5
5
  import './swipeable-row.js';
@@ -13,9 +13,21 @@ interface FilterPopoverProps<T> {
13
13
  value: string;
14
14
  label: string;
15
15
  }[];
16
- /** BCP-47 locale for the date picker / labels. */
16
+ /** BCP-47 locale for the date picker / labels. Falls back to the provider's. */
17
17
  locale?: string;
18
+ /** Already resolved by {@link DataTable}. Standalone (it is exported), omit it and
19
+ * the `dataTable` namespace of `<UiKitProvider labels>` is used. */
18
20
  labels?: DataTableLabels;
21
+ /**
22
+ * Focus the text filter's input on mount. True, because that is right for the one
23
+ * place this component normally lives: a Popover opened by an explicit press on
24
+ * "Filter", which exists in order to be typed into and which the user has just asked
25
+ * for. It was hard-coded, which made it right there and wrong everywhere else — a
26
+ * browser scrolls whatever takes focus into view, so embedding this control in an
27
+ * ordinary page (the showcase documents it on one) landed the reader 25,000px down a
28
+ * page they had not scrolled. An embedder that is not a popover passes `false`.
29
+ */
30
+ autoFocus?: boolean;
19
31
  }
20
32
  /**
21
33
  * The contents of a column's filter Popover. Renders the right control set for
@@ -23,6 +35,6 @@ interface FilterPopoverProps<T> {
23
35
  * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs
24
36
  * toggle — driving the shared {@link FilterValue} state.
25
37
  */
26
- declare function FilterPopover<T>({ column, state, onChange, onClear, selectOptions, locale, labels, }: FilterPopoverProps<T>): react.JSX.Element | null;
38
+ declare function FilterPopover<T>({ column, state, onChange, onClear, selectOptions, locale: localeProp, labels: labelsProp, autoFocus, }: FilterPopoverProps<T>): react.JSX.Element | null;
27
39
 
28
40
  export { FilterPopover };
@@ -1,23 +1,29 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import { X } from "lucide-react";
3
4
  import { cn } from "../lib/cn.js";
4
5
  import { MiniCalendar } from "./mini-calendar.js";
5
6
  import { dateRangePresets } from "../lib/dates.js";
6
7
  import { resolveFilter } from "./data-table-filters.js";
7
- import { DEFAULT_DATA_TABLE_LABELS } from "./data-table-labels.js";
8
+ import { resolveDataTableLabels } from "./data-table-labels.js";
9
+ import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels.js";
8
10
  function FilterPopover({
9
11
  column,
10
12
  state,
11
13
  onChange,
12
14
  onClear,
13
15
  selectOptions,
14
- locale,
15
- labels = DEFAULT_DATA_TABLE_LABELS
16
+ locale: localeProp,
17
+ labels: labelsProp,
18
+ autoFocus = true
16
19
  }) {
20
+ const overrides = useKitLabelOverrides("dataTable");
21
+ const labels = labelsProp ?? resolveDataTableLabels(overrides);
22
+ const locale = useKitLocale(localeProp);
17
23
  const filter = resolveFilter(column);
18
24
  if (!filter) return null;
19
- const inputBase = "block w-full rounded border border-slate-200 bg-white px-2 py-1.5 text-sm text-slate-700 placeholder:text-slate-400 focus:border-slate-400 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:placeholder:text-slate-500";
20
- const buttonBase = "rounded border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800";
25
+ const inputBase = "block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5 text-sm text-[var(--text-secondary)] placeholder:text-[var(--text-placeholder)] focus:border-[var(--border-strong)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]";
26
+ const buttonBase = "rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]";
21
27
  const clearRow = /* @__PURE__ */ jsx("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: onClear, className: cn(buttonBase, "flex items-center gap-1"), children: [
22
28
  /* @__PURE__ */ jsx(X, { className: "size-3" }),
23
29
  " ",
@@ -30,7 +36,7 @@ function FilterPopover({
30
36
  "input",
31
37
  {
32
38
  type: "text",
33
- autoFocus: true,
39
+ autoFocus,
34
40
  value: v2.q,
35
41
  onChange: (e) => onChange({ type: "text", q: e.target.value }),
36
42
  placeholder: labels.filterPlaceholder,
@@ -49,7 +55,7 @@ function FilterPopover({
49
55
  onChange({ type: "select", values: Array.from(set) });
50
56
  };
51
57
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
52
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-slate-500 dark:text-slate-400", children: [
58
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-[var(--text-muted)]", children: [
53
59
  /* @__PURE__ */ jsx("span", { children: labels.selectFilter }),
54
60
  /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
55
61
  /* @__PURE__ */ jsx(
@@ -72,10 +78,10 @@ function FilterPopover({
72
78
  )
73
79
  ] })
74
80
  ] }),
75
- /* @__PURE__ */ jsxs("ul", { className: "max-h-56 overflow-y-auto rounded border border-slate-100 dark:border-slate-800", children: [
81
+ /* @__PURE__ */ jsxs("ul", { className: "max-h-56 overflow-y-auto rounded border border-[var(--border)]", children: [
76
82
  selectOptions.map((o) => {
77
83
  const checked = v2.values.includes(o.value);
78
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-slate-50 dark:hover:bg-slate-800", children: [
84
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-[var(--bg-hover)]", children: [
79
85
  /* @__PURE__ */ jsx(
80
86
  "input",
81
87
  {
@@ -88,7 +94,7 @@ function FilterPopover({
88
94
  /* @__PURE__ */ jsx("span", { className: "truncate", children: o.label })
89
95
  ] }) }, o.value);
90
96
  }),
91
- selectOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-2 py-2 text-sm text-slate-500 dark:text-slate-400", children: "\u2014" })
97
+ selectOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-2 py-2 text-sm text-[var(--text-muted)]", children: "\u2014" })
92
98
  ] }),
93
99
  clearRow
94
100
  ] });
@@ -108,16 +114,16 @@ function FilterPopover({
108
114
  onClick: () => set(p.from, p.to),
109
115
  className: cn(
110
116
  "block w-full rounded px-2 py-1 text-left transition-colors",
111
- active ? "bg-sky-100 text-sky-700 dark:bg-sky-500/20 dark:text-sky-200" : "text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
117
+ active ? "bg-[var(--brand-bg)] text-[var(--brand-muted)]" : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
112
118
  ),
113
119
  children: labels.presets[p.key] ?? p.key
114
120
  }
115
121
  ) }, p.key);
116
122
  }) }),
117
123
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
118
- /* @__PURE__ */ jsx(MiniCalendar, { from: v2.from, to: v2.to, locale: locale ?? "en", onSelect: set }),
124
+ /* @__PURE__ */ jsx(MiniCalendar, { from: v2.from, to: v2.to, locale, onSelect: set }),
119
125
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
120
- /* @__PURE__ */ jsxs("label", { className: "text-xs text-slate-500 dark:text-slate-400", children: [
126
+ /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--text-muted)]", children: [
121
127
  labels.dateFrom,
122
128
  /* @__PURE__ */ jsx(
123
129
  "input",
@@ -129,7 +135,7 @@ function FilterPopover({
129
135
  }
130
136
  )
131
137
  ] }),
132
- /* @__PURE__ */ jsxs("label", { className: "text-xs text-slate-500 dark:text-slate-400", children: [
138
+ /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--text-muted)]", children: [
133
139
  labels.dateTo,
134
140
  /* @__PURE__ */ jsx(
135
141
  "input",
@@ -149,7 +155,7 @@ function FilterPopover({
149
155
  const v = state.type === "number" ? state : { type: "number", min: "", max: "", abs: false };
150
156
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
151
157
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
152
- /* @__PURE__ */ jsxs("label", { className: "text-xs text-slate-500 dark:text-slate-400", children: [
158
+ /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--text-muted)]", children: [
153
159
  labels.numberMin,
154
160
  " (\u2265)",
155
161
  /* @__PURE__ */ jsx(
@@ -164,7 +170,7 @@ function FilterPopover({
164
170
  }
165
171
  )
166
172
  ] }),
167
- /* @__PURE__ */ jsxs("label", { className: "text-xs text-slate-500 dark:text-slate-400", children: [
173
+ /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--text-muted)]", children: [
168
174
  labels.numberMax,
169
175
  " (\u2264)",
170
176
  /* @__PURE__ */ jsx(
@@ -180,7 +186,7 @@ function FilterPopover({
180
186
  )
181
187
  ] })
182
188
  ] }),
183
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300", children: [
189
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm text-[var(--text-secondary)]", children: [
184
190
  /* @__PURE__ */ jsx(
185
191
  "input",
186
192
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-filter-popover.tsx"],"sourcesContent":["import { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { MiniCalendar } from \"./mini-calendar\";\nimport { dateRangePresets } from \"../lib/dates\";\nimport { resolveFilter } from \"./data-table-filters\";\nimport type { FilterValue } from \"./data-table-filters\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\n\ninterface FilterPopoverProps<T> {\n column: DataTableColumn<T>;\n state: FilterValue;\n onChange: (next: FilterValue) => void;\n onClear: () => void;\n selectOptions: { value: string; label: string }[];\n /** BCP-47 locale for the date picker / labels. */\n locale?: string;\n labels?: DataTableLabels;\n}\n\n/**\n * The contents of a column's filter Popover. Renders the right control set for\n * the column's filter type — text search, select checklist, date range (presets\n * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs\n * toggle — driving the shared {@link FilterValue} state.\n */\nexport function FilterPopover<T>({\n column,\n state,\n onChange,\n onClear,\n selectOptions,\n locale,\n labels = DEFAULT_DATA_TABLE_LABELS,\n}: FilterPopoverProps<T>) {\n const filter = resolveFilter(column);\n if (!filter) return null;\n\n const inputBase =\n \"block w-full rounded border border-slate-200 bg-white px-2 py-1.5 text-sm text-slate-700 placeholder:text-slate-400 focus:border-slate-400 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:placeholder:text-slate-500\";\n const buttonBase =\n \"rounded border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800\";\n\n const clearRow = (\n <div className=\"flex justify-end pt-1\">\n <button type=\"button\" onClick={onClear} className={cn(buttonBase, \"flex items-center gap-1\")}>\n <X className=\"size-3\" /> {labels.clearFilter}\n </button>\n </div>\n );\n\n if (filter.type === \"text\") {\n const v = state.type === \"text\" ? state : { type: \"text\" as const, q: \"\" };\n return (\n <div className=\"space-y-2\">\n <input\n type=\"text\"\n autoFocus\n value={v.q}\n onChange={(e) => onChange({ type: \"text\", q: e.target.value })}\n placeholder={labels.filterPlaceholder}\n className={inputBase}\n />\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"select\") {\n const v = state.type === \"select\" ? state : { type: \"select\" as const, values: [] };\n const toggle = (val: string) => {\n const set = new Set(v.values);\n if (set.has(val)) set.delete(val);\n else set.add(val);\n onChange({ type: \"select\", values: Array.from(set) });\n };\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-xs text-slate-500 dark:text-slate-400\">\n <span>{labels.selectFilter}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: selectOptions.map((o) => o.value) })}\n className={buttonBase}\n >\n {labels.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: [] })}\n className={buttonBase}\n >\n {labels.selectNone}\n </button>\n </div>\n </div>\n <ul className=\"max-h-56 overflow-y-auto rounded border border-slate-100 dark:border-slate-800\">\n {selectOptions.map((o) => {\n const checked = v.values.includes(o.value);\n return (\n <li key={o.value}>\n <label className=\"flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-slate-50 dark:hover:bg-slate-800\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => toggle(o.value)}\n className=\"size-3.5\"\n />\n <span className=\"truncate\">{o.label}</span>\n </label>\n </li>\n );\n })}\n {selectOptions.length === 0 && (\n <li className=\"px-2 py-2 text-sm text-slate-500 dark:text-slate-400\">—</li>\n )}\n </ul>\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"date\") {\n const v = state.type === \"date\" ? state : { type: \"date\" as const, from: \"\", to: \"\" };\n const set = (from: string, to: string) => onChange({ type: \"date\", from, to });\n const presets = dateRangePresets();\n const isActivePreset = (p: { from: string; to: string }) => v.from === p.from && v.to === p.to;\n return (\n <div className=\"flex gap-3\">\n <ul className=\"flex w-28 shrink-0 flex-col gap-0.5 text-xs\">\n {presets.map((p) => {\n const active = isActivePreset(p);\n return (\n <li key={p.key}>\n <button\n type=\"button\"\n onClick={() => set(p.from, p.to)}\n className={cn(\n \"block w-full rounded px-2 py-1 text-left transition-colors\",\n active\n ? \"bg-sky-100 text-sky-700 dark:bg-sky-500/20 dark:text-sky-200\"\n : \"text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800\",\n )}\n >\n {labels.presets[p.key] ?? p.key}\n </button>\n </li>\n );\n })}\n </ul>\n <div className=\"min-w-0 flex-1 space-y-2\">\n <MiniCalendar from={v.from} to={v.to} locale={locale ?? \"en\"} onSelect={set} />\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-slate-500 dark:text-slate-400\">\n {labels.dateFrom}\n <input\n type=\"date\"\n value={v.from}\n onChange={(e) => set(e.target.value, v.to)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-slate-500 dark:text-slate-400\">\n {labels.dateTo}\n <input\n type=\"date\"\n value={v.to}\n onChange={(e) => set(v.from, e.target.value)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n {clearRow}\n </div>\n </div>\n );\n }\n\n // number\n const v = state.type === \"number\" ? state : { type: \"number\" as const, min: \"\", max: \"\", abs: false };\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-slate-500 dark:text-slate-400\">\n {labels.numberMin} (≥)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.min}\n onChange={(e) => onChange({ type: \"number\", min: e.target.value, max: v.max, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-slate-500 dark:text-slate-400\">\n {labels.numberMax} (≤)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.max}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: e.target.value, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300\">\n <input\n type=\"checkbox\"\n checked={v.abs}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: v.max, abs: e.target.checked })}\n className=\"size-3.5\"\n />\n {labels.numberAbs}\n </label>\n {clearRow}\n </div>\n );\n}\n"],"mappings":"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","names":["v"]}
1
+ {"version":3,"sources":["../../src/components/data-table-filter-popover.tsx"],"sourcesContent":["import { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { MiniCalendar } from \"./mini-calendar\";\nimport { dateRangePresets } from \"../lib/dates\";\nimport { resolveFilter } from \"./data-table-filters\";\nimport type { FilterValue } from \"./data-table-filters\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { resolveDataTableLabels, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabelOverrides, useKitLocale } from \"../i18n/kit-labels\";\n\ninterface FilterPopoverProps<T> {\n column: DataTableColumn<T>;\n state: FilterValue;\n onChange: (next: FilterValue) => void;\n onClear: () => void;\n selectOptions: { value: string; label: string }[];\n /** BCP-47 locale for the date picker / labels. Falls back to the provider's. */\n locale?: string;\n /** Already resolved by {@link DataTable}. Standalone (it is exported), omit it and\n * the `dataTable` namespace of `<UiKitProvider labels>` is used. */\n labels?: DataTableLabels;\n /**\n * Focus the text filter's input on mount. True, because that is right for the one\n * place this component normally lives: a Popover opened by an explicit press on\n * \"Filter\", which exists in order to be typed into and which the user has just asked\n * for. It was hard-coded, which made it right there and wrong everywhere else — a\n * browser scrolls whatever takes focus into view, so embedding this control in an\n * ordinary page (the showcase documents it on one) landed the reader 25,000px down a\n * page they had not scrolled. An embedder that is not a popover passes `false`.\n */\n autoFocus?: boolean;\n}\n\n/**\n * The contents of a column's filter Popover. Renders the right control set for\n * the column's filter type — text search, select checklist, date range (presets\n * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs\n * toggle — driving the shared {@link FilterValue} state.\n */\nexport function FilterPopover<T>({\n column,\n state,\n onChange,\n onClear,\n selectOptions,\n locale: localeProp,\n labels: labelsProp,\n autoFocus = true,\n}: FilterPopoverProps<T>) {\n // Both hooks run before the early return below, unconditionally.\n const overrides = useKitLabelOverrides(\"dataTable\");\n const labels = labelsProp ?? resolveDataTableLabels(overrides);\n // No `\"en\"` fallback any more: that pinned the calendar to English in a table that\n // was never handed a locale, under a provider that had one.\n const locale = useKitLocale(localeProp);\n const filter = resolveFilter(column);\n if (!filter) return null;\n\n // `focus:outline-none` with only a 1px border tint to replace it is the same finding\n // the audit records against the numpad's keys: on a panel of four identical fields a\n // border going from --border to --border-strong is not a location. The ring is, and it\n // is the package's brand focus colour, as on every field in ui.tsx.\n const inputBase =\n \"block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5 text-sm text-[var(--text-secondary)] placeholder:text-[var(--text-placeholder)] focus:border-[var(--border-strong)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\";\n const buttonBase =\n \"rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\";\n\n const clearRow = (\n <div className=\"flex justify-end pt-1\">\n <button type=\"button\" onClick={onClear} className={cn(buttonBase, \"flex items-center gap-1\")}>\n <X className=\"size-3\" /> {labels.clearFilter}\n </button>\n </div>\n );\n\n if (filter.type === \"text\") {\n const v = state.type === \"text\" ? state : { type: \"text\" as const, q: \"\" };\n return (\n <div className=\"space-y-2\">\n <input\n type=\"text\"\n // A prop now, defaulting to what the popover needs; see `autoFocus` on the\n // props above. Deliberately NOT an eslint-disable: `jsx-a11y/no-autofocus` is\n // one of the config's ratcheted warnings and this is still one of the four it\n // counts, because the default here is still to take focus. Silencing it would\n // shrink the backlog without emptying it.\n autoFocus={autoFocus}\n value={v.q}\n onChange={(e) => onChange({ type: \"text\", q: e.target.value })}\n placeholder={labels.filterPlaceholder}\n className={inputBase}\n />\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"select\") {\n const v = state.type === \"select\" ? state : { type: \"select\" as const, values: [] };\n const toggle = (val: string) => {\n const set = new Set(v.values);\n if (set.has(val)) set.delete(val);\n else set.add(val);\n onChange({ type: \"select\", values: Array.from(set) });\n };\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)]\">\n <span>{labels.selectFilter}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: selectOptions.map((o) => o.value) })}\n className={buttonBase}\n >\n {labels.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: [] })}\n className={buttonBase}\n >\n {labels.selectNone}\n </button>\n </div>\n </div>\n <ul className=\"max-h-56 overflow-y-auto rounded border border-[var(--border)]\">\n {selectOptions.map((o) => {\n const checked = v.values.includes(o.value);\n return (\n <li key={o.value}>\n <label className=\"flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-[var(--bg-hover)]\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => toggle(o.value)}\n className=\"size-3.5\"\n />\n <span className=\"truncate\">{o.label}</span>\n </label>\n </li>\n );\n })}\n {selectOptions.length === 0 && (\n <li className=\"px-2 py-2 text-sm text-[var(--text-muted)]\">—</li>\n )}\n </ul>\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"date\") {\n const v = state.type === \"date\" ? state : { type: \"date\" as const, from: \"\", to: \"\" };\n const set = (from: string, to: string) => onChange({ type: \"date\", from, to });\n const presets = dateRangePresets();\n const isActivePreset = (p: { from: string; to: string }) => v.from === p.from && v.to === p.to;\n return (\n <div className=\"flex gap-3\">\n <ul className=\"flex w-28 shrink-0 flex-col gap-0.5 text-xs\">\n {presets.map((p) => {\n const active = isActivePreset(p);\n return (\n <li key={p.key}>\n <button\n type=\"button\"\n onClick={() => set(p.from, p.to)}\n className={cn(\n \"block w-full rounded px-2 py-1 text-left transition-colors\",\n active\n ? \"bg-[var(--brand-bg)] text-[var(--brand-muted)]\"\n : \"text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\",\n )}\n >\n {labels.presets[p.key] ?? p.key}\n </button>\n </li>\n );\n })}\n </ul>\n <div className=\"min-w-0 flex-1 space-y-2\">\n {/* No `labels` here: the calendar reads `miniCalendar` from the provider\n itself, which is the only way a calendar nested this deep was ever going\n to be translated — the table has no prop to forward them through. */}\n <MiniCalendar from={v.from} to={v.to} locale={locale} onSelect={set} />\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateFrom}\n <input\n type=\"date\"\n value={v.from}\n onChange={(e) => set(e.target.value, v.to)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateTo}\n <input\n type=\"date\"\n value={v.to}\n onChange={(e) => set(v.from, e.target.value)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n {clearRow}\n </div>\n </div>\n );\n }\n\n // number\n const v = state.type === \"number\" ? state : { type: \"number\" as const, min: \"\", max: \"\", abs: false };\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMin} (≥)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.min}\n onChange={(e) => onChange({ type: \"number\", min: e.target.value, max: v.max, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMax} (≤)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.max}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: e.target.value, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-[var(--text-secondary)]\">\n <input\n type=\"checkbox\"\n checked={v.abs}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: v.max, abs: e.target.checked })}\n className=\"size-3.5\"\n />\n {labels.numberAbs}\n </label>\n {clearRow}\n </div>\n );\n}\n"],"mappings":"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","names":["v"]}
@@ -1,4 +1,4 @@
1
- export { C as ColumnFilter, c as FilterValue, d as decodeFilterValue, e as decodeFilterValueOfType, f as defaultFilterState, g as encodeFilterValue, i as isFilterActive, r as resolveFilter, h as rowMatches } from '../data-table-filters-aXyf0Xub.js';
1
+ export { C as ColumnFilter, c as FilterValue, d as decodeFilterValue, e as decodeFilterValueOfType, f as defaultFilterState, g as encodeFilterValue, i as isFilterActive, r as resolveFilter, h as rowMatches } from '../data-table-filters-DM1auUfH.js';
2
2
  import 'react';
3
3
  import './data-table-sort.js';
4
4
  import './swipeable-row.js';
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  function resolveFilter(col) {
2
3
  if (col.filter) return col.filter;
3
4
  if (col.filterBy) return { type: "text", getValue: col.filterBy };
@@ -16,13 +17,13 @@ function isFilterActive(v) {
16
17
  return v.min !== "" || v.max !== "" || v.abs;
17
18
  }
18
19
  }
19
- function rowMatches(col, row, state) {
20
+ function rowMatches(col, row, state, locale) {
20
21
  const filter = resolveFilter(col);
21
22
  if (!filter) return true;
22
23
  switch (filter.type) {
23
24
  case "text": {
24
25
  if (state.type !== "text" || !state.q.trim()) return true;
25
- return filter.getValue(row).toLowerCase().includes(state.q.trim().toLowerCase());
26
+ return filter.getValue(row).toLocaleLowerCase(locale).includes(state.q.trim().toLocaleLowerCase(locale));
26
27
  }
27
28
  case "select": {
28
29
  if (state.type !== "select" || state.values.length === 0) return true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return v.min !== \"\" || v.max !== \"\" || v.abs;\n }\n}\n\n/** Whether `row` passes column `col`'s filter given the user's `state`. */\nexport function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return filter.getValue(row).toLowerCase().includes(state.q.trim().toLowerCase());\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = raw.slice(0, 10);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n if (raw == null || Number.isNaN(raw)) return !state.min && !state.max;\n const x = state.abs ? Math.abs(raw) : raw;\n if (state.min !== \"\" && x < Number(state.min)) return false;\n if (state.max !== \"\" && x > Number(state.max)) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n return { type: \"number\", min, max, abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return v.min !== \"\" || v.max !== \"\" || v.abs;\n }\n}\n\n/**\n * Whether `row` passes column `col`'s filter given the user's `state`.\n *\n * `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the\n * root rules, under which a Turkish user typing \"istanbul\" does not find \"İSTANBUL\";\n * `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.\n */\nexport function rowMatches<T>(\n col: DataTableColumn<T>,\n row: T,\n state: FilterValue,\n locale?: string,\n): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return filter\n .getValue(row)\n .toLocaleLowerCase(locale)\n .includes(state.q.trim().toLocaleLowerCase(locale));\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = raw.slice(0, 10);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n if (raw == null || Number.isNaN(raw)) return !state.min && !state.max;\n const x = state.abs ? Math.abs(raw) : raw;\n if (state.min !== \"\" && x < Number(state.min)) return false;\n if (state.max !== \"\" && x > Number(state.max)) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n return { type: \"number\", min, max, abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n"],"mappings":"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","names":[]}
@@ -25,9 +25,60 @@ interface DataTableLabels {
25
25
  numberMax: string;
26
26
  numberAbs: string;
27
27
  presets: Record<string, string>;
28
+ /**
29
+ * The table's own accessible name. Deliberately a label rather than a second,
30
+ * parallel `tableLabel` prop: the audit already records "three different prop
31
+ * names for the accessible name" as a finding, and a table that is not "Data
32
+ * table" says so with `labels={{ table: "Transactions" }}` at the call site.
33
+ */
34
+ table: string;
35
+ /** After a filter changes: "12 of 137 rows". */
36
+ filterResults: (shown: number, total: number) => string;
37
+ /** After a header is activated: "Sorted by Name, ascending". */
38
+ sortedAscending: (column: string) => string;
39
+ sortedDescending: (column: string) => string;
40
+ /** The third click, which removes the column from the sort entirely. */
41
+ sortCleared: (column: string) => string;
42
+ /** After paging: "Page 3 of 14". `page` is 1-BASED, as spoken. */
43
+ pageChanged: (page: number, totalPages: number) => string;
44
+ /** The pagination range summary: "1–25 / 137". */
45
+ pageRange: (from: number, to: number, total: number) => string;
46
+ /** The same summary when the page size is "all" — just the row count. */
47
+ rowCount: (total: number) => string;
48
+ /** The column-settings rail and its heading: "Columns (4/9)". */
49
+ columnsCount: (visible: number, total: number) => string;
28
50
  }
29
51
  declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
30
- /** Merge caller overrides onto the English defaults (presets merged too). */
52
+ /**
53
+ * Merge caller overrides onto the English defaults (presets merged too).
54
+ *
55
+ * The parameter is `Partial` on purpose — a table with no labels at all still
56
+ * renders — but that is also how a translated app ends up half English and never
57
+ * hears about it: lenkbank ships nineteen of these keys in English in a German UI
58
+ * today, because every one of them is backfilled silently. The kit will not start
59
+ * throwing over it (three apps depend on the lenient merge), so
60
+ * {@link missingDataTableLabels} is the other half: it answers which keys fell back,
61
+ * and an app asserts on that in its OWN test, where the answer is actionable.
62
+ */
31
63
  declare function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels;
64
+ /**
65
+ * The label keys `labels` does not supply, and which therefore come back English
66
+ * from {@link resolveDataTableLabels}.
67
+ *
68
+ * For a consumer's own test:
69
+ *
70
+ * ```ts
71
+ * expect(missingDataTableLabels(de.dataTable)).toEqual([]);
72
+ * ```
73
+ *
74
+ * which fails the day the kit adds a key — which is the point. Adding a label is
75
+ * additive here and a silent regression in a translated app, and nothing else in
76
+ * either repository can tell the two apart.
77
+ *
78
+ * Preset labels are reported one at a time (`presets.yesterday`), because `presets`
79
+ * is a `Record` merged key by key: answering `"presets"` would say nothing about
80
+ * which of the eleven are still English.
81
+ */
82
+ declare function missingDataTableLabels(labels?: Partial<DataTableLabels>): string[];
32
83
 
33
- export { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels, resolveDataTableLabels };
84
+ export { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels, missingDataTableLabels, resolveDataTableLabels };
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  const DEFAULT_DATA_TABLE_LABELS = {
2
3
  columns: "Columns",
3
4
  selectAllRows: "Select all rows",
@@ -36,18 +37,46 @@ const DEFAULT_DATA_TABLE_LABELS = {
36
37
  last_3_months: "Last 3 months",
37
38
  ytd: "Year to date",
38
39
  last_year: "Last year"
39
- }
40
+ },
41
+ table: "Data table",
42
+ filterResults: (shown, total) => `${shown} of ${total} rows`,
43
+ sortedAscending: (column) => `Sorted by ${column}, ascending`,
44
+ sortedDescending: (column) => `Sorted by ${column}, descending`,
45
+ sortCleared: (column) => `Sort cleared on ${column}`,
46
+ pageChanged: (page, totalPages) => `Page ${page} of ${totalPages}`,
47
+ // The separators are the ones that shipped, so adding the seam changes no screen.
48
+ pageRange: (from, to, total) => `${from}\u2013${to} / ${total}`,
49
+ rowCount: (total) => `${total}`,
50
+ columnsCount: (visible, total) => `Columns (${visible}/${total})`
40
51
  };
41
52
  function resolveDataTableLabels(partial) {
42
53
  if (!partial) return DEFAULT_DATA_TABLE_LABELS;
43
- return {
54
+ const merged = {
44
55
  ...DEFAULT_DATA_TABLE_LABELS,
45
56
  ...partial,
46
57
  presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...partial.presets ?? {} }
47
58
  };
59
+ if (partial.columns !== void 0 && partial.columnsCount === void 0) {
60
+ merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;
61
+ }
62
+ return merged;
63
+ }
64
+ function missingDataTableLabels(labels) {
65
+ const missing = [];
66
+ for (const key of Object.keys(DEFAULT_DATA_TABLE_LABELS)) {
67
+ if (key === "presets") continue;
68
+ if (labels?.[key] !== void 0) continue;
69
+ if (key === "columnsCount" && labels?.columns !== void 0) continue;
70
+ missing.push(key);
71
+ }
72
+ for (const preset of Object.keys(DEFAULT_DATA_TABLE_LABELS.presets)) {
73
+ if (labels?.presets?.[preset] === void 0) missing.push(`presets.${preset}`);
74
+ }
75
+ return missing;
48
76
  }
49
77
  export {
50
78
  DEFAULT_DATA_TABLE_LABELS,
79
+ missingDataTableLabels,
51
80
  resolveDataTableLabels
52
81
  };
53
82
  //# sourceMappingURL=data-table-labels.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-labels.ts"],"sourcesContent":["// All user-facing strings for the DataTable system, injected as props so the\n// package stays i18n-free. Consumers pass translated overrides; the English\n// defaults below make the table usable with no wiring.\n\nexport interface DataTableLabels {\n // Table chrome\n columns: string;\n selectAllRows: string;\n sortHint: string;\n filter: string;\n close: string;\n selectRow: string;\n autoSize: string;\n loading: string;\n filters: string;\n clearAll: string;\n done: string;\n // Pagination\n pageSize: string;\n pageSizeAll: string;\n prevPage: string;\n nextPage: string;\n // Filter popover\n clearFilter: string;\n filterPlaceholder: string;\n selectFilter: string;\n selectAll: string;\n selectNone: string;\n dateFrom: string;\n dateTo: string;\n numberMin: string;\n numberMax: string;\n numberAbs: string;\n // Date-range preset labels, keyed by preset key (see dateRangePresets()).\n presets: Record<string, string>;\n}\n\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n columns: \"Columns\",\n selectAllRows: \"Select all rows\",\n sortHint: \"Click to sort · Shift-click to add a sort\",\n filter: \"Filter\",\n close: \"Close\",\n selectRow: \"Select row\",\n autoSize: \"Auto-size columns\",\n loading: \"Loading…\",\n filters: \"Filters\",\n clearAll: \"Clear all\",\n done: \"Done\",\n pageSize: \"Rows per page\",\n pageSizeAll: \"All\",\n prevPage: \"Previous page\",\n nextPage: \"Next page\",\n clearFilter: \"Clear filter\",\n filterPlaceholder: \"Filter…\",\n selectFilter: \"Select\",\n selectAll: \"All\",\n selectNone: \"None\",\n dateFrom: \"From\",\n dateTo: \"To\",\n numberMin: \"Min\",\n numberMax: \"Max\",\n numberAbs: \"Absolute value\",\n presets: {\n today: \"Today\",\n yesterday: \"Yesterday\",\n this_week: \"This week\",\n last_week: \"Last week\",\n last_7_days: \"Last 7 days\",\n last_30_days: \"Last 30 days\",\n this_month: \"This month\",\n last_month: \"Last month\",\n last_3_months: \"Last 3 months\",\n ytd: \"Year to date\",\n last_year: \"Last year\",\n },\n};\n\n/** Merge caller overrides onto the English defaults (presets merged too). */\nexport function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels {\n if (!partial) return DEFAULT_DATA_TABLE_LABELS;\n return {\n ...DEFAULT_DATA_TABLE_LABELS,\n ...partial,\n presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...(partial.presets ?? {}) },\n };\n}\n"],"mappings":"AAqCO,MAAM,4BAA6C;AAAA,EACxD,SAAS;AAAA,EACT,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AACF;AAGO,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,EAAE,GAAG,0BAA0B,SAAS,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,EAC9E;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/components/data-table-labels.ts"],"sourcesContent":["// All user-facing strings for the DataTable system — the `dataTable` namespace of\n// `<UiKitProvider labels>`, and per table the `labels` prop over that. The package\n// ships no translations; the English defaults below make the table usable with no\n// wiring.\n\nexport interface DataTableLabels {\n // Table chrome\n columns: string;\n selectAllRows: string;\n sortHint: string;\n filter: string;\n close: string;\n selectRow: string;\n autoSize: string;\n loading: string;\n filters: string;\n clearAll: string;\n done: string;\n // Pagination\n pageSize: string;\n pageSizeAll: string;\n prevPage: string;\n nextPage: string;\n // Filter popover\n clearFilter: string;\n filterPlaceholder: string;\n selectFilter: string;\n selectAll: string;\n selectNone: string;\n dateFrom: string;\n dateTo: string;\n numberMin: string;\n numberMax: string;\n numberAbs: string;\n // Date-range preset labels, keyed by preset key (see dateRangePresets()).\n presets: Record<string, string>;\n\n /**\n * The table's own accessible name. Deliberately a label rather than a second,\n * parallel `tableLabel` prop: the audit already records \"three different prop\n * names for the accessible name\" as a finding, and a table that is not \"Data\n * table\" says so with `labels={{ table: \"Transactions\" }}` at the call site.\n */\n table: string;\n\n // ---- Announcements ----\n // Spoken through the live region, never rendered. Each takes its numbers as\n // ARGUMENTS: a count glued into an English template literal is untranslatable,\n // and the grammar around it moves with the number in most languages.\n\n /** After a filter changes: \"12 of 137 rows\". */\n filterResults: (shown: number, total: number) => string;\n /** After a header is activated: \"Sorted by Name, ascending\". */\n sortedAscending: (column: string) => string;\n sortedDescending: (column: string) => string;\n /** The third click, which removes the column from the sort entirely. */\n sortCleared: (column: string) => string;\n /** After paging: \"Page 3 of 14\". `page` is 1-BASED, as spoken. */\n pageChanged: (page: number, totalPages: number) => string;\n\n // ---- Counted chrome ----\n // Visible text that interpolates numbers. These were template literals until the\n // audit flagged them: a consumer could translate every key in this file and still\n // be left with English (and with ASCII digit grouping) in the footer.\n\n /** The pagination range summary: \"1–25 / 137\". */\n pageRange: (from: number, to: number, total: number) => string;\n /** The same summary when the page size is \"all\" — just the row count. */\n rowCount: (total: number) => string;\n /** The column-settings rail and its heading: \"Columns (4/9)\". */\n columnsCount: (visible: number, total: number) => string;\n}\n\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n columns: \"Columns\",\n selectAllRows: \"Select all rows\",\n sortHint: \"Click to sort · Shift-click to add a sort\",\n filter: \"Filter\",\n close: \"Close\",\n selectRow: \"Select row\",\n autoSize: \"Auto-size columns\",\n loading: \"Loading…\",\n filters: \"Filters\",\n clearAll: \"Clear all\",\n done: \"Done\",\n pageSize: \"Rows per page\",\n pageSizeAll: \"All\",\n prevPage: \"Previous page\",\n nextPage: \"Next page\",\n clearFilter: \"Clear filter\",\n filterPlaceholder: \"Filter…\",\n selectFilter: \"Select\",\n selectAll: \"All\",\n selectNone: \"None\",\n dateFrom: \"From\",\n dateTo: \"To\",\n numberMin: \"Min\",\n numberMax: \"Max\",\n numberAbs: \"Absolute value\",\n presets: {\n today: \"Today\",\n yesterday: \"Yesterday\",\n this_week: \"This week\",\n last_week: \"Last week\",\n last_7_days: \"Last 7 days\",\n last_30_days: \"Last 30 days\",\n this_month: \"This month\",\n last_month: \"Last month\",\n last_3_months: \"Last 3 months\",\n ytd: \"Year to date\",\n last_year: \"Last year\",\n },\n table: \"Data table\",\n filterResults: (shown, total) => `${shown} of ${total} rows`,\n sortedAscending: (column) => `Sorted by ${column}, ascending`,\n sortedDescending: (column) => `Sorted by ${column}, descending`,\n sortCleared: (column) => `Sort cleared on ${column}`,\n pageChanged: (page, totalPages) => `Page ${page} of ${totalPages}`,\n // The separators are the ones that shipped, so adding the seam changes no screen.\n pageRange: (from, to, total) => `${from}–${to} / ${total}`,\n rowCount: (total) => `${total}`,\n columnsCount: (visible, total) => `Columns (${visible}/${total})`,\n};\n\n/**\n * Merge caller overrides onto the English defaults (presets merged too).\n *\n * The parameter is `Partial` on purpose — a table with no labels at all still\n * renders — but that is also how a translated app ends up half English and never\n * hears about it: lenkbank ships nineteen of these keys in English in a German UI\n * today, because every one of them is backfilled silently. The kit will not start\n * throwing over it (three apps depend on the lenient merge), so\n * {@link missingDataTableLabels} is the other half: it answers which keys fell back,\n * and an app asserts on that in its OWN test, where the answer is actionable.\n */\nexport function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels {\n if (!partial) return DEFAULT_DATA_TABLE_LABELS;\n const merged: DataTableLabels = {\n ...DEFAULT_DATA_TABLE_LABELS,\n ...partial,\n presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...(partial.presets ?? {}) },\n };\n // `columnsCount` was split out of `columns` + a template literal. Consumers have\n // been translating `columns` since 0.1, and backfilling the English default over\n // that would hand them \"Columns (4/9)\" back for having translated the key that\n // existed at the time — the same silent-English failure the audit records against\n // this file. So the count is derived from the RESOLVED `columns` unless the caller\n // states the whole sentence, which is the escape hatch for a language where the\n // parenthetical does not fit.\n if (partial.columns !== undefined && partial.columnsCount === undefined) {\n merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;\n }\n return merged;\n}\n\n/**\n * The label keys `labels` does not supply, and which therefore come back English\n * from {@link resolveDataTableLabels}.\n *\n * For a consumer's own test:\n *\n * ```ts\n * expect(missingDataTableLabels(de.dataTable)).toEqual([]);\n * ```\n *\n * which fails the day the kit adds a key — which is the point. Adding a label is\n * additive here and a silent regression in a translated app, and nothing else in\n * either repository can tell the two apart.\n *\n * Preset labels are reported one at a time (`presets.yesterday`), because `presets`\n * is a `Record` merged key by key: answering `\"presets\"` would say nothing about\n * which of the eleven are still English.\n */\nexport function missingDataTableLabels(labels?: Partial<DataTableLabels>): string[] {\n const missing: string[] = [];\n for (const key of Object.keys(DEFAULT_DATA_TABLE_LABELS) as Array<keyof DataTableLabels>) {\n if (key === \"presets\") continue;\n if (labels?.[key] !== undefined) continue;\n // `columnsCount` is DERIVED from a translated `columns` by the resolver above,\n // so a caller who set that one is not leaking English here. Reporting it would\n // send them to translate a key they have already covered, and an `toEqual([])`\n // assertion built on this would never go green.\n if (key === \"columnsCount\" && labels?.columns !== undefined) continue;\n missing.push(key);\n }\n for (const preset of Object.keys(DEFAULT_DATA_TABLE_LABELS.presets)) {\n if (labels?.presets?.[preset] === undefined) missing.push(`presets.${preset}`);\n }\n return missing;\n}\n"],"mappings":";AAyEO,MAAM,4BAA6C;AAAA,EACxD,SAAS;AAAA,EACT,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AAAA,EACA,OAAO;AAAA,EACP,eAAe,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,KAAK;AAAA,EACrD,iBAAiB,CAAC,WAAW,aAAa,MAAM;AAAA,EAChD,kBAAkB,CAAC,WAAW,aAAa,MAAM;AAAA,EACjD,aAAa,CAAC,WAAW,mBAAmB,MAAM;AAAA,EAClD,aAAa,CAAC,MAAM,eAAe,QAAQ,IAAI,OAAO,UAAU;AAAA;AAAA,EAEhE,WAAW,CAAC,MAAM,IAAI,UAAU,GAAG,IAAI,SAAI,EAAE,MAAM,KAAK;AAAA,EACxD,UAAU,CAAC,UAAU,GAAG,KAAK;AAAA,EAC7B,cAAc,CAAC,SAAS,UAAU,YAAY,OAAO,IAAI,KAAK;AAChE;AAaO,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,EAAE,GAAG,0BAA0B,SAAS,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,EAC9E;AAQA,MAAI,QAAQ,YAAY,UAAa,QAAQ,iBAAiB,QAAW;AACvE,WAAO,eAAe,CAAC,SAAS,UAAU,GAAG,OAAO,OAAO,KAAK,OAAO,IAAI,KAAK;AAAA,EAClF;AACA,SAAO;AACT;AAoBO,SAAS,uBAAuB,QAA6C;AAClF,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,OAAO,KAAK,yBAAyB,GAAmC;AACxF,QAAI,QAAQ,UAAW;AACvB,QAAI,SAAS,GAAG,MAAM,OAAW;AAKjC,QAAI,QAAQ,kBAAkB,QAAQ,YAAY,OAAW;AAC7D,YAAQ,KAAK,GAAG;AAAA,EAClB;AACA,aAAW,UAAU,OAAO,KAAK,0BAA0B,OAAO,GAAG;AACnE,QAAI,QAAQ,UAAU,MAAM,MAAM,OAAW,SAAQ,KAAK,WAAW,MAAM,EAAE;AAAA,EAC/E;AACA,SAAO;AACT;","names":[]}
@@ -10,9 +10,11 @@ interface PaginationProps {
10
10
  onPage: (p: number) => void;
11
11
  onPageSize: (n: number) => void;
12
12
  labels?: DataTableLabels;
13
+ /** For the page numbers and page-size options; falls back to the provider's. */
14
+ locale?: string;
13
15
  }
14
16
  /** Footer for {@link DataTable}: range summary, page-size select, and a
15
17
  * windowed page-number strip (first/last + ±2 around the current page). */
16
- declare function Pagination({ page, totalPages, pageSize, total, onPage, onPageSize, labels, }: PaginationProps): react.JSX.Element;
18
+ declare function Pagination({ page, totalPages, pageSize, total, onPage, onPageSize, labels, locale: localeProp, }: PaginationProps): react.JSX.Element;
17
19
 
18
20
  export { PAGE_SIZE_OPTIONS, Pagination };