@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,1239 +0,0 @@
1
- import { cn } from "../lib/cn";
2
- import { hasContent } from "../lib/presence";
3
- import {
4
- useEffect,
5
- useLayoutEffect,
6
- useRef,
7
- useState,
8
- type ClipboardEvent as ReactClipboardEvent,
9
- type ReactNode,
10
- type RefObject,
11
- } from "react";
12
- import { Camera, FileText, Image as ImageIcon, Paperclip, X } from "lucide-react";
13
- import { Button } from "../components/button";
14
- import { IconButton } from "../components/icon-button";
15
- import { Spinner } from "../components/spinner";
16
- import type { ButtonSize, ButtonVariant } from "../components/button-look";
17
- import { DEFAULT_FILE_PICKER_LABELS, formatAccept } from "../components/file-button";
18
- import { useCommitReason, type CommitScope } from "../components/write-lock";
19
- import { useKitFileLabels, useKitLabels, type LabelOverride } from "../i18n/kit-labels";
20
- import type { FeedbackAttachmentLabels } from "./feedback-dialog";
21
- import { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from "./feedback-attachment-labels";
22
- import { DEFAULT_ATTACHMENT_ACCEPT, DEFAULT_MAX_ATTACHMENT_BYTES, DEFAULT_MAX_ATTACHMENTS } from "./feedback-record";
23
-
24
- // Defined in ./feedback-record since 0.34, which a top bar can import without this
25
- // field (MOD-1); still exported from here, where they always were.
26
- export { DEFAULT_ATTACHMENT_ACCEPT, DEFAULT_MAX_ATTACHMENT_BYTES, DEFAULT_MAX_ATTACHMENTS };
27
-
28
- /**
29
- * The field's own strings — the `feedbackAttachment` namespace of `<UiKitProvider
30
- * labels>`. The keys are {@link FeedbackAttachmentLabels}' own, so the `labels` prop
31
- * (and a whole `FeedbackDialogLabels` handed down by the dialog) merges over them
32
- * one to one. The optional heading, `attachment`, stays a prop: a note editor puts
33
- * the buttons straight under its textarea and wants none.
34
- *
35
- * Before 0.7.0 the two optional keys fell back to hard-coded English, so a German app
36
- * that relied on its provider got "Capture screenshot" under a German form.
37
- *
38
- * The three keys added in 0.15.5 for `multiple` mode are optional, so a complete
39
- * `UiKitLabels` typed before it still compiles; they fall back to English. So is
40
- * `attachmentScreenshot` (0.16.0).
41
- */
42
- export interface FeedbackAttachmentFieldLabels {
43
- attachmentAdd: string;
44
- attachmentCapture: string;
45
- attachmentPaste: string;
46
- attachmentRemove: string;
47
- /** `multiple` mode: the accessible name of the list of chosen files — and, since
48
- * 0.16.0, the heading `<FeedbackDialog attachments="multiple">` puts over it. */
49
- attachmentList?: string;
50
- /** `multiple` mode (0.16.0): what the screenshot slot's chip is called — "Screenshot"
51
- * rather than the capture's file name ("screenshot.webp"), which says nothing a
52
- * reporter chose. The file name stays as the chip's second line. */
53
- attachmentScreenshot?: string;
54
- /** `multiple` mode: one chip's remove button — "Remove photo.jpg". Named per file,
55
- * because five buttons all called "Remove attachment" say nothing about which. */
56
- attachmentRemoveFile?: (name: string) => string;
57
- /** `multiple` mode: the line shown in place of the add buttons once `max` is reached. */
58
- attachmentLimit?: (max: number) => string;
59
- /** `refs` mode (0.22.0, keksdose K16): the second line of a chip whose upload is still
60
- * running, where an uploaded one shows its size — "Uploading…". Optional, like the
61
- * keys above, so a `UiKitLabels` typed before it still compiles. */
62
- attachmentUploading?: string;
63
- /** `refs` mode (0.23.0, keksdose G5a): {@link FeedbackAttachmentErrorInfo.message} for
64
- * an `"upload"` refusal — "“huge.png” could not be uploaded". The other refusals'
65
- * sentences are the `filePicker` namespace's, shared with FileButton. Optional, like
66
- * the keys above. */
67
- attachmentUploadFailed?: (name: string) => string;
68
- }
69
-
70
- // Defined in ./feedback-attachment-labels, which holds no component code (0.34, KS U8).
71
- export { DEFAULT_FEEDBACK_ATTACHMENT_LABELS };
72
-
73
- /** Why a file was turned away. `"count"` only ever comes from `multiple` (and `refs`)
74
- * mode: more files arrived at once than `max` left room for, and the surplus was
75
- * dropped. */
76
- export type FeedbackAttachmentError = "type" | "size" | "count";
77
-
78
- /** `refs` mode's reasons: {@link FeedbackAttachmentError}, or `"upload"` — `onUpload`
79
- * rejected, and what it rejected with is `onError`'s second argument. A type of its
80
- * own so a host's exhaustive switch over the File modes' reasons stays exhaustive. */
81
- export type FeedbackAttachmentRefsError = FeedbackAttachmentError | "upload";
82
-
83
- /**
84
- * What `onError` knows about a refusal beyond its kind (0.23.0, keksdose G5a): the file
85
- * it was about, the limit that refused it, and a sentence that says both.
86
- *
87
- * keksdose's support chat toasted "File too large" with no name — the field's `onError`
88
- * carried a kind and nothing else, so a refusal could not say WHICH of three pasted
89
- * screenshots went, nor what the ceiling was, and its own `AttachmentPicker` (a
90
- * FileButton, whose `onReject` hands over `{ file, message }`) said more than the kit's
91
- * field would have. This is that, for every mode.
92
- *
93
- * It arrives as an EXTRA argument, after the ones `onError` already had, so a handler
94
- * written for 0.22 — `(kind) => …`, or `refs` mode's `(kind, error) => …` — compiles
95
- * and behaves as before:
96
- *
97
- * - single and `multiple`: `onError(kind, info)`;
98
- * - `refs`: `onError(kind, error, info)` — the second place is `onUpload`'s rejection
99
- * since 0.22 (`undefined` for the other kinds, as it always was), so the info comes
100
- * third rather than changing what a 0.22 handler reads there. It also carries that
101
- * `error`, so one helper `(kind, info) => …` serves all three modes.
102
- *
103
- * An object rather than more positional arguments because what a host reaches for
104
- * differs by kind — the bytes for `"size"`, `max` for `"count"`, the server's answer for
105
- * `"upload"` — and a field added later must not shift anyone's arguments.
106
- */
107
- export interface FeedbackAttachmentErrorInfo {
108
- /** The file refused — under the name the field would have given it (a pasted image
109
- * is already "pasted.png"). For `"count"`, the first of the surplus; for
110
- * `"upload"`, the file whose upload failed. */
111
- file: File;
112
- /** `"count"`: every file of the surplus (several can arrive in one pick). Otherwise
113
- * `[file]`. */
114
- files: File[];
115
- /**
116
- * One translated sentence naming the file and the limit — the ones FileButton's
117
- * `onReject` hands over, from the same `filePicker` namespace: "“huge.png” is larger
118
- * than 10 MB", "Only image/png, image/jpeg files", "“c.png” was not added: at most 5
119
- * files" (several at once: "3 files were not added"). `"upload"`: the field's own
120
- * `attachmentUploadFailed`, "“huge.png” could not be uploaded" — a host with a better
121
- * sentence from the server's answer uses `error`. Ready for a toast as it is.
122
- */
123
- message: string;
124
- /** The field's `maxBytes` — the ceiling a `"size"` refusal went over, in bytes. */
125
- maxBytes: number;
126
- /** The field's `accept` — the types a `"type"` refusal was not among. */
127
- accept: string[];
128
- /** The field's `max` — the ceiling a `"count"` refusal hit. Absent in single mode,
129
- * which holds one file by definition. */
130
- max?: number;
131
- /** `"upload"` only: what `onUpload` rejected with — `onError`'s second argument. */
132
- error?: unknown;
133
- }
134
-
135
- /**
136
- * One uploaded attachment in `refs` mode: what the host's upload answered with. `key`
137
- * identifies it (a storage key, an upload id) and is what a remove goes by; `name` is
138
- * what the chip says. `size` (bytes) and `type` (mime) are optional — given, the chip
139
- * shows the size and an image icon or a file icon accordingly.
140
- */
141
- export interface FeedbackAttachmentRef {
142
- key: string;
143
- name: string;
144
- size?: number;
145
- type?: string;
146
- }
147
-
148
- interface FeedbackAttachmentFieldBaseProps {
149
- /** Prop > `<UiKitProvider labels={{ feedbackAttachment }}>` > English. Optional
150
- * since 0.7.0; `attachment` (the heading) is only ever read from here. */
151
- labels?: LabelOverride<FeedbackAttachmentLabels> &
152
- Pick<Partial<FeedbackAttachmentFieldLabels>, "attachmentUploading" | "attachmentUploadFailed">;
153
- accept?: string[];
154
- maxBytes?: number;
155
- /**
156
- * Take no files for now (0.23.0, keksdose G5b): the add and capture buttons, the
157
- * hidden file input and every chip's remove button are natively disabled, and a paste
158
- * is let through untouched. The chips stay as they are — what is about to be sent
159
- * stays readable.
160
- *
161
- * For the moment a send is in flight. keksdose's support picker is `disabled` while
162
- * the message goes out, because the message carries the attachments AS THEY WERE at
163
- * the press: a chip removed mid-send has gone with it all the same, and one added
164
- * mid-send looks as if it had. The field has no drop target; the paste is its drop.
165
- *
166
- * This is a pause, not a lock that explains itself — for that, `refs` mode (the mode
167
- * that commits) takes `commit` and `disabledReason`.
168
- */
169
- disabled?: boolean;
170
- /** Snapshot the app view behind this and return it as a File. A "Capture
171
- * screenshot" button appears only when it is given. */
172
- onCaptureScreenshot?: () => Promise<File | null>;
173
- /** Listen for the paste on `document` rather than on this field's own
174
- * subtree. For a modal, which owns the whole page while it is up. */
175
- documentPaste?: boolean;
176
- /** Listen for the paste within this element's subtree rather than this
177
- * field's own — for a field standing *beside* the text box a paste is made
178
- * in, whose common parent is where the event bubbles to. Ignored when
179
- * `documentPaste` is set. */
180
- pasteFrom?: RefObject<HTMLElement | null>;
181
- /**
182
- * The add and capture buttons' look (0.24.0, keksdose) — Button's own `variant`.
183
- * Default `"secondary"`, the look they always had.
184
- *
185
- * keksdose's support chat puts this field in ChatComposer's `attachmentSlot`, in the
186
- * row with Send, where its own picker had been a `ghost` / `sm` FileButton: two
187
- * bordered full-size buttons under a chat box read as a second form, and the field
188
- * gave no way to say otherwise. Every mode, wherever the buttons appear; the chips'
189
- * remove buttons keep their size.
190
- */
191
- buttonVariant?: ButtonVariant;
192
- /** The add and capture buttons' size — Button's own `size`, with `buttonVariant`.
193
- * Default `"md"`. `"sm"` draws their icons at 14px, as a small button's are. */
194
- buttonSize?: ButtonSize;
195
- className?: string;
196
- }
197
-
198
- /**
199
- * The field's root, in every mode. `relative` for the `sr-only` file input (see the
200
- * single field). `min-w-0 max-w-full` (0.24.0, keksdose) because the field is often a
201
- * FLEX ITEM — ChatComposer's `attachmentSlot` row, FormActions' `start` — and a flex item
202
- * is never narrower than its content's min-content width unless told it may be. A chip
203
- * truncates its name only inside a box of bounded width, and a screenshot's name
204
- * ("Screenshot 2026-10-03 at 07.45.12.png") was the min-content: the field grew to it
205
- * and ran off a 390px phone composer (measured in Chromium: the field's right edge at
206
- * 592px, the page 614px wide). keksdose passed `className="min-w-0 max-w-full"` at both
207
- * its callers to stop it; here once, for every host. In normal flow — a dialog, a note
208
- * editor — both are no-ops.
209
- *
210
- * The names and sizes under it are `truncate-until-large` (0.33, docs/text-size-harmonization.md
211
- * §10.17): "…" at Normal, and from Large up they wrap, breaking a long file name
212
- * anywhere, so the bound above still holds and nothing is cut.
213
- */
214
- const FIELD_ROOT = "relative min-w-0 max-w-full";
215
-
216
- /** Paperclip and camera at the size the button's text is: 16px at `md`, 14px at `sm`. */
217
- const buttonIcon = (size: ButtonSize) => (size === "sm" ? "size-3.5" : "size-4");
218
-
219
- /** One file: choosing a second means removing the first. The field's only mode until 0.15.5. */
220
- export interface FeedbackAttachmentFieldSingleProps extends FeedbackAttachmentFieldBaseProps {
221
- multiple?: false;
222
- refs?: false;
223
- value: File | null;
224
- onChange: (file: File | null) => void;
225
- /** A file was refused. `info` (0.23.0) names it and the limit — see
226
- * {@link FeedbackAttachmentErrorInfo}. */
227
- onError?: (kind: "type" | "size", info: FeedbackAttachmentErrorInfo) => void;
228
- // Multiple-mode props, refused here: without `multiple` they would be silently ignored.
229
- max?: never;
230
- screenshot?: never;
231
- onScreenshotChange?: never;
232
- // `refs` mode's lock: a staged File is not a commit (see the refs props).
233
- commit?: never;
234
- disabledReason?: never;
235
- }
236
-
237
- /**
238
- * Several files (0.15.5, keksdose dev#578: "pasted two photos, the second overwrote
239
- * the first"). Every way in ADDS — a pick of several files, a paste, a capture — up to
240
- * `max`, and each file is a removable chip.
241
- */
242
- export interface FeedbackAttachmentFieldMultipleProps extends FeedbackAttachmentFieldBaseProps {
243
- multiple: true;
244
- refs?: false;
245
- value: File[];
246
- onChange: (files: File[]) => void;
247
- /** How many files `value` may hold. Default {@link DEFAULT_MAX_ATTACHMENTS} (5). The
248
- * add buttons and the paste hint are offered only while there is room. */
249
- max?: number;
250
- /** Per file: a rejected one is reported and the others are still added. `"count"` is
251
- * once per pick, with the whole surplus in `info.files`. `info` (0.23.0): see
252
- * {@link FeedbackAttachmentErrorInfo}. */
253
- onError?: (kind: FeedbackAttachmentError, info: FeedbackAttachmentErrorInfo) => void;
254
- /**
255
- * A separate slot for THE screenshot, outside `value` and `max`. Given
256
- * `onScreenshotChange`, what `onCaptureScreenshot` returns lands here instead of in
257
- * `value`, shown as the first chip, and the capture button is offered only while the
258
- * slot is empty — a second snapshot of the same view is not more evidence. This is
259
- * how `<FeedbackDialog attachments="multiple">` keeps the screenshot apart from the
260
- * photos; a form built from the parts can do the same.
261
- */
262
- screenshot?: File | null;
263
- onScreenshotChange?: (file: File | null) => void;
264
- // `refs` mode's lock: a staged File is not a commit (see the refs props).
265
- commit?: never;
266
- disabledReason?: never;
267
- }
268
-
269
- /**
270
- * Uploaded on pick (0.22.0, keksdose K16). keksdose's support chat uploads each file
271
- * the moment it is chosen and sends the message with the REFS the uploads answered —
272
- * a 10 MB screenshot is refused while the reporter is still typing, not when they
273
- * press Send — so its `AttachmentPicker` could not use this field, whose value is
274
- * `File`s held until the form is submitted. Here the value is
275
- * {@link FeedbackAttachmentRef}s: every way in (pick, paste, capture) validates the
276
- * file as the other modes do, then hands it to `onUpload` and shows a chip with a
277
- * spinner until it answers; the ref it resolves to joins `value` through `onChange`.
278
- * A rejection drops the chip and reports `onError("upload", error)`. A chip is removed
279
- * BY KEY: `onChange` gets the list without it, and `onRemove` the key, for a host that
280
- * deletes the upload on the server too.
281
- *
282
- * Uploads still running count against `max`, so five picks in quick succession cannot
283
- * overshoot it; they are not in `value` until they land, so a send that reads `value`
284
- * sends only what has arrived — hold Send back while {@link onUploadingChange} says
285
- * an upload is running. Several files upload side by side and join in the order they
286
- * finish. The File modes are unchanged.
287
- */
288
- export interface FeedbackAttachmentFieldRefsProps extends FeedbackAttachmentFieldBaseProps {
289
- refs: true;
290
- multiple?: never;
291
- value: FeedbackAttachmentRef[];
292
- onChange: (refs: FeedbackAttachmentRef[]) => void;
293
- /** Upload one file; resolve to its ref, or reject to refuse it. Called once per
294
- * accepted file, the moment it is chosen. */
295
- onUpload: (file: File) => Promise<FeedbackAttachmentRef>;
296
- /** A chip was removed — after `onChange` — with its ref's key. */
297
- onRemove?: (key: string) => void;
298
- /** Whether any upload is running: `true` when the first starts, `false` when the
299
- * last settles — for the host's Send, which should wait. */
300
- onUploadingChange?: (uploading: boolean) => void;
301
- /** How many refs `value` may hold, uploads in flight included. Default
302
- * {@link DEFAULT_MAX_ATTACHMENTS} (5). */
303
- max?: number;
304
- /** Per file. `"upload"` carries what `onUpload` rejected with as `error` (`undefined`
305
- * for the other kinds). `info` (0.23.0) comes third so a 0.22 handler's `error` is
306
- * still the second argument — see {@link FeedbackAttachmentErrorInfo}. */
307
- onError?: (kind: FeedbackAttachmentRefsError, error: unknown, info: FeedbackAttachmentErrorInfo) => void;
308
- /**
309
- * This field COMMITS (0.23.0): each file is uploaded the moment it is chosen, and a
310
- * remove may delete the upload (`onRemove`) — a save, unlike the File modes, which
311
- * only stage a file for a form sent later (and so stay open under a lock, like every
312
- * field). Under a locked {@link WriteLockProvider} it takes the `disabledReason` path
313
- * with the lock's reason, which wins over one of its own. No provider, or an unlocked
314
- * one: no effect.
315
- */
316
- commit?: CommitScope;
317
- /**
318
- * Why no file can be added or removed — the write lock's sentence, a quota, a thread
319
- * closed to replies. Unlike `disabled`, it SAYS so, the kit's commit-control way: the
320
- * buttons (add, capture, every chip's remove) stay focusable, `aria-disabled`, with
321
- * the reason in the kit Tooltip and their description; no pick, paste or remove gets
322
- * through; and the reason stands where the paste hint was, for a phone that never
323
- * hovers. The chips stay readable.
324
- */
325
- disabledReason?: ReactNode;
326
- screenshot?: never;
327
- onScreenshotChange?: never;
328
- }
329
-
330
- export type FeedbackAttachmentFieldProps =
331
- FeedbackAttachmentFieldSingleProps | FeedbackAttachmentFieldMultipleProps | FeedbackAttachmentFieldRefsProps;
332
-
333
- /**
334
- * Picking one picture: the file dialog, a capture of the app view, or a paste.
335
- * With `multiple`, picking several (see {@link FeedbackAttachmentFieldMultipleProps}).
336
- *
337
- * Lifted out of {@link FeedbackDialog} so the *reply* half of the feature can
338
- * have it too (Steering Design feedback #128). A report is a conversation — it
339
- * is filed, it is answered, and the reporter sends it back saying that is not
340
- * what they meant — and the screenshot showing what they mean is taken at
341
- * whichever of those points they looked. Only the first of them had a way to
342
- * attach one, so everything after it had to be described in words.
343
- *
344
- * **Three ways in, and they are three because no one of them covers the others.**
345
- * The file dialog needs a file on disk; `onCaptureScreenshot` snapshots the
346
- * *whole* app view; and the clipboard is where a region snip already is on every
347
- * platform (Steering Design feedback #39). Somebody who wanted to show one panel
348
- * had to save a crop and then find it again.
349
- *
350
- * `documentPaste` and `pasteFrom` decide where the paste is listened for, and
351
- * it is a real choice rather than a flag with a default. A modal traps focus,
352
- * so while it is up every paste in the page is meant for it — including one
353
- * made with nothing in particular focused, which never reaches a React
354
- * `onPaste` on a child: that is `documentPaste`. An editor **inline on a
355
- * page** is not that: the page around it has its own fields, so it listens
356
- * within its own subtree and a paste elsewhere stays where it was aimed. But
357
- * "its own subtree" has to include the text box the paste is actually made in,
358
- * and that box is this field's *sibling*, not its child — a paste in it bubbles
359
- * to their common parent and never through here. `pasteFrom` is that parent
360
- * (Steering Design feedback #140): the element whose subtree is listened to,
361
- * handed in by whoever renders both the box and this field side by side.
362
- */
363
- export function FeedbackAttachmentField(props: FeedbackAttachmentFieldProps) {
364
- // One component per mode rather than one with branches: the modes hold different
365
- // state (one preview vs. one per chip vs. uploads in flight), and a hook order that
366
- // depends on a prop is a crash the day a caller flips it.
367
- if (props.refs) return <RefsField {...props} />;
368
- return props.multiple ? <MultipleField {...props} /> : <SingleField {...props} />;
369
- }
370
-
371
- function SingleField({
372
- value,
373
- onChange,
374
- labels: labelsProp,
375
- accept = DEFAULT_ATTACHMENT_ACCEPT,
376
- maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
377
- onError,
378
- onCaptureScreenshot,
379
- documentPaste = false,
380
- pasteFrom,
381
- disabled = false,
382
- buttonVariant = "secondary",
383
- buttonSize = "md",
384
- className,
385
- }: FeedbackAttachmentFieldSingleProps) {
386
- // The object URL is keyed to the file it was made for, so a stale one (from the
387
- // previous file, or after `value` is cleared) is never shown — no reset needed.
388
- const [previewFor, setPreviewFor] = useState<{ file: File; url: string } | null>(null);
389
- const preview = value && previewFor?.file === value ? previewFor.url : null;
390
- const fileInputRef = useRef<HTMLInputElement>(null);
391
- // `file.size` from `<UiKitProvider labels>`, formatted in its locale — see FileDropzone.
392
- const fileText = useKitFileLabels();
393
- const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
394
- const errorInfo = useErrorInfo(accept, maxBytes, undefined, text);
395
-
396
- // Object-URL preview lifecycle (create on change, revoke on cleanup).
397
- useEffect(() => {
398
- if (!value) return;
399
- const url = URL.createObjectURL(value);
400
- // The URL is an external resource that must be created and revoked in step
401
- // with the effect (a render-time `createObjectURL` would leak, and StrictMode's
402
- // remount would revoke a memoised one out from under the <img>).
403
- // eslint-disable-next-line react-hooks/set-state-in-effect -- publishing an external resource's handle
404
- setPreviewFor({ file: value, url });
405
- return () => URL.revokeObjectURL(url);
406
- }, [value]);
407
-
408
- const pick = (file: File | undefined | null) => {
409
- if (!file) return;
410
- const problem = rejection(file, accept, maxBytes);
411
- if (problem) {
412
- onError?.(problem, errorInfo(problem, [file]));
413
- return;
414
- }
415
- onChange(file);
416
- };
417
-
418
- const onPaste = usePaste(
419
- (images) => {
420
- // Clipboard images arrive named "image.png" at best and unnamed at worst,
421
- // and the name is what the inbox shows beside the thumbnail. A name that
422
- // says where it came from is more use than the browser's.
423
- const file = images[0];
424
- pick(new File([file], pastedName(file.type), { type: file.type }));
425
- },
426
- documentPaste,
427
- pasteFrom,
428
- disabled,
429
- );
430
-
431
- return (
432
- <div
433
- // `relative` for the same reason as FileDropzone: the `sr-only` file input below
434
- // is `position: absolute`, and without a positioned ancestor it is laid out
435
- // against the initial containing block — extending the DOCUMENT height to its own
436
- // offset and producing a phantom second scrollbar on any long page. And
437
- // `min-w-0 max-w-full`, so a long name truncates in a flex row: see FIELD_ROOT.
438
- className={cn(FIELD_ROOT, className)}
439
- onPaste={onPaste}
440
- >
441
- <Heading text={labelsProp?.attachment} />
442
- {value ? (
443
- <div className="flex items-start gap-2">
444
- {value.type.startsWith("image/") && preview ? (
445
- <img src={preview} alt={value.name} className="h-20 w-20 rounded border border-subtle object-cover" />
446
- ) : (
447
- // Non-image attachments (PDF, text) can't preview as an <img>, so
448
- // show a neutral file tile with the name/size beside it instead.
449
- <div className="flex h-20 w-20 items-center justify-center rounded border border-subtle text-placeholder">
450
- <FileText className="size-8" />
451
- </div>
452
- )}
453
- <div className="min-w-0 flex-1">
454
- <div className="truncate-until-large text-sm text-secondary">{value.name}</div>
455
- <div className="text-xs text-muted">{fileText.size(value.size)}</div>
456
- </div>
457
- <RemoveButton label={text.attachmentRemove} onClick={() => onChange(null)} disabled={disabled} />
458
- </div>
459
- ) : (
460
- <div className="space-y-1.5">
461
- <div className="flex flex-wrap gap-2">
462
- <Button
463
- type="button"
464
- variant={buttonVariant}
465
- size={buttonSize}
466
- disabled={disabled}
467
- onClick={() => fileInputRef.current?.click()}
468
- >
469
- <Paperclip className={buttonIcon(buttonSize)} /> {text.attachmentAdd}
470
- </Button>
471
- {onCaptureScreenshot && (
472
- <CaptureButton
473
- capture={onCaptureScreenshot}
474
- onFile={pick}
475
- label={text.attachmentCapture}
476
- disabled={disabled}
477
- variant={buttonVariant}
478
- size={buttonSize}
479
- />
480
- )}
481
- </div>
482
- {/* Said out loud, because a gesture with no affordance is a gesture
483
- nobody finds. */}
484
- <p className="text-xs text-muted">{text.attachmentPaste}</p>
485
- </div>
486
- )}
487
- <input
488
- ref={fileInputRef}
489
- type="file"
490
- accept={accept.join(",")}
491
- className="sr-only"
492
- // Out of the tab order and the accessibility tree, as in the other two modes:
493
- // the add button is the way in (round 0.34, the showcase's axe check — this one
494
- // was a second, nameless stop).
495
- tabIndex={-1}
496
- aria-hidden
497
- disabled={disabled}
498
- onChange={(e) => {
499
- pick(e.target.files?.[0]);
500
- e.target.value = "";
501
- }}
502
- />
503
- </div>
504
- );
505
- }
506
-
507
- function MultipleField({
508
- value,
509
- onChange,
510
- max = DEFAULT_MAX_ATTACHMENTS,
511
- screenshot = null,
512
- onScreenshotChange,
513
- labels: labelsProp,
514
- accept = DEFAULT_ATTACHMENT_ACCEPT,
515
- maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
516
- onError,
517
- onCaptureScreenshot,
518
- documentPaste = false,
519
- pasteFrom,
520
- disabled = false,
521
- buttonVariant = "secondary",
522
- buttonSize = "md",
523
- className,
524
- }: FeedbackAttachmentFieldMultipleProps) {
525
- const rootRef = useRef<HTMLDivElement>(null);
526
- const fileInputRef = useRef<HTMLInputElement>(null);
527
- const fileText = useKitFileLabels();
528
- const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
529
- const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile!;
530
- const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit!;
531
- const screenshotLabel = text.attachmentScreenshot ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentScreenshot!;
532
- const errorInfo = useErrorInfo(accept, maxBytes, max, text);
533
-
534
- // What an add or a remove builds on: the `value` prop, plus whatever this field has
535
- // already handed to `onChange` since the parent last rendered. The field is
536
- // controlled, so between an `onChange` and the re-render that brings it back, `value`
537
- // is stale — and two adds in that gap (two pastes, a paste and a pick, delivered by
538
- // native listeners whose updates React batches) each spread the SAME old list, and
539
- // the second dropped the first. Re-synced to the prop after every commit, so a
540
- // parent that refuses a change (or edits the list itself) is what wins.
541
- const latest = useRef(value);
542
- useLayoutEffect(() => {
543
- latest.current = value;
544
- });
545
- const commit = (next: File[]) => {
546
- latest.current = next;
547
- onChange(next);
548
- };
549
-
550
- const hasSlot = onScreenshotChange !== undefined;
551
- const room = Math.max(0, max - value.length);
552
- // The screenshot slot is its own thing: it neither counts against `max` nor is
553
- // closed by it, so a full set of photos still leaves the capture on offer.
554
- const canCapture = !!onCaptureScreenshot && (hasSlot ? !screenshot : room > 0);
555
-
556
- const add = (incoming: File[], pasted = false) => {
557
- const current = latest.current;
558
- const accepted: File[] = [];
559
- const taken = new Set([...current, ...(screenshot ? [screenshot] : [])].map((f) => f.name));
560
- for (const raw of incoming) {
561
- // Two pastes are two files called "pasted.png", and a backend that stores by
562
- // name keeps one of them — the very loss this mode exists to end. A picked
563
- // file keeps its own name: it is the user's, and they may look for it. Named
564
- // before the check, so a refusal names it as the single field's does.
565
- const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
566
- const problem = rejection(file, accept, maxBytes);
567
- if (problem) {
568
- onError?.(problem, errorInfo(problem, [file]));
569
- continue;
570
- }
571
- taken.add(file.name);
572
- // The same File object twice is one file chosen twice, not two.
573
- if (!current.includes(file)) accepted.push(file);
574
- }
575
- if (accepted.length === 0) return;
576
- const space = Math.max(0, max - current.length);
577
- if (accepted.length > space) onError?.("count", errorInfo("count", accepted.slice(space)));
578
- const kept = accepted.slice(0, space);
579
- if (kept.length > 0) commit([...current, ...kept]);
580
- };
581
-
582
- const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, disabled);
583
-
584
- const capture = (file: File) => {
585
- if (!hasSlot) {
586
- add([file]);
587
- return;
588
- }
589
- const problem = rejection(file, accept, maxBytes);
590
- if (problem) onError?.(problem, errorInfo(problem, [file]));
591
- else onScreenshotChange(file);
592
- };
593
-
594
- // Removing a chip removes the button that had focus. Focus goes to the chip that
595
- // took its place — the next one — and, when it was the last, to the first action
596
- // button (there is room again, so "Attach" is back), else to the chip before it.
597
- // Applied once the parent has re-rendered with the shorter list: the field is
598
- // controlled, and focusing before that would land on the chip being removed.
599
- const pendingFocus = useRef<number | null>(null);
600
- useEffect(() => {
601
- const index = pendingFocus.current;
602
- if (index === null) return;
603
- pendingFocus.current = null;
604
- const root = rootRef.current;
605
- if (!root) return;
606
- const removes = root.querySelectorAll<HTMLElement>("[data-attachment-remove]");
607
- const target = removes[index] ?? root.querySelector<HTMLElement>("[data-attachment-action]") ?? removes[index - 1];
608
- target?.focus();
609
- }, [value, screenshot]);
610
-
611
- const chips: Array<{ file: File; screenshot: boolean; name: string; remove: () => void }> = [
612
- ...(hasSlot && screenshot
613
- ? [{ file: screenshot, screenshot: true, name: screenshotLabel, remove: () => onScreenshotChange(null) }]
614
- : []),
615
- ...value.map((file) => ({
616
- file,
617
- screenshot: false,
618
- name: file.name,
619
- remove: () => commit(latest.current.filter((f) => f !== file)),
620
- })),
621
- ];
622
-
623
- return (
624
- <div ref={rootRef} className={cn(FIELD_ROOT, "space-y-2", className)} onPaste={onPaste}>
625
- <Heading text={labelsProp?.attachment} className="mb-0" />
626
- {chips.length > 0 && (
627
- // One chip per row, full width: wrapped chips of name-dependent widths made a
628
- // ragged block in a 430px dialog, and a column keeps every name and remove
629
- // button in the same place — the next chip's button is right under the last.
630
- <ul aria-label={text.attachmentList} className="flex flex-col gap-2">
631
- {chips.map((chip, index) => (
632
- <li
633
- key={fileKey(chip.file)}
634
- className="flex min-w-0 items-center gap-2 rounded-md border border-subtle bg-surface p-1"
635
- >
636
- <ChipPreview file={chip.file} screenshot={chip.screenshot} />
637
- <div className="min-w-0 flex-1">
638
- <div className="truncate-until-large text-sm text-secondary">{chip.name}</div>
639
- <div className="truncate-until-large text-xs text-muted">
640
- {/* The screenshot's file name moves down here: still there for the
641
- reporter who wants to know what will be sent, not its title. */}
642
- {chip.screenshot
643
- ? `${chip.file.name} · ${fileText.size(chip.file.size)}`
644
- : fileText.size(chip.file.size)}
645
- </div>
646
- </div>
647
- <RemoveButton
648
- label={removeLabel(chip.name)}
649
- disabled={disabled}
650
- onClick={() => {
651
- pendingFocus.current = index;
652
- chip.remove();
653
- }}
654
- />
655
- </li>
656
- ))}
657
- </ul>
658
- )}
659
- {(room > 0 || canCapture) && (
660
- <div className="flex flex-wrap gap-2">
661
- {room > 0 && (
662
- <Button
663
- type="button"
664
- variant={buttonVariant}
665
- size={buttonSize}
666
- data-attachment-action=""
667
- disabled={disabled}
668
- onClick={() => fileInputRef.current?.click()}
669
- >
670
- <Paperclip className={buttonIcon(buttonSize)} /> {text.attachmentAdd}
671
- </Button>
672
- )}
673
- {canCapture && onCaptureScreenshot && (
674
- <CaptureButton
675
- capture={onCaptureScreenshot}
676
- onFile={capture}
677
- label={text.attachmentCapture}
678
- disabled={disabled}
679
- variant={buttonVariant}
680
- size={buttonSize}
681
- data-attachment-action=""
682
- />
683
- )}
684
- </div>
685
- )}
686
- <p className="text-xs text-muted">{room > 0 ? text.attachmentPaste : limitLabel(max)}</p>
687
- <input
688
- ref={fileInputRef}
689
- type="file"
690
- multiple={room > 1}
691
- accept={accept.join(",")}
692
- className="sr-only"
693
- // Out of the tab order and the accessibility tree: the button above is
694
- // the way in, and a second, unlabelled "Choose files" stop is noise.
695
- tabIndex={-1}
696
- aria-hidden
697
- disabled={disabled}
698
- onChange={(e) => {
699
- add(Array.from(e.target.files ?? []));
700
- e.target.value = "";
701
- }}
702
- />
703
- </div>
704
- );
705
- }
706
-
707
- /** A file `refs` mode is uploading: its own id (a ref has no key yet), and the file. */
708
- interface Upload {
709
- id: number;
710
- file: File;
711
- }
712
-
713
- let nextUploadId = 0;
714
-
715
- function RefsField({
716
- value,
717
- onChange,
718
- onUpload,
719
- onRemove,
720
- onUploadingChange,
721
- max = DEFAULT_MAX_ATTACHMENTS,
722
- labels: labelsProp,
723
- accept = DEFAULT_ATTACHMENT_ACCEPT,
724
- maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
725
- onError,
726
- onCaptureScreenshot,
727
- documentPaste = false,
728
- pasteFrom,
729
- disabled = false,
730
- commit: commits,
731
- disabledReason: ownDisabledReason,
732
- buttonVariant = "secondary",
733
- buttonSize = "md",
734
- className,
735
- }: FeedbackAttachmentFieldRefsProps) {
736
- const rootRef = useRef<HTMLDivElement>(null);
737
- const fileInputRef = useRef<HTMLInputElement>(null);
738
- const fileText = useKitFileLabels();
739
- const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
740
- const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile!;
741
- const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit!;
742
- const uploadingLabel = text.attachmentUploading ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploading!;
743
- const errorInfo = useErrorInfo(accept, maxBytes, max, text);
744
- // The lock's reason (under `commit`) over the field's own; with either, the controls
745
- // take the focusable `aria-disabled` path and say why — Button's and IconButton's
746
- // `disabledReason`, handed down as is.
747
- const disabledReason = useCommitReason(commits, ownDisabledReason);
748
- const locked = hasContent(disabledReason);
749
- const reason = locked ? disabledReason : undefined;
750
- const inert = disabled || locked;
751
-
752
- // As in `multiple` mode: `value` plus whatever has been handed to `onChange` since
753
- // the parent last rendered — two uploads landing in one tick must both survive.
754
- const latest = useRef(value);
755
- useLayoutEffect(() => {
756
- latest.current = value;
757
- });
758
- const commit = (next: FeedbackAttachmentRef[]) => {
759
- latest.current = next;
760
- onChange(next);
761
- };
762
-
763
- const [uploads, setUploads] = useState<Upload[]>([]);
764
- // The uploads in flight, kept in a ref as well: `add` runs several times before a
765
- // render when files arrive together, and each must see the others — their count
766
- // against `max`, their names against a pasted image's.
767
- const inFlight = useRef<Upload[]>([]);
768
- // Unmounted (the dialog closed mid-upload): a late answer is not this field's to
769
- // commit, and setting state on it would be a no-op at best.
770
- const mounted = useRef(true);
771
- useEffect(() => {
772
- mounted.current = true;
773
- return () => {
774
- mounted.current = false;
775
- };
776
- }, []);
777
- const uploadingChange = useRef(onUploadingChange);
778
- useEffect(() => {
779
- uploadingChange.current = onUploadingChange;
780
- });
781
-
782
- const room = Math.max(0, max - value.length - uploads.length);
783
-
784
- const start = (file: File) => {
785
- const upload: Upload = { id: nextUploadId++, file };
786
- inFlight.current = [...inFlight.current, upload];
787
- if (inFlight.current.length === 1) uploadingChange.current?.(true);
788
- setUploads(inFlight.current);
789
- const settle = () => {
790
- inFlight.current = inFlight.current.filter((u) => u.id !== upload.id);
791
- if (!mounted.current) return;
792
- setUploads(inFlight.current);
793
- if (inFlight.current.length === 0) uploadingChange.current?.(false);
794
- };
795
- let answer: Promise<FeedbackAttachmentRef>;
796
- try {
797
- answer = Promise.resolve(onUpload(file));
798
- } catch (error) {
799
- answer = Promise.reject(error);
800
- }
801
- answer.then(
802
- (ref) => {
803
- settle();
804
- if (mounted.current) commit([...latest.current, ref]);
805
- },
806
- (error: unknown) => {
807
- settle();
808
- if (mounted.current) onError?.("upload", error, errorInfo("upload", [file], error));
809
- },
810
- );
811
- };
812
-
813
- const add = (incoming: File[], pasted = false) => {
814
- let space = Math.max(0, max - latest.current.length - inFlight.current.length);
815
- const taken = new Set([...latest.current.map((r) => r.name), ...inFlight.current.map((u) => u.file.name)]);
816
- const dropped: File[] = [];
817
- for (const raw of incoming) {
818
- // Pasted images are all "image.png" to the clipboard; as in `multiple` mode,
819
- // each gets a name of its own before it is checked and uploaded under it.
820
- const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
821
- const problem = rejection(file, accept, maxBytes);
822
- if (problem) {
823
- // `undefined` in `error`'s place, as in 0.22: only an upload has one.
824
- onError?.(problem, undefined, errorInfo(problem, [file]));
825
- continue;
826
- }
827
- taken.add(file.name);
828
- if (space === 0) {
829
- dropped.push(file);
830
- continue;
831
- }
832
- space -= 1;
833
- start(file);
834
- }
835
- if (dropped.length > 0) onError?.("count", undefined, errorInfo("count", dropped));
836
- };
837
-
838
- const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, inert);
839
-
840
- // Focus after a remove, as in `multiple` mode: the next chip, else the first action.
841
- const pendingFocus = useRef<number | null>(null);
842
- useEffect(() => {
843
- const index = pendingFocus.current;
844
- if (index === null) return;
845
- pendingFocus.current = null;
846
- const root = rootRef.current;
847
- if (!root) return;
848
- const removes = root.querySelectorAll<HTMLElement>("[data-attachment-remove]");
849
- const target = removes[index] ?? root.querySelector<HTMLElement>("[data-attachment-action]") ?? removes[index - 1];
850
- target?.focus();
851
- }, [value]);
852
-
853
- const remove = (key: string, index: number) => {
854
- if (inert) return;
855
- pendingFocus.current = index;
856
- commit(latest.current.filter((r) => r.key !== key));
857
- onRemove?.(key);
858
- };
859
-
860
- const hasChips = value.length > 0 || uploads.length > 0;
861
-
862
- return (
863
- <div ref={rootRef} className={cn(FIELD_ROOT, "space-y-2", className)} onPaste={onPaste}>
864
- <Heading text={labelsProp?.attachment} className="mb-0" />
865
- {hasChips && (
866
- <ul aria-label={text.attachmentList} className="flex flex-col gap-2">
867
- {value.map((ref, index) => (
868
- <li
869
- key={`ref:${ref.key}`}
870
- className="flex min-w-0 items-center gap-2 rounded-md border border-subtle bg-surface p-1"
871
- >
872
- <RefGlyph type={ref.type} />
873
- <div className="min-w-0 flex-1">
874
- <div className="truncate-until-large text-sm text-secondary">{ref.name}</div>
875
- {ref.size !== undefined && (
876
- <div className="truncate-until-large text-xs text-muted">{fileText.size(ref.size)}</div>
877
- )}
878
- </div>
879
- <RemoveButton
880
- label={removeLabel(ref.name)}
881
- onClick={() => remove(ref.key, index)}
882
- disabled={disabled}
883
- reason={reason}
884
- />
885
- </li>
886
- ))}
887
- {uploads.map((upload) => (
888
- <li
889
- key={`upload:${upload.id}`}
890
- aria-busy="true"
891
- data-attachment-uploading=""
892
- className="flex min-w-0 items-center gap-2 rounded-md border border-dashed border-subtle bg-surface p-1"
893
- >
894
- <span className="flex size-12 shrink-0 items-center justify-center rounded border border-subtle bg-surface-2 text-placeholder">
895
- <Spinner label={null} className="size-5" />
896
- </span>
897
- <div className="min-w-0 flex-1">
898
- <div className="truncate-until-large text-sm text-secondary">{upload.file.name}</div>
899
- <div className="truncate-until-large text-xs text-muted">{uploadingLabel}</div>
900
- </div>
901
- </li>
902
- ))}
903
- </ul>
904
- )}
905
- {room > 0 && (
906
- <div className="flex flex-wrap gap-2">
907
- <Button
908
- type="button"
909
- variant={buttonVariant}
910
- size={buttonSize}
911
- data-attachment-action=""
912
- disabled={disabled}
913
- disabledReason={reason}
914
- onClick={() => fileInputRef.current?.click()}
915
- >
916
- <Paperclip className={buttonIcon(buttonSize)} /> {text.attachmentAdd}
917
- </Button>
918
- {onCaptureScreenshot && (
919
- <CaptureButton
920
- capture={onCaptureScreenshot}
921
- onFile={(file) => add([file])}
922
- label={text.attachmentCapture}
923
- disabled={disabled}
924
- disabledReason={reason}
925
- variant={buttonVariant}
926
- size={buttonSize}
927
- data-attachment-action=""
928
- />
929
- )}
930
- </div>
931
- )}
932
- {locked ? (
933
- // Where the paste hint was: a phone never hovers, so the Tooltip alone would
934
- // leave a touch user with buttons that do nothing and no word why.
935
- <div data-attachment-reason="" className="text-xs text-muted">
936
- {disabledReason}
937
- </div>
938
- ) : (
939
- <p className="text-xs text-muted">{room > 0 ? text.attachmentPaste : limitLabel(max)}</p>
940
- )}
941
- <input
942
- ref={fileInputRef}
943
- type="file"
944
- multiple={room > 1}
945
- accept={accept.join(",")}
946
- className="sr-only"
947
- tabIndex={-1}
948
- aria-hidden
949
- disabled={inert}
950
- onChange={(e) => {
951
- add(Array.from(e.target.files ?? []));
952
- e.target.value = "";
953
- }}
954
- />
955
- </div>
956
- );
957
- }
958
-
959
- /** A ref chip's picture. The bytes are on the server, not here, so there is nothing to
960
- * preview: an image glyph for an image `type`, a file glyph for anything else. */
961
- function RefGlyph({ type }: { type?: string }) {
962
- return (
963
- <span className="flex size-12 shrink-0 items-center justify-center rounded border border-subtle bg-surface-2 text-placeholder">
964
- {type?.startsWith("image/") ? (
965
- <ImageIcon aria-hidden className="size-6" />
966
- ) : (
967
- <FileText aria-hidden className="size-6" />
968
- )}
969
- </span>
970
- );
971
- }
972
-
973
- function Heading({ text, className }: { text?: string; className?: string }) {
974
- if (!text) return null;
975
- return <div className={cn("mb-1 text-xs font-medium uppercase tracking-wide text-muted", className)}>{text}</div>;
976
- }
977
-
978
- function CaptureButton({
979
- capture,
980
- onFile,
981
- label,
982
- disabled = false,
983
- disabledReason,
984
- variant,
985
- size,
986
- ...rest
987
- }: {
988
- capture: () => Promise<File | null>;
989
- onFile: (file: File) => void;
990
- label: string;
991
- disabled?: boolean;
992
- disabledReason?: ReactNode;
993
- variant: ButtonVariant;
994
- size: ButtonSize;
995
- "data-attachment-action"?: string;
996
- }) {
997
- const [capturing, setCapturing] = useState(false);
998
- return (
999
- <Button
1000
- type="button"
1001
- variant={variant}
1002
- size={size}
1003
- disabled={capturing || disabled}
1004
- disabledReason={disabledReason}
1005
- {...rest}
1006
- onClick={() => {
1007
- if (capturing || disabled) return;
1008
- setCapturing(true);
1009
- void capture()
1010
- .then((file) => {
1011
- if (file) onFile(file);
1012
- })
1013
- .finally(() => setCapturing(false));
1014
- }}
1015
- >
1016
- <Camera className={buttonIcon(size)} /> {capturing ? "…" : label}
1017
- </Button>
1018
- );
1019
- }
1020
-
1021
- /**
1022
- * A chip's (or the single file's) remove button: the kit's IconButton at the 28px the
1023
- * hand-built one had, so `disabled` (0.23.0) dims and disables it the kit way and a
1024
- * `reason` takes IconButton's `disabledReason` path — focusable, `aria-disabled`, the
1025
- * reason in the Tooltip — rather than a third copy of either here.
1026
- */
1027
- function RemoveButton({
1028
- label,
1029
- onClick,
1030
- disabled,
1031
- reason,
1032
- }: {
1033
- label: string;
1034
- onClick: () => void;
1035
- disabled: boolean;
1036
- reason?: ReactNode;
1037
- }) {
1038
- return (
1039
- <IconButton
1040
- type="button"
1041
- size="xs"
1042
- tone="muted"
1043
- data-attachment-remove=""
1044
- aria-label={label}
1045
- disabled={disabled}
1046
- disabledReason={reason}
1047
- onClick={onClick}
1048
- className="shrink-0"
1049
- >
1050
- <X aria-hidden />
1051
- </IconButton>
1052
- );
1053
- }
1054
-
1055
- /**
1056
- * The {@link FeedbackAttachmentErrorInfo} builder of one field: its limits, and the
1057
- * sentence for each refusal. The sentences are the `filePicker` namespace's — the ones
1058
- * FileButton and FileDropzone say — so an app that translated those has these too, and
1059
- * a refusal reads the same whichever of the kit's pickers made it. Only the upload
1060
- * failure, which no other picker has, is this field's own (`attachmentUploadFailed`).
1061
- */
1062
- function useErrorInfo(
1063
- accept: string[],
1064
- maxBytes: number,
1065
- max: number | undefined,
1066
- text: FeedbackAttachmentFieldLabels,
1067
- ) {
1068
- const picker = useKitLabels("filePicker", DEFAULT_FILE_PICKER_LABELS);
1069
- const fileText = useKitFileLabels();
1070
- const uploadFailed = text.attachmentUploadFailed ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploadFailed!;
1071
- return (kind: FeedbackAttachmentRefsError, files: File[], error?: unknown): FeedbackAttachmentErrorInfo => {
1072
- const file = files[0];
1073
- let message: string;
1074
- if (kind === "type") {
1075
- // FileButton's rule: name what IS accepted ("Only image/png, image/jpeg files"),
1076
- // unless the app translated `rejectedType` but not yet `rejectedTypeOnly` — its
1077
- // own sentence beats an English one.
1078
- const acceptText = formatAccept(accept.join(","));
1079
- const useOnly =
1080
- acceptText !== "" &&
1081
- (picker.rejectedTypeOnly !== DEFAULT_FILE_PICKER_LABELS.rejectedTypeOnly ||
1082
- picker.rejectedType === DEFAULT_FILE_PICKER_LABELS.rejectedType);
1083
- message = useOnly ? picker.rejectedTypeOnly(acceptText, file.name) : picker.rejectedType(file.name);
1084
- } else if (kind === "size") {
1085
- message = picker.rejectedSize(file.name, fileText.size(maxBytes));
1086
- } else if (kind === "count") {
1087
- message = files.length === 1 ? picker.rejectedCount(file.name, max ?? 1) : picker.rejectedMany(files.length);
1088
- } else {
1089
- message = uploadFailed(file.name);
1090
- }
1091
- return {
1092
- file,
1093
- files,
1094
- message,
1095
- maxBytes,
1096
- accept,
1097
- ...(max !== undefined ? { max } : null),
1098
- ...(kind === "upload" ? { error } : null),
1099
- };
1100
- };
1101
- }
1102
-
1103
- /** A chip's picture: the image itself, or a file glyph for anything that is not one.
1104
- * Decorative (`alt=""`) — the name stands beside it. Its own component so the object
1105
- * URL lives exactly as long as the chip: removing the chip unmounts it and revokes. */
1106
- function ChipPreview({ file, screenshot }: { file: File; screenshot: boolean }) {
1107
- const isImage = file.type.startsWith("image/");
1108
- const [previewFor, setPreviewFor] = useState<{ file: File; url: string } | null>(null);
1109
- const url = previewFor?.file === file ? previewFor.url : null;
1110
- useEffect(() => {
1111
- if (!isImage) return;
1112
- const next = URL.createObjectURL(file);
1113
- // As in the single field: an external resource created and revoked with the effect.
1114
- // eslint-disable-next-line react-hooks/set-state-in-effect -- publishing an external resource's handle
1115
- setPreviewFor({ file, url: next });
1116
- return () => URL.revokeObjectURL(next);
1117
- }, [file, isImage]);
1118
-
1119
- return (
1120
- <span className="relative flex size-12 shrink-0 items-center justify-center overflow-hidden rounded border border-subtle bg-surface-2 text-placeholder">
1121
- {isImage && url ? (
1122
- <img src={url} alt="" className="size-full object-cover" />
1123
- ) : (
1124
- <FileText aria-hidden className="size-6" />
1125
- )}
1126
- {screenshot && (
1127
- // Marks the slot apart from the photos: it is why the capture button is gone.
1128
- <span className="absolute bottom-0.5 end-0.5 rounded-sm bg-surface p-0.5 text-secondary">
1129
- <Camera aria-hidden className="size-3" />
1130
- </span>
1131
- )}
1132
- </span>
1133
- );
1134
- }
1135
-
1136
- /** A stable React key per File object — the name is not one (two pastes, two
1137
- * "pasted.png" before renaming; the same photo picked from two folders). */
1138
- const fileKeys = new WeakMap<File, number>();
1139
- let nextFileKey = 0;
1140
- function fileKey(file: File): number {
1141
- let key = fileKeys.get(file);
1142
- if (key === undefined) {
1143
- key = nextFileKey++;
1144
- fileKeys.set(file, key);
1145
- }
1146
- return key;
1147
- }
1148
-
1149
- function rejection(file: File, accept: string[], maxBytes: number): "type" | "size" | null {
1150
- if (!accept.includes(file.type)) return "type";
1151
- if (file.size > maxBytes) return "size";
1152
- return null;
1153
- }
1154
-
1155
- function renamed(file: File, name: string): File {
1156
- return new File([file], name, { type: file.type });
1157
- }
1158
-
1159
- /** `pasted.png`, then `pasted-2.png`, `pasted-3.png`… — the first name not in `taken`. */
1160
- function uniqueName(name: string, taken: Set<string>): string {
1161
- if (!taken.has(name)) return name;
1162
- const dot = name.lastIndexOf(".");
1163
- const [stem, ext] = dot > 0 ? [name.slice(0, dot), name.slice(dot)] : [name, ""];
1164
- let n = 2;
1165
- while (taken.has(`${stem}-${n}${ext}`)) n++;
1166
- return `${stem}-${n}${ext}`;
1167
- }
1168
-
1169
- /**
1170
- * The paste listener both modes share. `onImages` gets every image file on the
1171
- * clipboard (the single field takes the first). It is registered once rather than per
1172
- * render, so it reads the handler — which closes over props that change identity on
1173
- * every render — out of a ref rather than out of its own dependency list. Returns the
1174
- * `onPaste` for the field's root, or `undefined` when listening elsewhere.
1175
- */
1176
- function usePaste(
1177
- onImages: (images: File[]) => void,
1178
- documentPaste: boolean,
1179
- pasteFrom: RefObject<HTMLElement | null> | undefined,
1180
- /** `disabled` / locked (0.23.0): the paste goes where it was going, untouched — not
1181
- * swallowed, so a text half still lands in the box it was made in. */
1182
- off: boolean,
1183
- ) {
1184
- // `null` while off, so the listeners below leave the event alone entirely.
1185
- const handler = off ? null : onImages;
1186
- const latest = useRef(handler);
1187
- useEffect(() => {
1188
- latest.current = handler;
1189
- });
1190
-
1191
- // The element listened on, where it is not this field's own subtree: the
1192
- // document for a modal, the given parent for an inline editor. Read inside
1193
- // the effect rather than in render, because a ref's `current` is only set
1194
- // once the parent has mounted — which it has by the time effects run.
1195
- useEffect(() => {
1196
- const target: EventTarget | null = documentPaste ? document : (pasteFrom?.current ?? null);
1197
- if (!target) return;
1198
- const onPaste = (event: Event) => {
1199
- if (!latest.current) return;
1200
- const clipboard = (event as ClipboardEvent).clipboardData;
1201
- takeImages(clipboard, () => event.preventDefault(), latest.current);
1202
- };
1203
- target.addEventListener("paste", onPaste);
1204
- return () => target.removeEventListener("paste", onPaste);
1205
- }, [documentPaste, pasteFrom]);
1206
-
1207
- const listensElsewhere = documentPaste || pasteFrom !== undefined;
1208
- return listensElsewhere
1209
- ? undefined
1210
- : (event: ReactClipboardEvent) => {
1211
- if (!latest.current) return;
1212
- takeImages(event.clipboardData, () => event.preventDefault(), latest.current);
1213
- };
1214
- }
1215
-
1216
- function takeImages(clipboard: DataTransfer | null, stop: () => void, onImages: (images: File[]) => void) {
1217
- const images = Array.from(clipboard?.items ?? [])
1218
- .filter((item) => item.kind === "file" && item.type.startsWith("image/"))
1219
- .map((item) => item.getAsFile())
1220
- .filter((file): file is File => file !== null);
1221
- if (images.length === 0) return;
1222
- // Only once there IS an image: a paste of text into a field must stay a
1223
- // paste of text, and a clipboard holding both is a copy whose text half is
1224
- // what the field was focused for.
1225
- stop();
1226
- onImages(images);
1227
- }
1228
-
1229
- /** What a pasted image is called once it is an attachment.
1230
- *
1231
- * The extension is read off the mime type rather than assumed to be `.png`:
1232
- * Safari puts TIFF on the clipboard and a file called `pasted.png` that is not
1233
- * a PNG is one the receiving end opens wrong. */
1234
- export function pastedName(type: string): string {
1235
- const subtype = type.split("/")[1] ?? "png";
1236
- // `image/svg+xml` and friends carry a suffix that is not part of the
1237
- // extension, and none of them are in the accepted list anyway.
1238
- return `pasted.${subtype.split("+")[0]}`;
1239
- }