@eifi1/ui-kit 0.54.0 → 0.56.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 (847) hide show
  1. package/README.md +90 -76
  2. package/dist/admin/plan-change.js +2 -2
  3. package/dist/admin/plan-change.js.map +1 -1
  4. package/dist/admin/user-roster.d.ts +6 -0
  5. package/dist/admin/user-roster.js +4 -0
  6. package/dist/admin/user-roster.js.map +1 -1
  7. package/dist/billing/billing-labels.d.ts +26 -0
  8. package/dist/billing/billing-labels.js +9 -0
  9. package/dist/billing/billing-labels.js.map +1 -1
  10. package/dist/billing/subscription-standing.d.ts +61 -0
  11. package/dist/billing/subscription-standing.js +51 -0
  12. package/dist/billing/subscription-standing.js.map +1 -0
  13. package/dist/billing/usage-meter.d.ts +52 -0
  14. package/dist/billing/usage-meter.js +52 -0
  15. package/dist/billing/usage-meter.js.map +1 -0
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +4 -0
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/alert-banner.js +2 -3
  20. package/dist/components/alert-banner.js.map +1 -1
  21. package/dist/components/amount-input.js +3 -15
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +2 -2
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/bulk-action-bar.js +2 -1
  26. package/dist/components/bulk-action-bar.js.map +1 -1
  27. package/dist/components/button.d.ts +35 -1
  28. package/dist/components/button.js +6 -9
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar-heatmap.js +2 -2
  31. package/dist/components/calendar-heatmap.js.map +1 -1
  32. package/dist/components/camera-button.d.ts +89 -0
  33. package/dist/components/camera-button.js +122 -0
  34. package/dist/components/camera-button.js.map +1 -0
  35. package/dist/components/camera-capture.d.ts +66 -0
  36. package/dist/components/camera-capture.js +190 -0
  37. package/dist/components/camera-capture.js.map +1 -0
  38. package/dist/components/camera-labels.d.ts +20 -0
  39. package/dist/components/camera-labels.js +15 -0
  40. package/dist/components/camera-labels.js.map +1 -0
  41. package/dist/components/chart-edge.d.ts +9 -0
  42. package/dist/components/chart-edge.js +14 -3
  43. package/dist/components/chart-edge.js.map +1 -1
  44. package/dist/components/chart-zoom-core.js +34 -29
  45. package/dist/components/chart-zoom-core.js.map +1 -1
  46. package/dist/components/checkbox-group.js +1 -1
  47. package/dist/components/checkbox-group.js.map +1 -1
  48. package/dist/components/combobox-core.js +3 -2
  49. package/dist/components/combobox-core.js.map +1 -1
  50. package/dist/components/combobox.js +2 -2
  51. package/dist/components/combobox.js.map +1 -1
  52. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  53. package/dist/components/consent-again-notice-labels.js +15 -0
  54. package/dist/components/consent-again-notice-labels.js.map +1 -0
  55. package/dist/components/consent-again-notice.d.ts +85 -0
  56. package/dist/components/consent-again-notice.js +94 -0
  57. package/dist/components/consent-again-notice.js.map +1 -0
  58. package/dist/components/country-select.js +2 -7
  59. package/dist/components/country-select.js.map +1 -1
  60. package/dist/components/currency-select.js +2 -2
  61. package/dist/components/currency-select.js.map +1 -1
  62. package/dist/components/danger-confirm.js +2 -2
  63. package/dist/components/danger-confirm.js.map +1 -1
  64. package/dist/components/data-table-column-settings.js +2 -2
  65. package/dist/components/data-table-column-settings.js.map +1 -1
  66. package/dist/components/data-table-fit.js.map +1 -1
  67. package/dist/components/data-table-virtual.js +13 -3
  68. package/dist/components/data-table-virtual.js.map +1 -1
  69. package/dist/components/date-field.js +2 -2
  70. package/dist/components/date-field.js.map +1 -1
  71. package/dist/components/date-picker.js +2 -2
  72. package/dist/components/date-picker.js.map +1 -1
  73. package/dist/components/date-range-picker.js +2 -6
  74. package/dist/components/date-range-picker.js.map +1 -1
  75. package/dist/components/dialog-frame.d.ts +3 -1
  76. package/dist/components/dialog-frame.js.map +1 -1
  77. package/dist/components/dropdown.js +7 -3
  78. package/dist/components/dropdown.js.map +1 -1
  79. package/dist/components/entity-combobox.js +2 -2
  80. package/dist/components/entity-combobox.js.map +1 -1
  81. package/dist/components/field-parts.d.ts +108 -6
  82. package/dist/components/field-parts.js +59 -6
  83. package/dist/components/field-parts.js.map +1 -1
  84. package/dist/components/field-suggestion-labels.d.ts +2 -0
  85. package/dist/components/field-suggestion-labels.js +10 -0
  86. package/dist/components/field-suggestion-labels.js.map +1 -0
  87. package/dist/components/field.js +1 -1
  88. package/dist/components/field.js.map +1 -1
  89. package/dist/components/file-button.d.ts +1 -1
  90. package/dist/components/file-button.js.map +1 -1
  91. package/dist/components/floating-panel.d.ts +7 -6
  92. package/dist/components/floating-panel.js +3 -17
  93. package/dist/components/floating-panel.js.map +1 -1
  94. package/dist/components/form-actions.d.ts +3 -2
  95. package/dist/components/form-actions.js.map +1 -1
  96. package/dist/components/hover-menu.js +3 -2
  97. package/dist/components/hover-menu.js.map +1 -1
  98. package/dist/components/icon-button.d.ts +23 -5
  99. package/dist/components/icon-button.js +7 -8
  100. package/dist/components/icon-button.js.map +1 -1
  101. package/dist/components/inline-confirm.js +2 -1
  102. package/dist/components/inline-confirm.js.map +1 -1
  103. package/dist/components/inline-entity-combobox.js +2 -2
  104. package/dist/components/inline-entity-combobox.js.map +1 -1
  105. package/dist/components/input.js +2 -2
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/jump-calendar.js +3 -3
  108. package/dist/components/jump-calendar.js.map +1 -1
  109. package/dist/components/legal.d.ts +13 -1
  110. package/dist/components/legal.js +12 -0
  111. package/dist/components/legal.js.map +1 -1
  112. package/dist/components/legended-series-chart.d.ts +83 -0
  113. package/dist/components/legended-series-chart.js +77 -0
  114. package/dist/components/legended-series-chart.js.map +1 -0
  115. package/dist/components/lightbox.js +2 -1
  116. package/dist/components/lightbox.js.map +1 -1
  117. package/dist/components/mini-calendar.js +13 -5
  118. package/dist/components/mini-calendar.js.map +1 -1
  119. package/dist/components/modal.d.ts +23 -2
  120. package/dist/components/modal.js +24 -6
  121. package/dist/components/modal.js.map +1 -1
  122. package/dist/components/month-picker.d.ts +1 -1
  123. package/dist/components/month-picker.js +6 -6
  124. package/dist/components/month-picker.js.map +1 -1
  125. package/dist/components/multi-entity-combobox.js +2 -2
  126. package/dist/components/multi-entity-combobox.js.map +1 -1
  127. package/dist/components/multi-select.js +2 -6
  128. package/dist/components/multi-select.js.map +1 -1
  129. package/dist/components/number-input.js +2 -2
  130. package/dist/components/number-input.js.map +1 -1
  131. package/dist/components/page-header-layout.d.ts +18 -0
  132. package/dist/components/page-header-layout.js +23 -0
  133. package/dist/components/page-header-layout.js.map +1 -0
  134. package/dist/components/page-header-skeleton.d.ts +41 -0
  135. package/dist/components/page-header-skeleton.js +52 -0
  136. package/dist/components/page-header-skeleton.js.map +1 -0
  137. package/dist/components/page-header.js +4 -17
  138. package/dist/components/page-header.js.map +1 -1
  139. package/dist/components/popover.js +7 -2
  140. package/dist/components/popover.js.map +1 -1
  141. package/dist/components/release-notes-labels.d.ts +16 -0
  142. package/dist/components/release-notes-labels.js +18 -0
  143. package/dist/components/release-notes-labels.js.map +1 -0
  144. package/dist/components/release-notes.d.ts +65 -0
  145. package/dist/components/release-notes.js +65 -0
  146. package/dist/components/release-notes.js.map +1 -0
  147. package/dist/components/select.js +2 -2
  148. package/dist/components/select.js.map +1 -1
  149. package/dist/components/series-chart-labels.d.ts +9 -0
  150. package/dist/components/series-chart-labels.js +3 -1
  151. package/dist/components/series-chart-labels.js.map +1 -1
  152. package/dist/components/series-chart.d.ts +5 -0
  153. package/dist/components/series-chart.js +89 -36
  154. package/dist/components/series-chart.js.map +1 -1
  155. package/dist/components/toggle-group.js +3 -2
  156. package/dist/components/toggle-group.js.map +1 -1
  157. package/dist/components/treemap-layout.js.map +1 -1
  158. package/dist/components/treemap.d.ts +17 -15
  159. package/dist/components/treemap.js +167 -85
  160. package/dist/components/treemap.js.map +1 -1
  161. package/dist/eslint/index.js +1 -1
  162. package/dist/eslint/role-utilities.d.ts +2 -2
  163. package/dist/eslint/role-utilities.js.map +1 -1
  164. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  165. package/dist/feedback/feedback-inbox-page.js +207 -0
  166. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  167. package/dist/feedback/feedback-vocabulary.js +2 -2
  168. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  169. package/dist/feedback.d.ts +1 -0
  170. package/dist/feedback.js +6 -0
  171. package/dist/feedback.js.map +1 -1
  172. package/dist/hooks/use-debounce.d.ts +32 -5
  173. package/dist/hooks/use-debounce.js +6 -5
  174. package/dist/hooks/use-debounce.js.map +1 -1
  175. package/dist/hooks/use-file-picker.d.ts +3 -1
  176. package/dist/hooks/use-file-picker.js.map +1 -1
  177. package/dist/hooks/use-focus-trap.d.ts +19 -3
  178. package/dist/hooks/use-focus-trap.js +15 -5
  179. package/dist/hooks/use-focus-trap.js.map +1 -1
  180. package/dist/hooks/use-has-camera.d.ts +36 -0
  181. package/dist/hooks/use-has-camera.js +53 -0
  182. package/dist/hooks/use-has-camera.js.map +1 -0
  183. package/dist/hooks/use-hotkey.js +1 -10
  184. package/dist/hooks/use-hotkey.js.map +1 -1
  185. package/dist/hooks/use-overlay-history.d.ts +6 -1
  186. package/dist/hooks/use-overlay-history.js +10 -2
  187. package/dist/hooks/use-overlay-history.js.map +1 -1
  188. package/dist/i18n/defaults.js +11 -1
  189. package/dist/i18n/defaults.js.map +1 -1
  190. package/dist/i18n/german.js +75 -3
  191. package/dist/i18n/german.js.map +1 -1
  192. package/dist/i18n/kit-labels.d.ts +20 -0
  193. package/dist/i18n/kit-labels.js.map +1 -1
  194. package/dist/i18n/locales/en.js +92 -4
  195. package/dist/i18n/locales/en.js.map +3 -3
  196. package/dist/i18n/locales/es.js +71 -3
  197. package/dist/i18n/locales/es.js.map +1 -1
  198. package/dist/i18n/locales/fr.js +72 -2
  199. package/dist/i18n/locales/fr.js.map +1 -1
  200. package/dist/i18n/locales/hu.js +74 -3
  201. package/dist/i18n/locales/hu.js.map +1 -1
  202. package/dist/i18n/locales/it.js +74 -3
  203. package/dist/i18n/locales/it.js.map +1 -1
  204. package/dist/i18n/locales/zh.js +71 -3
  205. package/dist/i18n/locales/zh.js.map +1 -1
  206. package/dist/i18n/review.js +22 -1
  207. package/dist/i18n/review.js.map +1 -1
  208. package/dist/index.d.ts +24 -5
  209. package/dist/index.js +35 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/landing/landing-labels.d.ts +8 -0
  212. package/dist/landing/landing-labels.js +2 -1
  213. package/dist/landing/landing-labels.js.map +1 -1
  214. package/dist/lib/changelog.d.ts +130 -0
  215. package/dist/lib/changelog.js +137 -0
  216. package/dist/lib/changelog.js.map +1 -0
  217. package/dist/lib/clipboard-minus.js +2 -2
  218. package/dist/lib/clipboard-minus.js.map +1 -1
  219. package/dist/lib/dates.d.ts +29 -3
  220. package/dist/lib/dates.js +41 -12
  221. package/dist/lib/dates.js.map +1 -1
  222. package/dist/lib/editable-content.d.ts +33 -0
  223. package/dist/lib/editable-content.js +15 -0
  224. package/dist/lib/editable-content.js.map +1 -0
  225. package/dist/lib/focus-left.d.ts +47 -1
  226. package/dist/lib/focus-left.js +11 -2
  227. package/dist/lib/focus-left.js.map +1 -1
  228. package/dist/lib/server-wake.d.ts +49 -9
  229. package/dist/lib/server-wake.js +25 -14
  230. package/dist/lib/server-wake.js.map +1 -1
  231. package/dist/lib/session-refresh.d.ts +53 -0
  232. package/dist/lib/session-refresh.js +27 -1
  233. package/dist/lib/session-refresh.js.map +1 -1
  234. package/dist/rhf/fields.js +3 -3
  235. package/dist/rhf/fields.js.map +1 -1
  236. package/dist/shell/app-shell.d.ts +25 -1
  237. package/dist/shell/app-shell.js +3 -1
  238. package/dist/shell/app-shell.js.map +1 -1
  239. package/dist/shell/dev-menu.d.ts +3 -0
  240. package/dist/shell/dev-menu.js +4 -2
  241. package/dist/shell/dev-menu.js.map +1 -1
  242. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  243. package/dist/shell/held-changes-banner-labels.js +14 -0
  244. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  245. package/dist/shell/held-changes-banner.d.ts +75 -0
  246. package/dist/shell/held-changes-banner.js +106 -0
  247. package/dist/shell/held-changes-banner.js.map +1 -0
  248. package/dist/shell/sync-status-indicator.d.ts +27 -12
  249. package/dist/shell/sync-status-indicator.js +13 -4
  250. package/dist/shell/sync-status-indicator.js.map +1 -1
  251. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  252. package/dist/shell/top-bar-parts-labels.js +3 -1
  253. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  254. package/dist/shell.d.ts +2 -0
  255. package/dist/shell.js +3 -0
  256. package/dist/shell.js.map +1 -1
  257. package/dist/sync/engine.d.ts +11 -2
  258. package/dist/sync/engine.js +13 -5
  259. package/dist/sync/engine.js.map +1 -1
  260. package/dist/sync/hooks.d.ts +3 -2
  261. package/dist/sync/hooks.js +4 -2
  262. package/dist/sync/hooks.js.map +1 -1
  263. package/dist/testing/css-variables.generated.d.ts +2 -2
  264. package/dist/testing/css-variables.generated.js +1 -1
  265. package/dist/testing/css-variables.generated.js.map +1 -1
  266. package/dist/theme/chart-palette.d.ts +18 -0
  267. package/dist/theme/chart-palette.js +17 -1
  268. package/dist/theme/chart-palette.js.map +1 -1
  269. package/dist/theme/contrast-tokens.d.ts +3 -3
  270. package/dist/theme/contrast-tokens.js.map +1 -1
  271. package/dist/theme/contrast.d.ts +4 -4
  272. package/dist/theme/contrast.js.map +1 -1
  273. package/dist/tour/tour-overlay.js +2 -1
  274. package/dist/tour/tour-overlay.js.map +1 -1
  275. package/package.json +9 -12
  276. package/tokens.css +19 -54
  277. package/src/account/account-parts.ts +0 -47
  278. package/src/account/account-settings-labels.ts +0 -240
  279. package/src/account/account-settings.tsx +0 -698
  280. package/src/account/data-export-setting-labels.ts +0 -20
  281. package/src/account/data-export-setting.tsx +0 -200
  282. package/src/account/delete-account-setting-labels.ts +0 -33
  283. package/src/account/delete-account-setting.tsx +0 -342
  284. package/src/account/email-change-setting-labels.ts +0 -31
  285. package/src/account/email-change-setting.tsx +0 -443
  286. package/src/account/passkeys-setting.tsx +0 -445
  287. package/src/account/sessions-setting-labels.ts +0 -24
  288. package/src/account/sessions-setting.tsx +0 -283
  289. package/src/admin/admin-action-confirm-labels.ts +0 -19
  290. package/src/admin/admin-action-confirm.tsx +0 -509
  291. package/src/admin/admin-action-log-labels.ts +0 -83
  292. package/src/admin/admin-action-log.tsx +0 -464
  293. package/src/admin/admin-area-labels.ts +0 -114
  294. package/src/admin/admin-forbidden.tsx +0 -75
  295. package/src/admin/admin-overview.tsx +0 -258
  296. package/src/admin/admin-parts.ts +0 -68
  297. package/src/admin/admin-sections.tsx +0 -208
  298. package/src/admin/admin-tool-links.tsx +0 -90
  299. package/src/admin/invitations-panel-labels.ts +0 -31
  300. package/src/admin/invitations-panel.tsx +0 -516
  301. package/src/admin/mail-test-card-labels.ts +0 -19
  302. package/src/admin/mail-test-card.tsx +0 -189
  303. package/src/admin/plan-change-labels.ts +0 -5
  304. package/src/admin/plan-change.tsx +0 -474
  305. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  306. package/src/admin/reviewer-scope-editor.tsx +0 -224
  307. package/src/admin/role-select-labels.ts +0 -16
  308. package/src/admin/role-select.tsx +0 -282
  309. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  310. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  311. package/src/admin/user-roster-labels.ts +0 -17
  312. package/src/admin/user-roster.tsx +0 -559
  313. package/src/auth/accept-invitation-labels.ts +0 -21
  314. package/src/auth/accept-invitation.tsx +0 -210
  315. package/src/auth/auth-errors-labels.ts +0 -23
  316. package/src/auth/auth-errors.ts +0 -439
  317. package/src/auth/complete-name-dialog-labels.ts +0 -16
  318. package/src/auth/complete-name-dialog.tsx +0 -253
  319. package/src/auth/email-tag.ts +0 -75
  320. package/src/auth/forgot-password-form-labels.ts +0 -20
  321. package/src/auth/forgot-password-form.tsx +0 -221
  322. package/src/auth/form-rules.ts +0 -40
  323. package/src/auth/new-password-fields.tsx +0 -85
  324. package/src/auth/not-found-page-labels.ts +0 -12
  325. package/src/auth/not-found-page.tsx +0 -95
  326. package/src/auth/register-form-labels.ts +0 -31
  327. package/src/auth/register-form.tsx +0 -456
  328. package/src/auth/reset-password-form-labels.ts +0 -27
  329. package/src/auth/reset-password-form.tsx +0 -341
  330. package/src/auth/sign-in-form-labels.ts +0 -44
  331. package/src/auth/sign-in-form.tsx +0 -858
  332. package/src/auth/status-parts.tsx +0 -132
  333. package/src/auth/verify-email-labels.ts +0 -26
  334. package/src/auth/verify-email.tsx +0 -445
  335. package/src/billing/billing-banner.tsx +0 -258
  336. package/src/billing/billing-labels.ts +0 -381
  337. package/src/billing/billing-lock.ts +0 -108
  338. package/src/billing/billing-overview.ts +0 -157
  339. package/src/billing/billing-standing.ts +0 -80
  340. package/src/billing/cancel-request.ts +0 -97
  341. package/src/billing/cancel-subscription.tsx +0 -98
  342. package/src/billing/checkout-processing.ts +0 -394
  343. package/src/billing/checkout-return.ts +0 -90
  344. package/src/billing/pay-page.ts +0 -330
  345. package/src/billing/plan-card.tsx +0 -562
  346. package/src/billing/plan-change-labels.ts +0 -66
  347. package/src/billing/plan-limit-notice.tsx +0 -148
  348. package/src/billing/plan-limit-toast.tsx +0 -90
  349. package/src/billing/plan-limit.ts +0 -52
  350. package/src/billing/plan-price.ts +0 -98
  351. package/src/billing/plan-switch.tsx +0 -216
  352. package/src/billing/provider-page.ts +0 -84
  353. package/src/billing/subscription-actions.tsx +0 -247
  354. package/src/billing/subscription-status-chip.tsx +0 -95
  355. package/src/chart.ts +0 -103
  356. package/src/components/account-chips-labels.ts +0 -16
  357. package/src/components/account-chips.tsx +0 -474
  358. package/src/components/alert-banner.tsx +0 -670
  359. package/src/components/amount-input.tsx +0 -816
  360. package/src/components/appearance-labels.ts +0 -63
  361. package/src/components/authed-image-labels.ts +0 -11
  362. package/src/components/authed-image.tsx +0 -209
  363. package/src/components/autocomplete.tsx +0 -645
  364. package/src/components/breadcrumbs-labels.ts +0 -10
  365. package/src/components/breadcrumbs.tsx +0 -205
  366. package/src/components/bulk-action-bar-labels.ts +0 -11
  367. package/src/components/bulk-action-bar.tsx +0 -443
  368. package/src/components/button-group.tsx +0 -223
  369. package/src/components/button-look.ts +0 -256
  370. package/src/components/button.tsx +0 -339
  371. package/src/components/calculator.tsx +0 -341
  372. package/src/components/calendar-heatmap-labels.ts +0 -15
  373. package/src/components/calendar-heatmap.tsx +0 -600
  374. package/src/components/card.tsx +0 -377
  375. package/src/components/chart-edge.ts +0 -106
  376. package/src/components/chart-figure.tsx +0 -35
  377. package/src/components/chart-height.ts +0 -30
  378. package/src/components/chart-zoom-core.tsx +0 -627
  379. package/src/components/chart-zoom.tsx +0 -22
  380. package/src/components/chart.tsx +0 -554
  381. package/src/components/checkbox-group.tsx +0 -247
  382. package/src/components/checkbox.tsx +0 -346
  383. package/src/components/chip-input.tsx +0 -370
  384. package/src/components/chip-labels.ts +0 -13
  385. package/src/components/chip.tsx +0 -843
  386. package/src/components/choice-card.tsx +0 -544
  387. package/src/components/column-mapper-labels.ts +0 -38
  388. package/src/components/column-mapper.tsx +0 -655
  389. package/src/components/combobox-core.tsx +0 -1321
  390. package/src/components/combobox-suggestions.tsx +0 -131
  391. package/src/components/combobox.tsx +0 -716
  392. package/src/components/confirm-context.ts +0 -7
  393. package/src/components/confirm-dialog-labels.ts +0 -13
  394. package/src/components/confirm-dialog.tsx +0 -341
  395. package/src/components/copy-button-labels.ts +0 -13
  396. package/src/components/copy-button.tsx +0 -192
  397. package/src/components/country-select-labels.ts +0 -11
  398. package/src/components/country-select.tsx +0 -539
  399. package/src/components/currency-option-list.tsx +0 -188
  400. package/src/components/currency-select.tsx +0 -343
  401. package/src/components/danger-confirm-labels.ts +0 -20
  402. package/src/components/danger-confirm.tsx +0 -987
  403. package/src/components/data-table-cells.tsx +0 -118
  404. package/src/components/data-table-column-settings.tsx +0 -198
  405. package/src/components/data-table-filter-popover.tsx +0 -289
  406. package/src/components/data-table-filters.ts +0 -315
  407. package/src/components/data-table-fit.ts +0 -301
  408. package/src/components/data-table-groups.tsx +0 -288
  409. package/src/components/data-table-head.tsx +0 -280
  410. package/src/components/data-table-labels.ts +0 -198
  411. package/src/components/data-table-leading-row.tsx +0 -58
  412. package/src/components/data-table-mobile.tsx +0 -1117
  413. package/src/components/data-table-pagination.tsx +0 -202
  414. package/src/components/data-table-resize.ts +0 -188
  415. package/src/components/data-table-row-actions.tsx +0 -302
  416. package/src/components/data-table-row.tsx +0 -496
  417. package/src/components/data-table-selection.ts +0 -71
  418. package/src/components/data-table-shared.tsx +0 -287
  419. package/src/components/data-table-sort.ts +0 -163
  420. package/src/components/data-table-virtual.ts +0 -576
  421. package/src/components/data-table.tsx +0 -2598
  422. package/src/components/date-field.tsx +0 -684
  423. package/src/components/date-format-setting.tsx +0 -64
  424. package/src/components/date-picker.tsx +0 -404
  425. package/src/components/date-range-picker.tsx +0 -518
  426. package/src/components/description-list-labels.ts +0 -9
  427. package/src/components/description-list.tsx +0 -386
  428. package/src/components/dialog-frame-labels.ts +0 -7
  429. package/src/components/dialog-frame.tsx +0 -284
  430. package/src/components/disclosure.tsx +0 -476
  431. package/src/components/dropdown.tsx +0 -461
  432. package/src/components/empty-state.tsx +0 -136
  433. package/src/components/entity-combobox.tsx +0 -422
  434. package/src/components/error-boundary-labels.ts +0 -20
  435. package/src/components/error-boundary.tsx +0 -675
  436. package/src/components/facing-pair.ts +0 -93
  437. package/src/components/field-base-labels.ts +0 -20
  438. package/src/components/field-base.tsx +0 -656
  439. package/src/components/field-parts.tsx +0 -825
  440. package/src/components/field-strip.tsx +0 -129
  441. package/src/components/field.tsx +0 -206
  442. package/src/components/file-button.tsx +0 -295
  443. package/src/components/file-dropzone.tsx +0 -559
  444. package/src/components/floating-panel-labels.ts +0 -10
  445. package/src/components/floating-panel.tsx +0 -828
  446. package/src/components/focus-ring.ts +0 -16
  447. package/src/components/form-actions-labels.ts +0 -11
  448. package/src/components/form-actions.tsx +0 -578
  449. package/src/components/full-bleed-dialog.tsx +0 -294
  450. package/src/components/grouped-picker.tsx +0 -184
  451. package/src/components/help-popover.tsx +0 -117
  452. package/src/components/hover-menu.tsx +0 -343
  453. package/src/components/iban-input-labels.ts +0 -14
  454. package/src/components/iban-input.tsx +0 -236
  455. package/src/components/icon-button.tsx +0 -959
  456. package/src/components/icon-picker-labels.ts +0 -13
  457. package/src/components/icon-picker.tsx +0 -232
  458. package/src/components/image-grid-labels.ts +0 -11
  459. package/src/components/image-grid.tsx +0 -202
  460. package/src/components/inline-confirm.tsx +0 -116
  461. package/src/components/inline-edit-field-labels.ts +0 -11
  462. package/src/components/inline-edit-field.tsx +0 -552
  463. package/src/components/inline-entity-combobox.tsx +0 -849
  464. package/src/components/input.tsx +0 -394
  465. package/src/components/jump-calendar.tsx +0 -265
  466. package/src/components/language-select.tsx +0 -93
  467. package/src/components/legal-labels.ts +0 -105
  468. package/src/components/legal-page.tsx +0 -711
  469. package/src/components/legal.tsx +0 -668
  470. package/src/components/lightbox-labels.ts +0 -18
  471. package/src/components/lightbox.tsx +0 -399
  472. package/src/components/line-items-labels.ts +0 -14
  473. package/src/components/line-items.tsx +0 -602
  474. package/src/components/list-labels.ts +0 -12
  475. package/src/components/list.tsx +0 -870
  476. package/src/components/loading-state.tsx +0 -78
  477. package/src/components/measured-grid-labels.ts +0 -25
  478. package/src/components/measured-grid.tsx +0 -837
  479. package/src/components/menu-item.tsx +0 -241
  480. package/src/components/mini-calendar-labels.ts +0 -17
  481. package/src/components/mini-calendar.tsx +0 -792
  482. package/src/components/modal.tsx +0 -534
  483. package/src/components/money-draft.ts +0 -83
  484. package/src/components/money-field.tsx +0 -76
  485. package/src/components/month-grid.tsx +0 -229
  486. package/src/components/month-picker-labels.ts +0 -22
  487. package/src/components/month-picker.tsx +0 -1087
  488. package/src/components/multi-entity-combobox.tsx +0 -369
  489. package/src/components/multi-select.tsx +0 -376
  490. package/src/components/nav-pills.tsx +0 -198
  491. package/src/components/number-field.tsx +0 -327
  492. package/src/components/number-input.tsx +0 -559
  493. package/src/components/numeric-field.ts +0 -169
  494. package/src/components/numpad-sheet.tsx +0 -285
  495. package/src/components/one-time-code-input.tsx +0 -270
  496. package/src/components/page-contents-labels.ts +0 -9
  497. package/src/components/page-contents.tsx +0 -282
  498. package/src/components/page-header-context.ts +0 -8
  499. package/src/components/page-header.tsx +0 -298
  500. package/src/components/password-strength-labels.ts +0 -22
  501. package/src/components/password-strength.tsx +0 -241
  502. package/src/components/phone-input-labels.ts +0 -10
  503. package/src/components/phone-input.tsx +0 -282
  504. package/src/components/picker-sheet.tsx +0 -330
  505. package/src/components/pie-chart-labels.ts +0 -40
  506. package/src/components/pie-chart.tsx +0 -480
  507. package/src/components/popover-labels.ts +0 -14
  508. package/src/components/popover.tsx +0 -268
  509. package/src/components/progress-bar-labels.ts +0 -10
  510. package/src/components/progress-bar.tsx +0 -479
  511. package/src/components/qr-code.tsx +0 -76
  512. package/src/components/reauth-dialog-labels.ts +0 -13
  513. package/src/components/reauth-dialog.tsx +0 -265
  514. package/src/components/row-actions-labels.ts +0 -12
  515. package/src/components/row-actions.tsx +0 -795
  516. package/src/components/scroll-area.tsx +0 -123
  517. package/src/components/search-field.tsx +0 -192
  518. package/src/components/select.tsx +0 -294
  519. package/src/components/separator.tsx +0 -33
  520. package/src/components/series-chart-axes.ts +0 -342
  521. package/src/components/series-chart-budget.ts +0 -150
  522. package/src/components/series-chart-labels.ts +0 -33
  523. package/src/components/series-chart-thin.ts +0 -180
  524. package/src/components/series-chart-ticks.ts +0 -194
  525. package/src/components/series-chart.tsx +0 -1906
  526. package/src/components/server-wake-labels.ts +0 -11
  527. package/src/components/server-wake.tsx +0 -129
  528. package/src/components/settings-fields.tsx +0 -237
  529. package/src/components/share-card-labels.ts +0 -30
  530. package/src/components/share-card.tsx +0 -675
  531. package/src/components/signature-pad-labels.ts +0 -24
  532. package/src/components/signature-pad.tsx +0 -747
  533. package/src/components/signed-amount-labels.ts +0 -10
  534. package/src/components/signed-amount.tsx +0 -336
  535. package/src/components/skeleton.tsx +0 -80
  536. package/src/components/slider.tsx +0 -338
  537. package/src/components/sparkline-labels.ts +0 -14
  538. package/src/components/sparkline.tsx +0 -322
  539. package/src/components/spinner.tsx +0 -81
  540. package/src/components/stat-tile-labels.ts +0 -15
  541. package/src/components/stat-tile.tsx +0 -754
  542. package/src/components/status-dot.tsx +0 -170
  543. package/src/components/swatch-picker-labels.ts +0 -10
  544. package/src/components/swatch-picker.tsx +0 -283
  545. package/src/components/swipeable-row.tsx +0 -386
  546. package/src/components/switch.tsx +0 -273
  547. package/src/components/table-skeleton.tsx +0 -136
  548. package/src/components/table.tsx +0 -647
  549. package/src/components/tabs-labels.ts +0 -13
  550. package/src/components/tabs.tsx +0 -693
  551. package/src/components/text-link.tsx +0 -359
  552. package/src/components/text.tsx +0 -150
  553. package/src/components/textarea.tsx +0 -330
  554. package/src/components/tile-radio.tsx +0 -332
  555. package/src/components/time-input.tsx +0 -121
  556. package/src/components/toast-labels.ts +0 -12
  557. package/src/components/toast.tsx +0 -519
  558. package/src/components/toggle-group.tsx +0 -752
  559. package/src/components/toggle-legend.tsx +0 -394
  560. package/src/components/tooltip.tsx +0 -1149
  561. package/src/components/translation-review-editor.tsx +0 -290
  562. package/src/components/translation-review-labels.ts +0 -181
  563. package/src/components/translation-review-parts.tsx +0 -272
  564. package/src/components/translation-review.tsx +0 -1240
  565. package/src/components/tree-view.tsx +0 -589
  566. package/src/components/treemap-labels.ts +0 -47
  567. package/src/components/treemap-layout.ts +0 -210
  568. package/src/components/treemap.tsx +0 -1184
  569. package/src/components/trigger-aria.ts +0 -44
  570. package/src/components/use-anchor-dir.ts +0 -20
  571. package/src/components/use-mobile-reveal.ts +0 -62
  572. package/src/components/use-table-state.ts +0 -753
  573. package/src/components/user-avatar.tsx +0 -116
  574. package/src/components/wizard-stepper.tsx +0 -42
  575. package/src/components/write-lock-labels.ts +0 -10
  576. package/src/components/write-lock.tsx +0 -253
  577. package/src/currencies.ts +0 -90
  578. package/src/data-table.ts +0 -42
  579. package/src/demo/demo-banner.tsx +0 -153
  580. package/src/demo/demo-countdown.ts +0 -136
  581. package/src/demo/demo-ended.tsx +0 -90
  582. package/src/demo/demo-labels.ts +0 -78
  583. package/src/demo/demo-session.ts +0 -32
  584. package/src/demo/demo-start.tsx +0 -156
  585. package/src/eslint/index.ts +0 -161
  586. package/src/eslint/role-utilities.generated.ts +0 -146
  587. package/src/eslint/role-utilities.ts +0 -233
  588. package/src/feedback/feedback-attachment-download.tsx +0 -77
  589. package/src/feedback/feedback-attachment-labels.ts +0 -37
  590. package/src/feedback/feedback-attachment.tsx +0 -1239
  591. package/src/feedback/feedback-capture.ts +0 -287
  592. package/src/feedback/feedback-comment-thread.tsx +0 -353
  593. package/src/feedback/feedback-context-labels.ts +0 -11
  594. package/src/feedback/feedback-context.tsx +0 -245
  595. package/src/feedback/feedback-crash.ts +0 -547
  596. package/src/feedback/feedback-diagnostics.tsx +0 -41
  597. package/src/feedback/feedback-dialog-labels.ts +0 -29
  598. package/src/feedback/feedback-dialog.tsx +0 -452
  599. package/src/feedback/feedback-entry-file.tsx +0 -150
  600. package/src/feedback/feedback-inbox.tsx +0 -552
  601. package/src/feedback/feedback-labels.ts +0 -144
  602. package/src/feedback/feedback-menu-labels.ts +0 -12
  603. package/src/feedback/feedback-menu.tsx +0 -177
  604. package/src/feedback/feedback-mutation.ts +0 -20
  605. package/src/feedback/feedback-page-link.ts +0 -90
  606. package/src/feedback/feedback-public-labels.ts +0 -81
  607. package/src/feedback/feedback-public-list.tsx +0 -524
  608. package/src/feedback/feedback-public-switch.tsx +0 -102
  609. package/src/feedback/feedback-public-types.ts +0 -101
  610. package/src/feedback/feedback-record.ts +0 -428
  611. package/src/feedback/feedback-replies-context.ts +0 -9
  612. package/src/feedback/feedback-replies.tsx +0 -343
  613. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  614. package/src/feedback/feedback-row-detail-labels.ts +0 -31
  615. package/src/feedback/feedback-row-detail.tsx +0 -871
  616. package/src/feedback/feedback-status-undo.ts +0 -109
  617. package/src/feedback/feedback-submit-dialog.tsx +0 -131
  618. package/src/feedback/feedback-submit.tsx +0 -270
  619. package/src/feedback/feedback-swipe.tsx +0 -192
  620. package/src/feedback/feedback-table-labels.ts +0 -27
  621. package/src/feedback/feedback-table.tsx +0 -912
  622. package/src/feedback/feedback-thread-labels.ts +0 -22
  623. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  624. package/src/feedback/feedback-thread.tsx +0 -905
  625. package/src/feedback/feedback-vocabulary.ts +0 -263
  626. package/src/feedback/feedback-vote-button.tsx +0 -124
  627. package/src/feedback.ts +0 -116
  628. package/src/hooks/use-anchored-panel.ts +0 -396
  629. package/src/hooks/use-anchored-rect.ts +0 -77
  630. package/src/hooks/use-announce.ts +0 -85
  631. package/src/hooks/use-authed-src.ts +0 -167
  632. package/src/hooks/use-body-scroll-lock.ts +0 -119
  633. package/src/hooks/use-breakpoint.ts +0 -133
  634. package/src/hooks/use-close-transition.ts +0 -107
  635. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  636. package/src/hooks/use-debounce.ts +0 -114
  637. package/src/hooks/use-dismiss.ts +0 -371
  638. package/src/hooks/use-document-title.ts +0 -33
  639. package/src/hooks/use-file-drop.ts +0 -121
  640. package/src/hooks/use-file-picker-labels.ts +0 -28
  641. package/src/hooks/use-file-picker.tsx +0 -359
  642. package/src/hooks/use-focus-trap.ts +0 -199
  643. package/src/hooks/use-hotkey.ts +0 -212
  644. package/src/hooks/use-large-text.ts +0 -41
  645. package/src/hooks/use-long-press.ts +0 -140
  646. package/src/hooks/use-media-query.ts +0 -39
  647. package/src/hooks/use-mounted.ts +0 -39
  648. package/src/hooks/use-noindex.ts +0 -37
  649. package/src/hooks/use-overlay-history.ts +0 -714
  650. package/src/hooks/use-row-swipe.ts +0 -207
  651. package/src/hooks/use-search-param-state.ts +0 -434
  652. package/src/hooks/use-windowed-rows.ts +0 -225
  653. package/src/i18n/defaults.ts +0 -267
  654. package/src/i18n/german.ts +0 -2059
  655. package/src/i18n/kit-labels.tsx +0 -1003
  656. package/src/i18n/languages.ts +0 -349
  657. package/src/i18n/locales/de-CH.ts +0 -24
  658. package/src/i18n/locales/en.ts +0 -267
  659. package/src/i18n/locales/es.ts +0 -1999
  660. package/src/i18n/locales/fr.ts +0 -2037
  661. package/src/i18n/locales/hu.ts +0 -2004
  662. package/src/i18n/locales/it.ts +0 -2014
  663. package/src/i18n/locales/zh.ts +0 -1912
  664. package/src/i18n/plurals.ts +0 -86
  665. package/src/i18n/review.ts +0 -475
  666. package/src/i18n/shared-labels.ts +0 -138
  667. package/src/i18n/swiss.ts +0 -29
  668. package/src/index.ts +0 -1191
  669. package/src/landing/access.ts +0 -93
  670. package/src/landing/landing-actions.tsx +0 -97
  671. package/src/landing/landing-labels.ts +0 -59
  672. package/src/landing/landing-sections.tsx +0 -341
  673. package/src/landing/page-seo.ts +0 -261
  674. package/src/landing/public-header.tsx +0 -121
  675. package/src/landing/public-page.tsx +0 -106
  676. package/src/landing/routing.tsx +0 -225
  677. package/src/lib/broadcast.ts +0 -52
  678. package/src/lib/calc.ts +0 -323
  679. package/src/lib/chip-edge-snap.ts +0 -212
  680. package/src/lib/clipboard-minus.ts +0 -107
  681. package/src/lib/clipping.ts +0 -34
  682. package/src/lib/cn.ts +0 -28
  683. package/src/lib/column-mapping.ts +0 -234
  684. package/src/lib/countries.ts +0 -91
  685. package/src/lib/data-attributes.ts +0 -14
  686. package/src/lib/date-format.ts +0 -227
  687. package/src/lib/dates.ts +0 -288
  688. package/src/lib/day-format.ts +0 -96
  689. package/src/lib/decimal-marks.ts +0 -108
  690. package/src/lib/describe-failure.ts +0 -18
  691. package/src/lib/device-settings.ts +0 -147
  692. package/src/lib/direction.ts +0 -74
  693. package/src/lib/document-navigation.ts +0 -17
  694. package/src/lib/document-title.ts +0 -41
  695. package/src/lib/download.ts +0 -172
  696. package/src/lib/float-clearance.ts +0 -70
  697. package/src/lib/floating-stack.ts +0 -70
  698. package/src/lib/focus-left.ts +0 -156
  699. package/src/lib/format.ts +0 -585
  700. package/src/lib/http-status.ts +0 -38
  701. package/src/lib/iban.ts +0 -251
  702. package/src/lib/ime.ts +0 -24
  703. package/src/lib/intl-cache.ts +0 -27
  704. package/src/lib/iso-day.ts +0 -13
  705. package/src/lib/lazy-with-fallback.ts +0 -82
  706. package/src/lib/logger.ts +0 -69
  707. package/src/lib/minus.ts +0 -50
  708. package/src/lib/path-pattern.ts +0 -55
  709. package/src/lib/person-name.ts +0 -155
  710. package/src/lib/phone.ts +0 -208
  711. package/src/lib/plain-text.ts +0 -22
  712. package/src/lib/presence.ts +0 -18
  713. package/src/lib/qr-encode.ts +0 -444
  714. package/src/lib/quiet-focus.ts +0 -32
  715. package/src/lib/reduced-motion.ts +0 -22
  716. package/src/lib/refs.ts +0 -22
  717. package/src/lib/round.ts +0 -43
  718. package/src/lib/safe-storage.ts +0 -119
  719. package/src/lib/server-wake.ts +0 -315
  720. package/src/lib/session-refresh.ts +0 -472
  721. package/src/lib/share-format.ts +0 -18
  722. package/src/lib/status-tone.ts +0 -43
  723. package/src/lib/strip-fade.ts +0 -287
  724. package/src/lib/table-text.ts +0 -554
  725. package/src/lib/text-case.ts +0 -19
  726. package/src/lib/text-match.ts +0 -169
  727. package/src/lib/thenable.ts +0 -19
  728. package/src/lib/translation-review.ts +0 -769
  729. package/src/notifications/device-zone-report.ts +0 -67
  730. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  731. package/src/notifications/feedback-notification-types.ts +0 -71
  732. package/src/notifications/notification-bell.tsx +0 -119
  733. package/src/notifications/notification-inbox.tsx +0 -309
  734. package/src/notifications/notification-parts.tsx +0 -81
  735. package/src/notifications/notification-types-setting.tsx +0 -406
  736. package/src/notifications/notification-types.ts +0 -89
  737. package/src/notifications/notifications-labels.ts +0 -287
  738. package/src/notifications/push-device-setting.tsx +0 -230
  739. package/src/notifications/push-devices-setting.tsx +0 -374
  740. package/src/notifications/push-worker.ts +0 -168
  741. package/src/notifications/quiet-hours-setting.tsx +0 -238
  742. package/src/notifications/unsubscribe-panel.tsx +0 -200
  743. package/src/notifications/use-push-device.ts +0 -448
  744. package/src/offline/local-first.ts +0 -100
  745. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  746. package/src/offline/pwa-update-prompt.tsx +0 -78
  747. package/src/offline/query-persistence.ts +0 -391
  748. package/src/offline/service-worker-updates.ts +0 -81
  749. package/src/offline/shared-computer-labels.ts +0 -8
  750. package/src/offline/shared-computer.tsx +0 -87
  751. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  752. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  753. package/src/offline.ts +0 -37
  754. package/src/push-worker.ts +0 -25
  755. package/src/rhf/fields.tsx +0 -2187
  756. package/src/rhf/form-core.tsx +0 -324
  757. package/src/rhf/form.tsx +0 -12
  758. package/src/rhf/line-items.tsx +0 -135
  759. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  760. package/src/rhf.ts +0 -21
  761. package/src/search/command-palette-labels.ts +0 -16
  762. package/src/search/command-palette.tsx +0 -624
  763. package/src/search/global-search-labels.ts +0 -13
  764. package/src/search/global-search.tsx +0 -660
  765. package/src/search/search-index.ts +0 -283
  766. package/src/search/use-command-key.ts +0 -23
  767. package/src/search.ts +0 -14
  768. package/src/settings/device-reset-setting-labels.ts +0 -20
  769. package/src/settings/device-reset-setting.tsx +0 -116
  770. package/src/settings/settings-card-classes.ts +0 -11
  771. package/src/settings/settings-catalogue.ts +0 -150
  772. package/src/settings/settings-context.ts +0 -33
  773. package/src/settings/settings-heading.tsx +0 -67
  774. package/src/settings/settings-labels.ts +0 -87
  775. package/src/settings/settings-layout.tsx +0 -471
  776. package/src/settings/settings-section.tsx +0 -108
  777. package/src/settings/use-account-appearance.ts +0 -148
  778. package/src/settings/use-account-language.ts +0 -146
  779. package/src/settings/use-settings-focus.ts +0 -128
  780. package/src/settings/use-settings-route.ts +0 -258
  781. package/src/shell/account-menu-language.tsx +0 -130
  782. package/src/shell/account-menu-text-size.tsx +0 -80
  783. package/src/shell/admin-menu.tsx +0 -183
  784. package/src/shell/app-shell-labels.ts +0 -11
  785. package/src/shell/app-shell.tsx +0 -1560
  786. package/src/shell/auth-layout.tsx +0 -186
  787. package/src/shell/company-switcher-labels.ts +0 -13
  788. package/src/shell/company-switcher.tsx +0 -136
  789. package/src/shell/dev-menu.tsx +0 -257
  790. package/src/shell/fill-height.tsx +0 -45
  791. package/src/shell/language-code.ts +0 -5
  792. package/src/shell/option-switcher-menu.tsx +0 -84
  793. package/src/shell/sync-status-indicator.tsx +0 -303
  794. package/src/shell/top-bar-actions.tsx +0 -87
  795. package/src/shell/top-bar-brand.tsx +0 -78
  796. package/src/shell/top-bar-parts-labels.ts +0 -148
  797. package/src/shell/top-bar.tsx +0 -40
  798. package/src/shell/topbar-action-menu.tsx +0 -567
  799. package/src/shell/topbar-controls.tsx +0 -269
  800. package/src/shell/tour-menu.tsx +0 -187
  801. package/src/shell.ts +0 -108
  802. package/src/sync/db.ts +0 -129
  803. package/src/sync/engine.ts +0 -1011
  804. package/src/sync/hooks.ts +0 -98
  805. package/src/sync/testing.ts +0 -73
  806. package/src/sync/wire.ts +0 -61
  807. package/src/sync.ts +0 -37
  808. package/src/table-text.ts +0 -8
  809. package/src/testing/css-variables.generated.ts +0 -112
  810. package/src/testing/css-variables.ts +0 -189
  811. package/src/testing/index.ts +0 -29
  812. package/src/testing/react.ts +0 -58
  813. package/src/testing/tour.ts +0 -41
  814. package/src/testing/viewport.ts +0 -357
  815. package/src/theme/chart-palette.ts +0 -232
  816. package/src/theme/color.ts +0 -272
  817. package/src/theme/contrast-tokens.ts +0 -268
  818. package/src/theme/contrast.ts +0 -250
  819. package/src/theme/palette-derive.ts +0 -825
  820. package/src/theme/palette-presets.ts +0 -544
  821. package/src/theme/palette-store.ts +0 -108
  822. package/src/theme/text-size.ts +0 -289
  823. package/src/theme/theme-store.ts +0 -157
  824. package/src/tour/tour-catalogue-labels.ts +0 -75
  825. package/src/tour/tour-catalogue.tsx +0 -335
  826. package/src/tour/tour-dock.ts +0 -134
  827. package/src/tour/tour-engine.ts +0 -37
  828. package/src/tour/tour-gates.ts +0 -218
  829. package/src/tour/tour-guide-button.tsx +0 -64
  830. package/src/tour/tour-guide-labels.ts +0 -15
  831. package/src/tour/tour-info.ts +0 -144
  832. package/src/tour/tour-labels.ts +0 -23
  833. package/src/tour/tour-overlay.tsx +0 -1170
  834. package/src/tour/tour.tsx +0 -750
  835. package/src/tour/use-tour-progress.ts +0 -671
  836. package/src/tour/use-tour-url-sync.ts +0 -266
  837. package/src/tour.ts +0 -21
  838. package/src/vite-env.d.ts +0 -23
  839. package/src/wizard/stepper-nav.tsx +0 -360
  840. package/src/wizard/types.ts +0 -275
  841. package/src/wizard/use-wizard.ts +0 -560
  842. package/src/wizard/validation.ts +0 -39
  843. package/src/wizard/wizard-context.tsx +0 -74
  844. package/src/wizard/wizard-labels.ts +0 -29
  845. package/src/wizard/wizard-step.tsx +0 -76
  846. package/src/wizard/wizard-summary.tsx +0 -84
  847. 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
- }