@eifi1/ui-kit 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +19 -14
  3. package/dist/components/account-chips.d.ts +10 -3
  4. package/dist/components/account-chips.js +5 -2
  5. package/dist/components/account-chips.js.map +1 -1
  6. package/dist/components/account-settings.d.ts +33 -2
  7. package/dist/components/account-settings.js +43 -21
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/amount-input.d.ts +53 -14
  10. package/dist/components/amount-input.js +17 -28
  11. package/dist/components/amount-input.js.map +1 -1
  12. package/dist/components/autocomplete.d.ts +29 -0
  13. package/dist/components/autocomplete.js +134 -104
  14. package/dist/components/autocomplete.js.map +1 -1
  15. package/dist/components/button-group.d.ts +19 -14
  16. package/dist/components/calculator.d.ts +19 -14
  17. package/dist/components/checkbox-group.d.ts +104 -0
  18. package/dist/components/checkbox-group.js +111 -0
  19. package/dist/components/checkbox-group.js.map +1 -0
  20. package/dist/components/checkbox.d.ts +19 -0
  21. package/dist/components/checkbox.js +30 -7
  22. package/dist/components/checkbox.js.map +1 -1
  23. package/dist/components/chip.d.ts +18 -0
  24. package/dist/components/chip.js +47 -10
  25. package/dist/components/chip.js.map +1 -1
  26. package/dist/components/column-mapper.d.ts +182 -0
  27. package/dist/components/column-mapper.js +375 -0
  28. package/dist/components/column-mapper.js.map +1 -0
  29. package/dist/components/combobox-core.d.ts +39 -2
  30. package/dist/components/combobox-core.js +29 -6
  31. package/dist/components/combobox-core.js.map +1 -1
  32. package/dist/components/combobox.d.ts +91 -2
  33. package/dist/components/combobox.js +454 -308
  34. package/dist/components/combobox.js.map +1 -1
  35. package/dist/components/confirm-dialog.d.ts +69 -161
  36. package/dist/components/confirm-dialog.js +7 -8
  37. package/dist/components/confirm-dialog.js.map +1 -1
  38. package/dist/components/copy-button.d.ts +16 -11
  39. package/dist/components/country-select.d.ts +167 -0
  40. package/dist/components/country-select.js +293 -0
  41. package/dist/components/country-select.js.map +1 -0
  42. package/dist/components/currency-select.d.ts +17 -1
  43. package/dist/components/currency-select.js +56 -23
  44. package/dist/components/currency-select.js.map +1 -1
  45. package/dist/components/danger-confirm.d.ts +69 -166
  46. package/dist/components/danger-confirm.js +144 -50
  47. package/dist/components/danger-confirm.js.map +1 -1
  48. package/dist/components/data-table-cells.d.ts +1 -2
  49. package/dist/components/data-table-filter-popover.d.ts +1 -2
  50. package/dist/components/data-table-filters.d.ts +1 -2
  51. package/dist/components/data-table-labels.d.ts +4 -97
  52. package/dist/components/data-table-labels.js +9 -1
  53. package/dist/components/data-table-labels.js.map +1 -1
  54. package/dist/components/data-table-pagination.d.ts +3 -1
  55. package/dist/components/data-table.d.ts +1 -2
  56. package/dist/components/data-table.js +98 -3
  57. package/dist/components/data-table.js.map +1 -1
  58. package/dist/components/date-picker.d.ts +66 -5
  59. package/dist/components/date-picker.js +378 -66
  60. package/dist/components/date-picker.js.map +1 -1
  61. package/dist/components/entity-combobox.d.ts +42 -1
  62. package/dist/components/entity-combobox.js +154 -113
  63. package/dist/components/entity-combobox.js.map +1 -1
  64. package/dist/components/facing-pair.d.ts +19 -14
  65. package/dist/components/field-parts.d.ts +224 -0
  66. package/dist/components/field-parts.js +156 -0
  67. package/dist/components/field-parts.js.map +1 -0
  68. package/dist/components/field-strip.d.ts +105 -0
  69. package/dist/components/field-strip.js +46 -0
  70. package/dist/components/field-strip.js.map +1 -0
  71. package/dist/components/file-button.d.ts +19 -14
  72. package/dist/components/file-button.js +13 -3
  73. package/dist/components/file-button.js.map +1 -1
  74. package/dist/components/file-dropzone.d.ts +41 -15
  75. package/dist/components/file-dropzone.js +57 -34
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/form-actions.d.ts +18 -13
  78. package/dist/components/form-actions.js +69 -5
  79. package/dist/components/form-actions.js.map +1 -1
  80. package/dist/components/iban-input.d.ts +69 -0
  81. package/dist/components/iban-input.js +125 -0
  82. package/dist/components/iban-input.js.map +1 -0
  83. package/dist/components/icon-picker.d.ts +21 -2
  84. package/dist/components/icon-picker.js +19 -4
  85. package/dist/components/icon-picker.js.map +1 -1
  86. package/dist/components/inline-edit-field.d.ts +167 -0
  87. package/dist/components/inline-edit-field.js +293 -0
  88. package/dist/components/inline-edit-field.js.map +1 -0
  89. package/dist/components/language-select.d.ts +130 -0
  90. package/dist/components/language-select.js +18 -0
  91. package/dist/components/language-select.js.map +1 -0
  92. package/dist/components/line-items.d.ts +25 -3
  93. package/dist/components/line-items.js +55 -46
  94. package/dist/components/line-items.js.map +1 -1
  95. package/dist/components/list.d.ts +25 -0
  96. package/dist/components/list.js +16 -5
  97. package/dist/components/list.js.map +1 -1
  98. package/dist/components/money-field.d.ts +32 -14
  99. package/dist/components/money-field.js.map +1 -1
  100. package/dist/components/month-picker.d.ts +52 -4
  101. package/dist/components/month-picker.js +291 -86
  102. package/dist/components/month-picker.js.map +1 -1
  103. package/dist/components/multi-entity-combobox.d.ts +26 -1
  104. package/dist/components/multi-entity-combobox.js +143 -102
  105. package/dist/components/multi-entity-combobox.js.map +1 -1
  106. package/dist/components/multi-select.d.ts +8 -1
  107. package/dist/components/multi-select.js +9 -4
  108. package/dist/components/multi-select.js.map +1 -1
  109. package/dist/components/number-field.d.ts +34 -19
  110. package/dist/components/number-field.js +6 -20
  111. package/dist/components/number-field.js.map +1 -1
  112. package/dist/components/number-input.d.ts +79 -19
  113. package/dist/components/number-input.js +90 -38
  114. package/dist/components/number-input.js.map +1 -1
  115. package/dist/components/numpad-sheet.d.ts +19 -14
  116. package/dist/components/one-time-code-input.d.ts +100 -0
  117. package/dist/components/one-time-code-input.js +137 -0
  118. package/dist/components/one-time-code-input.js.map +1 -0
  119. package/dist/components/phone-input.d.ts +69 -0
  120. package/dist/components/phone-input.js +171 -0
  121. package/dist/components/phone-input.js.map +1 -0
  122. package/dist/components/reauth-dialog.d.ts +2 -2
  123. package/dist/components/reauth-dialog.js +6 -8
  124. package/dist/components/reauth-dialog.js.map +1 -1
  125. package/dist/components/series-chart.d.ts +19 -14
  126. package/dist/components/settings-fields.d.ts +19 -14
  127. package/dist/components/share-card.d.ts +16 -11
  128. package/dist/components/sign-chip.d.ts +66 -0
  129. package/dist/components/sign-chip.js +39 -0
  130. package/dist/components/sign-chip.js.map +1 -0
  131. package/dist/components/swatch-picker.d.ts +43 -2
  132. package/dist/components/swatch-picker.js +80 -32
  133. package/dist/components/swatch-picker.js.map +1 -1
  134. package/dist/components/switch.d.ts +19 -0
  135. package/dist/components/switch.js +24 -6
  136. package/dist/components/switch.js.map +1 -1
  137. package/dist/components/text-link.d.ts +18 -13
  138. package/dist/components/tile-radio.d.ts +88 -13
  139. package/dist/components/tile-radio.js +28 -11
  140. package/dist/components/tile-radio.js.map +1 -1
  141. package/dist/components/time-input.d.ts +24 -15
  142. package/dist/components/time-input.js.map +1 -1
  143. package/dist/components/toggle-group.d.ts +47 -1
  144. package/dist/components/toggle-group.js +110 -69
  145. package/dist/components/toggle-group.js.map +1 -1
  146. package/dist/components/trigger-aria.d.ts +3 -0
  147. package/dist/components/trigger-aria.js +3 -1
  148. package/dist/components/trigger-aria.js.map +1 -1
  149. package/dist/components/ui.d.ts +16 -11
  150. package/dist/components/ui.js +284 -88
  151. package/dist/components/ui.js.map +1 -1
  152. package/dist/components/use-table-state.d.ts +1 -2
  153. package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
  154. package/dist/data-table.d.ts +1 -2
  155. package/dist/data-table.js +2 -1
  156. package/dist/data-table.js.map +1 -1
  157. package/dist/feedback/feedback-attachment.d.ts +1 -1
  158. package/dist/feedback/feedback-attachment.js +348 -41
  159. package/dist/feedback/feedback-attachment.js.map +1 -1
  160. package/dist/feedback/feedback-dialog.d.ts +1 -1
  161. package/dist/feedback/feedback-dialog.js.map +1 -1
  162. package/dist/feedback/feedback-inbox.d.ts +3 -2
  163. package/dist/feedback/feedback-inbox.js.map +1 -1
  164. package/dist/feedback/feedback-thread.d.ts +68 -253
  165. package/dist/feedback/feedback-thread.js +46 -10
  166. package/dist/feedback/feedback-thread.js.map +1 -1
  167. package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
  168. package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
  169. package/dist/feedback.d.ts +66 -2
  170. package/dist/hooks/use-file-drop.d.ts +19 -14
  171. package/dist/i18n/defaults.d.ts +19 -14
  172. package/dist/i18n/defaults.js +19 -2
  173. package/dist/i18n/defaults.js.map +1 -1
  174. package/dist/i18n/german.d.ts +19 -14
  175. package/dist/i18n/german.js +84 -5
  176. package/dist/i18n/german.js.map +1 -1
  177. package/dist/i18n/kit-labels.d.ts +15 -10
  178. package/dist/i18n/kit-labels.js +8 -2
  179. package/dist/i18n/kit-labels.js.map +1 -1
  180. package/dist/i18n/languages.d.ts +19 -14
  181. package/dist/i18n/locales/de-CH.d.ts +19 -14
  182. package/dist/i18n/locales/en.d.ts +19 -14
  183. package/dist/i18n/locales/en.js +16 -0
  184. package/dist/i18n/locales/en.js.map +1 -1
  185. package/dist/i18n/locales/es.d.ts +19 -14
  186. package/dist/i18n/locales/es.js +83 -5
  187. package/dist/i18n/locales/es.js.map +1 -1
  188. package/dist/i18n/locales/fr.d.ts +19 -14
  189. package/dist/i18n/locales/fr.js +82 -5
  190. package/dist/i18n/locales/fr.js.map +1 -1
  191. package/dist/i18n/locales/hu.d.ts +19 -14
  192. package/dist/i18n/locales/hu.js +85 -5
  193. package/dist/i18n/locales/hu.js.map +1 -1
  194. package/dist/i18n/locales/it.d.ts +19 -14
  195. package/dist/i18n/locales/it.js +84 -5
  196. package/dist/i18n/locales/it.js.map +1 -1
  197. package/dist/i18n/locales/zh.d.ts +19 -14
  198. package/dist/i18n/locales/zh.js +80 -5
  199. package/dist/i18n/locales/zh.js.map +1 -1
  200. package/dist/i18n/review.d.ts +19 -14
  201. package/dist/i18n/review.js +27 -1
  202. package/dist/i18n/review.js.map +1 -1
  203. package/dist/index.d.ts +21 -10
  204. package/dist/index.js +73 -2
  205. package/dist/index.js.map +1 -1
  206. package/dist/lib/column-mapping.d.ts +81 -0
  207. package/dist/lib/column-mapping.js +108 -0
  208. package/dist/lib/column-mapping.js.map +1 -0
  209. package/dist/lib/countries.d.ts +43 -0
  210. package/dist/lib/countries.js +39 -0
  211. package/dist/lib/countries.js.map +1 -0
  212. package/dist/lib/decimal-marks.d.ts +46 -0
  213. package/dist/lib/decimal-marks.js +40 -0
  214. package/dist/lib/decimal-marks.js.map +1 -0
  215. package/dist/lib/iban.d.ts +98 -0
  216. package/dist/lib/iban.js +154 -0
  217. package/dist/lib/iban.js.map +1 -0
  218. package/dist/lib/isin.d.ts +30 -0
  219. package/dist/lib/isin.js +36 -0
  220. package/dist/lib/isin.js.map +1 -0
  221. package/dist/lib/phone.d.ts +69 -0
  222. package/dist/lib/phone.js +121 -0
  223. package/dist/lib/phone.js.map +1 -0
  224. package/dist/lib/table-text.d.ts +109 -1
  225. package/dist/lib/table-text.js +122 -1
  226. package/dist/lib/table-text.js.map +1 -1
  227. package/dist/rhf/fields.d.ts +233 -18
  228. package/dist/rhf/fields.js +456 -3
  229. package/dist/rhf/fields.js.map +1 -1
  230. package/dist/rhf/form.d.ts +19 -14
  231. package/dist/rhf.d.ts +22 -15
  232. package/dist/rhf.js.map +1 -1
  233. package/dist/shell/app-shell.d.ts +18 -13
  234. package/dist/shell/top-bar-brand.d.ts +19 -14
  235. package/dist/shell/topbar-action-menu.d.ts +36 -3
  236. package/dist/shell/topbar-action-menu.js +74 -33
  237. package/dist/shell/topbar-action-menu.js.map +1 -1
  238. package/dist/shell.d.ts +18 -13
  239. package/dist/table-text.d.ts +1 -1
  240. package/dist/tour/tour.js +20 -24
  241. package/dist/tour/tour.js.map +1 -1
  242. package/dist/wizard/stepper-nav.d.ts +49 -16
  243. package/dist/wizard/stepper-nav.js +4 -0
  244. package/dist/wizard/stepper-nav.js.map +1 -1
  245. package/dist/wizard.d.ts +19 -14
  246. package/package.json +1 -1
  247. package/src/components/account-chips.tsx +17 -3
  248. package/src/components/account-settings.tsx +92 -26
  249. package/src/components/amount-input.tsx +68 -56
  250. package/src/components/autocomplete.tsx +198 -114
  251. package/src/components/checkbox-group.tsx +229 -0
  252. package/src/components/checkbox.tsx +72 -8
  253. package/src/components/chip.tsx +80 -10
  254. package/src/components/column-mapper.tsx +666 -0
  255. package/src/components/combobox-core.tsx +82 -9
  256. package/src/components/combobox.tsx +661 -340
  257. package/src/components/confirm-dialog.tsx +12 -8
  258. package/src/components/country-select.tsx +556 -0
  259. package/src/components/currency-select.tsx +109 -33
  260. package/src/components/danger-confirm.tsx +429 -82
  261. package/src/components/data-table-labels.ts +16 -1
  262. package/src/components/data-table.tsx +175 -6
  263. package/src/components/date-picker.tsx +595 -71
  264. package/src/components/entity-combobox.tsx +235 -124
  265. package/src/components/field-parts.tsx +403 -0
  266. package/src/components/field-strip.tsx +149 -0
  267. package/src/components/file-button.tsx +36 -3
  268. package/src/components/file-dropzone.tsx +106 -38
  269. package/src/components/form-actions.tsx +147 -6
  270. package/src/components/iban-input.tsx +231 -0
  271. package/src/components/icon-picker.tsx +47 -6
  272. package/src/components/inline-edit-field.tsx +533 -0
  273. package/src/components/language-select.tsx +78 -0
  274. package/src/components/line-items.tsx +35 -2
  275. package/src/components/list.tsx +55 -6
  276. package/src/components/money-field.tsx +13 -0
  277. package/src/components/month-picker.tsx +400 -63
  278. package/src/components/multi-entity-combobox.tsx +207 -120
  279. package/src/components/multi-select.tsx +80 -58
  280. package/src/components/number-field.tsx +28 -46
  281. package/src/components/number-input.tsx +210 -43
  282. package/src/components/one-time-code-input.tsx +288 -0
  283. package/src/components/phone-input.tsx +299 -0
  284. package/src/components/reauth-dialog.tsx +17 -18
  285. package/src/components/sign-chip.tsx +103 -0
  286. package/src/components/swatch-picker.tsx +143 -31
  287. package/src/components/switch.tsx +58 -7
  288. package/src/components/tile-radio.tsx +145 -29
  289. package/src/components/time-input.tsx +8 -1
  290. package/src/components/toggle-group.tsx +206 -73
  291. package/src/components/trigger-aria.ts +5 -0
  292. package/src/components/ui.tsx +479 -138
  293. package/src/data-table.ts +2 -1
  294. package/src/feedback/feedback-attachment.tsx +597 -46
  295. package/src/feedback/feedback-dialog.tsx +9 -3
  296. package/src/feedback/feedback-inbox.tsx +3 -2
  297. package/src/feedback/feedback-thread.tsx +113 -9
  298. package/src/i18n/defaults.ts +18 -1
  299. package/src/i18n/german.ts +89 -0
  300. package/src/i18n/kit-labels.tsx +82 -2
  301. package/src/i18n/locales/en.ts +36 -4
  302. package/src/i18n/locales/es.ts +87 -0
  303. package/src/i18n/locales/fr.ts +87 -0
  304. package/src/i18n/locales/hu.ts +83 -0
  305. package/src/i18n/locales/it.ts +88 -0
  306. package/src/i18n/locales/zh.ts +76 -0
  307. package/src/i18n/review.ts +26 -0
  308. package/src/index.ts +71 -3
  309. package/src/lib/column-mapping.ts +234 -0
  310. package/src/lib/countries.ts +91 -0
  311. package/src/lib/decimal-marks.ts +99 -0
  312. package/src/lib/iban.ts +171 -0
  313. package/src/lib/isin.ts +62 -0
  314. package/src/lib/phone.ts +211 -0
  315. package/src/lib/table-text.ts +271 -0
  316. package/src/rhf/fields.tsx +860 -9
  317. package/src/rhf.ts +3 -1
  318. package/src/shell/topbar-action-menu.tsx +134 -38
  319. package/src/tour/tour.tsx +66 -28
  320. package/src/wizard/stepper-nav.tsx +35 -1
@@ -10,6 +10,7 @@ import {
10
10
  DEFAULT_MAX_ATTACHMENT_BYTES,
11
11
  FeedbackAttachmentField,
12
12
  type FeedbackAttachmentError,
13
+ type FeedbackAttachmentErrorInfo,
13
14
  } from "./feedback-attachment";
14
15
 
15
16
  export interface FeedbackCategoryOption {
@@ -40,6 +41,8 @@ export interface FeedbackAttachmentLabels {
40
41
  attachmentRemoveFile?: (name: string) => string;
41
42
  /** `multiple` mode: the line shown once `max` files are attached. */
42
43
  attachmentLimit?: (max: number) => string;
44
+ /** `refs` mode (0.22.0): the second line of a chip whose upload is still running. */
45
+ attachmentUploading?: string;
43
46
  }
44
47
 
45
48
  export interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
@@ -155,7 +158,9 @@ interface FeedbackDialogBaseProps {
155
158
  export interface FeedbackDialogSingleProps extends FeedbackDialogBaseProps {
156
159
  attachments?: "single";
157
160
  onSubmit: (data: FeedbackSubmission) => void | Promise<void>;
158
- onAttachmentError?: (kind: "type" | "size") => void;
161
+ /** `info` (0.23.0) names the refused file and the limit — see
162
+ * {@link FeedbackAttachmentErrorInfo}. */
163
+ onAttachmentError?: (kind: "type" | "size", info: FeedbackAttachmentErrorInfo) => void;
159
164
  /** Multiple mode only — refused here rather than silently ignored. */
160
165
  maxAttachments?: never;
161
166
  }
@@ -170,8 +175,9 @@ export interface FeedbackDialogMultipleProps extends FeedbackDialogBaseProps {
170
175
  /** Default `DEFAULT_MAX_ATTACHMENTS` (5). The screenshot does not count. */
171
176
  maxAttachments?: number;
172
177
  onSubmit: (data: FeedbackMultipleSubmission) => void | Promise<void>;
173
- /** `"count"`: more files arrived at once than there was room for; the surplus was dropped. */
174
- onAttachmentError?: (kind: FeedbackAttachmentError) => void;
178
+ /** `"count"`: more files arrived at once than there was room for; the surplus was
179
+ * dropped. `info` (0.23.0) names the refused file(s) and the limit. */
180
+ onAttachmentError?: (kind: FeedbackAttachmentError, info: FeedbackAttachmentErrorInfo) => void;
175
181
  }
176
182
 
177
183
  export type FeedbackDialogProps = FeedbackDialogSingleProps | FeedbackDialogMultipleProps;
@@ -18,7 +18,7 @@ import type { LucideIcon } from "lucide-react";
18
18
  import { cn } from "../lib/cn";
19
19
  import { Button, Textarea } from "../components/ui";
20
20
  import { Tooltip } from "../components/tooltip";
21
- import { FeedbackAttachmentField } from "./feedback-attachment";
21
+ import { FeedbackAttachmentField, type FeedbackAttachmentErrorInfo } from "./feedback-attachment";
22
22
  import type { FeedbackAttachmentLabels } from "./feedback-dialog";
23
23
 
24
24
  /**
@@ -566,7 +566,8 @@ export interface FeedbackNoteAttachment {
566
566
  labels: FeedbackAttachmentLabels;
567
567
  accept?: string[];
568
568
  maxBytes?: number;
569
- onError?: (kind: "type" | "size") => void;
569
+ /** `info` (0.23.0) names the refused file and the limit. */
570
+ onError?: (kind: "type" | "size", info: FeedbackAttachmentErrorInfo) => void;
570
571
  onCaptureScreenshot?: () => Promise<File | null>;
571
572
  }
572
573
 
@@ -1,13 +1,15 @@
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";
5
5
  import { formatDate, formatRelativeTime } from "../lib/format";
6
6
  import { Button, EmptyState, Spinner, Textarea } from "../components/ui";
7
+ import type { CharacterCountLabels } from "../components/ui";
7
8
  import { Skeleton } from "../components/skeleton";
8
9
  import { Tooltip } from "../components/tooltip";
9
10
  import { useKitFileLabels, useKitLabels, useKitLocale } from "../i18n/kit-labels";
10
11
  import { FeedbackAttachmentField } from "./feedback-attachment";
12
+ import type { FeedbackAttachmentFieldSingleProps } from "./feedback-attachment";
11
13
  import type { FeedbackNoteAttachment } from "./feedback-inbox";
12
14
  import type { FeedbackAttachmentLabels } from "./feedback-dialog";
13
15
 
@@ -463,9 +465,12 @@ function toDate(value: Date | string | number): Date | null {
463
465
  /* ── Composer ─────────────────────────────────────────────────────────────── */
464
466
 
465
467
  /** What the composer's attach control takes — {@link FeedbackNoteAttachment} with the
466
- * labels optional, since the field reads `feedbackAttachment` from the provider. */
467
- export type FeedbackComposerAttachment = Omit<FeedbackNoteAttachment, "labels"> & {
468
+ * labels optional, since the field reads `feedbackAttachment` from the provider, and
469
+ * `onError` the field's own (0.23.0): it is handed the refused file and the limit too
470
+ * (`FeedbackAttachmentErrorInfo`), and a `(kind) => …` handler still fits. */
471
+ export type FeedbackComposerAttachment = Omit<FeedbackNoteAttachment, "labels" | "onError"> & {
468
472
  labels?: Partial<FeedbackAttachmentLabels>;
473
+ onError?: FeedbackAttachmentFieldSingleProps["onError"];
469
474
  };
470
475
 
471
476
  export interface FeedbackComposerProps {
@@ -498,7 +503,7 @@ export interface FeedbackComposerProps {
498
503
  * for state the composer cannot see, i.e. `attachmentSlot`'s (keksdose G5): `true`
499
504
  * with files ready makes an empty draft sendable (`onSend("", null)`), `false` while
500
505
  * an upload is still running holds even a written reply back. Left out, the text
501
- * decides. `pending` blocks a send either way.
506
+ * decides. `pending` blocks a send either way, as does a draft past `maxLength`.
502
507
  */
503
508
  canSend?: boolean;
504
509
  /**
@@ -513,6 +518,31 @@ export interface FeedbackComposerProps {
513
518
  * reader. */
514
519
  disabledReason?: ReactNode;
515
520
  rows?: number;
521
+ /**
522
+ * The longest draft the box takes, in UTF-16 code units as the browser counts — the
523
+ * server's limit, so a reply too long for it is cut off while it is typed rather than
524
+ * refused by a 422 after the press (keksdose G6: its support replies are capped at
525
+ * 4,000 on the server, and its other text fields already count against that with
526
+ * Textarea's `maxLength` + `showCount` since 0.22; the reply box, a composer, could
527
+ * not). The box's own `maxLength`, so typing and pasting stop at it.
528
+ *
529
+ * {@link FeedbackComposerHandle.insertText} is not stopped by it — the browser limits
530
+ * typing, not a script — and a canned reply is not cut mid-sentence to fit. A draft
531
+ * past the limit therefore holds Send back (whatever `canSend` says, as `pending`
532
+ * does) until it is trimmed; with `showCount` the counter, red at the limit, says by
533
+ * how much.
534
+ */
535
+ maxLength?: number;
536
+ /**
537
+ * A live "12/4000" under the box, with `maxLength` — Textarea's `showCount`, the same
538
+ * counter (keksdose G6): the count is part of the box's description, read with it on
539
+ * focus, and a polite live region speaks only on entering the last stretch and at the
540
+ * limit, never per keystroke. Under `size="sm"` the hidden send hint stays the box's
541
+ * description too, before the count. Ignored without `maxLength`.
542
+ */
543
+ showCount?: boolean;
544
+ /** The counter's words — the `characterCount` namespace, as on Textarea. */
545
+ countLabels?: Partial<CharacterCountLabels>;
516
546
  /**
517
547
  * The box's hint, over the `placeholder` label — keksdose F9: an admin answering a
518
548
  * support ticket is asked something else ("Reply to Anna…") than a member adding to
@@ -535,6 +565,23 @@ export interface FeedbackComposerProps {
535
565
  ref?: Ref<FeedbackComposerHandle>;
536
566
  labels?: Partial<FeedbackComposerLabels>;
537
567
  className?: string;
568
+ /**
569
+ * `"sm"` for a narrow host — keksdose's assistant launcher, a small floating panel
570
+ * (0.22): a small Send button, and the send hint ("Enter to send, Shift + Enter for a
571
+ * new line") hidden from view but kept as the box's description, so a keyboard or
572
+ * screen-reader user still learns the shortcut. Default `"md"`.
573
+ */
574
+ size?: "md" | "sm";
575
+ /**
576
+ * On the composer's root — the box, or the `disabledReason` line in its place —
577
+ * with any `data-*` attribute beside it (keksdose K17). keksdose's assistant tour
578
+ * points at the chat box with `[data-tour="assistant-ask"]`, and a composer that
579
+ * dropped every attribute it was not told about left the anchor nowhere: the
580
+ * assistant kept its hand-built box rather than lose the tour step.
581
+ */
582
+ id?: string;
583
+ /** `data-*` attributes for the root, as `id` — a tour anchor, a test id. */
584
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
538
585
  }
539
586
 
540
587
  /**
@@ -577,14 +624,22 @@ export function FeedbackComposer({
577
624
  sendOn = "mod-enter",
578
625
  disabledReason,
579
626
  rows = 3,
627
+ maxLength,
628
+ showCount,
629
+ countLabels,
580
630
  placeholder,
581
631
  value,
582
632
  onValueChange,
583
633
  ref,
584
634
  labels: labelsProp,
585
635
  className,
636
+ id,
637
+ size = "md",
638
+ ...rest
586
639
  }: FeedbackComposerProps) {
587
640
  const labels = useKitLabels("feedbackComposer", DEFAULT_FEEDBACK_COMPOSER_LABELS, labelsProp);
641
+ const rootAttributes = { id, ...dataAttributes(rest) };
642
+ const hintId = useId();
588
643
  const [ownDraft, setOwnDraft] = useState("");
589
644
  const controlled = value !== undefined;
590
645
  const draft = controlled ? value : ownDraft;
@@ -639,10 +694,17 @@ export function FeedbackComposer({
639
694
  );
640
695
 
641
696
  if (disabledReason != null && disabledReason !== false) {
642
- return <p className={cn("text-xs text-[var(--text-muted)]", className)}>{disabledReason}</p>;
697
+ return (
698
+ <p {...rootAttributes} className={cn("text-xs text-[var(--text-muted)]", className)}>
699
+ {disabledReason}
700
+ </p>
701
+ );
643
702
  }
644
703
 
645
- const canSend = (canSendProp ?? draft.trim().length > 0) && !pending;
704
+ // Longer than `maxLength` only through `insertText` or a controlled value (typing
705
+ // stops at it): the server would refuse it, so it does not go.
706
+ const tooLong = maxLength !== undefined && draft.length > maxLength;
707
+ const canSend = (canSendProp ?? draft.trim().length > 0) && !pending && !tooLong;
646
708
  const send = () => {
647
709
  if (!canSend) return;
648
710
  const sentDraft = draft;
@@ -658,11 +720,19 @@ export function FeedbackComposer({
658
720
  const config = attachment === true ? {} : attachment || null;
659
721
 
660
722
  return (
661
- <div ref={root} className={cn("space-y-2", className)}>
723
+ <div {...rootAttributes} ref={root} className={cn("space-y-2", className)}>
662
724
  <Textarea
663
725
  rows={rows}
664
726
  dir="auto"
665
727
  aria-label={labels.field}
728
+ // Textarea puts the counter's description after this one, so under "sm" the box
729
+ // reads "Enter to send… 12 of 4000 characters".
730
+ aria-describedby={size === "sm" ? hintId : undefined}
731
+ maxLength={maxLength}
732
+ // Passed only when given: Textarea keeps a wrapper for a `showCount` it was
733
+ // handed at all, and a composer without one keeps the DOM it had.
734
+ {...(showCount !== undefined ? { showCount } : null)}
735
+ countLabels={countLabels}
666
736
  ref={box}
667
737
  placeholder={placeholder ?? labels.placeholder}
668
738
  value={draft}
@@ -707,10 +777,18 @@ export function FeedbackComposer({
707
777
  <span />
708
778
  )}
709
779
  <div className="ms-auto flex items-center gap-2">
710
- <span className="text-xs text-[var(--text-placeholder)]">
780
+ <span
781
+ id={hintId}
782
+ className={size === "sm" ? "sr-only" : "text-xs text-[var(--text-placeholder)]"}
783
+ >
711
784
  {sendOn === "enter" ? labels.sendHintEnter : labels.sendHint(modifier)}
712
785
  </span>
713
- <Button onClick={send} disabled={!canSend} aria-busy={pending || undefined}>
786
+ <Button
787
+ onClick={send}
788
+ disabled={!canSend}
789
+ aria-busy={pending || undefined}
790
+ size={size === "sm" ? "sm" : undefined}
791
+ >
714
792
  {pending ? (
715
793
  <Spinner label={null} className="size-4" />
716
794
  ) : (
@@ -723,3 +801,29 @@ export function FeedbackComposer({
723
801
  </div>
724
802
  );
725
803
  }
804
+
805
+ /** The `data-*` entries of a rest-props object, and nothing else: whatever a caller
806
+ * spread in that the composer does not know is not the root's to receive. */
807
+ function dataAttributes(props: object): Record<string, unknown> {
808
+ const out: Record<string, unknown> = {};
809
+ for (const [key, value] of Object.entries(props)) if (key.startsWith("data-")) out[key] = value;
810
+ return out;
811
+ }
812
+
813
+ /**
814
+ * {@link FeedbackComposer} under a name that is not about feedback (keksdose K17).
815
+ *
816
+ * The component was never feedback-specific: a textarea, an optional picture or a
817
+ * host's upload slot, Send, `sendOn="enter"` for the chat way. keksdose's assistant
818
+ * and support chat are chats, not reports, and an import of `FeedbackComposer` there
819
+ * reads as a mistake to the next person. The same function, not a wrapper, so the
820
+ * two names cannot drift.
821
+ */
822
+ export const ChatComposer = FeedbackComposer;
823
+ /** {@link FeedbackComposerProps}, for {@link ChatComposer}. */
824
+ export type ChatComposerProps = FeedbackComposerProps;
825
+ /** {@link FeedbackComposerHandle}, for {@link ChatComposer}. */
826
+ export type ChatComposerHandle = FeedbackComposerHandle;
827
+ /** {@link FeedbackComposerLabels}, for {@link ChatComposer} — still the `feedbackComposer`
828
+ * namespace of the provider. */
829
+ export type ChatComposerLabels = FeedbackComposerLabels;
@@ -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 { DEFAULT_PASSWORD_REVEAL_LABELS, DEFAULT_TABS_LABELS } from "../components/ui";
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,12 @@ 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";
65
+ import { DEFAULT_COLUMN_MAPPER_LABELS } from "../components/column-mapper";
56
66
  import {
57
67
  DEFAULT_APP_SHELL_LABELS,
58
68
  DEFAULT_CALCULATOR_LABELS,
@@ -142,4 +152,11 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
142
152
  serverWake: DEFAULT_SERVER_WAKE_LABELS,
143
153
  translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,
144
154
  legal: DEFAULT_LEGAL_LABELS,
155
+ characterCount: DEFAULT_CHARACTER_COUNT_LABELS,
156
+ countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,
157
+ inlineEdit: DEFAULT_INLINE_EDIT_LABELS,
158
+ ibanInput: DEFAULT_IBAN_INPUT_LABELS,
159
+ phoneInput: DEFAULT_PHONE_INPUT_LABELS,
160
+ signChip: DEFAULT_SIGN_CHIP_LABELS,
161
+ columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,
145
162
  };
@@ -28,6 +28,8 @@ 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…",
32
+ attachmentUploadFailed: (name) => `„${name}“ konnte nicht hochgeladen werden`,
31
33
  },
32
34
  feedbackDialog: {
33
35
  title: "Feedback senden",
@@ -210,6 +212,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
210
212
  booleanTrue: "Ja",
211
213
  booleanFalse: "Nein",
212
214
  booleanUnset: "Nicht festgelegt",
215
+ sortBy: "Sortieren nach",
216
+ sortDefault: "Standardreihenfolge",
217
+ sortAscending: "Aufsteigend",
218
+ sortDescending: "Absteigend",
213
219
  },
214
220
  miniCalendar: {
215
221
  previousMonth: "Vorheriger Monat",
@@ -249,6 +255,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
249
255
  previousMonth: "Vorheriger Monat",
250
256
  nextMonth: "Nächster Monat",
251
257
  today: "Heute",
258
+ yearPanel: "Jahr wählen",
259
+ earlierYears: "Frühere Jahre",
260
+ laterYears: "Spätere Jahre",
261
+ thisYear: "Dieses Jahr",
252
262
  },
253
263
  popover: {
254
264
  panel: "Aufklappfenster",
@@ -330,6 +340,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
330
340
  prompt: "Dies kann nicht rückgängig gemacht werden.",
331
341
  password: "Passwort",
332
342
  phrase: (phrase) => `Geben Sie zur Bestätigung „${phrase}“ ein`,
343
+ acknowledge: "Ich habe gelesen, was dies bewirkt, und möchte fortfahren.",
344
+ needsPhrase: (phrase) => `Geben Sie zur Bestätigung „${phrase}“ ein`,
345
+ needsAcknowledge: "Setzen Sie zur Bestätigung das Häkchen",
346
+ needsPassword: "Geben Sie zur Bestätigung Ihr Passwort ein",
333
347
  },
334
348
  tabs: {
335
349
  // "Tab" is what German UIs say; "Registerkarte" reads like a 1990s manual.
@@ -541,6 +555,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
541
555
  form: {
542
556
  save: "Speichern",
543
557
  cancel: "Abbrechen",
558
+ // German keyboards label the key "Strg"; Apple's ⌘ is the same everywhere.
559
+ submitShortcut: (apple) => (apple ? "⌘ Enter" : "Strg+Enter"),
544
560
  },
545
561
  descriptionList: {
546
562
  empty: "—",
@@ -651,6 +667,79 @@ export function germanLabels(numberLocale: string): UiKitLabels {
651
667
  legal: {
652
668
  navLabel: "Rechtliches",
653
669
  },
670
+ characterCount: {
671
+ // "Zeichen" is the same in singular and plural.
672
+ count: (used, max) => `${n(used)} von ${n(max)} Zeichen`,
673
+ remaining: (left) => `Noch ${n(left)} Zeichen`,
674
+ limitReached: "Zeichenlimit erreicht",
675
+ },
676
+ countrySelect: {
677
+ country: "Land",
678
+ search: "Land suchen",
679
+ others: "Weitere Länder",
680
+ },
681
+ inlineEdit: {
682
+ edit: (label) => `${label} bearbeiten`,
683
+ failed: "Die Änderung konnte nicht gespeichert werden.",
684
+ empty: "Leer",
685
+ },
686
+ ibanInput: {
687
+ format: "Eine IBAN beginnt mit einem Ländercode aus zwei Buchstaben und zwei Prüfziffern.",
688
+ country: (code) => `„${code}“ ist kein Ländercode einer IBAN.`,
689
+ length: (actual, expected) =>
690
+ `Eine IBAN aus diesem Land hat ${n(expected)} Zeichen – diese hat ${n(actual)}.`,
691
+ checksum: "Die Prüfziffern stimmen nicht – vermutlich ist ein Zeichen falsch eingegeben.",
692
+ qrRequired: "Dies ist eine reguläre IBAN. Eine QR-Rechnung braucht die QR-IBAN des Kontos.",
693
+ qrNotAllowed:
694
+ "Dies ist eine QR-IBAN, die nur Zahlungen mit QR-Rechnung empfängt. Geben Sie die reguläre IBAN des Kontos ein.",
695
+ },
696
+ phoneInput: {
697
+ countryCode: "Ländervorwahl",
698
+ other: "Andere",
699
+ },
700
+ signChip: {
701
+ outflow: "Ausgabe",
702
+ inflow: "Einnahme",
703
+ direction: (current, next) => `Richtung: ${current} – zu ${next} wechseln`,
704
+ },
705
+ columnMapper: {
706
+ paste: "Tabelle einfügen",
707
+ pasteHint:
708
+ "Die Zeilen aus einer Tabellenkalkulation kopieren und hier einfügen – oder eine CSV- oder Textdatei hier ablegen.",
709
+ chooseFile: "Datei auswählen",
710
+ readError: (name) => `„${name}“ konnte nicht gelesen werden`,
711
+ headerRow: "Die erste Zeile enthält die Spaltennamen",
712
+ summary: (columns, rows) =>
713
+ `${n(columns)} ${columns === 1 ? "Spalte" : "Spalten"}, ${n(rows)} ${rows === 1 ? "Zeile" : "Zeilen"}`,
714
+ // Fragments of the summary line ("3 Spalten, 120 Zeilen · durch Semikolons getrennt").
715
+ separatorSemicolon: "durch Semikolons getrennt",
716
+ separatorComma: "durch Kommas getrennt",
717
+ separatorTab: "durch Tabulatoren getrennt",
718
+ separatorSpace: "durch Leerzeichen getrennt",
719
+ // The examples stay as written: they show the convention, not the reader's locale.
720
+ decimalComma: "Dezimalkomma (1,5)",
721
+ decimalPoint: "Dezimalpunkt (1.5)",
722
+ unreadCount: (count) =>
723
+ count === 1
724
+ ? "1 Zeile konnte nicht gelesen werden"
725
+ : `${n(count)} Zeilen konnten nicht gelesen werden`,
726
+ // A line number is a position, not a count: unformatted, as `measuredGrid.lineError`.
727
+ unreadLine: (line) => `Zeile ${line} konnte nicht gelesen werden`,
728
+ unreadMore: (count) => `…und ${n(count)} weitere`,
729
+ noRows: "Keine Zeile dieses Textes lässt sich als Tabellenzeile lesen.",
730
+ table: "Spaltenzuordnung",
731
+ columnN: (column) => `Spalte ${column}`,
732
+ roleOf: (column) => `Was enthält die Spalte „${column}“?`,
733
+ ignore: "Ignorieren",
734
+ requiredRole: (role) => `${role} (erforderlich)`,
735
+ previewOf: (shown, total) =>
736
+ shown === 1
737
+ ? `Die erste von ${n(total)} Zeilen`
738
+ : `Die ersten ${n(shown)} von ${n(total)} Zeilen`,
739
+ // `roles` arrives joined with "oder" ("Soll oder Haben").
740
+ oneOf: (roles) => `entweder ${roles}`,
741
+ missing: (roles) => `Noch erforderlich: ${roles}.`,
742
+ },
654
743
  translationReview: {
655
744
  statusMissing: "Fehlt",
656
745
  statusUnreviewed: "Ungeprüft",
@@ -7,7 +7,7 @@ import type { CalendarHeatmapLabels } from "../components/calendar-heatmap";
7
7
  import type { PopoverLabels } from "../components/popover";
8
8
  import type { ChipInputLabels } from "../components/chip";
9
9
  import type { FieldSyncLabels } from "../components/field-sync";
10
- import type { PasswordRevealLabels, TabsLabels } from "../components/ui";
10
+ import type { CharacterCountLabels, PasswordRevealLabels, TabsLabels } from "../components/ui";
11
11
  import type { WizardLabels } from "../wizard/types";
12
12
  import type { TourLabels } from "../tour/tour";
13
13
  import type { CommandPaletteLabels } from "../search/command-palette";
@@ -53,6 +53,12 @@ import type { ShareCardLabels } from "../components/share-card";
53
53
  import type { ReauthDialogLabels } from "../components/reauth-dialog";
54
54
  import type { ServerWakeLabels } from "../components/server-wake";
55
55
  import type { TranslationReviewLabels } from "../components/translation-review-labels";
56
+ import type { CountrySelectLabels } from "../components/country-select";
57
+ import type { InlineEditLabels } from "../components/inline-edit-field";
58
+ import type { IbanInputLabels } from "../components/iban-input";
59
+ import type { PhoneInputLabels } from "../components/phone-input";
60
+ import type { SignChipLabels } from "../components/sign-chip";
61
+ import type { ColumnMapperLabels } from "../components/column-mapper";
56
62
 
57
63
  /**
58
64
  * EVERY string the kit renders, as one typed tree — and an optional provider that
@@ -294,6 +300,21 @@ export interface UiKitLabels {
294
300
  translationReview: TranslationReviewLabels;
295
301
  /** 0.19.0: `LegalLinks`' navigation name — the legal pages' shell, for every app. */
296
302
  legal: LegalLabels;
303
+ /** 0.22.0: the screen-reader words of `Input` / `Textarea`'s `showCount` counter. */
304
+ characterCount: CharacterCountLabels;
305
+ /** 0.22.0: `CountrySelect`. Its list's "no results" and counts are `combobox`'s. */
306
+ countrySelect: CountrySelectLabels;
307
+ /** 0.22.0: `InlineEditField` — keksdose K9. */
308
+ inlineEdit: InlineEditLabels;
309
+ /** 0.22.0: `IbanInput`'s messages, one per problem. */
310
+ ibanInput: IbanInputLabels;
311
+ /** 0.22.0: `PhoneInput`'s country-code select. */
312
+ phoneInput: PhoneInputLabels;
313
+ /** 0.22.0: `SignChip` — the outflow / inflow toggle beside an amount. */
314
+ signChip: SignChipLabels;
315
+ /** 0.23.0: `ColumnMapper` / `ColumnRoleTable` — a pasted or dropped table, a role per
316
+ * column. */
317
+ columnMapper: ColumnMapperLabels;
297
318
  }
298
319
 
299
320
  /**
@@ -438,8 +459,40 @@ interface KitI18n {
438
459
  weekStartsOn?: WeekDay;
439
460
  linkComponent?: KitLinkComponent;
440
461
  chartTooltipPlacement?: ChartTooltipPlacement;
462
+ formatDate?: KitDateFormatter;
441
463
  }
442
464
 
465
+ /** What a {@link KitDateFormatter} is told about the date it is formatting. */
466
+ export interface KitDateFormatContext {
467
+ /**
468
+ * How much of a date the string is: `"day"` — the `iso` is `"YYYY-MM-DD"`; `"month"` —
469
+ * `"YYYY-MM"` (a `MonthPicker`); `"year"` — `"YYYY"` (`MonthPicker mode="year"`).
470
+ */
471
+ unit: "day" | "month" | "year";
472
+ /** Which kit surface asks: the trigger of a `DatePicker`, of a `DateRangePicker`
473
+ * (called once per end), of a `MonthPicker`, or a `DateMark`. */
474
+ source: "datePicker" | "dateRangePicker" | "monthPicker" | "dateMark";
475
+ /** The locale the kit would have formatted in — the component's `locale` prop, else
476
+ * the provider's — or `undefined` for the runtime's. */
477
+ locale: string | undefined;
478
+ /**
479
+ * The kit's suggestion: whether this date stands alone where the day of the week
480
+ * helps — `true` for a `DatePicker`'s trigger and a `DateMark`, `false` for the two
481
+ * ends of a range (twice the width, in a sentence-like "from – to") and for a month
482
+ * or a year. keksdose's rule, which this follows: *"A date in a COLUMN carries the
483
+ * weekday. A date in a SENTENCE does not."* (date-cell.tsx, dev#546). A formatter
484
+ * is free to ignore it.
485
+ */
486
+ weekday: boolean;
487
+ }
488
+
489
+ /**
490
+ * The app's ONE answer to "what does a date look like here" — keksdose K12. Returns the
491
+ * text for an ISO date (see {@link KitDateFormatContext.unit} for the three shapes).
492
+ * Return `""` to fall back to the kit's own formatting for that date.
493
+ */
494
+ export type KitDateFormatter = (iso: string, context: KitDateFormatContext) => string;
495
+
443
496
  /** Where a chart shows the values under the pointer — see `SeriesChartTooltip.placement`. */
444
497
  export type ChartTooltipPlacement = "cursor" | "above" | "below" | "auto";
445
498
 
@@ -506,6 +559,25 @@ export interface UiKitProviderProps {
506
559
  * pointer, as before.
507
560
  */
508
561
  chartTooltipPlacement?: ChartTooltipPlacement;
562
+ /**
563
+ * How every kit date below is written, when the component is not told otherwise —
564
+ * keksdose K12. keksdose has a date-format preference (Settings ▸ date format, feedback
565
+ * #180) that is not the UI language, and a weekday in the UI language beside digits
566
+ * in the preference's order (live #246, dev#546): two locales in one string, which
567
+ * no `locale` + `Intl` options can say. Its `DateField` wraps every `DatePicker` to
568
+ * pass `formatValue`, and its report range field formats both ends by hand; with this
569
+ * set once on the provider, those wrappers thin to nothing.
570
+ *
571
+ * Used for the trigger text of `DatePicker`, `DateRangePicker` and `MonthPicker`
572
+ * (month and year mode), and for `DateMark display="date"`. A component's own
573
+ * `formatValue` or `formatOptions` (`dateStyle` on a DateMark) wins: prop >
574
+ * provider > the kit's `Intl` default, the order every kit setting resolves in. Left
575
+ * out (or returning `""`), everything formats exactly as before.
576
+ *
577
+ * Keep it stable (`useCallback`, or a module function): it is in the provider's
578
+ * context value, so a new function every render re-renders every consumer.
579
+ */
580
+ formatDate?: KitDateFormatter;
509
581
  children: ReactNode;
510
582
  }
511
583
 
@@ -523,6 +595,7 @@ export function UiKitProvider({
523
595
  weekStartsOn,
524
596
  linkComponent,
525
597
  chartTooltipPlacement,
598
+ formatDate,
526
599
  children,
527
600
  }: UiKitProviderProps) {
528
601
  const outer = useContext(KitI18nContext);
@@ -533,8 +606,9 @@ export function UiKitProvider({
533
606
  labels: mergeOverrides(outer.labels, labels),
534
607
  linkComponent: linkComponent ?? outer.linkComponent,
535
608
  chartTooltipPlacement: chartTooltipPlacement ?? outer.chartTooltipPlacement,
609
+ formatDate: formatDate ?? outer.formatDate,
536
610
  }),
537
- [outer, labels, locale, weekStartsOn, linkComponent, chartTooltipPlacement],
611
+ [outer, labels, locale, weekStartsOn, linkComponent, chartTooltipPlacement, formatDate],
538
612
  );
539
613
  return <KitI18nContext.Provider value={value}>{children}</KitI18nContext.Provider>;
540
614
  }
@@ -627,6 +701,12 @@ export function useKitChartTooltipPlacement(): ChartTooltipPlacement | undefined
627
701
  return useContext(KitI18nContext).chartTooltipPlacement;
628
702
  }
629
703
 
704
+ /** The nearest provider's `formatDate`, or `undefined` — a component's own
705
+ * `formatValue` / `formatOptions` wins over it. See {@link KitDateFormatter}. */
706
+ export function useKitDateFormatter(): KitDateFormatter | undefined {
707
+ return useContext(KitI18nContext).formatDate;
708
+ }
709
+
630
710
  /** {@link DEFAULT_FILE_LABELS}, but formatting in the provider's locale. */
631
711
  export function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels {
632
712
  const locale = useKitLocale();
@@ -33,7 +33,9 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
33
33
  ...d.measuredGrid,
34
34
  points: (count) => (count === 1 ? "1 point" : `${n(count)} points`),
35
35
  problems: (count) =>
36
- count === 1 ? "1 cell is not a number" : `${n(count)} cells are not numbers`,
36
+ count === 1
37
+ ? "1 cell is not a number"
38
+ : `${n(count)} cells are not numbers`,
37
39
  },
38
40
  dataTable: {
39
41
  ...d.dataTable,
@@ -51,9 +53,12 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
51
53
  combobox: {
52
54
  ...d.combobox,
53
55
  selectedCount: (count) => `${n(count)} selected`,
54
- resultCount: (count) => (count === 1 ? "1 result" : `${n(count)} results`),
56
+ resultCount: (count) =>
57
+ count === 1 ? "1 result" : `${n(count)} results`,
55
58
  minChars: (count) =>
56
- count === 1 ? "Type at least 1 character" : `Type at least ${n(count)} characters`,
59
+ count === 1
60
+ ? "Type at least 1 character"
61
+ : `Type at least ${n(count)} characters`,
57
62
  },
58
63
  multiSelect: {
59
64
  ...d.multiSelect,
@@ -81,7 +86,9 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
81
86
  `“${name}” was not added: at most ${n(maxFiles)} ${maxFiles === 1 ? "file" : "files"}`,
82
87
  rejectedMany: (count) => `${n(count)} files were not added`,
83
88
  rejectedPick: (count) =>
84
- count === 1 ? "The file was not added" : `None of the ${n(count)} files were added`,
89
+ count === 1
90
+ ? "The file was not added"
91
+ : `None of the ${n(count)} files were added`,
85
92
  selected: (count, firstName) =>
86
93
  count === 1 ? `“${firstName}” selected` : `${n(count)} files selected`,
87
94
  },
@@ -120,5 +127,30 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
120
127
  localeProgress: (approved, total) => `${n(approved)}/${n(total)}`,
121
128
  exportCorrections: (count) => `Export corrections (${n(count)})`,
122
129
  },
130
+ characterCount: {
131
+ ...d.characterCount,
132
+ count: (used, max) => `${n(used)} of ${n(max)} characters`,
133
+ remaining: (left) =>
134
+ left === 1 ? "1 character left" : `${n(left)} characters left`,
135
+ },
136
+ ibanInput: {
137
+ ...d.ibanInput,
138
+ length: (actual, expected) =>
139
+ `An IBAN from this country has ${n(expected)} characters — this one has ${n(actual)}.`,
140
+ },
141
+ columnMapper: {
142
+ ...d.columnMapper,
143
+ summary: (columns, rows) =>
144
+ `${n(columns)} ${columns === 1 ? "column" : "columns"}, ${n(rows)} ${rows === 1 ? "row" : "rows"}`,
145
+ unreadCount: (count) =>
146
+ count === 1
147
+ ? "1 line could not be read"
148
+ : `${n(count)} lines could not be read`,
149
+ unreadMore: (count) => `…and ${n(count)} more`,
150
+ previewOf: (shown, total) =>
151
+ shown === 1
152
+ ? `The first of ${n(total)} rows`
153
+ : `The first ${n(shown)} of ${n(total)} rows`,
154
+ },
123
155
  };
124
156
  }