@eifi1/ui-kit 0.21.0 → 0.23.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 (320) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +19 -14
  3. package/dist/components/account-chips.d.ts +10 -3
  4. package/dist/components/account-chips.js +5 -2
  5. package/dist/components/account-chips.js.map +1 -1
  6. package/dist/components/account-settings.d.ts +33 -2
  7. package/dist/components/account-settings.js +43 -21
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/amount-input.d.ts +53 -14
  10. package/dist/components/amount-input.js +17 -28
  11. package/dist/components/amount-input.js.map +1 -1
  12. package/dist/components/autocomplete.d.ts +29 -0
  13. package/dist/components/autocomplete.js +134 -104
  14. package/dist/components/autocomplete.js.map +1 -1
  15. package/dist/components/button-group.d.ts +19 -14
  16. package/dist/components/calculator.d.ts +19 -14
  17. package/dist/components/checkbox-group.d.ts +104 -0
  18. package/dist/components/checkbox-group.js +111 -0
  19. package/dist/components/checkbox-group.js.map +1 -0
  20. package/dist/components/checkbox.d.ts +19 -0
  21. package/dist/components/checkbox.js +30 -7
  22. package/dist/components/checkbox.js.map +1 -1
  23. package/dist/components/chip.d.ts +18 -0
  24. package/dist/components/chip.js +47 -10
  25. package/dist/components/chip.js.map +1 -1
  26. package/dist/components/column-mapper.d.ts +182 -0
  27. package/dist/components/column-mapper.js +375 -0
  28. package/dist/components/column-mapper.js.map +1 -0
  29. package/dist/components/combobox-core.d.ts +39 -2
  30. package/dist/components/combobox-core.js +29 -6
  31. package/dist/components/combobox-core.js.map +1 -1
  32. package/dist/components/combobox.d.ts +91 -2
  33. package/dist/components/combobox.js +454 -308
  34. package/dist/components/combobox.js.map +1 -1
  35. package/dist/components/confirm-dialog.d.ts +69 -161
  36. package/dist/components/confirm-dialog.js +7 -8
  37. package/dist/components/confirm-dialog.js.map +1 -1
  38. package/dist/components/copy-button.d.ts +16 -11
  39. package/dist/components/country-select.d.ts +167 -0
  40. package/dist/components/country-select.js +293 -0
  41. package/dist/components/country-select.js.map +1 -0
  42. package/dist/components/currency-select.d.ts +17 -1
  43. package/dist/components/currency-select.js +56 -23
  44. package/dist/components/currency-select.js.map +1 -1
  45. package/dist/components/danger-confirm.d.ts +69 -166
  46. package/dist/components/danger-confirm.js +144 -50
  47. package/dist/components/danger-confirm.js.map +1 -1
  48. package/dist/components/data-table-cells.d.ts +1 -2
  49. package/dist/components/data-table-filter-popover.d.ts +1 -2
  50. package/dist/components/data-table-filters.d.ts +1 -2
  51. package/dist/components/data-table-labels.d.ts +4 -97
  52. package/dist/components/data-table-labels.js +9 -1
  53. package/dist/components/data-table-labels.js.map +1 -1
  54. package/dist/components/data-table-pagination.d.ts +3 -1
  55. package/dist/components/data-table.d.ts +1 -2
  56. package/dist/components/data-table.js +98 -3
  57. package/dist/components/data-table.js.map +1 -1
  58. package/dist/components/date-picker.d.ts +66 -5
  59. package/dist/components/date-picker.js +378 -66
  60. package/dist/components/date-picker.js.map +1 -1
  61. package/dist/components/entity-combobox.d.ts +42 -1
  62. package/dist/components/entity-combobox.js +154 -113
  63. package/dist/components/entity-combobox.js.map +1 -1
  64. package/dist/components/facing-pair.d.ts +19 -14
  65. package/dist/components/field-parts.d.ts +224 -0
  66. package/dist/components/field-parts.js +156 -0
  67. package/dist/components/field-parts.js.map +1 -0
  68. package/dist/components/field-strip.d.ts +105 -0
  69. package/dist/components/field-strip.js +46 -0
  70. package/dist/components/field-strip.js.map +1 -0
  71. package/dist/components/file-button.d.ts +19 -14
  72. package/dist/components/file-button.js +13 -3
  73. package/dist/components/file-button.js.map +1 -1
  74. package/dist/components/file-dropzone.d.ts +41 -15
  75. package/dist/components/file-dropzone.js +57 -34
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/form-actions.d.ts +18 -13
  78. package/dist/components/form-actions.js +69 -5
  79. package/dist/components/form-actions.js.map +1 -1
  80. package/dist/components/iban-input.d.ts +69 -0
  81. package/dist/components/iban-input.js +125 -0
  82. package/dist/components/iban-input.js.map +1 -0
  83. package/dist/components/icon-picker.d.ts +21 -2
  84. package/dist/components/icon-picker.js +19 -4
  85. package/dist/components/icon-picker.js.map +1 -1
  86. package/dist/components/inline-edit-field.d.ts +167 -0
  87. package/dist/components/inline-edit-field.js +293 -0
  88. package/dist/components/inline-edit-field.js.map +1 -0
  89. package/dist/components/language-select.d.ts +130 -0
  90. package/dist/components/language-select.js +18 -0
  91. package/dist/components/language-select.js.map +1 -0
  92. package/dist/components/line-items.d.ts +25 -3
  93. package/dist/components/line-items.js +55 -46
  94. package/dist/components/line-items.js.map +1 -1
  95. package/dist/components/list.d.ts +25 -0
  96. package/dist/components/list.js +16 -5
  97. package/dist/components/list.js.map +1 -1
  98. package/dist/components/money-field.d.ts +32 -14
  99. package/dist/components/money-field.js.map +1 -1
  100. package/dist/components/month-picker.d.ts +52 -4
  101. package/dist/components/month-picker.js +291 -86
  102. package/dist/components/month-picker.js.map +1 -1
  103. package/dist/components/multi-entity-combobox.d.ts +26 -1
  104. package/dist/components/multi-entity-combobox.js +143 -102
  105. package/dist/components/multi-entity-combobox.js.map +1 -1
  106. package/dist/components/multi-select.d.ts +8 -1
  107. package/dist/components/multi-select.js +9 -4
  108. package/dist/components/multi-select.js.map +1 -1
  109. package/dist/components/number-field.d.ts +34 -19
  110. package/dist/components/number-field.js +6 -20
  111. package/dist/components/number-field.js.map +1 -1
  112. package/dist/components/number-input.d.ts +79 -19
  113. package/dist/components/number-input.js +90 -38
  114. package/dist/components/number-input.js.map +1 -1
  115. package/dist/components/numpad-sheet.d.ts +19 -14
  116. package/dist/components/one-time-code-input.d.ts +100 -0
  117. package/dist/components/one-time-code-input.js +137 -0
  118. package/dist/components/one-time-code-input.js.map +1 -0
  119. package/dist/components/phone-input.d.ts +69 -0
  120. package/dist/components/phone-input.js +171 -0
  121. package/dist/components/phone-input.js.map +1 -0
  122. package/dist/components/reauth-dialog.d.ts +2 -2
  123. package/dist/components/reauth-dialog.js +6 -8
  124. package/dist/components/reauth-dialog.js.map +1 -1
  125. package/dist/components/series-chart.d.ts +19 -14
  126. package/dist/components/settings-fields.d.ts +19 -14
  127. package/dist/components/share-card.d.ts +16 -11
  128. package/dist/components/sign-chip.d.ts +66 -0
  129. package/dist/components/sign-chip.js +39 -0
  130. package/dist/components/sign-chip.js.map +1 -0
  131. package/dist/components/swatch-picker.d.ts +43 -2
  132. package/dist/components/swatch-picker.js +80 -32
  133. package/dist/components/swatch-picker.js.map +1 -1
  134. package/dist/components/switch.d.ts +19 -0
  135. package/dist/components/switch.js +24 -6
  136. package/dist/components/switch.js.map +1 -1
  137. package/dist/components/text-link.d.ts +18 -13
  138. package/dist/components/tile-radio.d.ts +88 -13
  139. package/dist/components/tile-radio.js +28 -11
  140. package/dist/components/tile-radio.js.map +1 -1
  141. package/dist/components/time-input.d.ts +24 -15
  142. package/dist/components/time-input.js.map +1 -1
  143. package/dist/components/toggle-group.d.ts +47 -1
  144. package/dist/components/toggle-group.js +110 -69
  145. package/dist/components/toggle-group.js.map +1 -1
  146. package/dist/components/trigger-aria.d.ts +3 -0
  147. package/dist/components/trigger-aria.js +3 -1
  148. package/dist/components/trigger-aria.js.map +1 -1
  149. package/dist/components/ui.d.ts +16 -11
  150. package/dist/components/ui.js +284 -88
  151. package/dist/components/ui.js.map +1 -1
  152. package/dist/components/use-table-state.d.ts +1 -2
  153. package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
  154. package/dist/data-table.d.ts +1 -2
  155. package/dist/data-table.js +2 -1
  156. package/dist/data-table.js.map +1 -1
  157. package/dist/feedback/feedback-attachment.d.ts +1 -1
  158. package/dist/feedback/feedback-attachment.js +348 -41
  159. package/dist/feedback/feedback-attachment.js.map +1 -1
  160. package/dist/feedback/feedback-dialog.d.ts +1 -1
  161. package/dist/feedback/feedback-dialog.js.map +1 -1
  162. package/dist/feedback/feedback-inbox.d.ts +3 -2
  163. package/dist/feedback/feedback-inbox.js.map +1 -1
  164. package/dist/feedback/feedback-thread.d.ts +68 -253
  165. package/dist/feedback/feedback-thread.js +46 -10
  166. package/dist/feedback/feedback-thread.js.map +1 -1
  167. package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
  168. package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
  169. package/dist/feedback.d.ts +66 -2
  170. package/dist/hooks/use-file-drop.d.ts +19 -14
  171. package/dist/i18n/defaults.d.ts +19 -14
  172. package/dist/i18n/defaults.js +19 -2
  173. package/dist/i18n/defaults.js.map +1 -1
  174. package/dist/i18n/german.d.ts +19 -14
  175. package/dist/i18n/german.js +84 -5
  176. package/dist/i18n/german.js.map +1 -1
  177. package/dist/i18n/kit-labels.d.ts +15 -10
  178. package/dist/i18n/kit-labels.js +8 -2
  179. package/dist/i18n/kit-labels.js.map +1 -1
  180. package/dist/i18n/languages.d.ts +19 -14
  181. package/dist/i18n/locales/de-CH.d.ts +19 -14
  182. package/dist/i18n/locales/en.d.ts +19 -14
  183. package/dist/i18n/locales/en.js +16 -0
  184. package/dist/i18n/locales/en.js.map +1 -1
  185. package/dist/i18n/locales/es.d.ts +19 -14
  186. package/dist/i18n/locales/es.js +83 -5
  187. package/dist/i18n/locales/es.js.map +1 -1
  188. package/dist/i18n/locales/fr.d.ts +19 -14
  189. package/dist/i18n/locales/fr.js +82 -5
  190. package/dist/i18n/locales/fr.js.map +1 -1
  191. package/dist/i18n/locales/hu.d.ts +19 -14
  192. package/dist/i18n/locales/hu.js +85 -5
  193. package/dist/i18n/locales/hu.js.map +1 -1
  194. package/dist/i18n/locales/it.d.ts +19 -14
  195. package/dist/i18n/locales/it.js +84 -5
  196. package/dist/i18n/locales/it.js.map +1 -1
  197. package/dist/i18n/locales/zh.d.ts +19 -14
  198. package/dist/i18n/locales/zh.js +80 -5
  199. package/dist/i18n/locales/zh.js.map +1 -1
  200. package/dist/i18n/review.d.ts +19 -14
  201. package/dist/i18n/review.js +27 -1
  202. package/dist/i18n/review.js.map +1 -1
  203. package/dist/index.d.ts +21 -10
  204. package/dist/index.js +73 -2
  205. package/dist/index.js.map +1 -1
  206. package/dist/lib/column-mapping.d.ts +81 -0
  207. package/dist/lib/column-mapping.js +108 -0
  208. package/dist/lib/column-mapping.js.map +1 -0
  209. package/dist/lib/countries.d.ts +43 -0
  210. package/dist/lib/countries.js +39 -0
  211. package/dist/lib/countries.js.map +1 -0
  212. package/dist/lib/decimal-marks.d.ts +46 -0
  213. package/dist/lib/decimal-marks.js +40 -0
  214. package/dist/lib/decimal-marks.js.map +1 -0
  215. package/dist/lib/iban.d.ts +98 -0
  216. package/dist/lib/iban.js +154 -0
  217. package/dist/lib/iban.js.map +1 -0
  218. package/dist/lib/isin.d.ts +30 -0
  219. package/dist/lib/isin.js +36 -0
  220. package/dist/lib/isin.js.map +1 -0
  221. package/dist/lib/phone.d.ts +69 -0
  222. package/dist/lib/phone.js +121 -0
  223. package/dist/lib/phone.js.map +1 -0
  224. package/dist/lib/table-text.d.ts +109 -1
  225. package/dist/lib/table-text.js +122 -1
  226. package/dist/lib/table-text.js.map +1 -1
  227. package/dist/rhf/fields.d.ts +233 -18
  228. package/dist/rhf/fields.js +456 -3
  229. package/dist/rhf/fields.js.map +1 -1
  230. package/dist/rhf/form.d.ts +19 -14
  231. package/dist/rhf.d.ts +22 -15
  232. package/dist/rhf.js.map +1 -1
  233. package/dist/shell/app-shell.d.ts +18 -13
  234. package/dist/shell/top-bar-brand.d.ts +19 -14
  235. package/dist/shell/topbar-action-menu.d.ts +36 -3
  236. package/dist/shell/topbar-action-menu.js +74 -33
  237. package/dist/shell/topbar-action-menu.js.map +1 -1
  238. package/dist/shell.d.ts +18 -13
  239. package/dist/table-text.d.ts +1 -1
  240. package/dist/tour/tour.js +20 -24
  241. package/dist/tour/tour.js.map +1 -1
  242. package/dist/wizard/stepper-nav.d.ts +49 -16
  243. package/dist/wizard/stepper-nav.js +4 -0
  244. package/dist/wizard/stepper-nav.js.map +1 -1
  245. package/dist/wizard.d.ts +19 -14
  246. package/package.json +1 -1
  247. package/src/components/account-chips.tsx +17 -3
  248. package/src/components/account-settings.tsx +92 -26
  249. package/src/components/amount-input.tsx +68 -56
  250. package/src/components/autocomplete.tsx +198 -114
  251. package/src/components/checkbox-group.tsx +229 -0
  252. package/src/components/checkbox.tsx +72 -8
  253. package/src/components/chip.tsx +80 -10
  254. package/src/components/column-mapper.tsx +666 -0
  255. package/src/components/combobox-core.tsx +82 -9
  256. package/src/components/combobox.tsx +661 -340
  257. package/src/components/confirm-dialog.tsx +12 -8
  258. package/src/components/country-select.tsx +556 -0
  259. package/src/components/currency-select.tsx +109 -33
  260. package/src/components/danger-confirm.tsx +429 -82
  261. package/src/components/data-table-labels.ts +16 -1
  262. package/src/components/data-table.tsx +175 -6
  263. package/src/components/date-picker.tsx +595 -71
  264. package/src/components/entity-combobox.tsx +235 -124
  265. package/src/components/field-parts.tsx +403 -0
  266. package/src/components/field-strip.tsx +149 -0
  267. package/src/components/file-button.tsx +36 -3
  268. package/src/components/file-dropzone.tsx +106 -38
  269. package/src/components/form-actions.tsx +147 -6
  270. package/src/components/iban-input.tsx +231 -0
  271. package/src/components/icon-picker.tsx +47 -6
  272. package/src/components/inline-edit-field.tsx +533 -0
  273. package/src/components/language-select.tsx +78 -0
  274. package/src/components/line-items.tsx +35 -2
  275. package/src/components/list.tsx +55 -6
  276. package/src/components/money-field.tsx +13 -0
  277. package/src/components/month-picker.tsx +400 -63
  278. package/src/components/multi-entity-combobox.tsx +207 -120
  279. package/src/components/multi-select.tsx +80 -58
  280. package/src/components/number-field.tsx +28 -46
  281. package/src/components/number-input.tsx +210 -43
  282. package/src/components/one-time-code-input.tsx +288 -0
  283. package/src/components/phone-input.tsx +299 -0
  284. package/src/components/reauth-dialog.tsx +17 -18
  285. package/src/components/sign-chip.tsx +103 -0
  286. package/src/components/swatch-picker.tsx +143 -31
  287. package/src/components/switch.tsx +58 -7
  288. package/src/components/tile-radio.tsx +145 -29
  289. package/src/components/time-input.tsx +8 -1
  290. package/src/components/toggle-group.tsx +206 -73
  291. package/src/components/trigger-aria.ts +5 -0
  292. package/src/components/ui.tsx +479 -138
  293. package/src/data-table.ts +2 -1
  294. package/src/feedback/feedback-attachment.tsx +597 -46
  295. package/src/feedback/feedback-dialog.tsx +9 -3
  296. package/src/feedback/feedback-inbox.tsx +3 -2
  297. package/src/feedback/feedback-thread.tsx +113 -9
  298. package/src/i18n/defaults.ts +18 -1
  299. package/src/i18n/german.ts +89 -0
  300. package/src/i18n/kit-labels.tsx +82 -2
  301. package/src/i18n/locales/en.ts +36 -4
  302. package/src/i18n/locales/es.ts +87 -0
  303. package/src/i18n/locales/fr.ts +87 -0
  304. package/src/i18n/locales/hu.ts +83 -0
  305. package/src/i18n/locales/it.ts +88 -0
  306. package/src/i18n/locales/zh.ts +76 -0
  307. package/src/i18n/review.ts +26 -0
  308. package/src/index.ts +71 -3
  309. package/src/lib/column-mapping.ts +234 -0
  310. package/src/lib/countries.ts +91 -0
  311. package/src/lib/decimal-marks.ts +99 -0
  312. package/src/lib/iban.ts +171 -0
  313. package/src/lib/isin.ts +62 -0
  314. package/src/lib/phone.ts +211 -0
  315. package/src/lib/table-text.ts +271 -0
  316. package/src/rhf/fields.tsx +860 -9
  317. package/src/rhf.ts +3 -1
  318. package/src/shell/topbar-action-menu.tsx +134 -38
  319. package/src/tour/tour.tsx +66 -28
  320. package/src/wizard/stepper-nav.tsx +35 -1
@@ -1,9 +1,16 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "../lib/cn.js";
4
- import { useEffect, useLayoutEffect, useRef, useState } from "react";
5
- import { Camera, FileText, Paperclip, X } from "lucide-react";
6
- import { Button } from "../components/ui.js";
4
+ import {
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import { Camera, FileText, Image as ImageIcon, Paperclip, X } from "lucide-react";
11
+ import { Button, IconButton, Spinner } from "../components/ui.js";
12
+ import { DEFAULT_FILE_PICKER_LABELS, formatAccept } from "../components/file-button.js";
13
+ import { useCommitReason } from "../components/write-lock.js";
7
14
  import { useKitFileLabels, useKitLabels } from "../i18n/kit-labels.js";
8
15
  const DEFAULT_ATTACHMENT_ACCEPT = ["image/png", "image/jpeg", "image/webp", "image/gif"];
9
16
  const DEFAULT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
@@ -16,9 +23,12 @@ const DEFAULT_FEEDBACK_ATTACHMENT_LABELS = {
16
23
  attachmentList: "Attachments",
17
24
  attachmentScreenshot: "Screenshot",
18
25
  attachmentRemoveFile: (name) => `Remove ${name}`,
19
- attachmentLimit: (max) => `Up to ${max} ${max === 1 ? "attachment" : "attachments"} \u2014 remove one to add another.`
26
+ attachmentLimit: (max) => `Up to ${max} ${max === 1 ? "attachment" : "attachments"} \u2014 remove one to add another.`,
27
+ attachmentUploading: "Uploading\u2026",
28
+ attachmentUploadFailed: (name) => `\u201C${name}\u201D could not be uploaded`
20
29
  };
21
30
  function FeedbackAttachmentField(props) {
31
+ if (props.refs) return /* @__PURE__ */ jsx(RefsField, { ...props });
22
32
  return props.multiple ? /* @__PURE__ */ jsx(MultipleField, { ...props }) : /* @__PURE__ */ jsx(SingleField, { ...props });
23
33
  }
24
34
  function SingleField({
@@ -31,6 +41,7 @@ function SingleField({
31
41
  onCaptureScreenshot,
32
42
  documentPaste = false,
33
43
  pasteFrom,
44
+ disabled = false,
34
45
  className
35
46
  }) {
36
47
  const [previewFor, setPreviewFor] = useState(null);
@@ -38,6 +49,7 @@ function SingleField({
38
49
  const fileInputRef = useRef(null);
39
50
  const fileText = useKitFileLabels();
40
51
  const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
52
+ const errorInfo = useErrorInfo(accept, maxBytes, void 0, text);
41
53
  useEffect(() => {
42
54
  if (!value) return;
43
55
  const url = URL.createObjectURL(value);
@@ -48,7 +60,7 @@ function SingleField({
48
60
  if (!file) return;
49
61
  const problem = rejection(file, accept, maxBytes);
50
62
  if (problem) {
51
- onError?.(problem);
63
+ onError?.(problem, errorInfo(problem, [file]));
52
64
  return;
53
65
  }
54
66
  onChange(file);
@@ -59,7 +71,8 @@ function SingleField({
59
71
  pick(new File([file], pastedName(file.type), { type: file.type }));
60
72
  },
61
73
  documentPaste,
62
- pasteFrom
74
+ pasteFrom,
75
+ disabled
63
76
  );
64
77
  return /* @__PURE__ */ jsxs(
65
78
  "div",
@@ -85,24 +98,32 @@ function SingleField({
85
98
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm text-[var(--text-secondary)]", children: value.name }),
86
99
  /* @__PURE__ */ jsx("div", { className: "text-xs text-[var(--text-muted)]", children: fileText.size(value.size) })
87
100
  ] }),
88
- /* @__PURE__ */ jsx(
89
- "button",
90
- {
91
- type: "button",
92
- onClick: () => onChange(null),
93
- "aria-label": text.attachmentRemove,
94
- className: "rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
95
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
96
- }
97
- )
101
+ /* @__PURE__ */ jsx(RemoveButton, { label: text.attachmentRemove, onClick: () => onChange(null), disabled })
98
102
  ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
99
103
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
100
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", onClick: () => fileInputRef.current?.click(), children: [
101
- /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
102
- " ",
103
- text.attachmentAdd
104
- ] }),
105
- onCaptureScreenshot && /* @__PURE__ */ jsx(CaptureButton, { capture: onCaptureScreenshot, onFile: pick, label: text.attachmentCapture })
104
+ /* @__PURE__ */ jsxs(
105
+ Button,
106
+ {
107
+ type: "button",
108
+ variant: "secondary",
109
+ disabled,
110
+ onClick: () => fileInputRef.current?.click(),
111
+ children: [
112
+ /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
113
+ " ",
114
+ text.attachmentAdd
115
+ ]
116
+ }
117
+ ),
118
+ onCaptureScreenshot && /* @__PURE__ */ jsx(
119
+ CaptureButton,
120
+ {
121
+ capture: onCaptureScreenshot,
122
+ onFile: pick,
123
+ label: text.attachmentCapture,
124
+ disabled
125
+ }
126
+ )
106
127
  ] }),
107
128
  /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: text.attachmentPaste })
108
129
  ] }),
@@ -113,6 +134,7 @@ function SingleField({
113
134
  type: "file",
114
135
  accept: accept.join(","),
115
136
  className: "sr-only",
137
+ disabled,
116
138
  onChange: (e) => {
117
139
  pick(e.target.files?.[0]);
118
140
  e.target.value = "";
@@ -136,6 +158,7 @@ function MultipleField({
136
158
  onCaptureScreenshot,
137
159
  documentPaste = false,
138
160
  pasteFrom,
161
+ disabled = false,
139
162
  className
140
163
  }) {
141
164
  const rootRef = useRef(null);
@@ -145,6 +168,7 @@ function MultipleField({
145
168
  const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile;
146
169
  const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit;
147
170
  const screenshotLabel = text.attachmentScreenshot ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentScreenshot;
171
+ const errorInfo = useErrorInfo(accept, maxBytes, max, text);
148
172
  const latest = useRef(value);
149
173
  useLayoutEffect(() => {
150
174
  latest.current = value;
@@ -161,29 +185,29 @@ function MultipleField({
161
185
  const accepted = [];
162
186
  const taken = new Set([...current, ...screenshot ? [screenshot] : []].map((f) => f.name));
163
187
  for (const raw of incoming) {
164
- const problem = rejection(raw, accept, maxBytes);
188
+ const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
189
+ const problem = rejection(file, accept, maxBytes);
165
190
  if (problem) {
166
- onError?.(problem);
191
+ onError?.(problem, errorInfo(problem, [file]));
167
192
  continue;
168
193
  }
169
- const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
170
194
  taken.add(file.name);
171
195
  if (!current.includes(file)) accepted.push(file);
172
196
  }
173
197
  if (accepted.length === 0) return;
174
198
  const space = Math.max(0, max - current.length);
175
- if (accepted.length > space) onError?.("count");
199
+ if (accepted.length > space) onError?.("count", errorInfo("count", accepted.slice(space)));
176
200
  const kept = accepted.slice(0, space);
177
201
  if (kept.length > 0) commit([...current, ...kept]);
178
202
  };
179
- const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom);
203
+ const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, disabled);
180
204
  const capture = (file) => {
181
205
  if (!hasSlot) {
182
206
  add([file]);
183
207
  return;
184
208
  }
185
209
  const problem = rejection(file, accept, maxBytes);
186
- if (problem) onError?.(problem);
210
+ if (problem) onError?.(problem, errorInfo(problem, [file]));
187
211
  else onScreenshotChange(file);
188
212
  };
189
213
  const pendingFocus = useRef(null);
@@ -222,17 +246,14 @@ function MultipleField({
222
246
  /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-[var(--text-muted)]", children: chip.screenshot ? `${chip.file.name} \xB7 ${fileText.size(chip.file.size)}` : fileText.size(chip.file.size) })
223
247
  ] }),
224
248
  /* @__PURE__ */ jsx(
225
- "button",
249
+ RemoveButton,
226
250
  {
227
- type: "button",
228
- "data-attachment-remove": "",
251
+ label: removeLabel(chip.name),
252
+ disabled,
229
253
  onClick: () => {
230
254
  pendingFocus.current = index;
231
255
  chip.remove();
232
- },
233
- "aria-label": removeLabel(chip.name),
234
- className: "shrink-0 rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
235
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
256
+ }
236
257
  }
237
258
  )
238
259
  ]
@@ -246,6 +267,7 @@ function MultipleField({
246
267
  type: "button",
247
268
  variant: "secondary",
248
269
  "data-attachment-action": "",
270
+ disabled,
249
271
  onClick: () => fileInputRef.current?.click(),
250
272
  children: [
251
273
  /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
@@ -260,6 +282,7 @@ function MultipleField({
260
282
  capture: onCaptureScreenshot,
261
283
  onFile: capture,
262
284
  label: text.attachmentCapture,
285
+ disabled,
263
286
  "data-attachment-action": ""
264
287
  }
265
288
  )
@@ -275,6 +298,225 @@ function MultipleField({
275
298
  className: "sr-only",
276
299
  tabIndex: -1,
277
300
  "aria-hidden": true,
301
+ disabled,
302
+ onChange: (e) => {
303
+ add(Array.from(e.target.files ?? []));
304
+ e.target.value = "";
305
+ }
306
+ }
307
+ )
308
+ ] });
309
+ }
310
+ let nextUploadId = 0;
311
+ function RefsField({
312
+ value,
313
+ onChange,
314
+ onUpload,
315
+ onRemove,
316
+ onUploadingChange,
317
+ max = DEFAULT_MAX_ATTACHMENTS,
318
+ labels: labelsProp,
319
+ accept = DEFAULT_ATTACHMENT_ACCEPT,
320
+ maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
321
+ onError,
322
+ onCaptureScreenshot,
323
+ documentPaste = false,
324
+ pasteFrom,
325
+ disabled = false,
326
+ commit: commits,
327
+ disabledReason: ownDisabledReason,
328
+ className
329
+ }) {
330
+ const rootRef = useRef(null);
331
+ const fileInputRef = useRef(null);
332
+ const fileText = useKitFileLabels();
333
+ const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
334
+ const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile;
335
+ const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit;
336
+ const uploadingLabel = text.attachmentUploading ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploading;
337
+ const errorInfo = useErrorInfo(accept, maxBytes, max, text);
338
+ const disabledReason = useCommitReason(commits, ownDisabledReason);
339
+ const locked = hasReason(disabledReason);
340
+ const reason = locked ? disabledReason : void 0;
341
+ const inert = disabled || locked;
342
+ const latest = useRef(value);
343
+ useLayoutEffect(() => {
344
+ latest.current = value;
345
+ });
346
+ const commit = (next) => {
347
+ latest.current = next;
348
+ onChange(next);
349
+ };
350
+ const [uploads, setUploads] = useState([]);
351
+ const inFlight = useRef([]);
352
+ const mounted = useRef(true);
353
+ useEffect(() => {
354
+ mounted.current = true;
355
+ return () => {
356
+ mounted.current = false;
357
+ };
358
+ }, []);
359
+ const uploadingChange = useRef(onUploadingChange);
360
+ useEffect(() => {
361
+ uploadingChange.current = onUploadingChange;
362
+ });
363
+ const room = Math.max(0, max - value.length - uploads.length);
364
+ const start = (file) => {
365
+ const upload = { id: nextUploadId++, file };
366
+ inFlight.current = [...inFlight.current, upload];
367
+ if (inFlight.current.length === 1) uploadingChange.current?.(true);
368
+ setUploads(inFlight.current);
369
+ const settle = () => {
370
+ inFlight.current = inFlight.current.filter((u) => u.id !== upload.id);
371
+ if (!mounted.current) return;
372
+ setUploads(inFlight.current);
373
+ if (inFlight.current.length === 0) uploadingChange.current?.(false);
374
+ };
375
+ let answer;
376
+ try {
377
+ answer = Promise.resolve(onUpload(file));
378
+ } catch (error) {
379
+ answer = Promise.reject(error);
380
+ }
381
+ answer.then(
382
+ (ref) => {
383
+ settle();
384
+ if (mounted.current) commit([...latest.current, ref]);
385
+ },
386
+ (error) => {
387
+ settle();
388
+ if (mounted.current) onError?.("upload", error, errorInfo("upload", [file], error));
389
+ }
390
+ );
391
+ };
392
+ const add = (incoming, pasted = false) => {
393
+ let space = Math.max(0, max - latest.current.length - inFlight.current.length);
394
+ const taken = /* @__PURE__ */ new Set([...latest.current.map((r) => r.name), ...inFlight.current.map((u) => u.file.name)]);
395
+ const dropped = [];
396
+ for (const raw of incoming) {
397
+ const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
398
+ const problem = rejection(file, accept, maxBytes);
399
+ if (problem) {
400
+ onError?.(problem, void 0, errorInfo(problem, [file]));
401
+ continue;
402
+ }
403
+ taken.add(file.name);
404
+ if (space === 0) {
405
+ dropped.push(file);
406
+ continue;
407
+ }
408
+ space -= 1;
409
+ start(file);
410
+ }
411
+ if (dropped.length > 0) onError?.("count", void 0, errorInfo("count", dropped));
412
+ };
413
+ const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, inert);
414
+ const pendingFocus = useRef(null);
415
+ useEffect(() => {
416
+ const index = pendingFocus.current;
417
+ if (index === null) return;
418
+ pendingFocus.current = null;
419
+ const root = rootRef.current;
420
+ if (!root) return;
421
+ const removes = root.querySelectorAll("[data-attachment-remove]");
422
+ const target = removes[index] ?? root.querySelector("[data-attachment-action]") ?? removes[index - 1];
423
+ target?.focus();
424
+ }, [value]);
425
+ const remove = (key, index) => {
426
+ if (inert) return;
427
+ pendingFocus.current = index;
428
+ commit(latest.current.filter((r) => r.key !== key));
429
+ onRemove?.(key);
430
+ };
431
+ const hasChips = value.length > 0 || uploads.length > 0;
432
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative space-y-2", className), onPaste, children: [
433
+ /* @__PURE__ */ jsx(Heading, { text: labelsProp?.attachment, className: "mb-0" }),
434
+ hasChips && /* @__PURE__ */ jsxs("ul", { "aria-label": text.attachmentList, className: "flex flex-col gap-2", children: [
435
+ value.map((ref, index) => /* @__PURE__ */ jsxs(
436
+ "li",
437
+ {
438
+ className: "flex min-w-0 items-center gap-2 rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-1",
439
+ children: [
440
+ /* @__PURE__ */ jsx(RefGlyph, { type: ref.type }),
441
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
442
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm text-[var(--text-secondary)]", children: ref.name }),
443
+ ref.size !== void 0 && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-[var(--text-muted)]", children: fileText.size(ref.size) })
444
+ ] }),
445
+ /* @__PURE__ */ jsx(
446
+ RemoveButton,
447
+ {
448
+ label: removeLabel(ref.name),
449
+ onClick: () => remove(ref.key, index),
450
+ disabled,
451
+ reason
452
+ }
453
+ )
454
+ ]
455
+ },
456
+ `ref:${ref.key}`
457
+ )),
458
+ uploads.map((upload) => /* @__PURE__ */ jsxs(
459
+ "li",
460
+ {
461
+ "aria-busy": "true",
462
+ "data-attachment-uploading": "",
463
+ className: "flex min-w-0 items-center gap-2 rounded-md border border-dashed border-[var(--border)] bg-[var(--bg-surface)] p-1",
464
+ children: [
465
+ /* @__PURE__ */ jsx("span", { className: "flex size-12 shrink-0 items-center justify-center rounded border border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-placeholder)]", children: /* @__PURE__ */ jsx(Spinner, { label: null, className: "size-5" }) }),
466
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
467
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm text-[var(--text-secondary)]", children: upload.file.name }),
468
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-[var(--text-muted)]", children: uploadingLabel })
469
+ ] })
470
+ ]
471
+ },
472
+ `upload:${upload.id}`
473
+ ))
474
+ ] }),
475
+ room > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
476
+ /* @__PURE__ */ jsxs(
477
+ Button,
478
+ {
479
+ type: "button",
480
+ variant: "secondary",
481
+ "data-attachment-action": "",
482
+ disabled,
483
+ disabledReason: reason,
484
+ onClick: () => fileInputRef.current?.click(),
485
+ children: [
486
+ /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
487
+ " ",
488
+ text.attachmentAdd
489
+ ]
490
+ }
491
+ ),
492
+ onCaptureScreenshot && /* @__PURE__ */ jsx(
493
+ CaptureButton,
494
+ {
495
+ capture: onCaptureScreenshot,
496
+ onFile: (file) => add([file]),
497
+ label: text.attachmentCapture,
498
+ disabled,
499
+ disabledReason: reason,
500
+ "data-attachment-action": ""
501
+ }
502
+ )
503
+ ] }),
504
+ locked ? (
505
+ // Where the paste hint was: a phone never hovers, so the Tooltip alone would
506
+ // leave a touch user with buttons that do nothing and no word why.
507
+ /* @__PURE__ */ jsx("div", { "data-attachment-reason": "", className: "text-xs text-[var(--text-muted)]", children: disabledReason })
508
+ ) : /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: room > 0 ? text.attachmentPaste : limitLabel(max) }),
509
+ /* @__PURE__ */ jsx(
510
+ "input",
511
+ {
512
+ ref: fileInputRef,
513
+ type: "file",
514
+ multiple: room > 1,
515
+ accept: accept.join(","),
516
+ className: "sr-only",
517
+ tabIndex: -1,
518
+ "aria-hidden": true,
519
+ disabled: inert,
278
520
  onChange: (e) => {
279
521
  add(Array.from(e.target.files ?? []));
280
522
  e.target.value = "";
@@ -283,6 +525,9 @@ function MultipleField({
283
525
  )
284
526
  ] });
285
527
  }
528
+ function RefGlyph({ type }) {
529
+ return /* @__PURE__ */ jsx("span", { className: "flex size-12 shrink-0 items-center justify-center rounded border border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-placeholder)]", children: type?.startsWith("image/") ? /* @__PURE__ */ jsx(ImageIcon, { "aria-hidden": true, className: "size-6" }) : /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "size-6" }) });
530
+ }
286
531
  function Heading({ text, className }) {
287
532
  if (!text) return null;
288
533
  return /* @__PURE__ */ jsx("div", { className: cn("mb-1 text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]", className), children: text });
@@ -291,6 +536,8 @@ function CaptureButton({
291
536
  capture,
292
537
  onFile,
293
538
  label,
539
+ disabled = false,
540
+ disabledReason,
294
541
  ...rest
295
542
  }) {
296
543
  const [capturing, setCapturing] = useState(false);
@@ -299,10 +546,11 @@ function CaptureButton({
299
546
  {
300
547
  type: "button",
301
548
  variant: "secondary",
302
- disabled: capturing,
549
+ disabled: capturing || disabled,
550
+ disabledReason,
303
551
  ...rest,
304
552
  onClick: () => {
305
- if (capturing) return;
553
+ if (capturing || disabled) return;
306
554
  setCapturing(true);
307
555
  void capture().then((file) => {
308
556
  if (file) onFile(file);
@@ -316,6 +564,60 @@ function CaptureButton({
316
564
  }
317
565
  );
318
566
  }
567
+ function RemoveButton({
568
+ label,
569
+ onClick,
570
+ disabled,
571
+ reason
572
+ }) {
573
+ return /* @__PURE__ */ jsx(
574
+ IconButton,
575
+ {
576
+ type: "button",
577
+ size: "xs",
578
+ tone: "muted",
579
+ "data-attachment-remove": "",
580
+ "aria-label": label,
581
+ disabled,
582
+ disabledReason: reason,
583
+ onClick,
584
+ className: "shrink-0",
585
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
586
+ }
587
+ );
588
+ }
589
+ function hasReason(reason) {
590
+ return reason !== void 0 && reason !== null && reason !== false && reason !== "";
591
+ }
592
+ function useErrorInfo(accept, maxBytes, max, text) {
593
+ const picker = useKitLabels("filePicker", DEFAULT_FILE_PICKER_LABELS);
594
+ const fileText = useKitFileLabels();
595
+ const uploadFailed = text.attachmentUploadFailed ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploadFailed;
596
+ return (kind, files, error) => {
597
+ const file = files[0];
598
+ let message;
599
+ if (kind === "type") {
600
+ const acceptText = formatAccept(accept.join(","));
601
+ const useOnly = acceptText !== "" && (picker.rejectedTypeOnly !== DEFAULT_FILE_PICKER_LABELS.rejectedTypeOnly || picker.rejectedType === DEFAULT_FILE_PICKER_LABELS.rejectedType);
602
+ message = useOnly ? picker.rejectedTypeOnly(acceptText, file.name) : picker.rejectedType(file.name);
603
+ } else if (kind === "size") {
604
+ message = picker.rejectedSize(file.name, fileText.size(maxBytes));
605
+ } else if (kind === "count") {
606
+ message = files.length === 1 ? picker.rejectedCount(file.name, max ?? 1) : picker.rejectedMany(files.length);
607
+ } else {
608
+ message = uploadFailed(file.name);
609
+ }
610
+ return {
611
+ file,
612
+ files,
613
+ message,
614
+ maxBytes,
615
+ accept,
616
+ ...max !== void 0 ? { max } : null,
617
+ ...kind === "upload" ? { error } : null
618
+ };
619
+ };
620
+ }
319
621
  function ChipPreview({ file, screenshot }) {
320
622
  const isImage = file.type.startsWith("image/");
321
623
  const [previewFor, setPreviewFor] = useState(null);
@@ -358,15 +660,17 @@ function uniqueName(name, taken) {
358
660
  while (taken.has(`${stem}-${n}${ext}`)) n++;
359
661
  return `${stem}-${n}${ext}`;
360
662
  }
361
- function usePaste(onImages, documentPaste, pasteFrom) {
362
- const latest = useRef(onImages);
663
+ function usePaste(onImages, documentPaste, pasteFrom, off) {
664
+ const handler = off ? null : onImages;
665
+ const latest = useRef(handler);
363
666
  useEffect(() => {
364
- latest.current = onImages;
667
+ latest.current = handler;
365
668
  });
366
669
  useEffect(() => {
367
670
  const target = documentPaste ? document : pasteFrom?.current ?? null;
368
671
  if (!target) return;
369
672
  const onPaste = (event) => {
673
+ if (!latest.current) return;
370
674
  const clipboard = event.clipboardData;
371
675
  takeImages(clipboard, () => event.preventDefault(), latest.current);
372
676
  };
@@ -374,7 +678,10 @@ function usePaste(onImages, documentPaste, pasteFrom) {
374
678
  return () => target.removeEventListener("paste", onPaste);
375
679
  }, [documentPaste, pasteFrom]);
376
680
  const listensElsewhere = documentPaste || pasteFrom !== void 0;
377
- return listensElsewhere ? void 0 : (event) => takeImages(event.clipboardData, () => event.preventDefault(), latest.current);
681
+ return listensElsewhere ? void 0 : (event) => {
682
+ if (!latest.current) return;
683
+ takeImages(event.clipboardData, () => event.preventDefault(), latest.current);
684
+ };
378
685
  }
379
686
  function takeImages(clipboard, stop, onImages) {
380
687
  const images = Array.from(clipboard?.items ?? []).filter((item) => item.kind === "file" && item.type.startsWith("image/")).map((item) => item.getAsFile()).filter((file) => file !== null);