@eifi1/ui-kit 0.53.0 → 0.55.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 (902) hide show
  1. package/README.md +96 -82
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/admin/plan-change.js +2 -2
  5. package/dist/admin/plan-change.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +6 -0
  7. package/dist/admin/user-roster.js +4 -0
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/auth-errors.d.ts +36 -0
  10. package/dist/auth/auth-errors.js +20 -0
  11. package/dist/auth/auth-errors.js.map +1 -1
  12. package/dist/billing/billing-labels.d.ts +26 -0
  13. package/dist/billing/billing-labels.js +9 -0
  14. package/dist/billing/billing-labels.js.map +1 -1
  15. package/dist/billing/subscription-standing.d.ts +61 -0
  16. package/dist/billing/subscription-standing.js +51 -0
  17. package/dist/billing/subscription-standing.js.map +1 -0
  18. package/dist/billing/usage-meter.d.ts +52 -0
  19. package/dist/billing/usage-meter.js +52 -0
  20. package/dist/billing/usage-meter.js.map +1 -0
  21. package/dist/chart.d.ts +1 -0
  22. package/dist/chart.js +4 -0
  23. package/dist/chart.js.map +1 -1
  24. package/dist/components/alert-banner.js +2 -3
  25. package/dist/components/alert-banner.js.map +1 -1
  26. package/dist/components/amount-input.d.ts +2 -1
  27. package/dist/components/amount-input.js +3 -15
  28. package/dist/components/amount-input.js.map +1 -1
  29. package/dist/components/autocomplete.js +2 -2
  30. package/dist/components/autocomplete.js.map +1 -1
  31. package/dist/components/bulk-action-bar.js +2 -1
  32. package/dist/components/bulk-action-bar.js.map +1 -1
  33. package/dist/components/button.d.ts +35 -1
  34. package/dist/components/button.js +5 -9
  35. package/dist/components/button.js.map +1 -1
  36. package/dist/components/calendar-heatmap.js +6 -4
  37. package/dist/components/calendar-heatmap.js.map +1 -1
  38. package/dist/components/camera-button.d.ts +89 -0
  39. package/dist/components/camera-button.js +122 -0
  40. package/dist/components/camera-button.js.map +1 -0
  41. package/dist/components/camera-capture.d.ts +66 -0
  42. package/dist/components/camera-capture.js +190 -0
  43. package/dist/components/camera-capture.js.map +1 -0
  44. package/dist/components/camera-labels.d.ts +20 -0
  45. package/dist/components/camera-labels.js +15 -0
  46. package/dist/components/camera-labels.js.map +1 -0
  47. package/dist/components/chart-edge.d.ts +9 -0
  48. package/dist/components/chart-edge.js +14 -3
  49. package/dist/components/chart-edge.js.map +1 -1
  50. package/dist/components/chart-zoom-core.js +34 -29
  51. package/dist/components/chart-zoom-core.js.map +1 -1
  52. package/dist/components/checkbox-group.js +1 -1
  53. package/dist/components/checkbox-group.js.map +1 -1
  54. package/dist/components/combobox-core.js +3 -2
  55. package/dist/components/combobox-core.js.map +1 -1
  56. package/dist/components/combobox.js +2 -2
  57. package/dist/components/combobox.js.map +1 -1
  58. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  59. package/dist/components/consent-again-notice-labels.js +15 -0
  60. package/dist/components/consent-again-notice-labels.js.map +1 -0
  61. package/dist/components/consent-again-notice.d.ts +85 -0
  62. package/dist/components/consent-again-notice.js +94 -0
  63. package/dist/components/consent-again-notice.js.map +1 -0
  64. package/dist/components/country-select.js +2 -7
  65. package/dist/components/country-select.js.map +1 -1
  66. package/dist/components/currency-option-list.d.ts +2 -1
  67. package/dist/components/currency-option-list.js +10 -3
  68. package/dist/components/currency-option-list.js.map +1 -1
  69. package/dist/components/currency-select.d.ts +3 -1
  70. package/dist/components/currency-select.js +2 -2
  71. package/dist/components/currency-select.js.map +1 -1
  72. package/dist/components/danger-confirm.d.ts +22 -1
  73. package/dist/components/danger-confirm.js +52 -30
  74. package/dist/components/danger-confirm.js.map +1 -1
  75. package/dist/components/data-table-column-settings.js +2 -2
  76. package/dist/components/data-table-column-settings.js.map +1 -1
  77. package/dist/components/data-table-filter-popover.d.ts +2 -1
  78. package/dist/components/data-table-filter-popover.js +3 -2
  79. package/dist/components/data-table-filter-popover.js.map +1 -1
  80. package/dist/components/data-table-filters.d.ts +33 -3
  81. package/dist/components/data-table-filters.js +6 -4
  82. package/dist/components/data-table-filters.js.map +1 -1
  83. package/dist/components/data-table-fit.js.map +1 -1
  84. package/dist/components/data-table-virtual.js +13 -3
  85. package/dist/components/data-table-virtual.js.map +1 -1
  86. package/dist/components/date-field.js +2 -2
  87. package/dist/components/date-field.js.map +1 -1
  88. package/dist/components/date-picker.js +2 -2
  89. package/dist/components/date-picker.js.map +1 -1
  90. package/dist/components/date-range-picker.js +2 -6
  91. package/dist/components/date-range-picker.js.map +1 -1
  92. package/dist/components/dialog-frame.d.ts +5 -1
  93. package/dist/components/dialog-frame.js +6 -2
  94. package/dist/components/dialog-frame.js.map +1 -1
  95. package/dist/components/dropdown.js +7 -3
  96. package/dist/components/dropdown.js.map +1 -1
  97. package/dist/components/entity-combobox.js +2 -2
  98. package/dist/components/entity-combobox.js.map +1 -1
  99. package/dist/components/field-parts.d.ts +108 -6
  100. package/dist/components/field-parts.js +59 -6
  101. package/dist/components/field-parts.js.map +1 -1
  102. package/dist/components/field-suggestion-labels.d.ts +2 -0
  103. package/dist/components/field-suggestion-labels.js +10 -0
  104. package/dist/components/field-suggestion-labels.js.map +1 -0
  105. package/dist/components/field.js +1 -1
  106. package/dist/components/field.js.map +1 -1
  107. package/dist/components/file-button.d.ts +1 -1
  108. package/dist/components/file-button.js.map +1 -1
  109. package/dist/components/floating-panel.d.ts +7 -6
  110. package/dist/components/floating-panel.js +3 -17
  111. package/dist/components/floating-panel.js.map +1 -1
  112. package/dist/components/form-actions.d.ts +3 -2
  113. package/dist/components/form-actions.js.map +1 -1
  114. package/dist/components/hover-menu.js +3 -2
  115. package/dist/components/hover-menu.js.map +1 -1
  116. package/dist/components/icon-button.d.ts +23 -5
  117. package/dist/components/icon-button.js +6 -8
  118. package/dist/components/icon-button.js.map +1 -1
  119. package/dist/components/inline-confirm.js +2 -1
  120. package/dist/components/inline-confirm.js.map +1 -1
  121. package/dist/components/inline-entity-combobox.js +2 -2
  122. package/dist/components/inline-entity-combobox.js.map +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/input.js.map +1 -1
  125. package/dist/components/jump-calendar.js +3 -3
  126. package/dist/components/jump-calendar.js.map +1 -1
  127. package/dist/components/legal.d.ts +13 -1
  128. package/dist/components/legal.js +12 -0
  129. package/dist/components/legal.js.map +1 -1
  130. package/dist/components/legended-series-chart.d.ts +80 -0
  131. package/dist/components/legended-series-chart.js +70 -0
  132. package/dist/components/legended-series-chart.js.map +1 -0
  133. package/dist/components/lightbox.js +2 -1
  134. package/dist/components/lightbox.js.map +1 -1
  135. package/dist/components/mini-calendar.js +16 -7
  136. package/dist/components/mini-calendar.js.map +1 -1
  137. package/dist/components/modal.d.ts +48 -0
  138. package/dist/components/modal.js +26 -6
  139. package/dist/components/modal.js.map +1 -1
  140. package/dist/components/month-picker.d.ts +1 -1
  141. package/dist/components/month-picker.js +6 -6
  142. package/dist/components/month-picker.js.map +1 -1
  143. package/dist/components/multi-entity-combobox.js +2 -2
  144. package/dist/components/multi-entity-combobox.js.map +1 -1
  145. package/dist/components/multi-select.js +2 -6
  146. package/dist/components/multi-select.js.map +1 -1
  147. package/dist/components/number-input.js +2 -2
  148. package/dist/components/number-input.js.map +1 -1
  149. package/dist/components/page-header-layout.d.ts +18 -0
  150. package/dist/components/page-header-layout.js +23 -0
  151. package/dist/components/page-header-layout.js.map +1 -0
  152. package/dist/components/page-header-skeleton.d.ts +41 -0
  153. package/dist/components/page-header-skeleton.js +52 -0
  154. package/dist/components/page-header-skeleton.js.map +1 -0
  155. package/dist/components/page-header.js +4 -17
  156. package/dist/components/page-header.js.map +1 -1
  157. package/dist/components/popover.js +7 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/release-notes-labels.d.ts +16 -0
  160. package/dist/components/release-notes-labels.js +18 -0
  161. package/dist/components/release-notes-labels.js.map +1 -0
  162. package/dist/components/release-notes.d.ts +65 -0
  163. package/dist/components/release-notes.js +65 -0
  164. package/dist/components/release-notes.js.map +1 -0
  165. package/dist/components/select.js +2 -2
  166. package/dist/components/select.js.map +1 -1
  167. package/dist/components/series-chart-labels.d.ts +9 -0
  168. package/dist/components/series-chart-labels.js +3 -1
  169. package/dist/components/series-chart-labels.js.map +1 -1
  170. package/dist/components/series-chart.d.ts +5 -0
  171. package/dist/components/series-chart.js +89 -36
  172. package/dist/components/series-chart.js.map +1 -1
  173. package/dist/components/stat-tile.d.ts +21 -2
  174. package/dist/components/stat-tile.js +4 -2
  175. package/dist/components/stat-tile.js.map +1 -1
  176. package/dist/components/toggle-group.js +3 -2
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/treemap-layout.js.map +1 -1
  179. package/dist/components/treemap.d.ts +17 -15
  180. package/dist/components/treemap.js +167 -85
  181. package/dist/components/treemap.js.map +1 -1
  182. package/dist/currencies.d.ts +16 -2
  183. package/dist/currencies.js +2 -1
  184. package/dist/currencies.js.map +1 -1
  185. package/dist/eslint/index.js +1 -1
  186. package/dist/eslint/role-utilities.d.ts +2 -2
  187. package/dist/eslint/role-utilities.js.map +1 -1
  188. package/dist/feedback/feedback-context-labels.js +2 -1
  189. package/dist/feedback/feedback-context-labels.js.map +1 -1
  190. package/dist/feedback/feedback-context.d.ts +19 -3
  191. package/dist/feedback/feedback-context.js +12 -1
  192. package/dist/feedback/feedback-context.js.map +1 -1
  193. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  194. package/dist/feedback/feedback-diagnostics.js +27 -0
  195. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  196. package/dist/feedback/feedback-dialog.d.ts +10 -0
  197. package/dist/feedback/feedback-dialog.js +6 -4
  198. package/dist/feedback/feedback-dialog.js.map +1 -1
  199. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  200. package/dist/feedback/feedback-inbox-page.js +207 -0
  201. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  202. package/dist/feedback/feedback-record.d.ts +5 -0
  203. package/dist/feedback/feedback-record.js.map +1 -1
  204. package/dist/feedback/feedback-row-detail-labels.js +1 -0
  205. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  206. package/dist/feedback/feedback-row-detail.d.ts +3 -0
  207. package/dist/feedback/feedback-row-detail.js +3 -0
  208. package/dist/feedback/feedback-row-detail.js.map +1 -1
  209. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  210. package/dist/feedback/feedback-submit-dialog.js +15 -3
  211. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  212. package/dist/feedback/feedback-submit.d.ts +30 -2
  213. package/dist/feedback/feedback-submit.js +5 -2
  214. package/dist/feedback/feedback-submit.js.map +1 -1
  215. package/dist/feedback/feedback-vocabulary.js +2 -2
  216. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  217. package/dist/feedback.d.ts +1 -0
  218. package/dist/feedback.js +6 -0
  219. package/dist/feedback.js.map +1 -1
  220. package/dist/hooks/use-debounce.d.ts +32 -5
  221. package/dist/hooks/use-debounce.js +6 -5
  222. package/dist/hooks/use-debounce.js.map +1 -1
  223. package/dist/hooks/use-file-picker.d.ts +3 -1
  224. package/dist/hooks/use-file-picker.js.map +1 -1
  225. package/dist/hooks/use-focus-trap.d.ts +19 -3
  226. package/dist/hooks/use-focus-trap.js +15 -5
  227. package/dist/hooks/use-focus-trap.js.map +1 -1
  228. package/dist/hooks/use-has-camera.d.ts +36 -0
  229. package/dist/hooks/use-has-camera.js +53 -0
  230. package/dist/hooks/use-has-camera.js.map +1 -0
  231. package/dist/hooks/use-hotkey.js +2 -1
  232. package/dist/hooks/use-hotkey.js.map +1 -1
  233. package/dist/hooks/use-overlay-history.d.ts +6 -1
  234. package/dist/hooks/use-overlay-history.js +10 -2
  235. package/dist/hooks/use-overlay-history.js.map +1 -1
  236. package/dist/i18n/defaults.js +11 -1
  237. package/dist/i18n/defaults.js.map +1 -1
  238. package/dist/i18n/german.js +78 -4
  239. package/dist/i18n/german.js.map +1 -1
  240. package/dist/i18n/kit-labels.d.ts +20 -0
  241. package/dist/i18n/kit-labels.js.map +1 -1
  242. package/dist/i18n/locales/en.js +95 -5
  243. package/dist/i18n/locales/en.js.map +3 -3
  244. package/dist/i18n/locales/es.js +74 -4
  245. package/dist/i18n/locales/es.js.map +1 -1
  246. package/dist/i18n/locales/fr.js +75 -3
  247. package/dist/i18n/locales/fr.js.map +1 -1
  248. package/dist/i18n/locales/hu.js +77 -4
  249. package/dist/i18n/locales/hu.js.map +1 -1
  250. package/dist/i18n/locales/it.js +77 -4
  251. package/dist/i18n/locales/it.js.map +1 -1
  252. package/dist/i18n/locales/zh.js +74 -4
  253. package/dist/i18n/locales/zh.js.map +1 -1
  254. package/dist/i18n/review.js +22 -1
  255. package/dist/i18n/review.js.map +1 -1
  256. package/dist/index.d.ts +27 -8
  257. package/dist/index.js +41 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/landing/landing-labels.d.ts +8 -0
  260. package/dist/landing/landing-labels.js +2 -1
  261. package/dist/landing/landing-labels.js.map +1 -1
  262. package/dist/lib/changelog.d.ts +130 -0
  263. package/dist/lib/changelog.js +137 -0
  264. package/dist/lib/changelog.js.map +1 -0
  265. package/dist/lib/clipboard-minus.js +2 -2
  266. package/dist/lib/clipboard-minus.js.map +1 -1
  267. package/dist/lib/dates.d.ts +36 -4
  268. package/dist/lib/dates.js +49 -13
  269. package/dist/lib/dates.js.map +1 -1
  270. package/dist/lib/editable-content.d.ts +33 -0
  271. package/dist/lib/editable-content.js +15 -0
  272. package/dist/lib/editable-content.js.map +1 -0
  273. package/dist/lib/focus-left.d.ts +47 -1
  274. package/dist/lib/focus-left.js +11 -2
  275. package/dist/lib/focus-left.js.map +1 -1
  276. package/dist/lib/format.d.ts +37 -2
  277. package/dist/lib/format.js +25 -1
  278. package/dist/lib/format.js.map +1 -1
  279. package/dist/lib/intl-cache.d.ts +4 -2
  280. package/dist/lib/intl-cache.js.map +1 -1
  281. package/dist/lib/server-wake.d.ts +49 -9
  282. package/dist/lib/server-wake.js +25 -14
  283. package/dist/lib/server-wake.js.map +1 -1
  284. package/dist/lib/session-refresh.d.ts +53 -0
  285. package/dist/lib/session-refresh.js +27 -1
  286. package/dist/lib/session-refresh.js.map +1 -1
  287. package/dist/lib/translation-review.d.ts +26 -0
  288. package/dist/lib/translation-review.js +14 -0
  289. package/dist/lib/translation-review.js.map +1 -1
  290. package/dist/rhf/fields.js +3 -3
  291. package/dist/rhf/fields.js.map +1 -1
  292. package/dist/shell/app-shell.d.ts +25 -1
  293. package/dist/shell/app-shell.js +3 -1
  294. package/dist/shell/app-shell.js.map +1 -1
  295. package/dist/shell/dev-menu.d.ts +3 -0
  296. package/dist/shell/dev-menu.js +4 -2
  297. package/dist/shell/dev-menu.js.map +1 -1
  298. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  299. package/dist/shell/held-changes-banner-labels.js +14 -0
  300. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  301. package/dist/shell/held-changes-banner.d.ts +75 -0
  302. package/dist/shell/held-changes-banner.js +106 -0
  303. package/dist/shell/held-changes-banner.js.map +1 -0
  304. package/dist/shell/sync-status-indicator.d.ts +27 -12
  305. package/dist/shell/sync-status-indicator.js +13 -4
  306. package/dist/shell/sync-status-indicator.js.map +1 -1
  307. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  308. package/dist/shell/top-bar-parts-labels.js +3 -1
  309. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  310. package/dist/shell.d.ts +2 -0
  311. package/dist/shell.js +3 -0
  312. package/dist/shell.js.map +1 -1
  313. package/dist/sync/engine.d.ts +11 -2
  314. package/dist/sync/engine.js +13 -5
  315. package/dist/sync/engine.js.map +1 -1
  316. package/dist/sync/hooks.d.ts +3 -2
  317. package/dist/sync/hooks.js +4 -2
  318. package/dist/sync/hooks.js.map +1 -1
  319. package/dist/testing/css-variables.generated.d.ts +2 -2
  320. package/dist/testing/css-variables.generated.js +1 -1
  321. package/dist/testing/css-variables.generated.js.map +1 -1
  322. package/dist/theme/chart-palette.d.ts +18 -0
  323. package/dist/theme/chart-palette.js +17 -1
  324. package/dist/theme/chart-palette.js.map +1 -1
  325. package/dist/theme/contrast-tokens.d.ts +3 -3
  326. package/dist/theme/contrast-tokens.js.map +1 -1
  327. package/dist/theme/contrast.d.ts +4 -4
  328. package/dist/theme/contrast.js.map +1 -1
  329. package/dist/tour/tour-overlay.js +2 -1
  330. package/dist/tour/tour-overlay.js.map +1 -1
  331. package/package.json +5 -9
  332. package/tokens.css +19 -54
  333. package/src/account/account-parts.ts +0 -47
  334. package/src/account/account-settings-labels.ts +0 -240
  335. package/src/account/account-settings.tsx +0 -698
  336. package/src/account/data-export-setting-labels.ts +0 -20
  337. package/src/account/data-export-setting.tsx +0 -200
  338. package/src/account/delete-account-setting-labels.ts +0 -33
  339. package/src/account/delete-account-setting.tsx +0 -341
  340. package/src/account/email-change-setting-labels.ts +0 -31
  341. package/src/account/email-change-setting.tsx +0 -443
  342. package/src/account/passkeys-setting.tsx +0 -445
  343. package/src/account/sessions-setting-labels.ts +0 -24
  344. package/src/account/sessions-setting.tsx +0 -283
  345. package/src/admin/admin-action-confirm-labels.ts +0 -19
  346. package/src/admin/admin-action-confirm.tsx +0 -509
  347. package/src/admin/admin-action-log-labels.ts +0 -83
  348. package/src/admin/admin-action-log.tsx +0 -464
  349. package/src/admin/admin-area-labels.ts +0 -114
  350. package/src/admin/admin-forbidden.tsx +0 -75
  351. package/src/admin/admin-overview.tsx +0 -258
  352. package/src/admin/admin-parts.ts +0 -68
  353. package/src/admin/admin-sections.tsx +0 -208
  354. package/src/admin/admin-tool-links.tsx +0 -90
  355. package/src/admin/invitations-panel-labels.ts +0 -31
  356. package/src/admin/invitations-panel.tsx +0 -516
  357. package/src/admin/mail-test-card-labels.ts +0 -19
  358. package/src/admin/mail-test-card.tsx +0 -189
  359. package/src/admin/plan-change-labels.ts +0 -5
  360. package/src/admin/plan-change.tsx +0 -474
  361. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  362. package/src/admin/reviewer-scope-editor.tsx +0 -224
  363. package/src/admin/role-select-labels.ts +0 -16
  364. package/src/admin/role-select.tsx +0 -282
  365. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  366. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  367. package/src/admin/user-roster-labels.ts +0 -17
  368. package/src/admin/user-roster.tsx +0 -559
  369. package/src/auth/accept-invitation-labels.ts +0 -21
  370. package/src/auth/accept-invitation.tsx +0 -210
  371. package/src/auth/auth-errors-labels.ts +0 -23
  372. package/src/auth/auth-errors.ts +0 -370
  373. package/src/auth/complete-name-dialog-labels.ts +0 -16
  374. package/src/auth/complete-name-dialog.tsx +0 -253
  375. package/src/auth/email-tag.ts +0 -75
  376. package/src/auth/forgot-password-form-labels.ts +0 -20
  377. package/src/auth/forgot-password-form.tsx +0 -221
  378. package/src/auth/form-rules.ts +0 -40
  379. package/src/auth/new-password-fields.tsx +0 -85
  380. package/src/auth/not-found-page-labels.ts +0 -12
  381. package/src/auth/not-found-page.tsx +0 -95
  382. package/src/auth/register-form-labels.ts +0 -31
  383. package/src/auth/register-form.tsx +0 -456
  384. package/src/auth/reset-password-form-labels.ts +0 -27
  385. package/src/auth/reset-password-form.tsx +0 -341
  386. package/src/auth/sign-in-form-labels.ts +0 -44
  387. package/src/auth/sign-in-form.tsx +0 -858
  388. package/src/auth/status-parts.tsx +0 -132
  389. package/src/auth/verify-email-labels.ts +0 -26
  390. package/src/auth/verify-email.tsx +0 -445
  391. package/src/billing/billing-banner.tsx +0 -258
  392. package/src/billing/billing-labels.ts +0 -381
  393. package/src/billing/billing-lock.ts +0 -108
  394. package/src/billing/billing-overview.ts +0 -157
  395. package/src/billing/billing-standing.ts +0 -80
  396. package/src/billing/cancel-request.ts +0 -97
  397. package/src/billing/cancel-subscription.tsx +0 -98
  398. package/src/billing/checkout-processing.ts +0 -394
  399. package/src/billing/checkout-return.ts +0 -90
  400. package/src/billing/pay-page.ts +0 -330
  401. package/src/billing/plan-card.tsx +0 -562
  402. package/src/billing/plan-change-labels.ts +0 -66
  403. package/src/billing/plan-limit-notice.tsx +0 -148
  404. package/src/billing/plan-limit-toast.tsx +0 -90
  405. package/src/billing/plan-limit.ts +0 -52
  406. package/src/billing/plan-price.ts +0 -98
  407. package/src/billing/plan-switch.tsx +0 -216
  408. package/src/billing/provider-page.ts +0 -84
  409. package/src/billing/subscription-actions.tsx +0 -247
  410. package/src/billing/subscription-status-chip.tsx +0 -95
  411. package/src/chart.ts +0 -103
  412. package/src/components/account-chips-labels.ts +0 -16
  413. package/src/components/account-chips.tsx +0 -474
  414. package/src/components/alert-banner.tsx +0 -670
  415. package/src/components/amount-input.tsx +0 -815
  416. package/src/components/appearance-labels.ts +0 -63
  417. package/src/components/authed-image-labels.ts +0 -11
  418. package/src/components/authed-image.tsx +0 -209
  419. package/src/components/autocomplete.tsx +0 -645
  420. package/src/components/breadcrumbs-labels.ts +0 -10
  421. package/src/components/breadcrumbs.tsx +0 -205
  422. package/src/components/bulk-action-bar-labels.ts +0 -11
  423. package/src/components/bulk-action-bar.tsx +0 -443
  424. package/src/components/button-group.tsx +0 -223
  425. package/src/components/button-look.ts +0 -256
  426. package/src/components/button.tsx +0 -339
  427. package/src/components/calculator.tsx +0 -341
  428. package/src/components/calendar-heatmap-labels.ts +0 -15
  429. package/src/components/calendar-heatmap.tsx +0 -594
  430. package/src/components/card.tsx +0 -377
  431. package/src/components/chart-edge.ts +0 -106
  432. package/src/components/chart-figure.tsx +0 -35
  433. package/src/components/chart-height.ts +0 -30
  434. package/src/components/chart-zoom-core.tsx +0 -627
  435. package/src/components/chart-zoom.tsx +0 -22
  436. package/src/components/chart.tsx +0 -554
  437. package/src/components/checkbox-group.tsx +0 -247
  438. package/src/components/checkbox.tsx +0 -346
  439. package/src/components/chip-input.tsx +0 -370
  440. package/src/components/chip-labels.ts +0 -13
  441. package/src/components/chip.tsx +0 -843
  442. package/src/components/choice-card.tsx +0 -544
  443. package/src/components/column-mapper-labels.ts +0 -38
  444. package/src/components/column-mapper.tsx +0 -655
  445. package/src/components/combobox-core.tsx +0 -1321
  446. package/src/components/combobox-suggestions.tsx +0 -131
  447. package/src/components/combobox.tsx +0 -716
  448. package/src/components/confirm-context.ts +0 -7
  449. package/src/components/confirm-dialog-labels.ts +0 -13
  450. package/src/components/confirm-dialog.tsx +0 -341
  451. package/src/components/copy-button-labels.ts +0 -13
  452. package/src/components/copy-button.tsx +0 -192
  453. package/src/components/country-select-labels.ts +0 -11
  454. package/src/components/country-select.tsx +0 -539
  455. package/src/components/currency-option-list.tsx +0 -180
  456. package/src/components/currency-select.tsx +0 -341
  457. package/src/components/danger-confirm-labels.ts +0 -20
  458. package/src/components/danger-confirm.tsx +0 -934
  459. package/src/components/data-table-cells.tsx +0 -118
  460. package/src/components/data-table-column-settings.tsx +0 -198
  461. package/src/components/data-table-filter-popover.tsx +0 -281
  462. package/src/components/data-table-filters.ts +0 -287
  463. package/src/components/data-table-fit.ts +0 -301
  464. package/src/components/data-table-groups.tsx +0 -288
  465. package/src/components/data-table-head.tsx +0 -280
  466. package/src/components/data-table-labels.ts +0 -198
  467. package/src/components/data-table-leading-row.tsx +0 -58
  468. package/src/components/data-table-mobile.tsx +0 -1117
  469. package/src/components/data-table-pagination.tsx +0 -202
  470. package/src/components/data-table-resize.ts +0 -188
  471. package/src/components/data-table-row-actions.tsx +0 -302
  472. package/src/components/data-table-row.tsx +0 -496
  473. package/src/components/data-table-selection.ts +0 -71
  474. package/src/components/data-table-shared.tsx +0 -287
  475. package/src/components/data-table-sort.ts +0 -163
  476. package/src/components/data-table-virtual.ts +0 -576
  477. package/src/components/data-table.tsx +0 -2598
  478. package/src/components/date-field.tsx +0 -684
  479. package/src/components/date-format-setting.tsx +0 -64
  480. package/src/components/date-picker.tsx +0 -404
  481. package/src/components/date-range-picker.tsx +0 -518
  482. package/src/components/description-list-labels.ts +0 -9
  483. package/src/components/description-list.tsx +0 -386
  484. package/src/components/dialog-frame-labels.ts +0 -7
  485. package/src/components/dialog-frame.tsx +0 -276
  486. package/src/components/disclosure.tsx +0 -476
  487. package/src/components/dropdown.tsx +0 -461
  488. package/src/components/empty-state.tsx +0 -136
  489. package/src/components/entity-combobox.tsx +0 -422
  490. package/src/components/error-boundary-labels.ts +0 -20
  491. package/src/components/error-boundary.tsx +0 -675
  492. package/src/components/facing-pair.ts +0 -93
  493. package/src/components/field-base-labels.ts +0 -20
  494. package/src/components/field-base.tsx +0 -656
  495. package/src/components/field-parts.tsx +0 -825
  496. package/src/components/field-strip.tsx +0 -129
  497. package/src/components/field.tsx +0 -206
  498. package/src/components/file-button.tsx +0 -295
  499. package/src/components/file-dropzone.tsx +0 -559
  500. package/src/components/floating-panel-labels.ts +0 -10
  501. package/src/components/floating-panel.tsx +0 -828
  502. package/src/components/focus-ring.ts +0 -16
  503. package/src/components/form-actions-labels.ts +0 -11
  504. package/src/components/form-actions.tsx +0 -578
  505. package/src/components/full-bleed-dialog.tsx +0 -294
  506. package/src/components/grouped-picker.tsx +0 -184
  507. package/src/components/help-popover.tsx +0 -117
  508. package/src/components/hover-menu.tsx +0 -343
  509. package/src/components/iban-input-labels.ts +0 -14
  510. package/src/components/iban-input.tsx +0 -236
  511. package/src/components/icon-button.tsx +0 -959
  512. package/src/components/icon-picker-labels.ts +0 -13
  513. package/src/components/icon-picker.tsx +0 -232
  514. package/src/components/image-grid-labels.ts +0 -11
  515. package/src/components/image-grid.tsx +0 -202
  516. package/src/components/inline-confirm.tsx +0 -116
  517. package/src/components/inline-edit-field-labels.ts +0 -11
  518. package/src/components/inline-edit-field.tsx +0 -552
  519. package/src/components/inline-entity-combobox.tsx +0 -849
  520. package/src/components/input.tsx +0 -394
  521. package/src/components/jump-calendar.tsx +0 -265
  522. package/src/components/language-select.tsx +0 -93
  523. package/src/components/legal-labels.ts +0 -105
  524. package/src/components/legal-page.tsx +0 -711
  525. package/src/components/legal.tsx +0 -668
  526. package/src/components/lightbox-labels.ts +0 -18
  527. package/src/components/lightbox.tsx +0 -399
  528. package/src/components/line-items-labels.ts +0 -14
  529. package/src/components/line-items.tsx +0 -602
  530. package/src/components/list-labels.ts +0 -12
  531. package/src/components/list.tsx +0 -870
  532. package/src/components/loading-state.tsx +0 -78
  533. package/src/components/measured-grid-labels.ts +0 -25
  534. package/src/components/measured-grid.tsx +0 -837
  535. package/src/components/menu-item.tsx +0 -241
  536. package/src/components/mini-calendar-labels.ts +0 -17
  537. package/src/components/mini-calendar.tsx +0 -789
  538. package/src/components/modal.tsx +0 -505
  539. package/src/components/money-draft.ts +0 -83
  540. package/src/components/money-field.tsx +0 -76
  541. package/src/components/month-grid.tsx +0 -229
  542. package/src/components/month-picker-labels.ts +0 -22
  543. package/src/components/month-picker.tsx +0 -1087
  544. package/src/components/multi-entity-combobox.tsx +0 -369
  545. package/src/components/multi-select.tsx +0 -376
  546. package/src/components/nav-pills.tsx +0 -198
  547. package/src/components/number-field.tsx +0 -327
  548. package/src/components/number-input.tsx +0 -558
  549. package/src/components/numeric-field.ts +0 -169
  550. package/src/components/numpad-sheet.tsx +0 -285
  551. package/src/components/one-time-code-input.tsx +0 -270
  552. package/src/components/page-contents-labels.ts +0 -9
  553. package/src/components/page-contents.tsx +0 -282
  554. package/src/components/page-header-context.ts +0 -8
  555. package/src/components/page-header.tsx +0 -298
  556. package/src/components/password-strength-labels.ts +0 -22
  557. package/src/components/password-strength.tsx +0 -241
  558. package/src/components/phone-input-labels.ts +0 -10
  559. package/src/components/phone-input.tsx +0 -282
  560. package/src/components/picker-sheet.tsx +0 -330
  561. package/src/components/pie-chart-labels.ts +0 -40
  562. package/src/components/pie-chart.tsx +0 -480
  563. package/src/components/popover-labels.ts +0 -14
  564. package/src/components/popover.tsx +0 -268
  565. package/src/components/progress-bar-labels.ts +0 -10
  566. package/src/components/progress-bar.tsx +0 -479
  567. package/src/components/qr-code.tsx +0 -76
  568. package/src/components/reauth-dialog-labels.ts +0 -13
  569. package/src/components/reauth-dialog.tsx +0 -265
  570. package/src/components/row-actions-labels.ts +0 -12
  571. package/src/components/row-actions.tsx +0 -795
  572. package/src/components/scroll-area.tsx +0 -123
  573. package/src/components/search-field.tsx +0 -192
  574. package/src/components/select.tsx +0 -294
  575. package/src/components/separator.tsx +0 -33
  576. package/src/components/series-chart-axes.ts +0 -342
  577. package/src/components/series-chart-budget.ts +0 -150
  578. package/src/components/series-chart-labels.ts +0 -33
  579. package/src/components/series-chart-thin.ts +0 -180
  580. package/src/components/series-chart-ticks.ts +0 -194
  581. package/src/components/series-chart.tsx +0 -1906
  582. package/src/components/server-wake-labels.ts +0 -11
  583. package/src/components/server-wake.tsx +0 -129
  584. package/src/components/settings-fields.tsx +0 -237
  585. package/src/components/share-card-labels.ts +0 -30
  586. package/src/components/share-card.tsx +0 -675
  587. package/src/components/signature-pad-labels.ts +0 -24
  588. package/src/components/signature-pad.tsx +0 -747
  589. package/src/components/signed-amount-labels.ts +0 -10
  590. package/src/components/signed-amount.tsx +0 -336
  591. package/src/components/skeleton.tsx +0 -80
  592. package/src/components/slider.tsx +0 -338
  593. package/src/components/sparkline-labels.ts +0 -14
  594. package/src/components/sparkline.tsx +0 -322
  595. package/src/components/spinner.tsx +0 -81
  596. package/src/components/stat-tile-labels.ts +0 -15
  597. package/src/components/stat-tile.tsx +0 -725
  598. package/src/components/status-dot.tsx +0 -170
  599. package/src/components/swatch-picker-labels.ts +0 -10
  600. package/src/components/swatch-picker.tsx +0 -283
  601. package/src/components/swipeable-row.tsx +0 -386
  602. package/src/components/switch.tsx +0 -273
  603. package/src/components/table-skeleton.tsx +0 -136
  604. package/src/components/table.tsx +0 -647
  605. package/src/components/tabs-labels.ts +0 -13
  606. package/src/components/tabs.tsx +0 -693
  607. package/src/components/text-link.tsx +0 -359
  608. package/src/components/text.tsx +0 -150
  609. package/src/components/textarea.tsx +0 -330
  610. package/src/components/tile-radio.tsx +0 -332
  611. package/src/components/time-input.tsx +0 -121
  612. package/src/components/toast-labels.ts +0 -12
  613. package/src/components/toast.tsx +0 -519
  614. package/src/components/toggle-group.tsx +0 -752
  615. package/src/components/toggle-legend.tsx +0 -394
  616. package/src/components/tooltip.tsx +0 -1149
  617. package/src/components/translation-review-editor.tsx +0 -290
  618. package/src/components/translation-review-labels.ts +0 -181
  619. package/src/components/translation-review-parts.tsx +0 -272
  620. package/src/components/translation-review.tsx +0 -1240
  621. package/src/components/tree-view.tsx +0 -589
  622. package/src/components/treemap-labels.ts +0 -47
  623. package/src/components/treemap-layout.ts +0 -210
  624. package/src/components/treemap.tsx +0 -1184
  625. package/src/components/trigger-aria.ts +0 -44
  626. package/src/components/use-anchor-dir.ts +0 -20
  627. package/src/components/use-mobile-reveal.ts +0 -62
  628. package/src/components/use-table-state.ts +0 -753
  629. package/src/components/user-avatar.tsx +0 -116
  630. package/src/components/wizard-stepper.tsx +0 -42
  631. package/src/components/write-lock-labels.ts +0 -10
  632. package/src/components/write-lock.tsx +0 -253
  633. package/src/currencies.ts +0 -70
  634. package/src/data-table.ts +0 -42
  635. package/src/demo/demo-banner.tsx +0 -153
  636. package/src/demo/demo-countdown.ts +0 -136
  637. package/src/demo/demo-ended.tsx +0 -90
  638. package/src/demo/demo-labels.ts +0 -78
  639. package/src/demo/demo-session.ts +0 -32
  640. package/src/demo/demo-start.tsx +0 -156
  641. package/src/eslint/index.ts +0 -161
  642. package/src/eslint/role-utilities.generated.ts +0 -146
  643. package/src/eslint/role-utilities.ts +0 -233
  644. package/src/feedback/feedback-attachment-download.tsx +0 -77
  645. package/src/feedback/feedback-attachment-labels.ts +0 -37
  646. package/src/feedback/feedback-attachment.tsx +0 -1239
  647. package/src/feedback/feedback-capture.ts +0 -287
  648. package/src/feedback/feedback-comment-thread.tsx +0 -353
  649. package/src/feedback/feedback-context-labels.ts +0 -10
  650. package/src/feedback/feedback-context.tsx +0 -213
  651. package/src/feedback/feedback-crash.ts +0 -547
  652. package/src/feedback/feedback-dialog-labels.ts +0 -29
  653. package/src/feedback/feedback-dialog.tsx +0 -440
  654. package/src/feedback/feedback-entry-file.tsx +0 -150
  655. package/src/feedback/feedback-inbox.tsx +0 -552
  656. package/src/feedback/feedback-labels.ts +0 -144
  657. package/src/feedback/feedback-menu-labels.ts +0 -12
  658. package/src/feedback/feedback-menu.tsx +0 -177
  659. package/src/feedback/feedback-mutation.ts +0 -20
  660. package/src/feedback/feedback-page-link.ts +0 -90
  661. package/src/feedback/feedback-public-labels.ts +0 -81
  662. package/src/feedback/feedback-public-list.tsx +0 -524
  663. package/src/feedback/feedback-public-switch.tsx +0 -102
  664. package/src/feedback/feedback-public-types.ts +0 -101
  665. package/src/feedback/feedback-record.ts +0 -423
  666. package/src/feedback/feedback-replies-context.ts +0 -9
  667. package/src/feedback/feedback-replies.tsx +0 -343
  668. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  669. package/src/feedback/feedback-row-detail-labels.ts +0 -30
  670. package/src/feedback/feedback-row-detail.tsx +0 -858
  671. package/src/feedback/feedback-status-undo.ts +0 -109
  672. package/src/feedback/feedback-submit-dialog.tsx +0 -118
  673. package/src/feedback/feedback-submit.tsx +0 -237
  674. package/src/feedback/feedback-swipe.tsx +0 -192
  675. package/src/feedback/feedback-table-labels.ts +0 -27
  676. package/src/feedback/feedback-table.tsx +0 -912
  677. package/src/feedback/feedback-thread-labels.ts +0 -22
  678. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  679. package/src/feedback/feedback-thread.tsx +0 -905
  680. package/src/feedback/feedback-vocabulary.ts +0 -263
  681. package/src/feedback/feedback-vote-button.tsx +0 -124
  682. package/src/feedback.ts +0 -116
  683. package/src/hooks/use-anchored-panel.ts +0 -396
  684. package/src/hooks/use-anchored-rect.ts +0 -77
  685. package/src/hooks/use-announce.ts +0 -85
  686. package/src/hooks/use-authed-src.ts +0 -167
  687. package/src/hooks/use-body-scroll-lock.ts +0 -119
  688. package/src/hooks/use-breakpoint.ts +0 -133
  689. package/src/hooks/use-close-transition.ts +0 -107
  690. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  691. package/src/hooks/use-debounce.ts +0 -114
  692. package/src/hooks/use-dismiss.ts +0 -371
  693. package/src/hooks/use-document-title.ts +0 -33
  694. package/src/hooks/use-file-drop.ts +0 -121
  695. package/src/hooks/use-file-picker-labels.ts +0 -28
  696. package/src/hooks/use-file-picker.tsx +0 -359
  697. package/src/hooks/use-focus-trap.ts +0 -199
  698. package/src/hooks/use-hotkey.ts +0 -189
  699. package/src/hooks/use-large-text.ts +0 -41
  700. package/src/hooks/use-long-press.ts +0 -140
  701. package/src/hooks/use-media-query.ts +0 -39
  702. package/src/hooks/use-mounted.ts +0 -39
  703. package/src/hooks/use-noindex.ts +0 -37
  704. package/src/hooks/use-overlay-history.ts +0 -714
  705. package/src/hooks/use-row-swipe.ts +0 -207
  706. package/src/hooks/use-search-param-state.ts +0 -434
  707. package/src/hooks/use-windowed-rows.ts +0 -225
  708. package/src/i18n/defaults.ts +0 -267
  709. package/src/i18n/german.ts +0 -2057
  710. package/src/i18n/kit-labels.tsx +0 -1003
  711. package/src/i18n/languages.ts +0 -349
  712. package/src/i18n/locales/de-CH.ts +0 -24
  713. package/src/i18n/locales/en.ts +0 -267
  714. package/src/i18n/locales/es.ts +0 -1997
  715. package/src/i18n/locales/fr.ts +0 -2035
  716. package/src/i18n/locales/hu.ts +0 -2002
  717. package/src/i18n/locales/it.ts +0 -2012
  718. package/src/i18n/locales/zh.ts +0 -1910
  719. package/src/i18n/plurals.ts +0 -86
  720. package/src/i18n/review.ts +0 -475
  721. package/src/i18n/shared-labels.ts +0 -138
  722. package/src/i18n/swiss.ts +0 -29
  723. package/src/index.ts +0 -1179
  724. package/src/landing/access.ts +0 -93
  725. package/src/landing/landing-actions.tsx +0 -97
  726. package/src/landing/landing-labels.ts +0 -59
  727. package/src/landing/landing-sections.tsx +0 -341
  728. package/src/landing/page-seo.ts +0 -261
  729. package/src/landing/public-header.tsx +0 -121
  730. package/src/landing/public-page.tsx +0 -106
  731. package/src/landing/routing.tsx +0 -225
  732. package/src/lib/broadcast.ts +0 -52
  733. package/src/lib/calc.ts +0 -323
  734. package/src/lib/chip-edge-snap.ts +0 -212
  735. package/src/lib/clipboard-minus.ts +0 -107
  736. package/src/lib/clipping.ts +0 -34
  737. package/src/lib/cn.ts +0 -28
  738. package/src/lib/column-mapping.ts +0 -234
  739. package/src/lib/countries.ts +0 -91
  740. package/src/lib/data-attributes.ts +0 -14
  741. package/src/lib/date-format.ts +0 -227
  742. package/src/lib/dates.ts +0 -266
  743. package/src/lib/day-format.ts +0 -96
  744. package/src/lib/decimal-marks.ts +0 -108
  745. package/src/lib/describe-failure.ts +0 -18
  746. package/src/lib/device-settings.ts +0 -147
  747. package/src/lib/direction.ts +0 -74
  748. package/src/lib/document-navigation.ts +0 -17
  749. package/src/lib/document-title.ts +0 -41
  750. package/src/lib/download.ts +0 -172
  751. package/src/lib/float-clearance.ts +0 -70
  752. package/src/lib/floating-stack.ts +0 -70
  753. package/src/lib/focus-left.ts +0 -156
  754. package/src/lib/format.ts +0 -522
  755. package/src/lib/http-status.ts +0 -38
  756. package/src/lib/iban.ts +0 -251
  757. package/src/lib/ime.ts +0 -24
  758. package/src/lib/intl-cache.ts +0 -25
  759. package/src/lib/iso-day.ts +0 -13
  760. package/src/lib/lazy-with-fallback.ts +0 -82
  761. package/src/lib/logger.ts +0 -69
  762. package/src/lib/minus.ts +0 -50
  763. package/src/lib/path-pattern.ts +0 -55
  764. package/src/lib/person-name.ts +0 -155
  765. package/src/lib/phone.ts +0 -208
  766. package/src/lib/plain-text.ts +0 -22
  767. package/src/lib/presence.ts +0 -18
  768. package/src/lib/qr-encode.ts +0 -444
  769. package/src/lib/quiet-focus.ts +0 -32
  770. package/src/lib/reduced-motion.ts +0 -22
  771. package/src/lib/refs.ts +0 -22
  772. package/src/lib/round.ts +0 -43
  773. package/src/lib/safe-storage.ts +0 -119
  774. package/src/lib/server-wake.ts +0 -315
  775. package/src/lib/session-refresh.ts +0 -472
  776. package/src/lib/share-format.ts +0 -18
  777. package/src/lib/status-tone.ts +0 -43
  778. package/src/lib/strip-fade.ts +0 -287
  779. package/src/lib/table-text.ts +0 -554
  780. package/src/lib/text-case.ts +0 -19
  781. package/src/lib/text-match.ts +0 -169
  782. package/src/lib/thenable.ts +0 -19
  783. package/src/lib/translation-review.ts +0 -717
  784. package/src/notifications/device-zone-report.ts +0 -67
  785. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  786. package/src/notifications/feedback-notification-types.ts +0 -71
  787. package/src/notifications/notification-bell.tsx +0 -119
  788. package/src/notifications/notification-inbox.tsx +0 -309
  789. package/src/notifications/notification-parts.tsx +0 -81
  790. package/src/notifications/notification-types-setting.tsx +0 -406
  791. package/src/notifications/notification-types.ts +0 -89
  792. package/src/notifications/notifications-labels.ts +0 -287
  793. package/src/notifications/push-device-setting.tsx +0 -230
  794. package/src/notifications/push-devices-setting.tsx +0 -374
  795. package/src/notifications/push-worker.ts +0 -168
  796. package/src/notifications/quiet-hours-setting.tsx +0 -238
  797. package/src/notifications/unsubscribe-panel.tsx +0 -200
  798. package/src/notifications/use-push-device.ts +0 -448
  799. package/src/offline/local-first.ts +0 -100
  800. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  801. package/src/offline/pwa-update-prompt.tsx +0 -78
  802. package/src/offline/query-persistence.ts +0 -391
  803. package/src/offline/service-worker-updates.ts +0 -81
  804. package/src/offline/shared-computer-labels.ts +0 -8
  805. package/src/offline/shared-computer.tsx +0 -87
  806. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  807. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  808. package/src/offline.ts +0 -37
  809. package/src/push-worker.ts +0 -25
  810. package/src/rhf/fields.tsx +0 -2187
  811. package/src/rhf/form-core.tsx +0 -324
  812. package/src/rhf/form.tsx +0 -12
  813. package/src/rhf/line-items.tsx +0 -135
  814. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  815. package/src/rhf.ts +0 -21
  816. package/src/search/command-palette-labels.ts +0 -16
  817. package/src/search/command-palette.tsx +0 -624
  818. package/src/search/global-search-labels.ts +0 -13
  819. package/src/search/global-search.tsx +0 -660
  820. package/src/search/search-index.ts +0 -283
  821. package/src/search/use-command-key.ts +0 -23
  822. package/src/search.ts +0 -14
  823. package/src/settings/device-reset-setting-labels.ts +0 -20
  824. package/src/settings/device-reset-setting.tsx +0 -116
  825. package/src/settings/settings-card-classes.ts +0 -11
  826. package/src/settings/settings-catalogue.ts +0 -150
  827. package/src/settings/settings-context.ts +0 -33
  828. package/src/settings/settings-heading.tsx +0 -67
  829. package/src/settings/settings-labels.ts +0 -87
  830. package/src/settings/settings-layout.tsx +0 -471
  831. package/src/settings/settings-section.tsx +0 -108
  832. package/src/settings/use-account-appearance.ts +0 -148
  833. package/src/settings/use-account-language.ts +0 -146
  834. package/src/settings/use-settings-focus.ts +0 -128
  835. package/src/settings/use-settings-route.ts +0 -258
  836. package/src/shell/account-menu-language.tsx +0 -130
  837. package/src/shell/account-menu-text-size.tsx +0 -80
  838. package/src/shell/admin-menu.tsx +0 -183
  839. package/src/shell/app-shell-labels.ts +0 -11
  840. package/src/shell/app-shell.tsx +0 -1560
  841. package/src/shell/auth-layout.tsx +0 -186
  842. package/src/shell/company-switcher-labels.ts +0 -13
  843. package/src/shell/company-switcher.tsx +0 -136
  844. package/src/shell/dev-menu.tsx +0 -257
  845. package/src/shell/fill-height.tsx +0 -45
  846. package/src/shell/language-code.ts +0 -5
  847. package/src/shell/option-switcher-menu.tsx +0 -84
  848. package/src/shell/sync-status-indicator.tsx +0 -303
  849. package/src/shell/top-bar-actions.tsx +0 -87
  850. package/src/shell/top-bar-brand.tsx +0 -78
  851. package/src/shell/top-bar-parts-labels.ts +0 -148
  852. package/src/shell/top-bar.tsx +0 -40
  853. package/src/shell/topbar-action-menu.tsx +0 -567
  854. package/src/shell/topbar-controls.tsx +0 -269
  855. package/src/shell/tour-menu.tsx +0 -187
  856. package/src/shell.ts +0 -108
  857. package/src/sync/db.ts +0 -129
  858. package/src/sync/engine.ts +0 -1011
  859. package/src/sync/hooks.ts +0 -98
  860. package/src/sync/testing.ts +0 -73
  861. package/src/sync/wire.ts +0 -61
  862. package/src/sync.ts +0 -37
  863. package/src/table-text.ts +0 -8
  864. package/src/testing/css-variables.generated.ts +0 -112
  865. package/src/testing/css-variables.ts +0 -189
  866. package/src/testing/index.ts +0 -29
  867. package/src/testing/react.ts +0 -58
  868. package/src/testing/tour.ts +0 -41
  869. package/src/testing/viewport.ts +0 -357
  870. package/src/theme/chart-palette.ts +0 -232
  871. package/src/theme/color.ts +0 -272
  872. package/src/theme/contrast-tokens.ts +0 -268
  873. package/src/theme/contrast.ts +0 -250
  874. package/src/theme/palette-derive.ts +0 -825
  875. package/src/theme/palette-presets.ts +0 -544
  876. package/src/theme/palette-store.ts +0 -108
  877. package/src/theme/text-size.ts +0 -289
  878. package/src/theme/theme-store.ts +0 -157
  879. package/src/tour/tour-catalogue-labels.ts +0 -75
  880. package/src/tour/tour-catalogue.tsx +0 -335
  881. package/src/tour/tour-dock.ts +0 -134
  882. package/src/tour/tour-engine.ts +0 -37
  883. package/src/tour/tour-gates.ts +0 -218
  884. package/src/tour/tour-guide-button.tsx +0 -64
  885. package/src/tour/tour-guide-labels.ts +0 -15
  886. package/src/tour/tour-info.ts +0 -144
  887. package/src/tour/tour-labels.ts +0 -23
  888. package/src/tour/tour-overlay.tsx +0 -1170
  889. package/src/tour/tour.tsx +0 -750
  890. package/src/tour/use-tour-progress.ts +0 -671
  891. package/src/tour/use-tour-url-sync.ts +0 -266
  892. package/src/tour.ts +0 -21
  893. package/src/vite-env.d.ts +0 -23
  894. package/src/wizard/stepper-nav.tsx +0 -360
  895. package/src/wizard/types.ts +0 -275
  896. package/src/wizard/use-wizard.ts +0 -560
  897. package/src/wizard/validation.ts +0 -39
  898. package/src/wizard/wizard-context.tsx +0 -74
  899. package/src/wizard/wizard-labels.ts +0 -29
  900. package/src/wizard/wizard-step.tsx +0 -76
  901. package/src/wizard/wizard-summary.tsx +0 -84
  902. package/src/wizard.ts +0 -15
@@ -1,1240 +0,0 @@
1
- import { useEffect, useId, useMemo, useRef, useState } from "react";
2
- import type { ReactNode } from "react";
3
- import { Check, ListChecks, PencilLine, RotateCcw } from "lucide-react";
4
-
5
- import { usePhoneLayout } from "../hooks/use-breakpoint";
6
- import { useKitLabels, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
7
- import { cn } from "../lib/cn";
8
- import {
9
- DEFAULT_TRANSLATION_REVIEW_FILTER,
10
- DEFAULT_TRANSLATION_REVIEW_SWIPE,
11
- REVIEW_STATUSES,
12
- filterTranslationRows,
13
- groupTranslationRows,
14
- keyInAreas,
15
- reviewUndo,
16
- reviewWrite,
17
- summariseRows,
18
- translationReviewSwipePlan,
19
- unreviewedRows,
20
- } from "../lib/translation-review";
21
- import type {
22
- ReviewStatus,
23
- TranslationReviewFilter,
24
- TranslationReviewKey,
25
- TranslationReviewSwipeAction,
26
- TranslationReviewSwipeBinding,
27
- TranslationReviewWrite,
28
- TranslationRow,
29
- TranslationRowGroup,
30
- } from "../lib/translation-review";
31
- import { AlertBanner } from "./alert-banner";
32
- import { BulkActionBar } from "./bulk-action-bar";
33
- import { Checkbox } from "./checkbox";
34
- import { Chip } from "./chip";
35
- import { DataTable } from "./data-table";
36
- import type { DataTableColumn, DataTableProps, MobileSwipeActions } from "./data-table";
37
- import { Disclosure } from "./disclosure";
38
- import { SearchField } from "./search-field";
39
- import { toast } from "./toast";
40
- import type { SwipeAction } from "./swipeable-row";
41
- import { ToggleGroup } from "./toggle-group";
42
- import { useSharedTableView } from "./use-table-state";
43
- import { TranslationReviewEditor } from "./translation-review-editor";
44
- import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translation-review-labels";
45
- import type { TranslationReviewLabels } from "./translation-review-labels";
46
- import { ReviewStatusChip, TranslationProgress } from "./translation-review-parts";
47
- import { Button } from "./button";
48
- import { IconButton } from "./icon-button";
49
- import { DEFAULT_ROW_ACTIONS_LABELS, rowActionsColumn, type RowAction } from "./row-actions";
50
- import { Select } from "./select";
51
- import { COMMIT_EXCEPT_BILLING, useWriteLock } from "./write-lock";
52
-
53
- export {
54
- REVIEW_STATUS_TONES,
55
- ReviewStatusChip,
56
- TranslationExportButton,
57
- TranslationLocaleTabs,
58
- TranslationProgress,
59
- } from "./translation-review-parts";
60
- export type {
61
- ReviewStatusChipProps,
62
- TranslationExportButtonProps,
63
- TranslationLocaleTab,
64
- TranslationLocaleTabsProps,
65
- TranslationProgressProps,
66
- } from "./translation-review-parts";
67
- export { TranslationReviewEditor } from "./translation-review-editor";
68
- export type { TranslationReviewEditorProps } from "./translation-review-editor";
69
- export { DEFAULT_TRANSLATION_REVIEW_LABELS } from "./translation-review-labels";
70
- export type { TranslationReviewLabels } from "./translation-review-labels";
71
-
72
- /**
73
- * An app's translation review page, as kit parts (H1, docs/i18n-harmonization.md):
74
- * keksdose's /translations, which kastlan was about to build a second time. The app keeps
75
- * what is its own — where the strings come from, which locale is read against which, who
76
- * may review what, every request — and hands the kit rows (`translationRows`) and two
77
- * callbacks. Everything a reviewer touches is here.
78
- *
79
- * Presentational — no request, no route — and one LOCALE at a time. Its table is the
80
- * kit's DataTable, so like every DataTable it renders under the app's Router. The page
81
- * around it puts
82
- * {@link TranslationLocaleTabs} above it and {@link TranslationExportButton} in its header,
83
- * because both span every locale while this panel is the one open.
84
- *
85
- * Callbacks may return promises, and the panel waits for them: an editor stays open with
86
- * its text and the error when a save is refused (a 403 for a key outside the reviewer's
87
- * areas, a network failure), a bulk action keeps its selection. Chunking a long bulk
88
- * write to the server's batch limit is the app's; the panel sends one call per action.
89
- */
90
-
91
- type MaybePromise = void | Promise<unknown>;
92
-
93
- /** What asked for a write — see {@link TranslationReviewSaveInfo}. */
94
- export type TranslationReviewOrigin = "editor" | "row" | "swipe" | "group" | "selection" | "undo";
95
-
96
- /**
97
- * The second argument of `onSave` and `onClear` (0.25): where the write came from, and
98
- * whether the panel answers it with a toast of its own.
99
- *
100
- * WHY THE APP IS TOLD. keksdose and kastlan toast "Saved" from their mutation on every
101
- * save. With {@link TranslationReviewPanelProps.undo} on, an approval is answered by the
102
- * kit's Undo toast instead — and a reviewer swiping through a list then read two toasts
103
- * per card. The app skips its own where `toasted` is true:
104
- *
105
- * onSave={(writes, { toasted }) => save.mutateAsync({ items: writes.map(toApiWrite), quiet: toasted })}
106
- *
107
- * An app that ignores the argument keeps working; it only says "Saved" twice.
108
- */
109
- export interface TranslationReviewSaveInfo {
110
- /** The open editor, a row's approve button, a swipe on a phone card, a group's
111
- * "Approve unreviewed", the selection's bulk bar, or an Undo pressed on the toast. */
112
- origin: TranslationReviewOrigin;
113
- /** The panel shows its own toast for this write (an approval's Undo toast, or a
114
- * swiped `clear`'s). */
115
- toasted: boolean;
116
- }
117
-
118
- /** How {@link TranslationReviewPanelProps.groupBy} names a row's group. */
119
- export type TranslationReviewGroupBy = "namespace" | "source" | ((row: TranslationRow) => string);
120
-
121
- export interface TranslationReviewPanelProps {
122
- /** One locale's rows, from `translationRows`. */
123
- rows: readonly TranslationRow[];
124
- /** The reviewed language's own name — the text column's header ("Français"). */
125
- localeLabel: string;
126
- /** The reference language's own name — the reference column's header. */
127
- referenceLabel: string;
128
- /** Store verdicts: one for an editor or a row's approve button, many for a bulk
129
- * approve. Left out (or `readOnly`): nothing can be changed. The second argument
130
- * says where the write came from ({@link TranslationReviewSaveInfo}). */
131
- onSave?: (writes: TranslationReviewWrite[], info: TranslationReviewSaveInfo) => MaybePromise;
132
- /** Clear verdicts — back to unreviewed. Left out: no reset, and an approval of a row
133
- * that had no verdict cannot be undone. */
134
- onClear?: (keys: TranslationReviewKey[], info: TranslationReviewSaveInfo) => MaybePromise;
135
- /** A locale the viewer may read but not review: the rows and their verdicts, no
136
- * actions, and a line saying so. */
137
- readOnly?: boolean;
138
- /**
139
- * The areas the reviewer is limited to (keksdose's lawyer: `["legal"]`), as the server
140
- * answers them; `null` is every area. Rows outside are not shown or counted — build the
141
- * rows with the same `areas` and they never exist — and a notice names the areas.
142
- */
143
- areas?: readonly string[] | null;
144
- /** An area's name for the notice ("the legal pages (Imprint, …)"). Default: the key. */
145
- areaLabels?: Readonly<Record<string, string>>;
146
- /** Default `keyInArea`: the key is the area or sits under it (`legal.…`, `legal:…`,
147
- * and the kit's `kit.legal.…` since 0.28). */
148
- inArea?: (key: string, area: string) => boolean;
149
- /** A source's name for the source filter, which appears once the rows come from more
150
- * than one (kastlan: `{ screen: "Screen", documents: "PDFs and emails" }`). Default:
151
- * the source itself. */
152
- sourceLabels?: Readonly<Record<string, string>>;
153
- /**
154
- * Controlled filters — for an app that keeps them in the URL (keksdose's `?status=`,
155
- * `?ns=`, `?q=`). Whatever is left out is the panel's own. Pair with `onFilterChange`.
156
- *
157
- * A field passed here is the app's, and the control shows what the app says: one the app
158
- * passes but does not write back from `onFilterChange` is a control that does nothing.
159
- * A development build warns about that once per field (0.25.1, Kurvenschmiede); a field
160
- * left out entirely is fine — the panel keeps it.
161
- */
162
- filter?: Partial<TranslationReviewFilter>;
163
- /** Every filter, on every change — write them in ONE update: `useSearchParamsState`'s
164
- * setter takes this object as it is (`filter={filter} onFilterChange={setFilter}`).
165
- * Single `useSearchParamState` setters compose since 0.25; before, four in a row
166
- * clobbered each other (keksdose live #378). */
167
- onFilterChange?: (filter: TranslationReviewFilter) => void;
168
- /** The progress bar over the filters. Default true. */
169
- progress?: boolean;
170
- /** Rows per page. Default 50. */
171
- pageSize?: number;
172
- /** Persists the table's sort and page size (DataTable's `storageKey`), under the
173
- * prefix below — under {@link groupBy}, the one sort and page size every group
174
- * shares, in the same place (0.35). */
175
- storageKey?: string;
176
- /**
177
- * The `localStorage` prefix in front of `storageKey` (DataTable's `storageKeyPrefix`).
178
- * Left out, it is the provider's `tableStorageKeyPrefix`, then the kit's
179
- * `"hbui-table:"` — so an app that sets its prefix on `UiKitProvider` needs nothing
180
- * here. Before 0.34 the panel took no prefix and its view always landed under the
181
- * kit's, which an app listing what it stores had to list as its own (KS U4).
182
- */
183
- storageKeyPrefix?: string;
184
- /** How a verdict's date reads in the editor. Default: the editor's — the
185
- * `<UiKitProvider formatDate>`, else the kit's medium date (0.52). */
186
- formatDate?: (iso: string) => string;
187
- /** Turns a callback's rejection into words. Default {@link TranslationReviewLabels.failed}. */
188
- formatError?: (error: unknown) => ReactNode;
189
- /**
190
- * Swipe the phone cards (0.25, keksdose live #377 — Marcel reviewing on a phone): toward
191
- * the reading END to approve, toward the START for "Needs a change", which OPENS the
192
- * editor with the cursor in the wording rather than sending the string back blind — a
193
- * verdict without a note or a better wording tells whoever fixes it nothing. In a
194
- * right-to-left page the two sides mirror with the reading direction.
195
- *
196
- * DataTable's swipe sides (`mobileSwipeActions`) on {@link SwipeableRow}: the same
197
- * gesture, the same feedback, and the same keyboard path — every swipe is also a real
198
- * button, read out and reachable by Tab, so the gesture is a shortcut and never the only
199
- * way. A row has no swipe while its own write is out (a swipe's, a button's, or the open
200
- * editor's own), under a {@link WriteLockProvider} lock, in a `readOnly` locale, and
201
- * toward "Approve" where there is nothing to approve (a missing string, an approved one).
202
- * A reviewer limited to `areas` never sees a row outside them, so never swipes one.
203
- *
204
- * An OPEN row swipes too, on its top line (keksdose live #416, Marcel: *"The swipe shall
205
- * still be possible since it is faster than scrolling down to the buttons"*), as
206
- * DataTable's open card does since 0.40: the editor below is outside the gesture, and a
207
- * swipe neither toggles it shut nor resets it. A swipe acts on the STORED string, as the
208
- * row's buttons do — Approve approves what the locale says, not the editor's draft — and
209
- * the editor stays open with whatever was typed in it; "Needs a change" puts the cursor
210
- * back at the end of that draft.
211
- *
212
- * Off by default, so no existing page gains a gesture on the bump. The gesture is the
213
- * phone's; on a wide screen the binding is the row's actions column instead (0.46,
214
- * keksdose dev#598): Approve where the row can take it, bound or not, then the bound
215
- * `edit` and `clear`, each once — under a write lock they stay and say why.
216
- *
217
- * A BINDING instead of `true` (keksdose — Marcel's live #377 rework, 2026-10-03: *"Add
218
- * the swipe options to be shown also to the settings /settings#interaction area where
219
- * the other swipe options are defined"*): keksdose lets the user bind every list's
220
- * swipes in Settings → Interaction, per side a primary action at the first threshold and
221
- * a secondary one at a longer drag, and a fixed mapping is one its settings cannot
222
- * reach. So each logical side takes an ordered ladder of
223
- * {@link TranslationReviewSwipeAction}s — index 0 at the first threshold, index 1 at the
224
- * longer drag, as {@link SwipeableRow} stages them:
225
- *
226
- * swipe={{ end: ["approve", "clear"], start: ["edit"] }}
227
- *
228
- * `approve` (with Undo), `edit` (the editor, cursor in the wording — "Needs a change"),
229
- * `clear` (the verdict taken back, with Undo, which stores it again; only with
230
- * `onClear`). An action a row cannot offer drops out and the ladder closes up behind it
231
- * ({@link translationReviewSwipePlan}): `approve` on an approved or missing string,
232
- * `clear` on a row with no verdict. Every rule above still holds for every binding —
233
- * no side swipes in a `readOnly` locale, under a write lock, or while the row's write
234
- * (or its open editor's) is out.
235
- *
236
- * `true` is {@link DEFAULT_TRANSLATION_REVIEW_SWIPE} — `{ end: ["approve"], start:
237
- * ["edit"] }`, 0.25's mapping exactly — and `false` or nothing is no swipe. A binding
238
- * turns the Undo toast on by default, as `true` does.
239
- */
240
- swipe?: boolean | TranslationReviewSwipeBinding;
241
- /**
242
- * Answer every approval with the kit's Undo toast (`toast.undo`): a row's button, a
243
- * swipe, the editor's Approve, a group's and the selection's bulk approve. Undo stores
244
- * again what the rows said before ({@link reviewUndo}) — or clears them, for rows that
245
- * had no verdict, which needs `onClear` (without it the toast reports and offers
246
- * nothing). A batch has ONE Undo.
247
- *
248
- * Default: on with `swipe` or `groupBy` — the two ways to approve in one movement or many
249
- * rows at once bring their way back — and off otherwise, so an existing page does not
250
- * gain a toast on the bump. Needs the app's `<Toaster>`. Tell the app's own "Saved"
251
- * toast apart with the `toasted` flag ({@link TranslationReviewSaveInfo}).
252
- */
253
- undo?: boolean;
254
- /**
255
- * Group the strings under a header per area (0.25, keksdose live #377: *"grouping to the
256
- * translation review entries for faster reviewing"*): `"namespace"` — the row's
257
- * `namespace`, which `translationRows` derives from the key (`budget`, `kit.dataTable`
258
- * with keksdose's `namespaceOf`) — `"source"`, or a function of the row.
259
- *
260
- * Each group is a section with a heading ({@link groupHeadingAs}): its name, "n
261
- * unreviewed / total", and "Approve unreviewed (n)", which approves the group's rows
262
- * nobody has given a verdict ({@link unreviewedRows}) under the same rules as one
263
- * approval — write lock, `readOnly`, one write at a time — with one Undo for the batch.
264
- * A batch larger than `pageSize` asks first, inside the opened group: more than a page
265
- * is more than the reviewer can have had on screen.
266
- *
267
- * WHAT IT MEANS FOR THE FILTERS, THE SORT AND THE PAGES. The filters narrow the rows
268
- * first, then the rows are grouped: a header counts what the filters show in it (so
269
- * "Approve unreviewed" never takes a row the reviewer filtered away), and an area with
270
- * nothing left is not shown. Groups stand in the order their first string has in the
271
- * reference; a column sort orders the rows INSIDE every group alike. Each group pages
272
- * on its own, `pageSize` rows at a time — a header then always stands over its own
273
- * rows, and paging one area does not move another — rather than one pager cutting
274
- * through the headers. The page SIZE is one for all of them, as the sort is: a
275
- * group's pager changes it everywhere.
276
- *
277
- * `storageKey` persists that one view — the sort and the page size — in the place the
278
- * ungrouped table keeps its own (same key, same prefix, same blob), read on mount and
279
- * written on a change, so a reviewer switching the grouping on or off keeps both
280
- * (0.35, Kurvenschmiede; before, it persisted the ungrouped table only). What only
281
- * the ungrouped table has — column widths, hidden columns — stays in the blob for it.
282
- *
283
- * Groups fold (each on its own, remembered while the panel lives), and a folded group's
284
- * rows are not rendered at all — only its header. Which groups start open is
285
- * {@link defaultGroupsOpen}: by default, on a phone they start folded when there are
286
- * several — the headers are then the area list, and the reviewer opens the one to work
287
- * through — and on a wide screen they start open while that is cheap (see there).
288
- */
289
- groupBy?: TranslationReviewGroupBy;
290
- /**
291
- * Which groups start open under {@link groupBy} (0.25.1). A group the reviewer opens or
292
- * folds keeps that; this is what every other group follows.
293
- *
294
- * - `"auto"` (default): on a phone, open when there is one group and folded when there
295
- * are several, as in 0.25.0. On a wide screen, open while opening every group renders
296
- * at most 100 rows (each group shows up to `pageSize` of them; the budget is one page
297
- * where `pageSize` is larger) — and folded above that, the headers then being the area
298
- * list, as on a phone.
299
- * - `true`: every group starts open, on a phone too.
300
- * - `false`: every group starts folded, on a wide screen too.
301
- *
302
- * WHY A BUDGET OF ROWS (keksdose, measured on 0.25.0): a wide screen opened EVERY group.
303
- * keksdose's /translations has 122 areas and ~4700 strings — 122 tables and 2053 rows at
304
- * once, ~46 000 elements, 5.2 s before the panel appeared and 2.5 s for a filter click
305
- * to paint, against 50 rows and 0.34 s ungrouped; its page test went from 4 s to 146 s.
306
- * kastlan (~31 areas, ~1300 rows open) and Kurvenschmiede (28, ~700) had the same page.
307
- * What costs is the rows on screen, so the rule counts them rather than the groups:
308
- * 100 rows is two of the panel's default pages — any one or two areas still open as
309
- * before, however long, and so do a dozen small ones, or a search whose hits fall in
310
- * many areas — while a whole catalogue starts as its list of areas: keksdose's shape
311
- * in jsdom, 122 headers and ~2100 elements instead of ~44 000 (the ungrouped panel has
312
- * ~1200). The rule follows the filters: narrow them and the groups open.
313
- *
314
- * A boolean rather than a controlled open state: the panel already keeps each group's
315
- * own state, and an app that only wants "all open" or "all folded" should not have to
316
- * keep a record of 122 flags to say it.
317
- */
318
- defaultGroupsOpen?: boolean | "auto";
319
- /** A group's name. Default: `sourceLabels` for `groupBy="source"`, else the key. */
320
- groupLabel?: (group: string) => string;
321
- /** The level of the group headings. Default `h2`: the panel is a page's body, under
322
- * the page's `h1`. */
323
- groupHeadingAs?: "h2" | "h3" | "h4";
324
- className?: string;
325
- labels?: LabelOverride<TranslationReviewLabels>;
326
- }
327
-
328
- /**
329
- * How many rows the groups may render at once on a wide screen before they start folded
330
- * ({@link TranslationReviewPanelProps.defaultGroupsOpen}): two of the default 50-row pages.
331
- * A row costs about twenty elements in either layout, so this is the cost of two pages of
332
- * the ungrouped table — not of 2053 rows in 122 tables.
333
- */
334
- const GROUPS_OPEN_ROW_BUDGET = 100;
335
-
336
- /**
337
- * Would opening every group stay within the budget? Each group renders up to `pageSize`
338
- * rows (it pages on its own); the budget is never less than one page, so a single group —
339
- * which renders at most one — always starts open, as on a phone.
340
- */
341
- function groupsFitOpen(groups: readonly TranslationRowGroup[], pageSize: number): boolean {
342
- const budget = Math.max(GROUPS_OPEN_ROW_BUDGET, pageSize);
343
- let rendered = 0;
344
- for (const group of groups) {
345
- rendered += Math.min(group.rows.length, pageSize);
346
- if (rendered > budget) return false;
347
- }
348
- return true;
349
- }
350
-
351
- /**
352
- * The kit's dev switch, as NumberInput's money guard and `lib/logger.ts` read it: a
353
- * development build — the consumer's bundler makes `import.meta.env.DEV` false in a
354
- * production one, and the branch is dead code there — and never under vitest, where every
355
- * consumer suite would print it; a test opts in with `vi.stubEnv("VITEST", "")`. Read per
356
- * call, and optional-chained: `import.meta.env` is a Vite injection, absent under Node.
357
- */
358
- function devWarnings(): boolean {
359
- return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
360
- }
361
-
362
- type FilterField = keyof TranslationReviewFilter;
363
-
364
- /** How long after the last filter change the app's `filter` is looked at again: well past
365
- * a router's transition, so a URL that is still being written is not mistaken for one
366
- * that never will be. */
367
- const FILTER_ECHO_MS = 1500;
368
-
369
- /**
370
- * The development warning for a controlled filter that does not follow (Kurvenschmiede,
371
- * 0.25.1). A field the app passes in `filter` is the app's: the panel shows the app's
372
- * value, whatever the reviewer picked. An app that passes a field but does not write the
373
- * reported value back — its `onFilterChange` hands on only some fields, `filter` is built
374
- * as `{ ...DEFAULT_TRANSLATION_REVIEW_FILTER, ...fromTheUrl }`, a URL parser rejects what
375
- * its serialiser wrote, or there is no `onFilterChange` at all — has a control that
376
- * silently does nothing: the box unticks itself, the select jumps back.
377
- *
378
- * So, after the panel reports a change of a field the app controls, it looks at the
379
- * app's `filter` again once the change has had time to land ({@link FILTER_ECHO_MS}, read
380
- * from the last COMMITTED props, so a router transition still rendering does not count),
381
- * and warns — once per field — when that field still holds the value it had before.
382
- *
383
- * Quiet by construction for an app that passes no `filter`, and for a field the app LEAVES
384
- * OUT: that field is the panel's own and works (keksdose keeps the placeholder switch so,
385
- * on purpose). Quiet too when the app moved the field somewhere else itself. Development
386
- * builds only (see {@link devWarnings}): in production and under vitest nothing is
387
- * recorded and no timer is set.
388
- */
389
- function useFilterEchoCheck(filterProp: Partial<TranslationReviewFilter> | undefined) {
390
- const committed = useRef(filterProp);
391
- useEffect(() => {
392
- committed.current = filterProp;
393
- });
394
- // Per field: what the app's filter said when the reviewer first changed it, and what
395
- // the panel reported last.
396
- const pending = useRef(new Map<FilterField, { from: unknown; to: unknown }>());
397
- const warned = useRef(new Set<FilterField>());
398
- const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
399
- useEffect(() => () => clearTimeout(timer.current), []);
400
-
401
- return (next: TranslationReviewFilter) => {
402
- if (filterProp === undefined || !devWarnings()) return;
403
- for (const field of Object.keys(next) as FilterField[]) {
404
- const from = filterProp[field];
405
- if (from === undefined || warned.current.has(field)) continue;
406
- const seen = pending.current.get(field);
407
- if (seen) seen.to = next[field];
408
- else if (next[field] !== from) pending.current.set(field, { from, to: next[field] });
409
- }
410
- if (pending.current.size === 0) return;
411
- clearTimeout(timer.current);
412
- timer.current = setTimeout(() => {
413
- const now = committed.current;
414
- for (const [field, { from, to }] of pending.current) {
415
- if (to === from || now?.[field] !== from || warned.current.has(field)) continue;
416
- warned.current.add(field);
417
- console.warn(
418
- `[ui-kit] TranslationReviewPanel: the "${field}" filter was changed to ${JSON.stringify(to)}, but the ` +
419
- `\`filter\` prop still says ${JSON.stringify(from)}, so the control does nothing. A field passed in ` +
420
- `\`filter\` is the app's: write back every field \`onFilterChange\` reports, in one update ` +
421
- `(useSearchParamsState's setter takes the object as it is), or leave the field out of \`filter\` and ` +
422
- `the panel keeps it. (Development builds only; once per field.)`,
423
- );
424
- }
425
- pending.current.clear();
426
- }, FILTER_ECHO_MS);
427
- };
428
- }
429
-
430
- /** The row's text — or, for a missing string, the line that says so. */
431
- function TextCell({ row, labels, clamp }: { row: TranslationRow; labels: TranslationReviewLabels; clamp?: boolean }) {
432
- return row.text === "" ? (
433
- <span className="italic text-muted">{labels.missingText}</span>
434
- ) : (
435
- <span
436
- lang={row.locale}
437
- className={cn("whitespace-pre-line break-words", clamp && "line-clamp-3 large:line-clamp-none")}
438
- >
439
- {row.text}
440
- </span>
441
- );
442
- }
443
-
444
- /**
445
- * The page body for one locale: progress, filters (status, source, namespace, search,
446
- * placeholder problems), the strings with an editor that unfolds under each, and the
447
- * bulk actions over a selection.
448
- *
449
- * The status filter is the work queue in order — what is not there, what nobody has
450
- * read, what changed after it was read, what was sent back, what is done — with each
451
- * count on it. "Missing" is offered only where something is.
452
- *
453
- * Bulk approve takes the selected rows that are not approved yet and have a text:
454
- * approving an absent string says nothing, and re-approving one says it again. On a
455
- * phone the table has no checkboxes, so "Select all shown" selects what the filters
456
- * show — filter to `Unreviewed` in one area, read, select, approve. With `groupBy`, a
457
- * group's own "Approve unreviewed" is the shorter way, and with `swipe` a card is approved
458
- * by a swipe.
459
- *
460
- * The panel's writes go out ONE AT A TIME, in the order they were asked for: two writes on
461
- * one locale's verdicts can answer out of order, and an Undo must not overtake the
462
- * approval it takes back. A swipe on the next card while one is out is queued rather than
463
- * refused — a reviewer swiping down a list is faster than the server.
464
- */
465
- export function TranslationReviewPanel({
466
- rows: rowsProp,
467
- localeLabel,
468
- referenceLabel,
469
- onSave,
470
- onClear,
471
- readOnly = false,
472
- areas,
473
- areaLabels,
474
- inArea,
475
- sourceLabels,
476
- filter: filterProp,
477
- onFilterChange,
478
- progress = true,
479
- pageSize = 50,
480
- storageKey,
481
- storageKeyPrefix,
482
- formatDate,
483
- formatError,
484
- swipe = false,
485
- undo: undoProp,
486
- groupBy,
487
- groupLabel,
488
- groupHeadingAs = "h2",
489
- defaultGroupsOpen = "auto",
490
- className,
491
- labels: labelsProp,
492
- }: TranslationReviewPanelProps) {
493
- const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
494
- const locale = useKitLocale();
495
- // A verdict is an admin route, which a lapsed plan never locks
496
- // (docs/billing-harmonization.md §12.13, §12.36): the buttons take
497
- // COMMIT_EXCEPT_BILLING, and the swipes read the lock as they see it.
498
- const lock = useWriteLock(COMMIT_EXCEPT_BILLING);
499
- // The desktop actions column's plain-text name (dev#598), the provider's "Actions".
500
- const rowActionLabels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS);
501
- // The phone layout DataTable switches its layout on, so "a phone" means the same here
502
- // as in the table, at every text size: `usePhoneLayout()` itself (§10.4; 0.32.1,
503
- // keksdose's 0.32 report — `!useBreakpoint("md")` could disagree with it at 767.5 px).
504
- const phone = usePhoneLayout();
505
- const baseId = useId();
506
- const rootRef = useRef<HTMLDivElement>(null);
507
- const editable = !readOnly && onSave !== undefined;
508
- const binding: TranslationReviewSwipeBinding | null =
509
- swipe === true ? DEFAULT_TRANSLATION_REVIEW_SWIPE : swipe || null;
510
- const undoOn = undoProp ?? (binding !== null || groupBy !== undefined);
511
-
512
- const [ownFilter, setOwnFilter] = useState<TranslationReviewFilter>(DEFAULT_TRANSLATION_REVIEW_FILTER);
513
- // The app's fields over the panel's own; a field the app leaves `undefined` is the
514
- // panel's, not a blank.
515
- const filter: TranslationReviewFilter = { ...ownFilter };
516
- for (const [name, value] of Object.entries(filterProp ?? {})) {
517
- if (value !== undefined) Object.assign(filter, { [name]: value });
518
- }
519
- const echoFilter = useFilterEchoCheck(filterProp);
520
- const [openId, setOpenId] = useState<string | null>(null);
521
- // The row whose editor a swipe opened, to be written in: its wording field takes focus.
522
- const [focusId, setFocusId] = useState<string | null>(null);
523
- const [selected, setSelected] = useState<ReadonlySet<string>>(() => new Set());
524
- // The writes asked for and not yet answered, in order: the first is the one out. The
525
- // editor keeps its own, for its own buttons.
526
- const [queued, setQueued] = useState<readonly string[]>([]);
527
- const claimed = useRef(new Set<string>());
528
- const tail = useRef<Promise<unknown>>(Promise.resolve());
529
- const undoCount = useRef(0);
530
- const [failure, setFailure] = useState<ReactNode>(null);
531
- const busy = queued.length > 0;
532
- const isQueued = (key: string) => queued.includes(key);
533
- // The rows whose open editor has a write of its own out: their swipes stand down until
534
- // it is answered, as for a write in the queue (0.46, keksdose live #416).
535
- const [editorWrites, setEditorWrites] = useState<ReadonlySet<string>>(() => new Set());
536
- const whileEditorWrites = async (rowId: string, write: () => MaybePromise) => {
537
- setEditorWrites((prev) => new Set(prev).add(rowId));
538
- try {
539
- await write();
540
- } finally {
541
- setEditorWrites((prev) => {
542
- const next = new Set(prev);
543
- next.delete(rowId);
544
- return next;
545
- });
546
- }
547
- };
548
-
549
- const rows = useMemo(
550
- () => (areas ? rowsProp.filter((row) => keyInAreas(row.key, areas, inArea)) : [...rowsProp]),
551
- [rowsProp, areas, inArea],
552
- );
553
- const summary = useMemo(() => summariseRows(rows), [rows]);
554
- const sources = useMemo(() => [...new Set(rows.map((r) => r.source))], [rows]);
555
- const source = sources.includes(filter.source) ? filter.source : "all";
556
- const namespaces = useMemo(
557
- () => [...new Set(rows.filter((r) => source === "all" || r.source === source).map((r) => r.namespace))],
558
- [rows, source],
559
- );
560
- const namespace = namespaces.includes(filter.namespace) ? filter.namespace : "all";
561
- const mismatches = useMemo(() => rows.filter((r) => r.placeholderMismatch).length, [rows]);
562
- // `filter` is rebuilt every render; its fields are what the list depends on.
563
- const { status, query, placeholdersOnly } = filter;
564
- const visible = useMemo(
565
- () => filterTranslationRows(rows, { status, source, namespace, query, placeholdersOnly }, locale),
566
- [rows, status, source, namespace, query, placeholdersOnly, locale],
567
- );
568
-
569
- // ── Groups ──
570
- const groups = useMemo(() => {
571
- if (groupBy === undefined) return null;
572
- const of =
573
- groupBy === "namespace"
574
- ? (r: TranslationRow) => r.namespace
575
- : groupBy === "source"
576
- ? (r: TranslationRow) => r.source
577
- : groupBy;
578
- return groupTranslationRows(visible, of);
579
- }, [visible, groupBy]);
580
- const groupName = (key: string) =>
581
- groupLabel?.(key) ?? (groupBy === "source" ? sourceLabels?.[key] : undefined) ?? (key || "—");
582
- // Only what the reviewer chose; a group nobody has touched follows the default, which
583
- // follows the filters (see `defaultGroupsOpen`).
584
- const [groupOpen, setGroupOpen] = useState<Readonly<Record<string, boolean>>>({});
585
- const groupOpenByDefault =
586
- defaultGroupsOpen !== "auto"
587
- ? defaultGroupsOpen
588
- : groups !== null && (phone ? groups.length <= 1 : groupsFitOpen(groups, pageSize));
589
- const isGroupOpen = (key: string) => groupOpen[key] ?? groupOpenByDefault;
590
- const setGroupOpenFor = (key: string, open: boolean) => setGroupOpen((prev) => ({ ...prev, [key]: open }));
591
- /** A group the reviewer works in — an editor opened in it — stays open when the default
592
- * turns (a search cleared, the rows grown past the budget): folding it would drop the
593
- * editor and whatever was typed in it. */
594
- const keepGroupOpen = (key: string | undefined) => {
595
- if (key !== undefined) setGroupOpen((prev) => (key in prev ? prev : { ...prev, [key]: true }));
596
- };
597
- // The group whose large batch is waiting for a yes — and, until its confirm button
598
- // has mounted and taken focus, the group that asked.
599
- const [confirming, setConfirming] = useState<string | null>(null);
600
- const confirmFocus = useRef<string | null>(null);
601
- // One view for every group's table — one sort, so a header click orders all of them
602
- // alike, and one page size — kept under `storageKey` in the single table's own blob, so
603
- // switching the grouping keeps both (0.35, Kurvenschmiede).
604
- const groupView = useSharedTableView({
605
- active: groupBy !== undefined,
606
- storageKey,
607
- storageKeyPrefix,
608
- defaultPageSize: pageSize,
609
- });
610
-
611
- const setFilter = (patch: Partial<TranslationReviewFilter>) => {
612
- const next = { ...filter, ...patch };
613
- setOwnFilter(next);
614
- onFilterChange?.(next);
615
- echoFilter(next);
616
- // A selection the new filter hides would be acted on unseen.
617
- setSelected(new Set());
618
- };
619
-
620
- /** Queue a write under `key` (one per key at a time); resolves whether it worked. */
621
- const run = (key: string, action: () => MaybePromise): Promise<boolean> => {
622
- if (claimed.current.has(key)) return Promise.resolve(false);
623
- claimed.current.add(key);
624
- setQueued((q) => [...q, key]);
625
- const step = tail.current.then(async () => {
626
- setFailure(null);
627
- try {
628
- await action();
629
- return true;
630
- } catch (caught) {
631
- setFailure(formatError ? formatError(caught) : labels.failed);
632
- return false;
633
- } finally {
634
- claimed.current.delete(key);
635
- setQueued((q) => q.filter((k) => k !== key));
636
- }
637
- });
638
- tail.current = step;
639
- return step;
640
- };
641
-
642
- /** "String approved — Undo", once `targets` (as they were BEFORE the write) are
643
- * approved — or, after a swiped `clear`, "String marked unreviewed — Undo". Either way
644
- * Undo puts back what the rows said before ({@link reviewUndo}), through the same queue
645
- * as every write. */
646
- const sayDone = (targets: readonly TranslationRow[], description?: ReactNode, cleared = false) => {
647
- const back = reviewUndo(targets);
648
- const message = (cleared ? labels.clearedToast : labels.approvedToast)(targets.length);
649
- const save = onSave;
650
- const clear = onClear;
651
- // Taking back the approval of a row that had no verdict is a clear: without
652
- // `onClear` there is no way to say it, so the toast reports and offers nothing.
653
- if (!save || (back.clears.length > 0 && !clear)) {
654
- toast.success(message, { description });
655
- return;
656
- }
657
- const key = `undo:${++undoCount.current}`;
658
- toast.undo(message, {
659
- description,
660
- onUndo: () =>
661
- void run(key, async () => {
662
- if (back.writes.length > 0) await save(back.writes, { origin: "undo", toasted: false });
663
- if (back.clears.length > 0) await clear?.(back.clears, { origin: "undo", toasted: false });
664
- }),
665
- });
666
- };
667
-
668
- const approve = async (
669
- key: string,
670
- targets: readonly TranslationRow[],
671
- origin: TranslationReviewOrigin,
672
- description?: ReactNode,
673
- ): Promise<boolean> => {
674
- if (!onSave || targets.length === 0) return false;
675
- const save = onSave;
676
- const ok = await run(key, () =>
677
- save(
678
- targets.map((r) => reviewWrite(r, "APPROVED")),
679
- { origin, toasted: undoOn },
680
- ),
681
- );
682
- if (ok && undoOn) sayDone(targets, description);
683
- return ok;
684
- };
685
-
686
- /** A swipe bound to `clear`: the row's verdict taken back, answered like an approval —
687
- * the Undo toast stores the verdict again. Under the row's own key, so it queues behind
688
- * (and never overlaps) an approval of the same row. */
689
- const clearRow = async (row: TranslationRow, origin: TranslationReviewOrigin = "swipe") => {
690
- if (!onClear || row.review === null) return;
691
- const clear = onClear;
692
- const keys = [{ locale: row.locale, key: row.key }];
693
- const ok = await run(`row:${row.id}`, () => clear(keys, { origin, toasted: undoOn }));
694
- if (ok && undoOn) sayDone([row], row.key, true);
695
- };
696
-
697
- /** The editor's own save: it waits, closes and shows its errors itself, so it does
698
- * not go through the queue — but an approval there is answered like any other. */
699
- const saveFromEditor = async (row: TranslationRow, write: TranslationReviewWrite) => {
700
- const toasted = undoOn && write.verdict === "APPROVED";
701
- await whileEditorWrites(row.id, () => onSave?.([write], { origin: "editor", toasted }));
702
- if (toasted) sayDone([row], row.key);
703
- };
704
-
705
- /**
706
- * Focus back into the panel once a group's batch has settled, if it fell out: the
707
- * button that was pressed goes with the rows it approved (and under the `Unreviewed`
708
- * filter the whole group can go). Two frames, so the app's new rows have been drawn.
709
- */
710
- const keepFocusAt = (groupKey: string) => {
711
- const check = () => {
712
- const root = rootRef.current;
713
- const active = document.activeElement;
714
- if (!root || (active && active !== document.body && active.isConnected)) return;
715
- const sections = [...root.querySelectorAll<HTMLElement>("[data-review-group]")];
716
- const section = sections.find((el) => el.dataset.reviewGroup === groupKey) ?? sections[0];
717
- section?.querySelector<HTMLElement>("[data-disclosure-trigger]")?.focus();
718
- };
719
- requestAnimationFrame(() => requestAnimationFrame(check));
720
- };
721
-
722
- const approveGroup = async (group: TranslationRowGroup, confirmed: boolean) => {
723
- const targets = unreviewedRows(group.rows);
724
- if (targets.length === 0) return;
725
- if (!confirmed && targets.length > pageSize) {
726
- // Asked inside the opened group, over the rows it is about.
727
- setGroupOpenFor(group.key, true);
728
- setConfirming(group.key);
729
- confirmFocus.current = group.key;
730
- return;
731
- }
732
- const ok = await approve(`group:${group.key}`, targets, "group", groupName(group.key));
733
- if (ok) setConfirming((key) => (key === group.key ? null : key));
734
- keepFocusAt(group.key);
735
- };
736
-
737
- const visibleIds = new Set(visible.map((r) => r.id));
738
- const selectedRows = visible.filter((r) => selected.has(r.id));
739
- // What "Select all shown" takes: grouped, the rows of the open groups — a folded
740
- // group's rows are not shown.
741
- const shownRows = groups ? groups.filter((g) => isGroupOpen(g.key)).flatMap((g) => g.rows) : visible;
742
- const allShownSelected = shownRows.length > 0 && shownRows.every((r) => selected.has(r.id));
743
- const toApprove = selectedRows.filter((r) => r.text !== "" && r.status !== "approved");
744
- const toReset = selectedRows.filter((r) => r.review !== null);
745
-
746
- const approveSelected = async () => {
747
- if (await approve("bulk-approve", toApprove, "selection")) setSelected(new Set());
748
- };
749
- const resetSelected = async () => {
750
- if (!onClear || toReset.length === 0) return;
751
- const clear = onClear;
752
- const keys = toReset.map((r) => ({ locale: r.locale, key: r.key }));
753
- if (await run("bulk-reset", () => clear(keys, { origin: "selection", toasted: false }))) {
754
- setSelected(new Set());
755
- }
756
- };
757
-
758
- // The status options: "missing" only where something is (or it is the filter).
759
- const statuses = REVIEW_STATUSES.filter((s) => s !== "missing" || summary.missing > 0 || filter.status === s);
760
- const scopeNames = (areas ?? []).map((area) => areaLabels?.[area] ?? area);
761
- const scopeText =
762
- scopeNames.length > 0 ? new Intl.ListFormat(locale, { type: "conjunction", style: "long" }).format(scopeNames) : "";
763
-
764
- const columns: DataTableColumn<TranslationRow>[] = [
765
- {
766
- key: "key",
767
- header: labels.key,
768
- cell: (r) => <code className="break-all text-xs text-secondary">{r.key}</code>,
769
- sortBy: (r) => r.key,
770
- mobileHidden: true,
771
- },
772
- {
773
- key: "reference",
774
- header: referenceLabel,
775
- cell: (r) => <span className="line-clamp-3 whitespace-pre-line break-words text-secondary">{r.reference}</span>,
776
- mobileHidden: true,
777
- },
778
- {
779
- key: "text",
780
- header: localeLabel,
781
- cell: (r) => <TextCell row={r} labels={labels} clamp />,
782
- mobilePrimary: true,
783
- },
784
- {
785
- key: "status",
786
- header: labels.statusColumn,
787
- cell: (r) => (
788
- <span className="inline-flex flex-wrap gap-1">
789
- <ReviewStatusChip status={r.status} labels={labelsProp} />
790
- {r.placeholderMismatch && (
791
- <Chip size="sm" tone="danger" variant="outline">
792
- {labels.placeholderChip}
793
- </Chip>
794
- )}
795
- </span>
796
- ),
797
- sortBy: (r) => REVIEW_STATUSES.indexOf(r.status),
798
- },
799
- ];
800
- if (editable && binding) {
801
- // keksdose dev#598 item 2: with a binding, the desktop row offers what the card's
802
- // swipes offer — `clear` was reachable only by a swipe. Approve first on every row
803
- // that can take it, bound or not (the desktop always had it), then the binding's
804
- // actions, end side first, each once. The swipe's words, glyphs and handlers; unlike
805
- // the swipe, a locked row keeps its buttons and says why (`commit`), and a row whose
806
- // write is out shows it in flight. Hidden on the phone, where the swipes are.
807
- columns.push(
808
- rowActionsColumn<TranslationRow>({
809
- key: "actions",
810
- headerText: rowActionLabels.actions,
811
- name: (r) => r.key,
812
- tooltipSide: "start",
813
- actions: (r) =>
814
- translationReviewSwipePlan({ end: ["approve", ...(binding.end ?? []), ...(binding.start ?? [])] }, r, {
815
- canClear: onClear !== undefined,
816
- }).end.map((id): RowAction => {
817
- const swipe = swipeAction(r, id);
818
- // `edit` only opens the editor, whose own Save is the commit.
819
- const writes = id !== "edit";
820
- return {
821
- key: id,
822
- // `edit` opens the editor: its own name, apart from the editor's submit
823
- // ("Needs a change"), which saves (keksdose B2/S3).
824
- label: id === "edit" ? (r.text === "" ? labels.suggestRow : labels.flagRow) : swipe.label,
825
- icon: <>{swipe.icon}</>,
826
- tone: "muted",
827
- commit: writes ? COMMIT_EXCEPT_BILLING : undefined,
828
- pending: writes && isQueued(`row:${r.id}`),
829
- disabled: busy,
830
- onSelect:
831
- id === "approve"
832
- ? () => void approve(`row:${r.id}`, [r], "row", r.key)
833
- : id === "clear"
834
- ? () => void clearRow(r, "row")
835
- : // The row's group stays open while its editor is (`keepGroupOpen`).
836
- () => swipeAction(r, id, groups?.find((g) => g.rows.includes(r))?.key).onCommit(),
837
- };
838
- }),
839
- column: { mobileHidden: true },
840
- }),
841
- );
842
- } else if (editable) {
843
- columns.push({
844
- key: "approve",
845
- // On screen, as every actions column's heading is (0.46.1, keksdose dev#598 rework).
846
- header: rowActionLabels.actions,
847
- headerText: rowActionLabels.actions,
848
- cell: (r) =>
849
- r.status === "approved" || r.text === "" ? null : (
850
- <IconButton
851
- type="button"
852
- size="xs"
853
- tone="muted"
854
- commit={COMMIT_EXCEPT_BILLING}
855
- label={labels.approve}
856
- // The icon alone at every text size (docs/text-size-harmonization.md §10.8):
857
- // the row's one action, in a hugging column of a dense table beside the
858
- // string under review, which "Approve" as text at Large would squeeze. A
859
- // single action stays inline in `RowActions` too, so the menu would not help;
860
- // the swipe and the editor say it in words.
861
- labelVisible={false}
862
- pending={isQueued(`row:${r.id}`)}
863
- disabled={busy}
864
- onClick={(event) => {
865
- // The row's own click opens the editor; this one only approves.
866
- event.stopPropagation();
867
- void approve(`row:${r.id}`, [r], "row", r.key);
868
- }}
869
- >
870
- <Check />
871
- </IconButton>
872
- ),
873
- });
874
- }
875
-
876
- /** One bound swipe action on one row, as {@link SwipeableRow} draws and commits it. */
877
- const swipeAction = (r: TranslationRow, id: TranslationReviewSwipeAction, groupKey?: string): SwipeAction => {
878
- const missing = r.text === "";
879
- switch (id) {
880
- case "approve":
881
- return {
882
- id,
883
- label: labels.approve,
884
- icon: <Check className="size-4" aria-hidden />,
885
- onCommit: () => void approve(`row:${r.id}`, [r], "swipe", r.key),
886
- // SwipeableRow paints the tone: its soft wash idle, its fill and that fill's
887
- // foreground armed (0.33).
888
- tone: "success",
889
- };
890
- case "edit":
891
- return {
892
- id,
893
- label: missing ? labels.suggest : labels.flag,
894
- icon: <PencilLine className="size-4" aria-hidden />,
895
- // Opens the editor rather than sending the string back: "needs a change"
896
- // with neither a note nor a better wording tells nobody anything.
897
- onCommit: () => {
898
- keepGroupOpen(groupKey);
899
- setOpenId(r.id);
900
- setFocusId(r.id);
901
- // On a row already open (#416) the editor is mounted and keeps its draft:
902
- // the cursor goes to the end of its wording now.
903
- if (openId === r.id) {
904
- const field = rootRef.current?.querySelector<HTMLTextAreaElement>("[data-review-wording]");
905
- field?.focus();
906
- field?.setSelectionRange(field.value.length, field.value.length);
907
- }
908
- },
909
- tone: missing ? "brand" : "danger",
910
- };
911
- case "clear":
912
- return {
913
- id,
914
- label: labels.reset,
915
- icon: <RotateCcw className="size-4" aria-hidden />,
916
- onCommit: () => void clearRow(r),
917
- // A step back, not a verdict: neither the approval's green nor the send-back's
918
- // red. `neutral` is the pair this swipe improvised and 0.33 made a token: the
919
- // muted ink as a fill under the surface colour.
920
- tone: "neutral",
921
- };
922
- }
923
- };
924
-
925
- /**
926
- * A phone card's swipes: the binding ({@link TranslationReviewPanelProps.swipe}) resolved
927
- * against the row, each side's ladder closed up over what the row cannot offer. None
928
- * where the row must not be acted on — the same conditions under which its buttons are
929
- * locked or absent.
930
- */
931
- const swipeActions = (r: TranslationRow, groupKey?: string): MobileSwipeActions | null => {
932
- if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`) || editorWrites.has(r.id)) return null;
933
- const plan = translationReviewSwipePlan(binding, r, { canClear: onClear !== undefined });
934
- if (plan.start.length === 0 && plan.end.length === 0) return null;
935
- return {
936
- start: plan.start.map((id) => swipeAction(r, id, groupKey)),
937
- end: plan.end.map((id) => swipeAction(r, id, groupKey)),
938
- };
939
- };
940
-
941
- const toggleIn = (prev: ReadonlySet<string>, many: readonly TranslationRow[], checked: boolean) => {
942
- const next = new Set([...prev].filter((id) => visibleIds.has(id)));
943
- for (const r of many) {
944
- if (checked) next.add(r.id);
945
- else next.delete(r.id);
946
- }
947
- return next;
948
- };
949
- /** The checkbox column of a table over `list` — the whole list, or one group. */
950
- const selectionFor = (list: readonly TranslationRow[]): DataTableProps<TranslationRow>["selection"] => {
951
- if (!editable) return undefined;
952
- const picked = list.filter((r) => selected.has(r.id)).length;
953
- return {
954
- isSelected: (r) => selected.has(r.id),
955
- onToggle: (r, checked) => setSelected((prev) => toggleIn(prev, [r], checked)),
956
- onToggleMany: (many, checked) => setSelected((prev) => toggleIn(prev, many, checked)),
957
- allSelected: list.length > 0 && picked === list.length,
958
- someSelected: picked > 0 && picked < list.length,
959
- onToggleAll: (checked) => setSelected((prev) => toggleIn(prev, list, checked)),
960
- };
961
- };
962
-
963
- const table = (list: TranslationRow[], group?: { key: string; name: string }) => (
964
- <DataTable<TranslationRow>
965
- rows={list}
966
- rowKey={(r) => r.id}
967
- columns={columns}
968
- empty={labels.empty}
969
- defaultPageSize={pageSize}
970
- {...(group
971
- ? {
972
- // Inside the group's own card: no frame in a frame, no column rail per group,
973
- // no scroller per group, and the group's name as the table's.
974
- frame: false,
975
- chrome: "minimal" as const,
976
- maxBodyHeight: "none",
977
- sorts: groupView.sorts,
978
- onSortsChange: groupView.setSorts,
979
- pageSize: groupView.pageSize,
980
- onPageSizeChange: groupView.setPageSize,
981
- labels: { table: group.name },
982
- }
983
- : { storageKey, storageKeyPrefix })}
984
- isExpanded={(r) => r.id === openId}
985
- onRowClick={(r) => {
986
- if (openId !== r.id) keepGroupOpen(group?.key);
987
- setFocusId(null);
988
- setOpenId((open) => (open === r.id ? null : r.id));
989
- }}
990
- expandedRow={(r) => (
991
- <TranslationReviewEditor
992
- key={r.id}
993
- row={r}
994
- referenceLabel={referenceLabel}
995
- localeLabel={localeLabel}
996
- readOnly={!editable}
997
- focusWording={focusId === r.id}
998
- onSave={onSave ? (write) => saveFromEditor(r, write) : undefined}
999
- onClear={
1000
- onClear
1001
- ? (key) => whileEditorWrites(r.id, () => onClear([key], { origin: "editor", toasted: false }))
1002
- : undefined
1003
- }
1004
- onClose={() => {
1005
- setOpenId(null);
1006
- setFocusId(null);
1007
- }}
1008
- formatDate={formatDate}
1009
- formatError={formatError}
1010
- labels={labelsProp}
1011
- />
1012
- )}
1013
- mobileCard={(r) => (
1014
- <div className="min-w-0 space-y-1">
1015
- <code className="block break-all text-xs text-muted">{r.key}</code>
1016
- <div className="text-sm text-primary">
1017
- <TextCell row={r} labels={labels} clamp />
1018
- </div>
1019
- <div className="flex flex-wrap gap-1">
1020
- <ReviewStatusChip status={r.status} labels={labelsProp} />
1021
- {r.placeholderMismatch && (
1022
- <Chip size="sm" tone="danger" variant="outline">
1023
- {labels.placeholderChip}
1024
- </Chip>
1025
- )}
1026
- </div>
1027
- </div>
1028
- )}
1029
- mobileSwipeActions={binding ? (r) => swipeActions(r, group?.key) : undefined}
1030
- // The desktop's buttons for the binding are the panel's own "actions" column.
1031
- desktopSwipeActions={false}
1032
- selection={selectionFor(list)}
1033
- />
1034
- );
1035
-
1036
- const groupSection = (g: TranslationRowGroup, index: number) => {
1037
- const name = groupName(g.key);
1038
- const titleId = `${baseId}-group-${index}`;
1039
- const questionId = `${baseId}-confirm-${index}`;
1040
- const targets = editable ? unreviewedRows(g.rows) : [];
1041
- const key = `group:${g.key}`;
1042
- const open = isGroupOpen(g.key);
1043
- // A folded group renders its header and nothing else. Its body is handed over only
1044
- // while it is open, or when the REVIEWER folded it — so the fold animates over the
1045
- // rows it hides. A group folded by the default gets none: when the default turns (a
1046
- // search cleared over 122 areas), the groups that close would otherwise render their
1047
- // grown tables for the length of the fold.
1048
- const body = open || groupOpen[g.key] === false;
1049
- return (
1050
- <Disclosure
1051
- key={g.key}
1052
- data-review-group={g.key}
1053
- headingAs={groupHeadingAs}
1054
- title={<span id={titleId}>{name}</span>}
1055
- hint={labels.groupCount(g.summary.unreviewed, g.summary.total)}
1056
- open={open}
1057
- onOpenChange={(next) => {
1058
- setGroupOpenFor(g.key, next);
1059
- if (!next) setConfirming((c) => (c === g.key ? null : c));
1060
- }}
1061
- trailing={
1062
- targets.length > 0 ? (
1063
- <Button
1064
- variant="secondary"
1065
- size="sm"
1066
- commit={COMMIT_EXCEPT_BILLING}
1067
- // The visible words say what; the group's name, read after them, says where.
1068
- aria-describedby={titleId}
1069
- pending={isQueued(key) && confirming !== g.key}
1070
- disabled={busy}
1071
- onClick={() => void approveGroup(g, false)}
1072
- >
1073
- <Check className="size-4" aria-hidden />
1074
- {labels.approveGroup(targets.length)}
1075
- </Button>
1076
- ) : undefined
1077
- }
1078
- bodyClassName="space-y-0 px-0 pb-0 border-t border-subtle"
1079
- >
1080
- {body && confirming === g.key && targets.length > 0 && (
1081
- <div className="border-b border-subtle p-3">
1082
- <AlertBanner tone="warning" size="sm">
1083
- <div className="min-w-0 space-y-2">
1084
- <p id={questionId}>{labels.confirmGroup(targets.length, name)}</p>
1085
- <div className="flex flex-wrap gap-2">
1086
- <Button
1087
- // Focus lands here when the question appears: the expected answer to a
1088
- // question the reviewer just asked for, and Undo stays behind it.
1089
- ref={(el) => {
1090
- if (el && confirmFocus.current === g.key) {
1091
- confirmFocus.current = null;
1092
- el.focus();
1093
- }
1094
- }}
1095
- variant="default"
1096
- size="sm"
1097
- commit={COMMIT_EXCEPT_BILLING}
1098
- aria-describedby={questionId}
1099
- pending={isQueued(key)}
1100
- disabled={busy}
1101
- onClick={() => void approveGroup(g, true)}
1102
- >
1103
- {labels.approveGroup(targets.length)}
1104
- </Button>
1105
- <Button
1106
- variant="ghost"
1107
- size="sm"
1108
- disabled={isQueued(key)}
1109
- onClick={() => {
1110
- setConfirming(null);
1111
- keepFocusAt(g.key);
1112
- }}
1113
- >
1114
- {labels.cancel}
1115
- </Button>
1116
- </div>
1117
- </div>
1118
- </AlertBanner>
1119
- </div>
1120
- )}
1121
- {body && table(g.rows, { key: g.key, name })}
1122
- </Disclosure>
1123
- );
1124
- };
1125
- return (
1126
- <div ref={rootRef} className={cn("min-w-0 space-y-4", className)}>
1127
- {scopeText && (
1128
- <AlertBanner tone="info" variant="inline" size="sm" block live={false}>
1129
- {labels.scope(scopeText)}
1130
- </AlertBanner>
1131
- )}
1132
- {readOnly && (
1133
- <AlertBanner tone="info" variant="inline" size="sm" block live={false}>
1134
- {labels.readOnly}
1135
- </AlertBanner>
1136
- )}
1137
-
1138
- {progress && <TranslationProgress summary={summary} labels={labelsProp} />}
1139
-
1140
- <ToggleGroup<ReviewStatus | "all">
1141
- aria-label={labels.statusFilter}
1142
- overflow="wrap"
1143
- size="sm"
1144
- value={filter.status}
1145
- onChange={(next) => setFilter({ status: next })}
1146
- options={[
1147
- { value: "all", label: labels.filterCount(labels.all, summary.total) },
1148
- ...statuses.map((s) => ({ value: s, label: labels.filterCount(reviewStatusLabel(labels, s), summary[s]) })),
1149
- ]}
1150
- />
1151
-
1152
- <div className="flex flex-col gap-3 md:flex-row md:flex-wrap md:items-end">
1153
- {sources.length > 1 && (
1154
- <div className="md:w-48">
1155
- <Select label={labels.source} value={source} onChange={(e) => setFilter({ source: e.target.value })}>
1156
- <option value="all">{labels.allSources}</option>
1157
- {sources.map((s) => (
1158
- <option key={s} value={s}>
1159
- {sourceLabels?.[s] ?? s}
1160
- </option>
1161
- ))}
1162
- </Select>
1163
- </div>
1164
- )}
1165
- <div className="md:w-56">
1166
- <Select label={labels.namespace} value={namespace} onChange={(e) => setFilter({ namespace: e.target.value })}>
1167
- <option value="all">{labels.allNamespaces}</option>
1168
- {namespaces.map((ns) => (
1169
- <option key={ns} value={ns}>
1170
- {ns}
1171
- </option>
1172
- ))}
1173
- </Select>
1174
- </div>
1175
- <SearchField
1176
- className="min-w-0 md:flex-1"
1177
- aria-label={labels.search}
1178
- value={filter.query}
1179
- onChange={(next) => setFilter({ query: next })}
1180
- />
1181
- <Checkbox
1182
- label={labels.placeholdersOnly(mismatches)}
1183
- checked={filter.placeholdersOnly}
1184
- onCheckedChange={(next) => setFilter({ placeholdersOnly: next })}
1185
- />
1186
- </div>
1187
-
1188
- {editable && shownRows.length > 0 && (
1189
- // The phone's table has no checkboxes; this is its way into the bulk actions.
1190
- <Button
1191
- variant="ghost"
1192
- size="sm"
1193
- className="md:hidden"
1194
- disabled={allShownSelected}
1195
- onClick={() => setSelected((prev) => toggleIn(prev, shownRows, true))}
1196
- >
1197
- <ListChecks className="size-4" aria-hidden />
1198
- {labels.selectShown}
1199
- </Button>
1200
- )}
1201
-
1202
- {failure && (
1203
- <AlertBanner tone="danger" size="sm" role="alert">
1204
- {failure}
1205
- </AlertBanner>
1206
- )}
1207
-
1208
- {editable && (
1209
- <BulkActionBar count={selectedRows.length} onClear={() => setSelected(new Set())}>
1210
- <Button
1211
- variant="default"
1212
- size="sm"
1213
- commit={COMMIT_EXCEPT_BILLING}
1214
- pending={isQueued("bulk-approve")}
1215
- disabled={busy || toApprove.length === 0}
1216
- onClick={() => void approveSelected()}
1217
- >
1218
- <Check className="size-4" aria-hidden />
1219
- {labels.approveSelected}
1220
- </Button>
1221
- {onClear && (
1222
- <Button
1223
- variant="ghost"
1224
- size="sm"
1225
- commit={COMMIT_EXCEPT_BILLING}
1226
- pending={isQueued("bulk-reset")}
1227
- disabled={busy || toReset.length === 0}
1228
- onClick={() => void resetSelected()}
1229
- >
1230
- <RotateCcw className="size-4" aria-hidden />
1231
- {labels.resetSelected}
1232
- </Button>
1233
- )}
1234
- </BulkActionBar>
1235
- )}
1236
-
1237
- {groups && groups.length > 0 ? <div className="space-y-3">{groups.map(groupSection)}</div> : table(visible)}
1238
- </div>
1239
- );
1240
- }