@eifi1/ui-kit 0.15.4 → 0.15.5

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 (98) hide show
  1. package/README.md +5 -5
  2. package/dist/chart.d.ts +2 -3
  3. package/dist/components/alert-banner.js +1 -1
  4. package/dist/components/alert-banner.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +5 -3
  6. package/dist/components/amount-input.js +1 -1
  7. package/dist/components/amount-input.js.map +1 -1
  8. package/dist/components/button-group.d.ts +2 -3
  9. package/dist/components/calculator.d.ts +2 -3
  10. package/dist/components/checkbox.js +1 -1
  11. package/dist/components/checkbox.js.map +1 -1
  12. package/dist/components/choice-card.js +1 -1
  13. package/dist/components/choice-card.js.map +1 -1
  14. package/dist/components/copy-button.d.ts +2 -3
  15. package/dist/components/error-boundary.js +1 -1
  16. package/dist/components/error-boundary.js.map +1 -1
  17. package/dist/components/facing-pair.d.ts +2 -3
  18. package/dist/components/file-button.d.ts +2 -3
  19. package/dist/components/file-dropzone.d.ts +2 -3
  20. package/dist/components/file-dropzone.js +1 -1
  21. package/dist/components/file-dropzone.js.map +1 -1
  22. package/dist/components/form-actions.d.ts +2 -3
  23. package/dist/components/money-field.d.ts +2 -3
  24. package/dist/components/number-field.d.ts +2 -3
  25. package/dist/components/number-input.d.ts +2 -3
  26. package/dist/components/numpad-sheet.d.ts +2 -3
  27. package/dist/components/series-chart.d.ts +2 -3
  28. package/dist/components/settings-fields.d.ts +2 -3
  29. package/dist/components/text-link.d.ts +2 -3
  30. package/dist/components/time-input.d.ts +2 -3
  31. package/dist/components/ui.d.ts +2 -3
  32. package/dist/components/ui.js +7 -7
  33. package/dist/components/ui.js.map +1 -1
  34. package/dist/feedback/feedback-attachment.d.ts +2 -82
  35. package/dist/feedback/feedback-attachment.js +271 -54
  36. package/dist/feedback/feedback-attachment.js.map +1 -1
  37. package/dist/feedback/feedback-dialog.d.ts +2 -105
  38. package/dist/feedback/feedback-dialog.js +56 -25
  39. package/dist/feedback/feedback-dialog.js.map +1 -1
  40. package/dist/feedback/feedback-inbox.d.ts +1 -1
  41. package/dist/feedback/feedback-thread.d.ts +2 -2
  42. package/dist/feedback-attachment-BYAHJMOr.d.ts +289 -0
  43. package/dist/feedback.d.ts +1 -2
  44. package/dist/hooks/use-file-drop.d.ts +2 -3
  45. package/dist/i18n/defaults.d.ts +2 -3
  46. package/dist/i18n/kit-labels.d.ts +2 -3
  47. package/dist/i18n/locales/de-CH-informal.d.ts +2 -3
  48. package/dist/i18n/locales/de-CH.d.ts +2 -3
  49. package/dist/i18n/locales/de-informal.d.ts +2 -3
  50. package/dist/i18n/locales/de.d.ts +2 -3
  51. package/dist/i18n/locales/de.js +5 -1
  52. package/dist/i18n/locales/de.js.map +1 -1
  53. package/dist/i18n/locales/es.d.ts +2 -3
  54. package/dist/i18n/locales/es.js +5 -1
  55. package/dist/i18n/locales/es.js.map +1 -1
  56. package/dist/i18n/locales/fr.d.ts +2 -3
  57. package/dist/i18n/locales/fr.js +5 -1
  58. package/dist/i18n/locales/fr.js.map +1 -1
  59. package/dist/i18n/locales/hu.d.ts +2 -3
  60. package/dist/i18n/locales/hu.js +5 -1
  61. package/dist/i18n/locales/hu.js.map +1 -1
  62. package/dist/i18n/locales/it.d.ts +2 -3
  63. package/dist/i18n/locales/it.js +5 -1
  64. package/dist/i18n/locales/it.js.map +1 -1
  65. package/dist/i18n/locales/zh.d.ts +2 -3
  66. package/dist/i18n/locales/zh.js +5 -1
  67. package/dist/i18n/locales/zh.js.map +1 -1
  68. package/dist/index.d.ts +2 -3
  69. package/dist/{kit-labels-CnnBd-2N.d.ts → kit-labels-BD2F7sOz.d.ts} +2 -3
  70. package/dist/rhf/fields.d.ts +2 -3
  71. package/dist/rhf/form.d.ts +2 -3
  72. package/dist/rhf.d.ts +2 -3
  73. package/dist/shell/app-shell.d.ts +2 -3
  74. package/dist/shell/top-bar-brand.d.ts +2 -3
  75. package/dist/shell.d.ts +2 -3
  76. package/dist/theme/palette-derive.d.ts +3 -0
  77. package/dist/theme/palette-derive.js +7 -0
  78. package/dist/theme/palette-derive.js.map +1 -1
  79. package/dist/wizard/stepper-nav.d.ts +2 -3
  80. package/dist/wizard.d.ts +2 -3
  81. package/package.json +4 -2
  82. package/src/components/alert-banner.tsx +1 -1
  83. package/src/components/amount-input.tsx +8 -1
  84. package/src/components/checkbox.tsx +1 -1
  85. package/src/components/choice-card.tsx +1 -1
  86. package/src/components/error-boundary.tsx +1 -1
  87. package/src/components/file-dropzone.tsx +1 -1
  88. package/src/components/ui.tsx +7 -7
  89. package/src/feedback/feedback-attachment.tsx +441 -96
  90. package/src/feedback/feedback-dialog.tsx +147 -50
  91. package/src/i18n/locales/de.ts +4 -0
  92. package/src/i18n/locales/es.ts +4 -0
  93. package/src/i18n/locales/fr.ts +4 -0
  94. package/src/i18n/locales/hu.ts +4 -0
  95. package/src/i18n/locales/it.ts +4 -0
  96. package/src/i18n/locales/zh.ts +4 -0
  97. package/src/theme/palette-derive.ts +10 -0
  98. package/tokens.css +9 -1
@@ -8,6 +8,7 @@ import {
8
8
  DEFAULT_ATTACHMENT_ACCEPT,
9
9
  DEFAULT_MAX_ATTACHMENT_BYTES,
10
10
  FeedbackAttachmentField,
11
+ type FeedbackAttachmentError,
11
12
  } from "./feedback-attachment";
12
13
 
13
14
  export interface FeedbackCategoryOption {
@@ -27,6 +28,13 @@ export interface FeedbackAttachmentLabels {
27
28
  * straight in. Optional — falls back to the provider's `feedbackAttachment`, then English. */
28
29
  attachmentPaste?: string;
29
30
  attachmentRemove: string;
31
+ /** `multiple` mode (0.15.5): the chip list's accessible name. Optional, as are the
32
+ * two below — they fall back to the provider's `feedbackAttachment`, then English. */
33
+ attachmentList?: string;
34
+ /** `multiple` mode: one chip's remove button, given the file's name. */
35
+ attachmentRemoveFile?: (name: string) => string;
36
+ /** `multiple` mode: the line shown once `max` files are attached. */
37
+ attachmentLimit?: (max: number) => string;
30
38
  }
31
39
 
32
40
  export interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
@@ -38,6 +46,9 @@ export interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
38
46
  submitHint: string;
39
47
  cancel: string;
40
48
  save: string;
49
+ /** The heading over the attachments under `attachments="multiple"` (0.15.5), where
50
+ * they are a screenshot AND photos. Optional; falls back to the provider, then English. */
51
+ attachments?: string;
41
52
  }
42
53
 
43
54
  /**
@@ -62,6 +73,9 @@ export interface FeedbackDialogTextLabels {
62
73
  submitHint: string;
63
74
  cancel: string;
64
75
  save: string;
76
+ /** The heading over the attachments under `attachments="multiple"` (0.15.5).
77
+ * Optional, so a complete `UiKitLabels` typed before 0.15.5 still compiles. */
78
+ attachments?: string;
65
79
  }
66
80
 
67
81
  export const DEFAULT_FEEDBACK_DIALOG_LABELS: FeedbackDialogTextLabels = {
@@ -71,6 +85,7 @@ export const DEFAULT_FEEDBACK_DIALOG_LABELS: FeedbackDialogTextLabels = {
71
85
  body: "What happened?",
72
86
  bodyOptional: "What happened? (optional)",
73
87
  attachment: "Screenshot",
88
+ attachments: "Attachments",
74
89
  submitHint: "Ctrl/⌘ + Enter to send",
75
90
  cancel: "Cancel",
76
91
  save: "Send",
@@ -84,36 +99,22 @@ export interface FeedbackSubmission {
84
99
  }
85
100
 
86
101
  /**
87
- * The generic feedback form dialog: category + subject + body + an optional image
88
- * attachment, with Ctrl/Cmd+Enter to submit. Domain-free — the app supplies the
89
- * category options, labels and an `onSubmit` that talks to its own backend, plus
90
- * an optional `contextSlot` for app-specific context (user, current URL, …).
91
- *
92
- * **A screenshot can be pasted straight in.** Ctrl/Cmd+V anywhere in the dialog
93
- * takes an image off the clipboard and makes it the attachment, through the same
94
- * validation and the same preview as the file picker. It is the gesture the two
95
- * ways in did not cover: `onCaptureScreenshot` snapshots the *whole* app view,
96
- * and the file picker needs a file — so somebody who wanted to show one panel,
97
- * or one region of one, had to save a crop to disk first and then find it again
98
- * (Steering Design feedback #39). The clipboard is where a region snip already
99
- * is on every platform.
102
+ * What `onSubmit` gets under `attachments="multiple"` (0.15.5): the screenshot and
103
+ * the picked/pasted files apart, because an app files them differently — keksdose
104
+ * attaches the screenshot to the report and the photos as its evidence list. No
105
+ * `attachment`: there is no one file it could name.
100
106
  */
101
- export function FeedbackDialog({
102
- open,
103
- onClose,
104
- categories,
105
- category,
106
- onCategoryChange,
107
- labels: labelsProp,
108
- onSubmit,
109
- submitting = false,
110
- contextSlot,
111
- attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,
112
- maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
113
- onAttachmentError,
114
- onCaptureScreenshot,
115
- requireBody = true,
116
- }: {
107
+ export interface FeedbackMultipleSubmission {
108
+ title: string;
109
+ body: string;
110
+ category: string;
111
+ /** What `onCaptureScreenshot` returned, or `null` — at most one. */
112
+ screenshot: File | null;
113
+ /** Picked and pasted files, in the order they were added; at most `maxAttachments`. */
114
+ attachments: File[];
115
+ }
116
+
117
+ interface FeedbackDialogBaseProps {
117
118
  open: boolean;
118
119
  onClose: () => void;
119
120
  categories: FeedbackCategoryOption[];
@@ -122,12 +123,10 @@ export function FeedbackDialog({
122
123
  /** Prop > `<UiKitProvider labels={{ feedbackDialog, feedbackAttachment }}>` >
123
124
  * English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */
124
125
  labels?: Partial<FeedbackDialogLabels>;
125
- onSubmit: (data: FeedbackSubmission) => void | Promise<void>;
126
126
  submitting?: boolean;
127
127
  contextSlot?: ReactNode;
128
128
  attachmentAccept?: string[];
129
129
  maxAttachmentBytes?: number;
130
- onAttachmentError?: (kind: "type" | "size") => void;
131
130
  /**
132
131
  * Optional: capture a screenshot of the underlying app view and return it as a
133
132
  * File. When provided, a "Capture screenshot" button is shown next to "Add
@@ -140,15 +139,88 @@ export function FeedbackDialog({
140
139
  * dialog's behaviour until 0.14.2 (keksdose K1: its backend takes title-only reports).
141
140
  */
142
141
  requireBody?: boolean;
143
- }) {
142
+ }
143
+
144
+ /** One attachment — a screenshot, a picked or a pasted image; a second replaces
145
+ * nothing, it has to wait for the first to be removed. The default. */
146
+ export interface FeedbackDialogSingleProps extends FeedbackDialogBaseProps {
147
+ attachments?: "single";
148
+ onSubmit: (data: FeedbackSubmission) => void | Promise<void>;
149
+ onAttachmentError?: (kind: "type" | "size") => void;
150
+ /** Multiple mode only — refused here rather than silently ignored. */
151
+ maxAttachments?: never;
152
+ }
153
+
154
+ /**
155
+ * One screenshot plus up to `maxAttachments` picked or pasted files (0.15.5, keksdose
156
+ * dev#578: "pasted two photos, the second overwrote the first"). The capture button is
157
+ * offered while there is no screenshot yet; the add button while there is room.
158
+ */
159
+ export interface FeedbackDialogMultipleProps extends FeedbackDialogBaseProps {
160
+ attachments: "multiple";
161
+ /** Default `DEFAULT_MAX_ATTACHMENTS` (5). The screenshot does not count. */
162
+ maxAttachments?: number;
163
+ onSubmit: (data: FeedbackMultipleSubmission) => void | Promise<void>;
164
+ /** `"count"`: more files arrived at once than there was room for; the surplus was dropped. */
165
+ onAttachmentError?: (kind: FeedbackAttachmentError) => void;
166
+ }
167
+
168
+ export type FeedbackDialogProps = FeedbackDialogSingleProps | FeedbackDialogMultipleProps;
169
+
170
+ /**
171
+ * The generic feedback form dialog: category + subject + body + an optional image
172
+ * attachment, with Ctrl/Cmd+Enter to submit. Domain-free — the app supplies the
173
+ * category options, labels and an `onSubmit` that talks to its own backend, plus
174
+ * an optional `contextSlot` for app-specific context (user, current URL, …).
175
+ *
176
+ * **A screenshot can be pasted straight in.** Ctrl/Cmd+V anywhere in the dialog
177
+ * takes an image off the clipboard and makes it the attachment, through the same
178
+ * validation and the same preview as the file picker. It is the gesture the two
179
+ * ways in did not cover: `onCaptureScreenshot` snapshots the *whole* app view,
180
+ * and the file picker needs a file — so somebody who wanted to show one panel,
181
+ * or one region of one, had to save a crop to disk first and then find it again
182
+ * (Steering Design feedback #39). The clipboard is where a region snip already
183
+ * is on every platform.
184
+ *
185
+ * **`attachments="multiple"`** (0.15.5) takes several: every paste and pick adds a
186
+ * chip, and `onSubmit` gets `{ screenshot, attachments }` ({@link
187
+ * FeedbackMultipleSubmission}) instead of `attachment`. Opt-in, so a caller typed
188
+ * against the one-file submission keeps compiling and behaving as before.
189
+ */
190
+ export function FeedbackDialog(props: FeedbackDialogProps) {
191
+ const {
192
+ open,
193
+ onClose,
194
+ categories,
195
+ category,
196
+ onCategoryChange,
197
+ labels: labelsProp,
198
+ submitting = false,
199
+ contextSlot,
200
+ attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,
201
+ maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
202
+ onCaptureScreenshot,
203
+ requireBody = true,
204
+ } = props;
144
205
  const [title, setTitle] = useState("");
145
206
  const [body, setBody] = useState("");
146
207
  const [attachment, setAttachment] = useState<File | null>(null);
208
+ // `attachments="multiple"` only: the capture, apart from the picked/pasted files.
209
+ const [screenshot, setScreenshot] = useState<File | null>(null);
210
+ const [files, setFiles] = useState<File[]>([]);
147
211
  const isMobile = useMediaQuery(PHONE_QUERY, false);
148
212
  const labels = useKitLabels("feedbackDialog", DEFAULT_FEEDBACK_DIALOG_LABELS, labelsProp);
149
213
  // The attachment field resolves its own keys from `feedbackAttachment`; it gets the
150
214
  // prop's (so a caller's `attachmentAdd` still wins) plus the resolved heading.
151
- const attachmentLabels = { ...labelsProp, attachment: labels.attachment };
215
+ // Under `multiple` the heading is `attachments` — a screenshot AND photos — unless the
216
+ // caller named the multiple heading themselves.
217
+ const attachmentLabels = {
218
+ ...labelsProp,
219
+ attachment:
220
+ props.attachments === "multiple"
221
+ ? (labels.attachments ?? DEFAULT_FEEDBACK_DIALOG_LABELS.attachments)
222
+ : labels.attachment,
223
+ };
152
224
 
153
225
  // Reset the form whenever the dialog is (re)opened — during render, on the
154
226
  // closed-to-open transition, so the old draft never paints for a frame.
@@ -159,6 +231,8 @@ export function FeedbackDialog({
159
231
  setTitle("");
160
232
  setBody("");
161
233
  setAttachment(null);
234
+ setScreenshot(null);
235
+ setFiles([]);
162
236
  }
163
237
  }
164
238
 
@@ -166,7 +240,12 @@ export function FeedbackDialog({
166
240
  // with a 422, and a blank report is no report).
167
241
  const canSubmit = !!title.trim() && (!requireBody || !!body.trim()) && !submitting;
168
242
  const trySubmit = () => {
169
- if (canSubmit) void onSubmit({ title, body, category, attachment });
243
+ if (!canSubmit) return;
244
+ if (props.attachments === "multiple") {
245
+ void props.onSubmit({ title, body, category, screenshot, attachments: files });
246
+ } else {
247
+ void props.onSubmit({ title, body, category, attachment });
248
+ }
170
249
  };
171
250
 
172
251
  if (!open) return null;
@@ -194,22 +273,40 @@ export function FeedbackDialog({
194
273
  onChange={(e) => setBody(e.target.value)}
195
274
  />
196
275
  );
197
- const attachmentField = (
198
- <FeedbackAttachmentField
199
- value={attachment}
200
- onChange={setAttachment}
201
- labels={attachmentLabels}
202
- accept={attachmentAccept}
203
- maxBytes={maxAttachmentBytes}
204
- onError={onAttachmentError}
205
- onCaptureScreenshot={onCaptureScreenshot}
206
- // On `document`, not on the panel: the Modal focuses its own panel on open
207
- // and traps Tab inside it, so while this dialog is up every paste in the
208
- // page is meant for it — including the one made with nothing in
209
- // particular focused, which never reaches a React `onPaste` on a child.
210
- documentPaste={open}
211
- />
212
- );
276
+ const fieldProps = {
277
+ labels: attachmentLabels,
278
+ accept: attachmentAccept,
279
+ maxBytes: maxAttachmentBytes,
280
+ onCaptureScreenshot,
281
+ // On `document`, not on the panel: the Modal focuses its own panel on open
282
+ // and traps Tab inside it, so while this dialog is up every paste in the
283
+ // page is meant for it — including the one made with nothing in
284
+ // particular focused, which never reaches a React `onPaste` on a child.
285
+ documentPaste: open,
286
+ };
287
+ const attachmentField =
288
+ props.attachments === "multiple" ? (
289
+ <FeedbackAttachmentField
290
+ {...fieldProps}
291
+ multiple
292
+ value={files}
293
+ onChange={setFiles}
294
+ max={props.maxAttachments}
295
+ // The capture goes to its own slot, so it is offered once and never crowds
296
+ // out a photo; a paste is a photo (a region snip is chosen evidence, not "the
297
+ // screenshot").
298
+ screenshot={screenshot}
299
+ onScreenshotChange={setScreenshot}
300
+ onError={props.onAttachmentError}
301
+ />
302
+ ) : (
303
+ <FeedbackAttachmentField
304
+ {...fieldProps}
305
+ value={attachment}
306
+ onChange={setAttachment}
307
+ onError={props.onAttachmentError}
308
+ />
309
+ );
213
310
 
214
311
  return (
215
312
  <Modal
@@ -20,6 +20,9 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
20
20
  attachmentCapture: "Screenshot aufnehmen",
21
21
  attachmentPaste: "…oder einen Screenshot aus der Zwischenablage einfügen.",
22
22
  attachmentRemove: "Anhang entfernen",
23
+ attachmentList: "Anhänge",
24
+ attachmentRemoveFile: (name) => `${name} entfernen`,
25
+ attachmentLimit: (max) => `Maximal ${n(max)} ${max === 1 ? "Anhang" : "Anhänge"} – zum Hinzufügen erst einen entfernen.`,
23
26
  },
24
27
  feedbackDialog: {
25
28
  title: "Feedback senden",
@@ -28,6 +31,7 @@ export function uiKitLabelsDe(numberLocale = "de-DE"): UiKitLabels {
28
31
  body: "Was ist passiert?",
29
32
  bodyOptional: "Was ist passiert? (optional)",
30
33
  attachment: "Screenshot",
34
+ attachments: "Anhänge",
31
35
  submitHint: "Strg/⌘ + Enter zum Senden",
32
36
  cancel: "Abbrechen",
33
37
  save: "Senden",
@@ -21,6 +21,9 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
21
21
  attachmentCapture: "Capturar pantalla",
22
22
  attachmentPaste: "…o pega una captura de pantalla desde el portapapeles.",
23
23
  attachmentRemove: "Quitar adjunto",
24
+ attachmentList: "Adjuntos",
25
+ attachmentRemoveFile: (name) => `Quitar ${name}`,
26
+ attachmentLimit: (max) => `Máximo ${n(max)} ${max === 1 ? "adjunto" : "adjuntos"}: quita uno para añadir otro.`,
24
27
  },
25
28
  feedbackDialog: {
26
29
  title: "Enviar comentarios",
@@ -29,6 +32,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
29
32
  body: "¿Qué ha pasado?",
30
33
  bodyOptional: "¿Qué ha pasado? (opcional)",
31
34
  attachment: "Captura de pantalla",
35
+ attachments: "Adjuntos",
32
36
  submitHint: "Ctrl/⌘ + Intro para enviar",
33
37
  cancel: "Cancelar",
34
38
  save: "Enviar",
@@ -21,6 +21,9 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
21
21
  attachmentCapture: "Capturer l’écran",
22
22
  attachmentPaste: "…ou collez une capture d’écran depuis le presse-papiers.",
23
23
  attachmentRemove: "Retirer la pièce jointe",
24
+ attachmentList: "Pièces jointes",
25
+ attachmentRemoveFile: (name) => `Retirer ${name}`,
26
+ attachmentLimit: (max) => `${n(max)} ${max === 1 ? "pièce jointe" : "pièces jointes"} au maximum — retirez-en une pour en ajouter une autre.`,
24
27
  },
25
28
  feedbackDialog: {
26
29
  title: "Envoyer un retour",
@@ -29,6 +32,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
29
32
  body: "Que s’est-il passé\u202f?",
30
33
  bodyOptional: "Que s’est-il passé\u202f? (facultatif)",
31
34
  attachment: "Capture d’écran",
35
+ attachments: "Pièces jointes",
32
36
  submitHint: "Ctrl/⌘ + Entrée pour envoyer",
33
37
  cancel: "Annuler",
34
38
  save: "Envoyer",
@@ -20,6 +20,9 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
20
20
  attachmentCapture: "Képernyőkép készítése",
21
21
  attachmentPaste: "…vagy illesszen be egy képernyőképet a vágólapról.",
22
22
  attachmentRemove: "Melléklet eltávolítása",
23
+ attachmentList: "Mellékletek",
24
+ attachmentRemoveFile: (name) => `${name} eltávolítása`,
25
+ attachmentLimit: (max) => `Legfeljebb ${n(max)} melléklet – egy újabbhoz távolítson el egyet.`,
23
26
  },
24
27
  feedbackDialog: {
25
28
  title: "Visszajelzés küldése",
@@ -28,6 +31,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
28
31
  body: "Mi történt?",
29
32
  bodyOptional: "Mi történt? (nem kötelező)",
30
33
  attachment: "Képernyőkép",
34
+ attachments: "Mellékletek",
31
35
  submitHint: "Ctrl/⌘ + Enter a küldéshez",
32
36
  cancel: "Mégse",
33
37
  save: "Küldés",
@@ -21,6 +21,9 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
21
21
  attachmentCapture: "Cattura schermata",
22
22
  attachmentPaste: "…oppure incolla uno screenshot dagli appunti.",
23
23
  attachmentRemove: "Rimuovi allegato",
24
+ attachmentList: "Allegati",
25
+ attachmentRemoveFile: (name) => `Rimuovi ${name}`,
26
+ attachmentLimit: (max) => `Massimo ${n(max)} ${max === 1 ? "allegato" : "allegati"}: rimuovine uno per aggiungerne un altro.`,
24
27
  },
25
28
  feedbackDialog: {
26
29
  title: "Invia feedback",
@@ -29,6 +32,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
29
32
  body: "Cosa è successo?",
30
33
  bodyOptional: "Cosa è successo? (facoltativo)",
31
34
  attachment: "Screenshot",
35
+ attachments: "Allegati",
32
36
  submitHint: "Ctrl/⌘ + Invio per inviare",
33
37
  cancel: "Annulla",
34
38
  save: "Invia",
@@ -20,6 +20,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
20
20
  attachmentCapture: "截取屏幕",
21
21
  attachmentPaste: "……或从剪贴板粘贴截图。",
22
22
  attachmentRemove: "移除附件",
23
+ attachmentList: "附件",
24
+ attachmentRemoveFile: (name) => `移除 ${name}`,
25
+ attachmentLimit: (max) => `最多 ${n(max)} 个附件,请先移除一个再添加。`,
23
26
  },
24
27
  feedbackDialog: {
25
28
  title: "发送反馈",
@@ -28,6 +31,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
28
31
  body: "发生了什么?",
29
32
  bodyOptional: "发生了什么?(可选)",
30
33
  attachment: "截图",
34
+ attachments: "附件",
31
35
  submitHint: "Ctrl/⌘ + Enter 发送",
32
36
  cancel: "取消",
33
37
  save: "发送",
@@ -64,6 +64,9 @@ export interface SemanticTokens {
64
64
  dangerHover: string;
65
65
  dangerContrast: string;
66
66
  dangerBorder: string;
67
+ /** The error line (invalid field, danger banner, destructive focus ring): as loud as
68
+ * `dangerBorder` was before 0.15.5, so it keeps 3:1 against the surface. */
69
+ dangerBorderStrong: string;
67
70
  dangerBg: string;
68
71
  warning: string;
69
72
  warningBorder: string;
@@ -374,7 +377,14 @@ export function derivePalette({
374
377
  l: mode === "light" ? dangerLch.l - 0.06 : dangerLch.l + 0.06,
375
378
  }),
376
379
  dangerContrast: contrastOn(danger),
380
+ // The tone border follows its siblings' lightness in dark mode (warning 0.44, info
381
+ // and success 0.45) — a soft frame; the strong line keeps the old, louder recipe.
377
382
  dangerBorder: oklchToHex({
383
+ l: mode === "light" ? dangerLch.l + 0.22 : 0.45,
384
+ c: semanticChroma.danger,
385
+ h: semanticHue.danger,
386
+ }),
387
+ dangerBorderStrong: oklchToHex({
378
388
  l: mode === "light" ? dangerLch.l + 0.22 : dangerLch.l - 0.22,
379
389
  c: semanticChroma.danger,
380
390
  h: semanticHue.danger,
package/tokens.css CHANGED
@@ -191,6 +191,10 @@
191
191
  --danger-hover: #9f1239; /* rose-800 */
192
192
  --danger-contrast: #ffffff;
193
193
  --danger-border: #fb7185; /* rose-400 */
194
+ /* The error LINE: an invalid field, a failed upload, a danger banner's frame, a focus
195
+ ring on a destructive control. Equal to the border in light mode; in dark the tone
196
+ border is soft like its siblings and this one stays loud (WCAG 1.4.11's 3:1). */
197
+ --danger-border-strong: #fb7185; /* rose-400 */
194
198
  --danger-bg: #fff1f2; /* rose-50 */
195
199
 
196
200
  --warning: #b45309; /* amber-700 */
@@ -217,7 +221,11 @@
217
221
  --danger: #fda4af; /* rose-300 */
218
222
  --danger-hover: #fecdd3; /* rose-200 */
219
223
  --danger-contrast: #4c0519; /* rose-950 */
220
- --danger-border: #e11d48; /* rose-600 */
224
+ /* The tone family's recipe — hue at 0.6 alpha, as warning/info/success below — so a
225
+ soft danger Chip sits in the same weight of frame as its siblings (keksdose dev
226
+ #576: FAILED was visibly louder than READY and NEEDS REVIEW in dark mode). */
227
+ --danger-border: rgba(190, 18, 60, 0.6); /* rose-700 at the family's alpha */
228
+ --danger-border-strong: #e11d48; /* rose-600 */
221
229
  --danger-bg: rgba(136, 19, 55, 0.2);
222
230
 
223
231
  --warning: #fcd34d; /* amber-300 */