@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.
- package/README.md +42 -25
- package/dist/chart.d.ts +8 -7
- package/dist/components/amount-input.d.ts +34 -7
- package/dist/components/amount-input.js +14 -10
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +26 -0
- package/dist/components/autocomplete.js +164 -137
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +8 -7
- package/dist/components/calculator.d.ts +8 -7
- package/dist/components/column-mapper.d.ts +235 -0
- package/dist/components/column-mapper.js +411 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +113 -3
- package/dist/components/combobox.js +456 -313
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +8 -7
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -4
- package/dist/components/country-select.d.ts +50 -7
- package/dist/components/country-select.js +65 -9
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +8 -7
- package/dist/components/danger-confirm.js +64 -7
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/date-picker.d.ts +41 -0
- package/dist/components/date-picker.js +269 -8
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +48 -2
- package/dist/components/entity-combobox.js +159 -114
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +8 -7
- package/dist/components/field-parts.d.ts +116 -6
- package/dist/components/field-parts.js +84 -0
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +8 -7
- package/dist/components/file-dropzone.d.ts +8 -7
- package/dist/components/form-actions.d.ts +8 -7
- package/dist/components/form-actions.js +23 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +8 -7
- package/dist/components/icon-picker.d.ts +10 -1
- package/dist/components/icon-picker.js +12 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/language-select.d.ts +8 -7
- package/dist/components/money-field.d.ts +11 -7
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.js +2 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +31 -2
- package/dist/components/multi-entity-combobox.js +152 -103
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +8 -7
- package/dist/components/number-input.d.ts +8 -7
- package/dist/components/numpad-sheet.d.ts +8 -7
- package/dist/components/phone-input.d.ts +8 -7
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +8 -7
- package/dist/components/settings-fields.d.ts +8 -7
- package/dist/components/share-card.d.ts +8 -7
- package/dist/components/swatch-picker.d.ts +17 -1
- package/dist/components/swatch-picker.js +10 -4
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.d.ts +8 -7
- package/dist/components/tile-radio.d.ts +20 -0
- package/dist/components/tile-radio.js +25 -8
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +8 -7
- package/dist/components/toggle-group.js +2 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +5 -4
- package/dist/components/ui.js +60 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +66 -1
- package/dist/feedback/feedback-attachment.js +185 -65
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +66 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +67 -252
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +66 -287
- package/dist/feedback/feedback-thread.js +16 -3
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
- package/dist/feedback.d.ts +65 -3
- package/dist/hooks/use-file-drop.d.ts +8 -7
- package/dist/i18n/defaults.d.ts +8 -7
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +8 -7
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +8 -7
- package/dist/i18n/locales/de-CH.d.ts +8 -7
- package/dist/i18n/locales/en.d.ts +8 -7
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +8 -7
- package/dist/i18n/locales/es.js +37 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -7
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -7
- package/dist/i18n/locales/hu.js +38 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -7
- package/dist/i18n/locales/it.js +37 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -7
- package/dist/i18n/locales/zh.js +34 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +8 -7
- package/dist/i18n/review.js +20 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- package/dist/lib/strip-fade.d.ts +2 -2
- package/dist/lib/strip-fade.js.map +1 -1
- package/dist/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +142 -13
- package/dist/rhf/fields.js +185 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +8 -7
- package/dist/rhf.d.ts +9 -8
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +8 -7
- package/dist/shell/top-bar-brand.d.ts +8 -7
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +8 -7
- package/dist/table-text.d.ts +1 -1
- package/dist/wizard/stepper-nav.d.ts +38 -9
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +8 -7
- package/package.json +1 -1
- package/src/components/amount-input.tsx +50 -14
- package/src/components/autocomplete.tsx +188 -116
- package/src/components/column-mapper.tsx +768 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +655 -335
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +135 -22
- package/src/components/danger-confirm.tsx +274 -24
- package/src/components/date-picker.tsx +423 -10
- package/src/components/entity-combobox.tsx +243 -129
- package/src/components/field-parts.tsx +224 -5
- package/src/components/field-strip.tsx +149 -0
- package/src/components/form-actions.tsx +52 -5
- package/src/components/icon-picker.tsx +23 -4
- package/src/components/money-field.tsx +3 -0
- package/src/components/month-picker.tsx +2 -1
- package/src/components/multi-entity-combobox.tsx +221 -121
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/swatch-picker.tsx +29 -5
- package/src/components/tile-radio.tsx +74 -13
- package/src/components/toggle-group.tsx +2 -2
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +114 -33
- package/src/feedback/feedback-attachment.tsx +372 -72
- package/src/feedback/feedback-dialog.tsx +7 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +112 -9
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +42 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/en.ts +27 -5
- package/src/i18n/locales/es.ts +41 -0
- package/src/i18n/locales/fr.ts +42 -0
- package/src/i18n/locales/hu.ts +37 -0
- package/src/i18n/locales/it.ts +41 -0
- package/src/i18n/locales/zh.ts +32 -0
- package/src/i18n/review.ts +19 -0
- package/src/index.ts +18 -0
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/strip-fade.ts +2 -2
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +421 -6
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/wizard/stepper-nav.tsx +35 -1
- package/dist/components/field-anatomy.d.ts +0 -95
- package/dist/components/field-anatomy.js +0 -84
- package/dist/components/field-anatomy.js.map +0 -1
- package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
- package/src/components/field-anatomy.tsx +0 -190
|
@@ -1,2 +1,67 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export {
|
|
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 {
|
|
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(
|
|
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(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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("
|
|
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
|
-
|
|
258
|
+
RemoveButton,
|
|
228
259
|
{
|
|
229
|
-
|
|
230
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
409
|
+
const dropped = [];
|
|
366
410
|
for (const raw of incoming) {
|
|
367
|
-
const
|
|
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
|
|
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("
|
|
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
|
-
|
|
460
|
+
RemoveButton,
|
|
416
461
|
{
|
|
417
|
-
|
|
418
|
-
"data-attachment-remove": "",
|
|
462
|
+
label: removeLabel(ref.name),
|
|
419
463
|
onClick: () => remove(ref.key, index),
|
|
420
|
-
|
|
421
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
509
|
-
|
|
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:
|
|
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
|
|
683
|
+
function usePaste(onImages, documentPaste, pasteFrom, off) {
|
|
684
|
+
const handler = off ? null : onImages;
|
|
685
|
+
const latest = useRef(handler);
|
|
570
686
|
useEffect(() => {
|
|
571
|
-
latest.current =
|
|
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) =>
|
|
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);
|