@eifi1/ui-kit 0.21.0 → 0.22.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/dist/chart.d.ts +10 -8
- package/dist/components/account-chips.d.ts +10 -3
- package/dist/components/account-chips.js +5 -2
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings.d.ts +33 -2
- package/dist/components/account-settings.js +43 -21
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +27 -8
- package/dist/components/amount-input.js +15 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +8 -0
- package/dist/components/autocomplete.js +9 -4
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +10 -8
- package/dist/components/calculator.d.ts +10 -8
- package/dist/components/checkbox-group.d.ts +104 -0
- package/dist/components/checkbox-group.js +111 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.ts +19 -0
- package/dist/components/checkbox.js +30 -7
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +18 -0
- package/dist/components/chip.js +47 -10
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox.d.ts +13 -2
- package/dist/components/combobox.js +17 -8
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +66 -161
- package/dist/components/copy-button.d.ts +10 -8
- package/dist/components/country-select.d.ts +124 -0
- package/dist/components/country-select.js +237 -0
- package/dist/components/country-select.js.map +1 -0
- package/dist/components/currency-select.d.ts +17 -1
- package/dist/components/currency-select.js +56 -23
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +66 -166
- package/dist/components/danger-confirm.js +133 -49
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -2
- package/dist/components/data-table-filter-popover.d.ts +1 -2
- package/dist/components/data-table-filters.d.ts +1 -2
- package/dist/components/data-table-labels.d.ts +4 -97
- package/dist/components/data-table-labels.js +9 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table.d.ts +1 -2
- package/dist/components/data-table.js +98 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -5
- package/dist/components/date-picker.js +110 -59
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +8 -1
- package/dist/components/entity-combobox.js +9 -4
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +10 -8
- package/dist/components/field-anatomy.d.ts +95 -0
- package/dist/components/field-anatomy.js +84 -0
- package/dist/components/field-anatomy.js.map +1 -0
- package/dist/components/field-parts.d.ts +114 -0
- package/dist/components/field-parts.js +72 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/file-button.d.ts +10 -8
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +32 -9
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +10 -8
- package/dist/components/form-actions.js +61 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +66 -0
- package/dist/components/iban-input.js +125 -0
- package/dist/components/iban-input.js.map +1 -0
- package/dist/components/icon-picker.d.ts +12 -2
- package/dist/components/icon-picker.js +9 -2
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-edit-field.d.ts +167 -0
- package/dist/components/inline-edit-field.js +293 -0
- package/dist/components/inline-edit-field.js.map +1 -0
- package/dist/components/language-select.d.ts +127 -0
- package/dist/components/language-select.js +18 -0
- package/dist/components/language-select.js.map +1 -0
- package/dist/components/line-items.d.ts +25 -3
- package/dist/components/line-items.js +55 -46
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +25 -0
- package/dist/components/list.js +16 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +20 -8
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +290 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +8 -1
- package/dist/components/multi-select.js +9 -4
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +25 -13
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +70 -13
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +10 -8
- package/dist/components/one-time-code-input.d.ts +100 -0
- package/dist/components/one-time-code-input.js +137 -0
- package/dist/components/one-time-code-input.js.map +1 -0
- package/dist/components/phone-input.d.ts +66 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/series-chart.d.ts +10 -8
- package/dist/components/settings-fields.d.ts +10 -8
- package/dist/components/share-card.d.ts +8 -6
- package/dist/components/sign-chip.d.ts +66 -0
- package/dist/components/sign-chip.js +39 -0
- package/dist/components/sign-chip.js.map +1 -0
- package/dist/components/swatch-picker.d.ts +27 -2
- package/dist/components/swatch-picker.js +73 -31
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/switch.d.ts +19 -0
- package/dist/components/switch.js +24 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/text-link.d.ts +10 -8
- package/dist/components/tile-radio.d.ts +68 -13
- package/dist/components/tile-radio.js +3 -3
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +15 -9
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toggle-group.d.ts +47 -1
- package/dist/components/toggle-group.js +110 -69
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/ui.d.ts +10 -8
- package/dist/components/ui.js +280 -78
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -2
- package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +210 -3
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +37 -3
- package/dist/feedback/feedback-thread.js +38 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-NhUm7Zga.d.ts} +70 -5
- package/dist/feedback.d.ts +2 -2
- package/dist/hooks/use-file-drop.d.ts +10 -8
- package/dist/i18n/defaults.d.ts +10 -8
- package/dist/i18n/defaults.js +17 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +10 -8
- package/dist/i18n/german.js +50 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +10 -8
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +10 -8
- package/dist/i18n/locales/de-CH.d.ts +10 -8
- package/dist/i18n/locales/en.d.ts +10 -8
- package/dist/i18n/locales/en.js +9 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +10 -8
- package/dist/i18n/locales/es.js +48 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +10 -8
- package/dist/i18n/locales/fr.js +48 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +10 -8
- package/dist/i18n/locales/hu.js +49 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +10 -8
- package/dist/i18n/locales/it.js +49 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +10 -8
- package/dist/i18n/locales/zh.js +48 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +10 -8
- package/dist/i18n/review.js +8 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -10
- package/dist/index.js +57 -2
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → kit-labels-R8mIAc9N.d.ts} +811 -8
- package/dist/lib/countries.d.ts +43 -0
- package/dist/lib/countries.js +39 -0
- package/dist/lib/countries.js.map +1 -0
- package/dist/lib/decimal-marks.d.ts +46 -0
- package/dist/lib/decimal-marks.js +40 -0
- package/dist/lib/decimal-marks.js.map +1 -0
- package/dist/lib/iban.d.ts +98 -0
- package/dist/lib/iban.js +154 -0
- package/dist/lib/iban.js.map +1 -0
- package/dist/lib/isin.d.ts +30 -0
- package/dist/lib/isin.js +36 -0
- package/dist/lib/isin.js.map +1 -0
- package/dist/lib/phone.d.ts +69 -0
- package/dist/lib/phone.js +121 -0
- package/dist/lib/phone.js.map +1 -0
- package/dist/rhf/fields.d.ts +148 -11
- package/dist/rhf/fields.js +332 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +10 -8
- package/dist/rhf.d.ts +13 -9
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +10 -8
- package/dist/shell/top-bar-brand.d.ts +10 -8
- package/dist/shell.d.ts +10 -8
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +10 -8
- package/dist/wizard.d.ts +10 -8
- package/package.json +1 -1
- package/src/components/account-chips.tsx +17 -3
- package/src/components/account-settings.tsx +92 -26
- package/src/components/amount-input.tsx +49 -56
- package/src/components/autocomplete.tsx +133 -110
- package/src/components/checkbox-group.tsx +229 -0
- package/src/components/checkbox.tsx +72 -8
- package/src/components/chip.tsx +80 -10
- package/src/components/combobox.tsx +292 -253
- package/src/components/country-select.tsx +443 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +345 -77
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +173 -62
- package/src/components/entity-combobox.tsx +107 -87
- package/src/components/field-anatomy.tsx +190 -0
- package/src/components/field-parts.tsx +184 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +115 -2
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +26 -4
- package/src/components/inline-edit-field.tsx +533 -0
- package/src/components/language-select.tsx +78 -0
- package/src/components/line-items.tsx +35 -2
- package/src/components/list.tsx +55 -6
- package/src/components/money-field.tsx +10 -0
- package/src/components/month-picker.tsx +399 -63
- package/src/components/multi-select.tsx +80 -58
- package/src/components/number-field.tsx +28 -46
- package/src/components/number-input.tsx +210 -43
- package/src/components/one-time-code-input.tsx +288 -0
- package/src/components/phone-input.tsx +299 -0
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +118 -30
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +71 -16
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/ui.tsx +472 -105
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +313 -9
- package/src/feedback/feedback-dialog.tsx +2 -0
- package/src/feedback/feedback-thread.tsx +66 -5
- package/src/i18n/defaults.ts +16 -1
- package/src/i18n/german.ts +47 -0
- package/src/i18n/kit-labels.tsx +78 -2
- package/src/i18n/locales/en.ts +10 -0
- package/src/i18n/locales/es.ts +46 -0
- package/src/i18n/locales/fr.ts +45 -0
- package/src/i18n/locales/hu.ts +46 -0
- package/src/i18n/locales/it.ts +47 -0
- package/src/i18n/locales/zh.ts +44 -0
- package/src/i18n/review.ts +7 -0
- package/src/index.ts +53 -3
- package/src/lib/countries.ts +91 -0
- package/src/lib/decimal-marks.ts +99 -0
- package/src/lib/iban.ts +171 -0
- package/src/lib/isin.ts +62 -0
- package/src/lib/phone.ts +211 -0
- package/src/rhf/fields.tsx +606 -9
- package/src/rhf.ts +2 -1
- package/src/tour/tour.tsx +66 -28
package/src/data-table.ts
CHANGED
|
@@ -10,8 +10,9 @@ export * from "./components/data-table-pagination";
|
|
|
10
10
|
export { FilterPopover } from "./components/data-table-filter-popover";
|
|
11
11
|
// `SortState` comes from data-table-sort above; re-exporting the module wholesale
|
|
12
12
|
// would collide with it, which is why the main barrel names these explicitly too.
|
|
13
|
-
export { DataTable } from "./components/data-table";
|
|
13
|
+
export { DataTable, DEFAULT_DATA_TABLE_SORT_LABELS } from "./components/data-table";
|
|
14
14
|
export type {
|
|
15
|
+
DataTableSortLabels,
|
|
15
16
|
DataTableColumn,
|
|
16
17
|
DataTableProps,
|
|
17
18
|
ServerPagination,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn";
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState, type ClipboardEvent as ReactClipboardEvent, type RefObject } from "react";
|
|
3
|
-
import { Camera, FileText, Paperclip, X } from "lucide-react";
|
|
4
|
-
import { Button } from "../components/ui";
|
|
3
|
+
import { Camera, FileText, Image as ImageIcon, Paperclip, X } from "lucide-react";
|
|
4
|
+
import { Button, Spinner } from "../components/ui";
|
|
5
5
|
import { useKitFileLabels, useKitLabels } from "../i18n/kit-labels";
|
|
6
6
|
import type { FeedbackAttachmentLabels } from "./feedback-dialog";
|
|
7
7
|
|
|
@@ -43,6 +43,10 @@ export interface FeedbackAttachmentFieldLabels {
|
|
|
43
43
|
attachmentRemoveFile?: (name: string) => string;
|
|
44
44
|
/** `multiple` mode: the line shown in place of the add buttons once `max` is reached. */
|
|
45
45
|
attachmentLimit?: (max: number) => string;
|
|
46
|
+
/** `refs` mode (0.22.0, keksdose K16): the second line of a chip whose upload is still
|
|
47
|
+
* running, where an uploaded one shows its size — "Uploading…". Optional, like the
|
|
48
|
+
* keys above, so a `UiKitLabels` typed before it still compiles. */
|
|
49
|
+
attachmentUploading?: string;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {
|
|
@@ -55,16 +59,36 @@ export const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels =
|
|
|
55
59
|
attachmentRemoveFile: (name) => `Remove ${name}`,
|
|
56
60
|
attachmentLimit: (max) =>
|
|
57
61
|
`Up to ${max} ${max === 1 ? "attachment" : "attachments"} — remove one to add another.`,
|
|
62
|
+
attachmentUploading: "Uploading…",
|
|
58
63
|
};
|
|
59
64
|
|
|
60
|
-
/** Why a file was turned away. `"count"` only ever comes from `multiple`
|
|
61
|
-
* files arrived at once than `max` left room for, and the surplus was
|
|
65
|
+
/** Why a file was turned away. `"count"` only ever comes from `multiple` (and `refs`)
|
|
66
|
+
* mode: more files arrived at once than `max` left room for, and the surplus was
|
|
67
|
+
* dropped. */
|
|
62
68
|
export type FeedbackAttachmentError = "type" | "size" | "count";
|
|
63
69
|
|
|
70
|
+
/** `refs` mode's reasons: {@link FeedbackAttachmentError}, or `"upload"` — `onUpload`
|
|
71
|
+
* rejected, and what it rejected with is `onError`'s second argument. A type of its
|
|
72
|
+
* own so a host's exhaustive switch over the File modes' reasons stays exhaustive. */
|
|
73
|
+
export type FeedbackAttachmentRefsError = FeedbackAttachmentError | "upload";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* One uploaded attachment in `refs` mode: what the host's upload answered with. `key`
|
|
77
|
+
* identifies it (a storage key, an upload id) and is what a remove goes by; `name` is
|
|
78
|
+
* what the chip says. `size` (bytes) and `type` (mime) are optional — given, the chip
|
|
79
|
+
* shows the size and an image icon or a file icon accordingly.
|
|
80
|
+
*/
|
|
81
|
+
export interface FeedbackAttachmentRef {
|
|
82
|
+
key: string;
|
|
83
|
+
name: string;
|
|
84
|
+
size?: number;
|
|
85
|
+
type?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
interface FeedbackAttachmentFieldBaseProps {
|
|
65
89
|
/** Prop > `<UiKitProvider labels={{ feedbackAttachment }}>` > English. Optional
|
|
66
90
|
* since 0.7.0; `attachment` (the heading) is only ever read from here. */
|
|
67
|
-
labels?: Partial<FeedbackAttachmentLabels>;
|
|
91
|
+
labels?: Partial<FeedbackAttachmentLabels> & Pick<Partial<FeedbackAttachmentFieldLabels>, "attachmentUploading">;
|
|
68
92
|
accept?: string[];
|
|
69
93
|
maxBytes?: number;
|
|
70
94
|
/** Snapshot the app view behind this and return it as a File. A "Capture
|
|
@@ -84,6 +108,7 @@ interface FeedbackAttachmentFieldBaseProps {
|
|
|
84
108
|
/** One file: choosing a second means removing the first. The field's only mode until 0.15.5. */
|
|
85
109
|
export interface FeedbackAttachmentFieldSingleProps extends FeedbackAttachmentFieldBaseProps {
|
|
86
110
|
multiple?: false;
|
|
111
|
+
refs?: false;
|
|
87
112
|
value: File | null;
|
|
88
113
|
onChange: (file: File | null) => void;
|
|
89
114
|
onError?: (kind: "type" | "size") => void;
|
|
@@ -100,6 +125,7 @@ export interface FeedbackAttachmentFieldSingleProps extends FeedbackAttachmentFi
|
|
|
100
125
|
*/
|
|
101
126
|
export interface FeedbackAttachmentFieldMultipleProps extends FeedbackAttachmentFieldBaseProps {
|
|
102
127
|
multiple: true;
|
|
128
|
+
refs?: false;
|
|
103
129
|
value: File[];
|
|
104
130
|
onChange: (files: File[]) => void;
|
|
105
131
|
/** How many files `value` may hold. Default {@link DEFAULT_MAX_ATTACHMENTS} (5). The
|
|
@@ -119,9 +145,51 @@ export interface FeedbackAttachmentFieldMultipleProps extends FeedbackAttachment
|
|
|
119
145
|
onScreenshotChange?: (file: File | null) => void;
|
|
120
146
|
}
|
|
121
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Uploaded on pick (0.22.0, keksdose K16). keksdose's support chat uploads each file
|
|
150
|
+
* the moment it is chosen and sends the message with the REFS the uploads answered —
|
|
151
|
+
* a 10 MB screenshot is refused while the reporter is still typing, not when they
|
|
152
|
+
* press Send — so its `AttachmentPicker` could not use this field, whose value is
|
|
153
|
+
* `File`s held until the form is submitted. Here the value is
|
|
154
|
+
* {@link FeedbackAttachmentRef}s: every way in (pick, paste, capture) validates the
|
|
155
|
+
* file as the other modes do, then hands it to `onUpload` and shows a chip with a
|
|
156
|
+
* spinner until it answers; the ref it resolves to joins `value` through `onChange`.
|
|
157
|
+
* A rejection drops the chip and reports `onError("upload", error)`. A chip is removed
|
|
158
|
+
* BY KEY: `onChange` gets the list without it, and `onRemove` the key, for a host that
|
|
159
|
+
* deletes the upload on the server too.
|
|
160
|
+
*
|
|
161
|
+
* Uploads still running count against `max`, so five picks in quick succession cannot
|
|
162
|
+
* overshoot it; they are not in `value` until they land, so a send that reads `value`
|
|
163
|
+
* sends only what has arrived — hold Send back while {@link onUploadingChange} says
|
|
164
|
+
* an upload is running. Several files upload side by side and join in the order they
|
|
165
|
+
* finish. The File modes are unchanged.
|
|
166
|
+
*/
|
|
167
|
+
export interface FeedbackAttachmentFieldRefsProps extends FeedbackAttachmentFieldBaseProps {
|
|
168
|
+
refs: true;
|
|
169
|
+
multiple?: never;
|
|
170
|
+
value: FeedbackAttachmentRef[];
|
|
171
|
+
onChange: (refs: FeedbackAttachmentRef[]) => void;
|
|
172
|
+
/** Upload one file; resolve to its ref, or reject to refuse it. Called once per
|
|
173
|
+
* accepted file, the moment it is chosen. */
|
|
174
|
+
onUpload: (file: File) => Promise<FeedbackAttachmentRef>;
|
|
175
|
+
/** A chip was removed — after `onChange` — with its ref's key. */
|
|
176
|
+
onRemove?: (key: string) => void;
|
|
177
|
+
/** Whether any upload is running: `true` when the first starts, `false` when the
|
|
178
|
+
* last settles — for the host's Send, which should wait. */
|
|
179
|
+
onUploadingChange?: (uploading: boolean) => void;
|
|
180
|
+
/** How many refs `value` may hold, uploads in flight included. Default
|
|
181
|
+
* {@link DEFAULT_MAX_ATTACHMENTS} (5). */
|
|
182
|
+
max?: number;
|
|
183
|
+
/** Per file. `"upload"` carries what `onUpload` rejected with. */
|
|
184
|
+
onError?: (kind: FeedbackAttachmentRefsError, error?: unknown) => void;
|
|
185
|
+
screenshot?: never;
|
|
186
|
+
onScreenshotChange?: never;
|
|
187
|
+
}
|
|
188
|
+
|
|
122
189
|
export type FeedbackAttachmentFieldProps =
|
|
123
190
|
| FeedbackAttachmentFieldSingleProps
|
|
124
|
-
| FeedbackAttachmentFieldMultipleProps
|
|
191
|
+
| FeedbackAttachmentFieldMultipleProps
|
|
192
|
+
| FeedbackAttachmentFieldRefsProps;
|
|
125
193
|
|
|
126
194
|
/**
|
|
127
195
|
* Picking one picture: the file dialog, a capture of the app view, or a paste.
|
|
@@ -154,9 +222,10 @@ export type FeedbackAttachmentFieldProps =
|
|
|
154
222
|
* handed in by whoever renders both the box and this field side by side.
|
|
155
223
|
*/
|
|
156
224
|
export function FeedbackAttachmentField(props: FeedbackAttachmentFieldProps) {
|
|
157
|
-
//
|
|
158
|
-
// (one preview vs. one per chip), and a hook order that
|
|
159
|
-
// crash the day a caller flips it.
|
|
225
|
+
// One component per mode rather than one with branches: the modes hold different
|
|
226
|
+
// state (one preview vs. one per chip vs. uploads in flight), and a hook order that
|
|
227
|
+
// depends on a prop is a crash the day a caller flips it.
|
|
228
|
+
if (props.refs) return <RefsField {...props} />;
|
|
160
229
|
return props.multiple ? <MultipleField {...props} /> : <SingleField {...props} />;
|
|
161
230
|
}
|
|
162
231
|
|
|
@@ -480,6 +549,241 @@ function MultipleField({
|
|
|
480
549
|
);
|
|
481
550
|
}
|
|
482
551
|
|
|
552
|
+
/** A file `refs` mode is uploading: its own id (a ref has no key yet), and the file. */
|
|
553
|
+
interface Upload {
|
|
554
|
+
id: number;
|
|
555
|
+
file: File;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let nextUploadId = 0;
|
|
559
|
+
|
|
560
|
+
function RefsField({
|
|
561
|
+
value,
|
|
562
|
+
onChange,
|
|
563
|
+
onUpload,
|
|
564
|
+
onRemove,
|
|
565
|
+
onUploadingChange,
|
|
566
|
+
max = DEFAULT_MAX_ATTACHMENTS,
|
|
567
|
+
labels: labelsProp,
|
|
568
|
+
accept = DEFAULT_ATTACHMENT_ACCEPT,
|
|
569
|
+
maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
|
|
570
|
+
onError,
|
|
571
|
+
onCaptureScreenshot,
|
|
572
|
+
documentPaste = false,
|
|
573
|
+
pasteFrom,
|
|
574
|
+
className,
|
|
575
|
+
}: FeedbackAttachmentFieldRefsProps) {
|
|
576
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
577
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
578
|
+
const fileText = useKitFileLabels();
|
|
579
|
+
const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
|
|
580
|
+
const removeLabel = text.attachmentRemoveFile ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentRemoveFile!;
|
|
581
|
+
const limitLabel = text.attachmentLimit ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentLimit!;
|
|
582
|
+
const uploadingLabel = text.attachmentUploading ?? DEFAULT_FEEDBACK_ATTACHMENT_LABELS.attachmentUploading!;
|
|
583
|
+
|
|
584
|
+
// As in `multiple` mode: `value` plus whatever has been handed to `onChange` since
|
|
585
|
+
// the parent last rendered — two uploads landing in one tick must both survive.
|
|
586
|
+
const latest = useRef(value);
|
|
587
|
+
useLayoutEffect(() => {
|
|
588
|
+
latest.current = value;
|
|
589
|
+
});
|
|
590
|
+
const commit = (next: FeedbackAttachmentRef[]) => {
|
|
591
|
+
latest.current = next;
|
|
592
|
+
onChange(next);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const [uploads, setUploads] = useState<Upload[]>([]);
|
|
596
|
+
// The uploads in flight, kept in a ref as well: `add` runs several times before a
|
|
597
|
+
// render when files arrive together, and each must see the others — their count
|
|
598
|
+
// against `max`, their names against a pasted image's.
|
|
599
|
+
const inFlight = useRef<Upload[]>([]);
|
|
600
|
+
// Unmounted (the dialog closed mid-upload): a late answer is not this field's to
|
|
601
|
+
// commit, and setting state on it would be a no-op at best.
|
|
602
|
+
const mounted = useRef(true);
|
|
603
|
+
useEffect(() => {
|
|
604
|
+
mounted.current = true;
|
|
605
|
+
return () => {
|
|
606
|
+
mounted.current = false;
|
|
607
|
+
};
|
|
608
|
+
}, []);
|
|
609
|
+
const uploadingChange = useRef(onUploadingChange);
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
uploadingChange.current = onUploadingChange;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const room = Math.max(0, max - value.length - uploads.length);
|
|
615
|
+
|
|
616
|
+
const start = (file: File) => {
|
|
617
|
+
const upload: Upload = { id: nextUploadId++, file };
|
|
618
|
+
inFlight.current = [...inFlight.current, upload];
|
|
619
|
+
if (inFlight.current.length === 1) uploadingChange.current?.(true);
|
|
620
|
+
setUploads(inFlight.current);
|
|
621
|
+
const settle = () => {
|
|
622
|
+
inFlight.current = inFlight.current.filter((u) => u.id !== upload.id);
|
|
623
|
+
if (!mounted.current) return;
|
|
624
|
+
setUploads(inFlight.current);
|
|
625
|
+
if (inFlight.current.length === 0) uploadingChange.current?.(false);
|
|
626
|
+
};
|
|
627
|
+
let answer: Promise<FeedbackAttachmentRef>;
|
|
628
|
+
try {
|
|
629
|
+
answer = Promise.resolve(onUpload(file));
|
|
630
|
+
} catch (error) {
|
|
631
|
+
answer = Promise.reject(error);
|
|
632
|
+
}
|
|
633
|
+
answer.then(
|
|
634
|
+
(ref) => {
|
|
635
|
+
settle();
|
|
636
|
+
if (mounted.current) commit([...latest.current, ref]);
|
|
637
|
+
},
|
|
638
|
+
(error: unknown) => {
|
|
639
|
+
settle();
|
|
640
|
+
if (mounted.current) onError?.("upload", error);
|
|
641
|
+
},
|
|
642
|
+
);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const add = (incoming: File[], pasted = false) => {
|
|
646
|
+
let space = Math.max(0, max - latest.current.length - inFlight.current.length);
|
|
647
|
+
const taken = new Set([...latest.current.map((r) => r.name), ...inFlight.current.map((u) => u.file.name)]);
|
|
648
|
+
let dropped = false;
|
|
649
|
+
for (const raw of incoming) {
|
|
650
|
+
const problem = rejection(raw, accept, maxBytes);
|
|
651
|
+
if (problem) {
|
|
652
|
+
onError?.(problem);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (space === 0) {
|
|
656
|
+
dropped = true;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
// Pasted images are all "image.png" to the clipboard; as in `multiple` mode,
|
|
660
|
+
// each gets a name of its own before it is uploaded under it.
|
|
661
|
+
const file = pasted ? renamed(raw, uniqueName(pastedName(raw.type), taken)) : raw;
|
|
662
|
+
taken.add(file.name);
|
|
663
|
+
space -= 1;
|
|
664
|
+
start(file);
|
|
665
|
+
}
|
|
666
|
+
if (dropped) onError?.("count");
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const onPaste = usePaste((images) => add(images, true), documentPaste, pasteFrom);
|
|
670
|
+
|
|
671
|
+
// Focus after a remove, as in `multiple` mode: the next chip, else the first action.
|
|
672
|
+
const pendingFocus = useRef<number | null>(null);
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
const index = pendingFocus.current;
|
|
675
|
+
if (index === null) return;
|
|
676
|
+
pendingFocus.current = null;
|
|
677
|
+
const root = rootRef.current;
|
|
678
|
+
if (!root) return;
|
|
679
|
+
const removes = root.querySelectorAll<HTMLElement>("[data-attachment-remove]");
|
|
680
|
+
const target =
|
|
681
|
+
removes[index] ?? root.querySelector<HTMLElement>("[data-attachment-action]") ?? removes[index - 1];
|
|
682
|
+
target?.focus();
|
|
683
|
+
}, [value]);
|
|
684
|
+
|
|
685
|
+
const remove = (key: string, index: number) => {
|
|
686
|
+
pendingFocus.current = index;
|
|
687
|
+
commit(latest.current.filter((r) => r.key !== key));
|
|
688
|
+
onRemove?.(key);
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const hasChips = value.length > 0 || uploads.length > 0;
|
|
692
|
+
|
|
693
|
+
return (
|
|
694
|
+
<div ref={rootRef} className={cn("relative space-y-2", className)} onPaste={onPaste}>
|
|
695
|
+
<Heading text={labelsProp?.attachment} className="mb-0" />
|
|
696
|
+
{hasChips && (
|
|
697
|
+
<ul aria-label={text.attachmentList} className="flex flex-col gap-2">
|
|
698
|
+
{value.map((ref, index) => (
|
|
699
|
+
<li
|
|
700
|
+
key={`ref:${ref.key}`}
|
|
701
|
+
className="flex min-w-0 items-center gap-2 rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-1"
|
|
702
|
+
>
|
|
703
|
+
<RefGlyph type={ref.type} />
|
|
704
|
+
<div className="min-w-0 flex-1">
|
|
705
|
+
<div className="truncate text-sm text-[var(--text-secondary)]">{ref.name}</div>
|
|
706
|
+
{ref.size !== undefined && (
|
|
707
|
+
<div className="truncate text-xs text-[var(--text-muted)]">{fileText.size(ref.size)}</div>
|
|
708
|
+
)}
|
|
709
|
+
</div>
|
|
710
|
+
<button
|
|
711
|
+
type="button"
|
|
712
|
+
data-attachment-remove=""
|
|
713
|
+
onClick={() => remove(ref.key, index)}
|
|
714
|
+
aria-label={removeLabel(ref.name)}
|
|
715
|
+
className="shrink-0 rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]"
|
|
716
|
+
>
|
|
717
|
+
<X className="size-4" />
|
|
718
|
+
</button>
|
|
719
|
+
</li>
|
|
720
|
+
))}
|
|
721
|
+
{uploads.map((upload) => (
|
|
722
|
+
<li
|
|
723
|
+
key={`upload:${upload.id}`}
|
|
724
|
+
aria-busy="true"
|
|
725
|
+
data-attachment-uploading=""
|
|
726
|
+
className="flex min-w-0 items-center gap-2 rounded-md border border-dashed border-[var(--border)] bg-[var(--bg-surface)] p-1"
|
|
727
|
+
>
|
|
728
|
+
<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)]">
|
|
729
|
+
<Spinner label={null} className="size-5" />
|
|
730
|
+
</span>
|
|
731
|
+
<div className="min-w-0 flex-1">
|
|
732
|
+
<div className="truncate text-sm text-[var(--text-secondary)]">{upload.file.name}</div>
|
|
733
|
+
<div className="truncate text-xs text-[var(--text-muted)]">{uploadingLabel}</div>
|
|
734
|
+
</div>
|
|
735
|
+
</li>
|
|
736
|
+
))}
|
|
737
|
+
</ul>
|
|
738
|
+
)}
|
|
739
|
+
{room > 0 && (
|
|
740
|
+
<div className="flex flex-wrap gap-2">
|
|
741
|
+
<Button
|
|
742
|
+
type="button"
|
|
743
|
+
variant="secondary"
|
|
744
|
+
data-attachment-action=""
|
|
745
|
+
onClick={() => fileInputRef.current?.click()}
|
|
746
|
+
>
|
|
747
|
+
<Paperclip className="size-4" /> {text.attachmentAdd}
|
|
748
|
+
</Button>
|
|
749
|
+
{onCaptureScreenshot && (
|
|
750
|
+
<CaptureButton
|
|
751
|
+
capture={onCaptureScreenshot}
|
|
752
|
+
onFile={(file) => add([file])}
|
|
753
|
+
label={text.attachmentCapture}
|
|
754
|
+
data-attachment-action=""
|
|
755
|
+
/>
|
|
756
|
+
)}
|
|
757
|
+
</div>
|
|
758
|
+
)}
|
|
759
|
+
<p className="text-xs text-[var(--text-muted)]">{room > 0 ? text.attachmentPaste : limitLabel(max)}</p>
|
|
760
|
+
<input
|
|
761
|
+
ref={fileInputRef}
|
|
762
|
+
type="file"
|
|
763
|
+
multiple={room > 1}
|
|
764
|
+
accept={accept.join(",")}
|
|
765
|
+
className="sr-only"
|
|
766
|
+
tabIndex={-1}
|
|
767
|
+
aria-hidden
|
|
768
|
+
onChange={(e) => {
|
|
769
|
+
add(Array.from(e.target.files ?? []));
|
|
770
|
+
e.target.value = "";
|
|
771
|
+
}}
|
|
772
|
+
/>
|
|
773
|
+
</div>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** A ref chip's picture. The bytes are on the server, not here, so there is nothing to
|
|
778
|
+
* preview: an image glyph for an image `type`, a file glyph for anything else. */
|
|
779
|
+
function RefGlyph({ type }: { type?: string }) {
|
|
780
|
+
return (
|
|
781
|
+
<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)]">
|
|
782
|
+
{type?.startsWith("image/") ? <ImageIcon aria-hidden className="size-6" /> : <FileText aria-hidden className="size-6" />}
|
|
783
|
+
</span>
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
483
787
|
function Heading({ text, className }: { text?: string; className?: string }) {
|
|
484
788
|
if (!text) return null;
|
|
485
789
|
return (
|
|
@@ -40,6 +40,8 @@ export interface FeedbackAttachmentLabels {
|
|
|
40
40
|
attachmentRemoveFile?: (name: string) => string;
|
|
41
41
|
/** `multiple` mode: the line shown once `max` files are attached. */
|
|
42
42
|
attachmentLimit?: (max: number) => string;
|
|
43
|
+
/** `refs` mode (0.22.0): the second line of a chip whose upload is still running. */
|
|
44
|
+
attachmentUploading?: string;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
1
|
+
import { useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
import type { ReactNode, Ref } from "react";
|
|
3
3
|
import { ImageIcon, ImageOff, Paperclip, Send } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
@@ -535,6 +535,23 @@ export interface FeedbackComposerProps {
|
|
|
535
535
|
ref?: Ref<FeedbackComposerHandle>;
|
|
536
536
|
labels?: Partial<FeedbackComposerLabels>;
|
|
537
537
|
className?: string;
|
|
538
|
+
/**
|
|
539
|
+
* `"sm"` for a narrow host — keksdose's assistant launcher, a small floating panel
|
|
540
|
+
* (0.22): a small Send button, and the send hint ("Enter to send, Shift + Enter for a
|
|
541
|
+
* new line") hidden from view but kept as the box's description, so a keyboard or
|
|
542
|
+
* screen-reader user still learns the shortcut. Default `"md"`.
|
|
543
|
+
*/
|
|
544
|
+
size?: "md" | "sm";
|
|
545
|
+
/**
|
|
546
|
+
* On the composer's root — the box, or the `disabledReason` line in its place —
|
|
547
|
+
* with any `data-*` attribute beside it (keksdose K17). keksdose's assistant tour
|
|
548
|
+
* points at the chat box with `[data-tour="assistant-ask"]`, and a composer that
|
|
549
|
+
* dropped every attribute it was not told about left the anchor nowhere: the
|
|
550
|
+
* assistant kept its hand-built box rather than lose the tour step.
|
|
551
|
+
*/
|
|
552
|
+
id?: string;
|
|
553
|
+
/** `data-*` attributes for the root, as `id` — a tour anchor, a test id. */
|
|
554
|
+
[dataAttribute: `data-${string}`]: string | number | boolean | undefined;
|
|
538
555
|
}
|
|
539
556
|
|
|
540
557
|
/**
|
|
@@ -583,8 +600,13 @@ export function FeedbackComposer({
|
|
|
583
600
|
ref,
|
|
584
601
|
labels: labelsProp,
|
|
585
602
|
className,
|
|
603
|
+
id,
|
|
604
|
+
size = "md",
|
|
605
|
+
...rest
|
|
586
606
|
}: FeedbackComposerProps) {
|
|
587
607
|
const labels = useKitLabels("feedbackComposer", DEFAULT_FEEDBACK_COMPOSER_LABELS, labelsProp);
|
|
608
|
+
const rootAttributes = { id, ...dataAttributes(rest) };
|
|
609
|
+
const hintId = useId();
|
|
588
610
|
const [ownDraft, setOwnDraft] = useState("");
|
|
589
611
|
const controlled = value !== undefined;
|
|
590
612
|
const draft = controlled ? value : ownDraft;
|
|
@@ -639,7 +661,11 @@ export function FeedbackComposer({
|
|
|
639
661
|
);
|
|
640
662
|
|
|
641
663
|
if (disabledReason != null && disabledReason !== false) {
|
|
642
|
-
return
|
|
664
|
+
return (
|
|
665
|
+
<p {...rootAttributes} className={cn("text-xs text-[var(--text-muted)]", className)}>
|
|
666
|
+
{disabledReason}
|
|
667
|
+
</p>
|
|
668
|
+
);
|
|
643
669
|
}
|
|
644
670
|
|
|
645
671
|
const canSend = (canSendProp ?? draft.trim().length > 0) && !pending;
|
|
@@ -658,11 +684,12 @@ export function FeedbackComposer({
|
|
|
658
684
|
const config = attachment === true ? {} : attachment || null;
|
|
659
685
|
|
|
660
686
|
return (
|
|
661
|
-
<div ref={root} className={cn("space-y-2", className)}>
|
|
687
|
+
<div {...rootAttributes} ref={root} className={cn("space-y-2", className)}>
|
|
662
688
|
<Textarea
|
|
663
689
|
rows={rows}
|
|
664
690
|
dir="auto"
|
|
665
691
|
aria-label={labels.field}
|
|
692
|
+
aria-describedby={size === "sm" ? hintId : undefined}
|
|
666
693
|
ref={box}
|
|
667
694
|
placeholder={placeholder ?? labels.placeholder}
|
|
668
695
|
value={draft}
|
|
@@ -707,10 +734,18 @@ export function FeedbackComposer({
|
|
|
707
734
|
<span />
|
|
708
735
|
)}
|
|
709
736
|
<div className="ms-auto flex items-center gap-2">
|
|
710
|
-
<span
|
|
737
|
+
<span
|
|
738
|
+
id={hintId}
|
|
739
|
+
className={size === "sm" ? "sr-only" : "text-xs text-[var(--text-placeholder)]"}
|
|
740
|
+
>
|
|
711
741
|
{sendOn === "enter" ? labels.sendHintEnter : labels.sendHint(modifier)}
|
|
712
742
|
</span>
|
|
713
|
-
<Button
|
|
743
|
+
<Button
|
|
744
|
+
onClick={send}
|
|
745
|
+
disabled={!canSend}
|
|
746
|
+
aria-busy={pending || undefined}
|
|
747
|
+
size={size === "sm" ? "sm" : undefined}
|
|
748
|
+
>
|
|
714
749
|
{pending ? (
|
|
715
750
|
<Spinner label={null} className="size-4" />
|
|
716
751
|
) : (
|
|
@@ -723,3 +758,29 @@ export function FeedbackComposer({
|
|
|
723
758
|
</div>
|
|
724
759
|
);
|
|
725
760
|
}
|
|
761
|
+
|
|
762
|
+
/** The `data-*` entries of a rest-props object, and nothing else: whatever a caller
|
|
763
|
+
* spread in that the composer does not know is not the root's to receive. */
|
|
764
|
+
function dataAttributes(props: object): Record<string, unknown> {
|
|
765
|
+
const out: Record<string, unknown> = {};
|
|
766
|
+
for (const [key, value] of Object.entries(props)) if (key.startsWith("data-")) out[key] = value;
|
|
767
|
+
return out;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* {@link FeedbackComposer} under a name that is not about feedback (keksdose K17).
|
|
772
|
+
*
|
|
773
|
+
* The component was never feedback-specific: a textarea, an optional picture or a
|
|
774
|
+
* host's upload slot, Send, `sendOn="enter"` for the chat way. keksdose's assistant
|
|
775
|
+
* and support chat are chats, not reports, and an import of `FeedbackComposer` there
|
|
776
|
+
* reads as a mistake to the next person. The same function, not a wrapper, so the
|
|
777
|
+
* two names cannot drift.
|
|
778
|
+
*/
|
|
779
|
+
export const ChatComposer = FeedbackComposer;
|
|
780
|
+
/** {@link FeedbackComposerProps}, for {@link ChatComposer}. */
|
|
781
|
+
export type ChatComposerProps = FeedbackComposerProps;
|
|
782
|
+
/** {@link FeedbackComposerHandle}, for {@link ChatComposer}. */
|
|
783
|
+
export type ChatComposerHandle = FeedbackComposerHandle;
|
|
784
|
+
/** {@link FeedbackComposerLabels}, for {@link ChatComposer} — still the `feedbackComposer`
|
|
785
|
+
* namespace of the provider. */
|
|
786
|
+
export type ChatComposerLabels = FeedbackComposerLabels;
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -6,7 +6,11 @@ import { DEFAULT_PAGE_CONTENTS_LABELS } from "../components/page-contents";
|
|
|
6
6
|
import { DEFAULT_POPOVER_LABELS } from "../components/popover";
|
|
7
7
|
import { DEFAULT_CHIP_INPUT_LABELS } from "../components/chip";
|
|
8
8
|
import { DEFAULT_FIELD_SYNC_LABELS } from "../components/field-sync";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_CHARACTER_COUNT_LABELS,
|
|
11
|
+
DEFAULT_PASSWORD_REVEAL_LABELS,
|
|
12
|
+
DEFAULT_TABS_LABELS,
|
|
13
|
+
} from "../components/ui";
|
|
10
14
|
import { DEFAULT_WIZARD_LABELS } from "../wizard/types";
|
|
11
15
|
import { DEFAULT_TOUR_LABELS } from "../tour/tour";
|
|
12
16
|
import { DEFAULT_COMMAND_PALETTE_LABELS } from "../search/command-palette";
|
|
@@ -53,6 +57,11 @@ import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card";
|
|
|
53
57
|
import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog";
|
|
54
58
|
import { DEFAULT_SERVER_WAKE_LABELS } from "../components/server-wake";
|
|
55
59
|
import { DEFAULT_TRANSLATION_REVIEW_LABELS } from "../components/translation-review-labels";
|
|
60
|
+
import { DEFAULT_COUNTRY_SELECT_LABELS } from "../components/country-select";
|
|
61
|
+
import { DEFAULT_INLINE_EDIT_LABELS } from "../components/inline-edit-field";
|
|
62
|
+
import { DEFAULT_IBAN_INPUT_LABELS } from "../components/iban-input";
|
|
63
|
+
import { DEFAULT_PHONE_INPUT_LABELS } from "../components/phone-input";
|
|
64
|
+
import { DEFAULT_SIGN_CHIP_LABELS } from "../components/sign-chip";
|
|
56
65
|
import {
|
|
57
66
|
DEFAULT_APP_SHELL_LABELS,
|
|
58
67
|
DEFAULT_CALCULATOR_LABELS,
|
|
@@ -142,4 +151,10 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
142
151
|
serverWake: DEFAULT_SERVER_WAKE_LABELS,
|
|
143
152
|
translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,
|
|
144
153
|
legal: DEFAULT_LEGAL_LABELS,
|
|
154
|
+
characterCount: DEFAULT_CHARACTER_COUNT_LABELS,
|
|
155
|
+
countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,
|
|
156
|
+
inlineEdit: DEFAULT_INLINE_EDIT_LABELS,
|
|
157
|
+
ibanInput: DEFAULT_IBAN_INPUT_LABELS,
|
|
158
|
+
phoneInput: DEFAULT_PHONE_INPUT_LABELS,
|
|
159
|
+
signChip: DEFAULT_SIGN_CHIP_LABELS,
|
|
145
160
|
};
|
package/src/i18n/german.ts
CHANGED
|
@@ -28,6 +28,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
28
28
|
attachmentScreenshot: "Screenshot",
|
|
29
29
|
attachmentRemoveFile: (name) => `${name} entfernen`,
|
|
30
30
|
attachmentLimit: (max) => `Maximal ${n(max)} ${max === 1 ? "Anhang" : "Anhänge"} – zum Hinzufügen erst einen entfernen.`,
|
|
31
|
+
attachmentUploading: "Wird hochgeladen…",
|
|
31
32
|
},
|
|
32
33
|
feedbackDialog: {
|
|
33
34
|
title: "Feedback senden",
|
|
@@ -210,6 +211,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
210
211
|
booleanTrue: "Ja",
|
|
211
212
|
booleanFalse: "Nein",
|
|
212
213
|
booleanUnset: "Nicht festgelegt",
|
|
214
|
+
sortBy: "Sortieren nach",
|
|
215
|
+
sortDefault: "Standardreihenfolge",
|
|
216
|
+
sortAscending: "Aufsteigend",
|
|
217
|
+
sortDescending: "Absteigend",
|
|
213
218
|
},
|
|
214
219
|
miniCalendar: {
|
|
215
220
|
previousMonth: "Vorheriger Monat",
|
|
@@ -249,6 +254,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
249
254
|
previousMonth: "Vorheriger Monat",
|
|
250
255
|
nextMonth: "Nächster Monat",
|
|
251
256
|
today: "Heute",
|
|
257
|
+
yearPanel: "Jahr wählen",
|
|
258
|
+
earlierYears: "Frühere Jahre",
|
|
259
|
+
laterYears: "Spätere Jahre",
|
|
260
|
+
thisYear: "Dieses Jahr",
|
|
252
261
|
},
|
|
253
262
|
popover: {
|
|
254
263
|
panel: "Aufklappfenster",
|
|
@@ -330,6 +339,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
330
339
|
prompt: "Dies kann nicht rückgängig gemacht werden.",
|
|
331
340
|
password: "Passwort",
|
|
332
341
|
phrase: (phrase) => `Geben Sie zur Bestätigung „${phrase}“ ein`,
|
|
342
|
+
acknowledge: "Ich habe gelesen, was dies bewirkt, und möchte fortfahren.",
|
|
333
343
|
},
|
|
334
344
|
tabs: {
|
|
335
345
|
// "Tab" is what German UIs say; "Registerkarte" reads like a 1990s manual.
|
|
@@ -541,6 +551,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
541
551
|
form: {
|
|
542
552
|
save: "Speichern",
|
|
543
553
|
cancel: "Abbrechen",
|
|
554
|
+
// German keyboards label the key "Strg"; Apple's ⌘ is the same everywhere.
|
|
555
|
+
submitShortcut: (apple) => (apple ? "⌘ Enter" : "Strg+Enter"),
|
|
544
556
|
},
|
|
545
557
|
descriptionList: {
|
|
546
558
|
empty: "—",
|
|
@@ -651,6 +663,41 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
651
663
|
legal: {
|
|
652
664
|
navLabel: "Rechtliches",
|
|
653
665
|
},
|
|
666
|
+
characterCount: {
|
|
667
|
+
// "Zeichen" is the same in singular and plural.
|
|
668
|
+
count: (used, max) => `${n(used)} von ${n(max)} Zeichen`,
|
|
669
|
+
remaining: (left) => `Noch ${n(left)} Zeichen`,
|
|
670
|
+
limitReached: "Zeichenlimit erreicht",
|
|
671
|
+
},
|
|
672
|
+
countrySelect: {
|
|
673
|
+
country: "Land",
|
|
674
|
+
search: "Land suchen",
|
|
675
|
+
others: "Weitere Länder",
|
|
676
|
+
},
|
|
677
|
+
inlineEdit: {
|
|
678
|
+
edit: (label) => `${label} bearbeiten`,
|
|
679
|
+
failed: "Die Änderung konnte nicht gespeichert werden.",
|
|
680
|
+
empty: "Leer",
|
|
681
|
+
},
|
|
682
|
+
ibanInput: {
|
|
683
|
+
format: "Eine IBAN beginnt mit einem Ländercode aus zwei Buchstaben und zwei Prüfziffern.",
|
|
684
|
+
country: (code) => `„${code}“ ist kein Ländercode einer IBAN.`,
|
|
685
|
+
length: (actual, expected) =>
|
|
686
|
+
`Eine IBAN aus diesem Land hat ${n(expected)} Zeichen – diese hat ${n(actual)}.`,
|
|
687
|
+
checksum: "Die Prüfziffern stimmen nicht – vermutlich ist ein Zeichen falsch eingegeben.",
|
|
688
|
+
qrRequired: "Dies ist eine reguläre IBAN. Eine QR-Rechnung braucht die QR-IBAN des Kontos.",
|
|
689
|
+
qrNotAllowed:
|
|
690
|
+
"Dies ist eine QR-IBAN, die nur Zahlungen mit QR-Rechnung empfängt. Geben Sie die reguläre IBAN des Kontos ein.",
|
|
691
|
+
},
|
|
692
|
+
phoneInput: {
|
|
693
|
+
countryCode: "Ländervorwahl",
|
|
694
|
+
other: "Andere",
|
|
695
|
+
},
|
|
696
|
+
signChip: {
|
|
697
|
+
outflow: "Ausgabe",
|
|
698
|
+
inflow: "Einnahme",
|
|
699
|
+
direction: (current, next) => `Richtung: ${current} – zu ${next} wechseln`,
|
|
700
|
+
},
|
|
654
701
|
translationReview: {
|
|
655
702
|
statusMissing: "Fehlt",
|
|
656
703
|
statusUnreviewed: "Ungeprüft",
|