@eifi1/ui-kit 0.22.0 → 0.24.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 (208) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +8 -7
  3. package/dist/components/amount-input.d.ts +34 -7
  4. package/dist/components/amount-input.js +14 -10
  5. package/dist/components/amount-input.js.map +1 -1
  6. package/dist/components/autocomplete.d.ts +26 -0
  7. package/dist/components/autocomplete.js +164 -137
  8. package/dist/components/autocomplete.js.map +1 -1
  9. package/dist/components/button-group.d.ts +8 -7
  10. package/dist/components/calculator.d.ts +8 -7
  11. package/dist/components/column-mapper.d.ts +235 -0
  12. package/dist/components/column-mapper.js +411 -0
  13. package/dist/components/column-mapper.js.map +1 -0
  14. package/dist/components/combobox-core.d.ts +39 -2
  15. package/dist/components/combobox-core.js +29 -6
  16. package/dist/components/combobox-core.js.map +1 -1
  17. package/dist/components/combobox.d.ts +113 -3
  18. package/dist/components/combobox.js +456 -313
  19. package/dist/components/combobox.js.map +1 -1
  20. package/dist/components/confirm-dialog.d.ts +8 -7
  21. package/dist/components/confirm-dialog.js +7 -8
  22. package/dist/components/confirm-dialog.js.map +1 -1
  23. package/dist/components/copy-button.d.ts +5 -4
  24. package/dist/components/country-select.d.ts +50 -7
  25. package/dist/components/country-select.js +65 -9
  26. package/dist/components/country-select.js.map +1 -1
  27. package/dist/components/danger-confirm.d.ts +8 -7
  28. package/dist/components/danger-confirm.js +64 -7
  29. package/dist/components/danger-confirm.js.map +1 -1
  30. package/dist/components/date-picker.d.ts +41 -0
  31. package/dist/components/date-picker.js +269 -8
  32. package/dist/components/date-picker.js.map +1 -1
  33. package/dist/components/entity-combobox.d.ts +48 -2
  34. package/dist/components/entity-combobox.js +159 -114
  35. package/dist/components/entity-combobox.js.map +1 -1
  36. package/dist/components/facing-pair.d.ts +8 -7
  37. package/dist/components/field-parts.d.ts +116 -6
  38. package/dist/components/field-parts.js +84 -0
  39. package/dist/components/field-parts.js.map +1 -1
  40. package/dist/components/field-strip.d.ts +105 -0
  41. package/dist/components/field-strip.js +46 -0
  42. package/dist/components/field-strip.js.map +1 -0
  43. package/dist/components/file-button.d.ts +8 -7
  44. package/dist/components/file-dropzone.d.ts +8 -7
  45. package/dist/components/form-actions.d.ts +8 -7
  46. package/dist/components/form-actions.js +23 -3
  47. package/dist/components/form-actions.js.map +1 -1
  48. package/dist/components/iban-input.d.ts +8 -7
  49. package/dist/components/icon-picker.d.ts +10 -1
  50. package/dist/components/icon-picker.js +12 -4
  51. package/dist/components/icon-picker.js.map +1 -1
  52. package/dist/components/language-select.d.ts +8 -7
  53. package/dist/components/money-field.d.ts +11 -7
  54. package/dist/components/money-field.js.map +1 -1
  55. package/dist/components/month-picker.js +2 -1
  56. package/dist/components/month-picker.js.map +1 -1
  57. package/dist/components/multi-entity-combobox.d.ts +31 -2
  58. package/dist/components/multi-entity-combobox.js +152 -103
  59. package/dist/components/multi-entity-combobox.js.map +1 -1
  60. package/dist/components/number-field.d.ts +8 -7
  61. package/dist/components/number-input.d.ts +8 -7
  62. package/dist/components/numpad-sheet.d.ts +8 -7
  63. package/dist/components/phone-input.d.ts +8 -7
  64. package/dist/components/reauth-dialog.d.ts +2 -2
  65. package/dist/components/reauth-dialog.js +6 -8
  66. package/dist/components/reauth-dialog.js.map +1 -1
  67. package/dist/components/series-chart.d.ts +8 -7
  68. package/dist/components/settings-fields.d.ts +8 -7
  69. package/dist/components/share-card.d.ts +8 -7
  70. package/dist/components/swatch-picker.d.ts +17 -1
  71. package/dist/components/swatch-picker.js +10 -4
  72. package/dist/components/swatch-picker.js.map +1 -1
  73. package/dist/components/text-link.d.ts +8 -7
  74. package/dist/components/tile-radio.d.ts +20 -0
  75. package/dist/components/tile-radio.js +25 -8
  76. package/dist/components/tile-radio.js.map +1 -1
  77. package/dist/components/time-input.d.ts +8 -7
  78. package/dist/components/toggle-group.js +2 -2
  79. package/dist/components/toggle-group.js.map +1 -1
  80. package/dist/components/trigger-aria.d.ts +3 -0
  81. package/dist/components/trigger-aria.js +3 -1
  82. package/dist/components/trigger-aria.js.map +1 -1
  83. package/dist/components/ui.d.ts +5 -4
  84. package/dist/components/ui.js +60 -28
  85. package/dist/components/ui.js.map +1 -1
  86. package/dist/feedback/feedback-attachment.d.ts +66 -1
  87. package/dist/feedback/feedback-attachment.js +185 -65
  88. package/dist/feedback/feedback-attachment.js.map +1 -1
  89. package/dist/feedback/feedback-dialog.d.ts +66 -1
  90. package/dist/feedback/feedback-dialog.js.map +1 -1
  91. package/dist/feedback/feedback-inbox.d.ts +67 -252
  92. package/dist/feedback/feedback-inbox.js.map +1 -1
  93. package/dist/feedback/feedback-thread.d.ts +66 -287
  94. package/dist/feedback/feedback-thread.js +16 -3
  95. package/dist/feedback/feedback-thread.js.map +1 -1
  96. package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
  97. package/dist/feedback.d.ts +65 -3
  98. package/dist/hooks/use-file-drop.d.ts +8 -7
  99. package/dist/i18n/defaults.d.ts +8 -7
  100. package/dist/i18n/defaults.js +3 -1
  101. package/dist/i18n/defaults.js.map +1 -1
  102. package/dist/i18n/german.d.ts +8 -7
  103. package/dist/i18n/german.js +36 -2
  104. package/dist/i18n/german.js.map +1 -1
  105. package/dist/i18n/kit-labels.d.ts +5 -4
  106. package/dist/i18n/kit-labels.js.map +1 -1
  107. package/dist/i18n/languages.d.ts +8 -7
  108. package/dist/i18n/locales/de-CH.d.ts +8 -7
  109. package/dist/i18n/locales/en.d.ts +8 -7
  110. package/dist/i18n/locales/en.js +7 -0
  111. package/dist/i18n/locales/en.js.map +1 -1
  112. package/dist/i18n/locales/es.d.ts +8 -7
  113. package/dist/i18n/locales/es.js +37 -2
  114. package/dist/i18n/locales/es.js.map +1 -1
  115. package/dist/i18n/locales/fr.d.ts +8 -7
  116. package/dist/i18n/locales/fr.js +36 -2
  117. package/dist/i18n/locales/fr.js.map +1 -1
  118. package/dist/i18n/locales/hu.d.ts +8 -7
  119. package/dist/i18n/locales/hu.js +38 -2
  120. package/dist/i18n/locales/hu.js.map +1 -1
  121. package/dist/i18n/locales/it.d.ts +8 -7
  122. package/dist/i18n/locales/it.js +37 -2
  123. package/dist/i18n/locales/it.js.map +1 -1
  124. package/dist/i18n/locales/zh.d.ts +8 -7
  125. package/dist/i18n/locales/zh.js +34 -2
  126. package/dist/i18n/locales/zh.js.map +1 -1
  127. package/dist/i18n/review.d.ts +8 -7
  128. package/dist/i18n/review.js +20 -1
  129. package/dist/i18n/review.js.map +1 -1
  130. package/dist/index.d.ts +6 -4
  131. package/dist/index.js +16 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/lib/column-mapping.d.ts +81 -0
  134. package/dist/lib/column-mapping.js +108 -0
  135. package/dist/lib/column-mapping.js.map +1 -0
  136. package/dist/lib/strip-fade.d.ts +2 -2
  137. package/dist/lib/strip-fade.js.map +1 -1
  138. package/dist/lib/table-text.d.ts +109 -1
  139. package/dist/lib/table-text.js +122 -1
  140. package/dist/lib/table-text.js.map +1 -1
  141. package/dist/rhf/fields.d.ts +142 -13
  142. package/dist/rhf/fields.js +185 -3
  143. package/dist/rhf/fields.js.map +1 -1
  144. package/dist/rhf/form.d.ts +8 -7
  145. package/dist/rhf.d.ts +9 -8
  146. package/dist/rhf.js.map +1 -1
  147. package/dist/shell/app-shell.d.ts +8 -7
  148. package/dist/shell/top-bar-brand.d.ts +8 -7
  149. package/dist/shell/topbar-action-menu.d.ts +36 -3
  150. package/dist/shell/topbar-action-menu.js +74 -33
  151. package/dist/shell/topbar-action-menu.js.map +1 -1
  152. package/dist/shell.d.ts +8 -7
  153. package/dist/table-text.d.ts +1 -1
  154. package/dist/wizard/stepper-nav.d.ts +38 -9
  155. package/dist/wizard/stepper-nav.js +4 -0
  156. package/dist/wizard/stepper-nav.js.map +1 -1
  157. package/dist/wizard.d.ts +8 -7
  158. package/package.json +1 -1
  159. package/src/components/amount-input.tsx +50 -14
  160. package/src/components/autocomplete.tsx +188 -116
  161. package/src/components/column-mapper.tsx +768 -0
  162. package/src/components/combobox-core.tsx +82 -9
  163. package/src/components/combobox.tsx +655 -335
  164. package/src/components/confirm-dialog.tsx +12 -8
  165. package/src/components/country-select.tsx +135 -22
  166. package/src/components/danger-confirm.tsx +274 -24
  167. package/src/components/date-picker.tsx +423 -10
  168. package/src/components/entity-combobox.tsx +243 -129
  169. package/src/components/field-parts.tsx +224 -5
  170. package/src/components/field-strip.tsx +149 -0
  171. package/src/components/form-actions.tsx +52 -5
  172. package/src/components/icon-picker.tsx +23 -4
  173. package/src/components/money-field.tsx +3 -0
  174. package/src/components/month-picker.tsx +2 -1
  175. package/src/components/multi-entity-combobox.tsx +221 -121
  176. package/src/components/reauth-dialog.tsx +17 -18
  177. package/src/components/swatch-picker.tsx +29 -5
  178. package/src/components/tile-radio.tsx +74 -13
  179. package/src/components/toggle-group.tsx +2 -2
  180. package/src/components/trigger-aria.ts +5 -0
  181. package/src/components/ui.tsx +114 -33
  182. package/src/feedback/feedback-attachment.tsx +372 -72
  183. package/src/feedback/feedback-dialog.tsx +7 -3
  184. package/src/feedback/feedback-inbox.tsx +3 -2
  185. package/src/feedback/feedback-thread.tsx +112 -9
  186. package/src/i18n/defaults.ts +2 -0
  187. package/src/i18n/german.ts +42 -0
  188. package/src/i18n/kit-labels.tsx +4 -0
  189. package/src/i18n/locales/en.ts +27 -5
  190. package/src/i18n/locales/es.ts +41 -0
  191. package/src/i18n/locales/fr.ts +42 -0
  192. package/src/i18n/locales/hu.ts +37 -0
  193. package/src/i18n/locales/it.ts +41 -0
  194. package/src/i18n/locales/zh.ts +32 -0
  195. package/src/i18n/review.ts +19 -0
  196. package/src/index.ts +18 -0
  197. package/src/lib/column-mapping.ts +234 -0
  198. package/src/lib/strip-fade.ts +2 -2
  199. package/src/lib/table-text.ts +271 -0
  200. package/src/rhf/fields.tsx +421 -6
  201. package/src/rhf.ts +2 -1
  202. package/src/shell/topbar-action-menu.tsx +134 -38
  203. package/src/wizard/stepper-nav.tsx +35 -1
  204. package/dist/components/field-anatomy.d.ts +0 -95
  205. package/dist/components/field-anatomy.js +0 -84
  206. package/dist/components/field-anatomy.js.map +0 -1
  207. package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
  208. package/src/components/field-anatomy.tsx +0 -190
@@ -1,2 +1,67 @@
1
1
  import 'react';
2
- export { D as DEFAULT_ATTACHMENT_ACCEPT, a as DEFAULT_FEEDBACK_ATTACHMENT_LABELS, c as DEFAULT_MAX_ATTACHMENTS, d as DEFAULT_MAX_ATTACHMENT_BYTES, F as FeedbackAttachmentError, e as FeedbackAttachmentField, f as FeedbackAttachmentFieldLabels, g as FeedbackAttachmentFieldMultipleProps, h as FeedbackAttachmentFieldProps, i as FeedbackAttachmentFieldRefsProps, j as FeedbackAttachmentFieldSingleProps, l as FeedbackAttachmentRef, m as FeedbackAttachmentRefsError, w as pastedName } from '../feedback-attachment-NhUm7Zga.js';
2
+ export { X as DEFAULT_ATTACHMENT_ACCEPT, a5 as DEFAULT_FEEDBACK_ATTACHMENT_LABELS, ad as DEFAULT_MAX_ATTACHMENTS, ae as DEFAULT_MAX_ATTACHMENT_BYTES, aH as FeedbackAttachmentError, aI as FeedbackAttachmentErrorInfo, aJ as FeedbackAttachmentField, aK as FeedbackAttachmentFieldLabels, aL as FeedbackAttachmentFieldMultipleProps, aM as FeedbackAttachmentFieldProps, aN as FeedbackAttachmentFieldRefsProps, aO as FeedbackAttachmentFieldSingleProps, aQ as FeedbackAttachmentRef, aR as FeedbackAttachmentRefsError, cM as pastedName } from '../feedback-DOwPu-Il.js';
3
+ import '../components/tooltip.js';
4
+ import '../hooks/use-anchored-rect.js';
5
+ import '../lib/clipping.js';
6
+ import '../data-table-labels-B7OdnM0S.js';
7
+ import '../components/data-table-sort.js';
8
+ import '../components/swipeable-row.js';
9
+ import '../components/mini-calendar.js';
10
+ import '../components/calendar-heatmap.js';
11
+ import '../components/popover.js';
12
+ import '../components/chip.js';
13
+ import 'lucide-react';
14
+ import '../components/field-sync.js';
15
+ import '../wizard/types.js';
16
+ import '../tour/tour.js';
17
+ import '../search/command-palette.js';
18
+ import '../search/global-search.js';
19
+ import '../search/search-index.js';
20
+ import '../components/month-picker.js';
21
+ import '../components/page-contents.js';
22
+ import '../components/series-chart-labels.js';
23
+ import '../components/pie-chart-labels.js';
24
+ import '../components/sparkline.js';
25
+ import '../components/stat-tile.js';
26
+ import '../components/signature-pad.js';
27
+ import '../components/password-strength.js';
28
+ import '../components/swatch-picker.js';
29
+ import '../components/tile-radio.js';
30
+ import '../components/icon-picker.js';
31
+ import '../components/dialog-frame.js';
32
+ import '../components/modal.js';
33
+ import '../hooks/use-search-param-state.js';
34
+ import '../components/measured-grid.js';
35
+ import '../components/account-settings-labels.js';
36
+ import '../components/floating-panel.js';
37
+ import '../components/bulk-action-bar.js';
38
+ import '../components/list.js';
39
+ import '../components/signed-amount.js';
40
+ import '../components/breadcrumbs.js';
41
+ import '../components/toast.js';
42
+ import '../components/description-list.js';
43
+ import '../components/line-items.js';
44
+ import '../components/progress-bar.js';
45
+ import '../components/error-boundary.js';
46
+ import '../components/authed-image.js';
47
+ import '../hooks/use-authed-src.js';
48
+ import '../components/image-grid.js';
49
+ import '../components/lightbox.js';
50
+ import '../components/write-lock.js';
51
+ import '../components/legal.js';
52
+ import '../components/account-chips.js';
53
+ import '../lib/format.js';
54
+ import '../components/reauth-dialog.js';
55
+ import '../components/server-wake.js';
56
+ import '../lib/server-wake.js';
57
+ import '../components/translation-review-labels.js';
58
+ import '../lib/translation-review.js';
59
+ import '../components/country-select.js';
60
+ import '../components/inline-edit-field.js';
61
+ import '../lib/iban.js';
62
+ import '../lib/phone.js';
63
+ import '../components/sign-chip.js';
64
+ import '../components/column-mapper.js';
65
+ import '../lib/column-mapping.js';
66
+ import '../lib/table-text.js';
67
+ import '../components/field-parts.js';
@@ -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";
4
+ import {
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState
9
+ } from "react";
5
10
  import { Camera, FileText, Image as ImageIcon, Paperclip, X } from "lucide-react";
6
- import { Button, Spinner } from "../components/ui.js";
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;
@@ -17,8 +24,11 @@ const DEFAULT_FEEDBACK_ATTACHMENT_LABELS = {
17
24
  attachmentScreenshot: "Screenshot",
18
25
  attachmentRemoveFile: (name) => `Remove ${name}`,
19
26
  attachmentLimit: (max) => `Up to ${max} ${max === 1 ? "attachment" : "attachments"} \u2014 remove one to add another.`,
20
- attachmentUploading: "Uploading\u2026"
27
+ attachmentUploading: "Uploading\u2026",
28
+ attachmentUploadFailed: (name) => `\u201C${name}\u201D could not be uploaded`
21
29
  };
30
+ const FIELD_ROOT = "relative min-w-0 max-w-full";
31
+ const buttonIcon = (size) => size === "sm" ? "size-3.5" : "size-4";
22
32
  function FeedbackAttachmentField(props) {
23
33
  if (props.refs) return /* @__PURE__ */ jsx(RefsField, { ...props });
24
34
  return props.multiple ? /* @__PURE__ */ jsx(MultipleField, { ...props }) : /* @__PURE__ */ jsx(SingleField, { ...props });
@@ -33,6 +43,9 @@ function SingleField({
33
43
  onCaptureScreenshot,
34
44
  documentPaste = false,
35
45
  pasteFrom,
46
+ disabled = false,
47
+ buttonVariant = "secondary",
48
+ buttonSize = "md",
36
49
  className
37
50
  }) {
38
51
  const [previewFor, setPreviewFor] = useState(null);
@@ -40,6 +53,7 @@ function SingleField({
40
53
  const fileInputRef = useRef(null);
41
54
  const fileText = useKitFileLabels();
42
55
  const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
56
+ const errorInfo = useErrorInfo(accept, maxBytes, void 0, text);
43
57
  useEffect(() => {
44
58
  if (!value) return;
45
59
  const url = URL.createObjectURL(value);
@@ -50,7 +64,7 @@ function SingleField({
50
64
  if (!file) return;
51
65
  const problem = rejection(file, accept, maxBytes);
52
66
  if (problem) {
53
- onError?.(problem);
67
+ onError?.(problem, errorInfo(problem, [file]));
54
68
  return;
55
69
  }
56
70
  onChange(file);
@@ -61,12 +75,13 @@ function SingleField({
61
75
  pick(new File([file], pastedName(file.type), { type: file.type }));
62
76
  },
63
77
  documentPaste,
64
- pasteFrom
78
+ pasteFrom,
79
+ disabled
65
80
  );
66
81
  return /* @__PURE__ */ jsxs(
67
82
  "div",
68
83
  {
69
- className: cn("relative", className),
84
+ className: cn(FIELD_ROOT, className),
70
85
  onPaste,
71
86
  children: [
72
87
  /* @__PURE__ */ jsx(Heading, { text: labelsProp?.attachment }),
@@ -87,24 +102,35 @@ function SingleField({
87
102
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm text-[var(--text-secondary)]", children: value.name }),
88
103
  /* @__PURE__ */ jsx("div", { className: "text-xs text-[var(--text-muted)]", children: fileText.size(value.size) })
89
104
  ] }),
90
- /* @__PURE__ */ jsx(
91
- "button",
92
- {
93
- type: "button",
94
- onClick: () => onChange(null),
95
- "aria-label": text.attachmentRemove,
96
- className: "rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
97
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
98
- }
99
- )
105
+ /* @__PURE__ */ jsx(RemoveButton, { label: text.attachmentRemove, onClick: () => onChange(null), disabled })
100
106
  ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
101
107
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
102
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", onClick: () => fileInputRef.current?.click(), children: [
103
- /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
104
- " ",
105
- text.attachmentAdd
106
- ] }),
107
- onCaptureScreenshot && /* @__PURE__ */ jsx(CaptureButton, { capture: onCaptureScreenshot, onFile: pick, label: text.attachmentCapture })
108
+ /* @__PURE__ */ jsxs(
109
+ Button,
110
+ {
111
+ type: "button",
112
+ variant: buttonVariant,
113
+ size: buttonSize,
114
+ disabled,
115
+ onClick: () => fileInputRef.current?.click(),
116
+ children: [
117
+ /* @__PURE__ */ jsx(Paperclip, { className: buttonIcon(buttonSize) }),
118
+ " ",
119
+ text.attachmentAdd
120
+ ]
121
+ }
122
+ ),
123
+ onCaptureScreenshot && /* @__PURE__ */ jsx(
124
+ CaptureButton,
125
+ {
126
+ capture: onCaptureScreenshot,
127
+ onFile: pick,
128
+ label: text.attachmentCapture,
129
+ disabled,
130
+ variant: buttonVariant,
131
+ size: buttonSize
132
+ }
133
+ )
108
134
  ] }),
109
135
  /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: text.attachmentPaste })
110
136
  ] }),
@@ -115,6 +141,7 @@ function SingleField({
115
141
  type: "file",
116
142
  accept: accept.join(","),
117
143
  className: "sr-only",
144
+ disabled,
118
145
  onChange: (e) => {
119
146
  pick(e.target.files?.[0]);
120
147
  e.target.value = "";
@@ -138,6 +165,9 @@ function MultipleField({
138
165
  onCaptureScreenshot,
139
166
  documentPaste = false,
140
167
  pasteFrom,
168
+ disabled = false,
169
+ buttonVariant = "secondary",
170
+ buttonSize = "md",
141
171
  className
142
172
  }) {
143
173
  const rootRef = useRef(null);
@@ -147,6 +177,7 @@ function MultipleField({
147
177
  const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile;
148
178
  const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit;
149
179
  const screenshotLabel = text.attachmentScreenshot ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentScreenshot;
180
+ const errorInfo = useErrorInfo(accept, maxBytes, max, text);
150
181
  const latest = useRef(value);
151
182
  useLayoutEffect(() => {
152
183
  latest.current = value;
@@ -163,29 +194,29 @@ function MultipleField({
163
194
  const accepted = [];
164
195
  const taken = new Set([...current, ...screenshot ? [screenshot] : []].map((f) => f.name));
165
196
  for (const raw of incoming) {
166
- const problem = rejection(raw, accept, maxBytes);
197
+ const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
198
+ const problem = rejection(file, accept, maxBytes);
167
199
  if (problem) {
168
- onError?.(problem);
200
+ onError?.(problem, errorInfo(problem, [file]));
169
201
  continue;
170
202
  }
171
- const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
172
203
  taken.add(file.name);
173
204
  if (!current.includes(file)) accepted.push(file);
174
205
  }
175
206
  if (accepted.length === 0) return;
176
207
  const space = Math.max(0, max - current.length);
177
- if (accepted.length > space) onError?.("count");
208
+ if (accepted.length > space) onError?.("count", errorInfo("count", accepted.slice(space)));
178
209
  const kept = accepted.slice(0, space);
179
210
  if (kept.length > 0) commit([...current, ...kept]);
180
211
  };
181
- const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom);
212
+ const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, disabled);
182
213
  const capture = (file) => {
183
214
  if (!hasSlot) {
184
215
  add([file]);
185
216
  return;
186
217
  }
187
218
  const problem = rejection(file, accept, maxBytes);
188
- if (problem) onError?.(problem);
219
+ if (problem) onError?.(problem, errorInfo(problem, [file]));
189
220
  else onScreenshotChange(file);
190
221
  };
191
222
  const pendingFocus = useRef(null);
@@ -208,7 +239,7 @@ function MultipleField({
208
239
  remove: () => commit(latest.current.filter((f) => f !== file))
209
240
  }))
210
241
  ];
211
- return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative space-y-2", className), onPaste, children: [
242
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn(FIELD_ROOT, "space-y-2", className), onPaste, children: [
212
243
  /* @__PURE__ */ jsx(Heading, { text: labelsProp?.attachment, className: "mb-0" }),
213
244
  chips.length > 0 && // One chip per row, full width: wrapped chips of name-dependent widths made a
214
245
  // ragged block in a 430px dialog, and a column keeps every name and remove
@@ -224,17 +255,14 @@ function MultipleField({
224
255
  /* @__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) })
225
256
  ] }),
226
257
  /* @__PURE__ */ jsx(
227
- "button",
258
+ RemoveButton,
228
259
  {
229
- type: "button",
230
- "data-attachment-remove": "",
260
+ label: removeLabel(chip.name),
261
+ disabled,
231
262
  onClick: () => {
232
263
  pendingFocus.current = index;
233
264
  chip.remove();
234
- },
235
- "aria-label": removeLabel(chip.name),
236
- className: "shrink-0 rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
237
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
265
+ }
238
266
  }
239
267
  )
240
268
  ]
@@ -246,11 +274,13 @@ function MultipleField({
246
274
  Button,
247
275
  {
248
276
  type: "button",
249
- variant: "secondary",
277
+ variant: buttonVariant,
278
+ size: buttonSize,
250
279
  "data-attachment-action": "",
280
+ disabled,
251
281
  onClick: () => fileInputRef.current?.click(),
252
282
  children: [
253
- /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
283
+ /* @__PURE__ */ jsx(Paperclip, { className: buttonIcon(buttonSize) }),
254
284
  " ",
255
285
  text.attachmentAdd
256
286
  ]
@@ -262,6 +292,9 @@ function MultipleField({
262
292
  capture: onCaptureScreenshot,
263
293
  onFile: capture,
264
294
  label: text.attachmentCapture,
295
+ disabled,
296
+ variant: buttonVariant,
297
+ size: buttonSize,
265
298
  "data-attachment-action": ""
266
299
  }
267
300
  )
@@ -277,6 +310,7 @@ function MultipleField({
277
310
  className: "sr-only",
278
311
  tabIndex: -1,
279
312
  "aria-hidden": true,
313
+ disabled,
280
314
  onChange: (e) => {
281
315
  add(Array.from(e.target.files ?? []));
282
316
  e.target.value = "";
@@ -300,6 +334,11 @@ function RefsField({
300
334
  onCaptureScreenshot,
301
335
  documentPaste = false,
302
336
  pasteFrom,
337
+ disabled = false,
338
+ commit: commits,
339
+ disabledReason: ownDisabledReason,
340
+ buttonVariant = "secondary",
341
+ buttonSize = "md",
303
342
  className
304
343
  }) {
305
344
  const rootRef = useRef(null);
@@ -309,6 +348,11 @@ function RefsField({
309
348
  const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile;
310
349
  const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit;
311
350
  const uploadingLabel = text.attachmentUploading ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploading;
351
+ const errorInfo = useErrorInfo(accept, maxBytes, max, text);
352
+ const disabledReason = useCommitReason(commits, ownDisabledReason);
353
+ const locked = hasReason(disabledReason);
354
+ const reason = locked ? disabledReason : void 0;
355
+ const inert = disabled || locked;
312
356
  const latest = useRef(value);
313
357
  useLayoutEffect(() => {
314
358
  latest.current = value;
@@ -355,32 +399,32 @@ function RefsField({
355
399
  },
356
400
  (error) => {
357
401
  settle();
358
- if (mounted.current) onError?.("upload", error);
402
+ if (mounted.current) onError?.("upload", error, errorInfo("upload", [file], error));
359
403
  }
360
404
  );
361
405
  };
362
406
  const add = (incoming, pasted = false) => {
363
407
  let space = Math.max(0, max - latest.current.length - inFlight.current.length);
364
408
  const taken = /* @__PURE__ */ new Set([...latest.current.map((r) => r.name), ...inFlight.current.map((u) => u.file.name)]);
365
- let dropped = false;
409
+ const dropped = [];
366
410
  for (const raw of incoming) {
367
- const problem = rejection(raw, accept, maxBytes);
411
+ const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
412
+ const problem = rejection(file, accept, maxBytes);
368
413
  if (problem) {
369
- onError?.(problem);
414
+ onError?.(problem, void 0, errorInfo(problem, [file]));
370
415
  continue;
371
416
  }
417
+ taken.add(file.name);
372
418
  if (space === 0) {
373
- dropped = true;
419
+ dropped.push(file);
374
420
  continue;
375
421
  }
376
- const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
377
- taken.add(file.name);
378
422
  space -= 1;
379
423
  start(file);
380
424
  }
381
- if (dropped) onError?.("count");
425
+ if (dropped.length > 0) onError?.("count", void 0, errorInfo("count", dropped));
382
426
  };
383
- const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom);
427
+ const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom, inert);
384
428
  const pendingFocus = useRef(null);
385
429
  useEffect(() => {
386
430
  const index = pendingFocus.current;
@@ -393,12 +437,13 @@ function RefsField({
393
437
  target?.focus();
394
438
  }, [value]);
395
439
  const remove = (key, index) => {
440
+ if (inert) return;
396
441
  pendingFocus.current = index;
397
442
  commit(latest.current.filter((r) => r.key !== key));
398
443
  onRemove?.(key);
399
444
  };
400
445
  const hasChips = value.length > 0 || uploads.length > 0;
401
- return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative space-y-2", className), onPaste, children: [
446
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn(FIELD_ROOT, "space-y-2", className), onPaste, children: [
402
447
  /* @__PURE__ */ jsx(Heading, { text: labelsProp?.attachment, className: "mb-0" }),
403
448
  hasChips && /* @__PURE__ */ jsxs("ul", { "aria-label": text.attachmentList, className: "flex flex-col gap-2", children: [
404
449
  value.map((ref, index) => /* @__PURE__ */ jsxs(
@@ -412,14 +457,12 @@ function RefsField({
412
457
  ref.size !== void 0 && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-[var(--text-muted)]", children: fileText.size(ref.size) })
413
458
  ] }),
414
459
  /* @__PURE__ */ jsx(
415
- "button",
460
+ RemoveButton,
416
461
  {
417
- type: "button",
418
- "data-attachment-remove": "",
462
+ label: removeLabel(ref.name),
419
463
  onClick: () => remove(ref.key, index),
420
- "aria-label": removeLabel(ref.name),
421
- className: "shrink-0 rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
422
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
464
+ disabled,
465
+ reason
423
466
  }
424
467
  )
425
468
  ]
@@ -448,11 +491,14 @@ function RefsField({
448
491
  Button,
449
492
  {
450
493
  type: "button",
451
- variant: "secondary",
494
+ variant: buttonVariant,
495
+ size: buttonSize,
452
496
  "data-attachment-action": "",
497
+ disabled,
498
+ disabledReason: reason,
453
499
  onClick: () => fileInputRef.current?.click(),
454
500
  children: [
455
- /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
501
+ /* @__PURE__ */ jsx(Paperclip, { className: buttonIcon(buttonSize) }),
456
502
  " ",
457
503
  text.attachmentAdd
458
504
  ]
@@ -464,11 +510,19 @@ function RefsField({
464
510
  capture: onCaptureScreenshot,
465
511
  onFile: (file) => add([file]),
466
512
  label: text.attachmentCapture,
513
+ disabled,
514
+ disabledReason: reason,
515
+ variant: buttonVariant,
516
+ size: buttonSize,
467
517
  "data-attachment-action": ""
468
518
  }
469
519
  )
470
520
  ] }),
471
- /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: room > 0 ? text.attachmentPaste : limitLabel(max) }),
521
+ locked ? (
522
+ // Where the paste hint was: a phone never hovers, so the Tooltip alone would
523
+ // leave a touch user with buttons that do nothing and no word why.
524
+ /* @__PURE__ */ jsx("div", { "data-attachment-reason": "", className: "text-xs text-[var(--text-muted)]", children: disabledReason })
525
+ ) : /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: room > 0 ? text.attachmentPaste : limitLabel(max) }),
472
526
  /* @__PURE__ */ jsx(
473
527
  "input",
474
528
  {
@@ -479,6 +533,7 @@ function RefsField({
479
533
  className: "sr-only",
480
534
  tabIndex: -1,
481
535
  "aria-hidden": true,
536
+ disabled: inert,
482
537
  onChange: (e) => {
483
538
  add(Array.from(e.target.files ?? []));
484
539
  e.target.value = "";
@@ -498,6 +553,10 @@ function CaptureButton({
498
553
  capture,
499
554
  onFile,
500
555
  label,
556
+ disabled = false,
557
+ disabledReason,
558
+ variant,
559
+ size,
501
560
  ...rest
502
561
  }) {
503
562
  const [capturing, setCapturing] = useState(false);
@@ -505,24 +564,80 @@ function CaptureButton({
505
564
  Button,
506
565
  {
507
566
  type: "button",
508
- variant: "secondary",
509
- disabled: capturing,
567
+ variant,
568
+ size,
569
+ disabled: capturing || disabled,
570
+ disabledReason,
510
571
  ...rest,
511
572
  onClick: () => {
512
- if (capturing) return;
573
+ if (capturing || disabled) return;
513
574
  setCapturing(true);
514
575
  void capture().then((file) => {
515
576
  if (file) onFile(file);
516
577
  }).finally(() => setCapturing(false));
517
578
  },
518
579
  children: [
519
- /* @__PURE__ */ jsx(Camera, { className: "size-4" }),
580
+ /* @__PURE__ */ jsx(Camera, { className: buttonIcon(size) }),
520
581
  " ",
521
582
  capturing ? "\u2026" : label
522
583
  ]
523
584
  }
524
585
  );
525
586
  }
587
+ function RemoveButton({
588
+ label,
589
+ onClick,
590
+ disabled,
591
+ reason
592
+ }) {
593
+ return /* @__PURE__ */ jsx(
594
+ IconButton,
595
+ {
596
+ type: "button",
597
+ size: "xs",
598
+ tone: "muted",
599
+ "data-attachment-remove": "",
600
+ "aria-label": label,
601
+ disabled,
602
+ disabledReason: reason,
603
+ onClick,
604
+ className: "shrink-0",
605
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
606
+ }
607
+ );
608
+ }
609
+ function hasReason(reason) {
610
+ return reason !== void 0 && reason !== null && reason !== false && reason !== "";
611
+ }
612
+ function useErrorInfo(accept, maxBytes, max, text) {
613
+ const picker = useKitLabels("filePicker", DEFAULT_FILE_PICKER_LABELS);
614
+ const fileText = useKitFileLabels();
615
+ const uploadFailed = text.attachmentUploadFailed ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploadFailed;
616
+ return (kind, files, error) => {
617
+ const file = files[0];
618
+ let message;
619
+ if (kind === "type") {
620
+ const acceptText = formatAccept(accept.join(","));
621
+ const useOnly = acceptText !== "" && (picker.rejectedTypeOnly !== DEFAULT_FILE_PICKER_LABELS.rejectedTypeOnly || picker.rejectedType === DEFAULT_FILE_PICKER_LABELS.rejectedType);
622
+ message = useOnly ? picker.rejectedTypeOnly(acceptText, file.name) : picker.rejectedType(file.name);
623
+ } else if (kind === "size") {
624
+ message = picker.rejectedSize(file.name, fileText.size(maxBytes));
625
+ } else if (kind === "count") {
626
+ message = files.length === 1 ? picker.rejectedCount(file.name, max ?? 1) : picker.rejectedMany(files.length);
627
+ } else {
628
+ message = uploadFailed(file.name);
629
+ }
630
+ return {
631
+ file,
632
+ files,
633
+ message,
634
+ maxBytes,
635
+ accept,
636
+ ...max !== void 0 ? { max } : null,
637
+ ...kind === "upload" ? { error } : null
638
+ };
639
+ };
640
+ }
526
641
  function ChipPreview({ file, screenshot }) {
527
642
  const isImage = file.type.startsWith("image/");
528
643
  const [previewFor, setPreviewFor] = useState(null);
@@ -565,15 +680,17 @@ function uniqueName(name, taken) {
565
680
  while (taken.has(`${stem}-${n}${ext}`)) n++;
566
681
  return `${stem}-${n}${ext}`;
567
682
  }
568
- function usePaste(onImages, documentPaste, pasteFrom) {
569
- const latest = useRef(onImages);
683
+ function usePaste(onImages, documentPaste, pasteFrom, off) {
684
+ const handler = off ? null : onImages;
685
+ const latest = useRef(handler);
570
686
  useEffect(() => {
571
- latest.current = onImages;
687
+ latest.current = handler;
572
688
  });
573
689
  useEffect(() => {
574
690
  const target = documentPaste ? document : pasteFrom?.current ?? null;
575
691
  if (!target) return;
576
692
  const onPaste = (event) => {
693
+ if (!latest.current) return;
577
694
  const clipboard = event.clipboardData;
578
695
  takeImages(clipboard, () => event.preventDefault(), latest.current);
579
696
  };
@@ -581,7 +698,10 @@ function usePaste(onImages, documentPaste, pasteFrom) {
581
698
  return () => target.removeEventListener("paste", onPaste);
582
699
  }, [documentPaste, pasteFrom]);
583
700
  const listensElsewhere = documentPaste || pasteFrom !== void 0;
584
- return listensElsewhere ? void 0 : (event) => takeImages(event.clipboardData, () => event.preventDefault(), latest.current);
701
+ return listensElsewhere ? void 0 : (event) => {
702
+ if (!latest.current) return;
703
+ takeImages(event.clipboardData, () => event.preventDefault(), latest.current);
704
+ };
585
705
  }
586
706
  function takeImages(clipboard, stop, onImages) {
587
707
  const images = Array.from(clipboard?.items ?? []).filter((item) => item.kind === "file" && item.type.startsWith("image/")).map((item) => item.getAsFile()).filter((file) => file !== null);