@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
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/reauth-dialog.tsx"],"sourcesContent":["import { useContext, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { usePromisePending } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { ModalCloseContext } from \"./modal\";\nimport { Button, Input, Spinner } from \"./ui\";\n\n/**\n * Every string the dialog renders — the `reauthDialog` namespace of\n * `<UiKitProvider labels>`, and overridable per instance through `labels`. The title,\n * description and submit button are usually worded per action (\"Add a passkey\"),\n * which is what the `title` / `description` / `submitLabel` props are for; these are\n * the fallbacks.\n */\nexport interface ReauthDialogLabels {\n title: string;\n description: string;\n /** Label of the password field. */\n password: string;\n submit: string;\n cancel: string;\n}\n\nexport const DEFAULT_REAUTH_DIALOG_LABELS: ReauthDialogLabels = {\n title: \"Confirm it’s you\",\n description: \"Enter your current password to continue.\",\n password: \"Current password\",\n submit: \"Continue\",\n cancel: \"Cancel\",\n};\n\nexport interface ReauthDialogProps {\n /**\n * Verifies the password — the app's call, e.g. the request the password guards.\n *\n * Return a promise and the dialog manages itself: busy until it settles, closes\n * (`onClose(true)`) when it resolves, STAYS OPEN with the password kept and selected\n * when it rejects — a wrong password is a typo to correct, not a sign-out. Pass the\n * reason as `error`. Return nothing and the app owns the rest: `busy` for the\n * pending state, unmounting the dialog once it is done.\n */\n onSubmit: (password: string) => void | Promise<unknown>;\n /**\n * The dialog has closed: `true` after a resolved `onSubmit`, `false` for Cancel,\n * Escape, the backdrop and Back. A dismissal while `onSubmit` is still running also\n * answers `false`, and that call's late result is then ignored.\n */\n onClose: (confirmed: boolean) => void;\n /**\n * Why the last attempt failed (\"Wrong password\"), shown under the field, which turns\n * invalid (`aria-invalid`, `--danger-border-strong`). The app decides the wording —\n * a 400 is a wrong password, a 500 is not. Hidden again as soon as the user edits\n * the field (the message is about the password they had typed), and shown again by\n * the next failed submit.\n */\n error?: ReactNode;\n /** The check is running, for a caller that tracks it itself (a mutation's\n * `isPending`); a promise returned from `onSubmit` does the same on its own. */\n busy?: boolean;\n /** Default: `labels.title` — \"Confirm it’s you\". */\n title?: ReactNode;\n /** The line under the title. Default: `labels.description`. */\n description?: ReactNode;\n /** Text of the submit button. Default: `labels.submit` — \"Continue\". */\n submitLabel?: ReactNode;\n /** `\"danger\"` when what the password unlocks cannot be undone: the submit is then the\n * destructive button. Default `\"neutral\"`. */\n tone?: \"neutral\" | \"danger\";\n /** Extra body above the field — what is about to happen, a warning. */\n children?: ReactNode;\n /** Kept-mounted mode, as on `Modal`: left out, the dialog is open while mounted. A\n * flip to `false` animates it out and wipes the password. */\n open?: boolean;\n /** User-facing strings; see {@link ReauthDialogLabels}. */\n labels?: Partial<ReauthDialogLabels>;\n}\n\n/**\n * \"Enter your current password to continue\" — the step-up check in front of an\n * action that changes how an account signs in (adding a passkey) or what it can\n * reach. Kurvenschmiede asked for the password before registering a passkey in a\n * dialog of its own; keksdose asks for it inline in `DangerConfirm`'s\n * `requirePassword` tile. Both are the same three facts — the field is the CURRENT\n * password (the password manager fills it, never generates one), a wrong one stays in\n * the dialog with its error, and the APP verifies it — so the promise handling is\n * `DangerConfirm`'s own, shared, the field is its `CurrentPasswordInput` spelled out\n * with an `error`, and this is the modal form of them.\n *\n * A component rather than a `useReauth()` promise: the step that follows the check\n * (Kurvenschmiede's WebAuthn ceremony) needs what the server answered, and the error\n * wording needs the app's own status codes — both already live in the app's mutation,\n * which this dialog only has to be handed (`onSubmit={(pw) => begin.mutateAsync(pw)}`,\n * `error={…}`).\n *\n * The field is a `<form>`, so Enter submits once something is typed. Focus starts in\n * the field and returns to the trigger through `Modal`'s trap. While a check runs,\n * the field is read-only, Cancel is disabled and Escape is held: a request that has\n * left cannot be cancelled by closing the dialog, and the dialog should not pretend it\n * can.\n */\nexport function ReauthDialog({\n onSubmit,\n onClose,\n error,\n busy: busyProp,\n title,\n description,\n submitLabel,\n tone = \"neutral\",\n children,\n open,\n labels: labelsProp,\n}: ReauthDialogProps) {\n const labels = useKitLabels(\"reauthDialog\", DEFAULT_REAUTH_DIALOG_LABELS, labelsProp);\n const [password, setPassword] = useState(\"\");\n // The error describes the password that was submitted; once the user edits it, it\n // describes nothing on screen. Cleared by the next submit, so a second failure with\n // the same message shows again.\n const [edited, setEdited] = useState(false);\n const { pending, run } = usePromisePending();\n const busy = Boolean(busyProp) || pending;\n const inputRef = useRef<HTMLInputElement>(null);\n // `onSubmit` resolved: the actions row lowers the panel (state, so it re-renders),\n // and `onClose` answers true (a ref, because `Modal` may call the `onClose` it saw\n // BEFORE that re-render — its latest-callback ref is refreshed in an effect that runs\n // after the actions row's).\n const [done, setDone] = useState(false);\n const confirmed = useRef(false);\n const formId = useId();\n const isOpen = open ?? true;\n\n // A close (kept-mounted) wipes the password: it must not sit in a hidden dialog.\n // During render, like DangerConfirm's disarm, so no frame holds it.\n const [wasOpen, setWasOpen] = useState(isOpen);\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n if (!isOpen) {\n setPassword(\"\");\n setEdited(false);\n setDone(false);\n }\n }\n\n // After `Modal`'s own effect (a parent's effects run after its children's), so the\n // trap has recorded the trigger as its restore target before focus moves in.\n useEffect(() => {\n if (!isOpen) return;\n confirmed.current = false;\n inputRef.current?.focus();\n }, [isOpen]);\n\n const submit = () => {\n if (busy || password === \"\") return;\n setEdited(false);\n run(onSubmit(password), () => {\n confirmed.current = true;\n setDone(true);\n });\n };\n\n // A rejection leaves focus where it was (the field, or the submit button) — select\n // the password there so the retry replaces it rather than appending to it.\n const wasPending = useRef(false);\n useEffect(() => {\n if (wasPending.current && !pending && !done) inputRef.current?.select();\n wasPending.current = pending;\n }, [pending, done]);\n\n const shownError = edited ? undefined : error;\n\n return (\n <DialogFrame\n open={open}\n title={title ?? labels.title}\n description={description ?? labels.description}\n onClose={() => onClose(confirmed.current)}\n onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => {\n if (busy && e.key === \"Escape\") e.preventDefault();\n }}\n aria-busy={busy || undefined}\n actions={\n <ReauthActions\n formId={formId}\n done={done}\n busy={busy}\n canSubmit={password !== \"\"}\n danger={tone === \"danger\"}\n cancelLabel={labels.cancel}\n submitLabel={submitLabel ?? labels.submit}\n />\n }\n >\n {children}\n <form\n id={formId}\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n {/* CurrentPasswordInput's field — the password manager fills it with the CURRENT\n password and never offers to generate one; `readOnly`, not `disabled`, while\n busy, because disabling the field that has focus drops focus to <body> —\n plus `Input`'s own `error`, which that helper does not take. The message\n paints the field, sits under it and is its `aria-describedby`; passing the\n prop on every render keeps the field's box steady, so the message going away\n on the first keystroke of a retry no longer remounts the <input> under the\n typist (it did, which is why this used to draw its own message). */}\n <Input\n ref={inputRef}\n type=\"password\"\n autoComplete=\"current-password\"\n label={labels.password}\n value={password}\n readOnly={busy}\n error={shownError}\n onChange={(e) => {\n setPassword(e.target.value);\n setEdited(true);\n }}\n />\n </form>\n </DialogFrame>\n );\n}\n\n/**\n * The actions row. A component of its own because it renders INSIDE the `Modal`, where\n * the panel's animated close is in context: a resolved `onSubmit` lowers the panel\n * through it, the way Cancel and Escape do, and `Modal` then calls `onClose`.\n */\nfunction ReauthActions({\n formId,\n done,\n busy,\n canSubmit,\n danger,\n cancelLabel,\n submitLabel,\n}: {\n formId: string;\n done: boolean;\n busy: boolean;\n canSubmit: boolean;\n danger: boolean;\n cancelLabel: ReactNode;\n submitLabel: ReactNode;\n}) {\n const close = useContext(ModalCloseContext);\n useEffect(() => {\n if (done) close?.();\n }, [done, close]);\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => close?.()}>\n {cancelLabel}\n </Button>\n <Button\n type=\"submit\"\n form={formId}\n variant={danger ? \"danger\" : \"primary\"}\n disabled={!canSubmit || busy}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {submitLabel}\n </Button>\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/reauth-dialog.tsx"],"sourcesContent":["import { useContext, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { CurrentPasswordInput, usePromisePending } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { ModalCloseContext } from \"./modal\";\nimport { Button, Spinner } from \"./ui\";\n\n/**\n * Every string the dialog renders — the `reauthDialog` namespace of\n * `<UiKitProvider labels>`, and overridable per instance through `labels`. The title,\n * description and submit button are usually worded per action (\"Add a passkey\"),\n * which is what the `title` / `description` / `submitLabel` props are for; these are\n * the fallbacks.\n */\nexport interface ReauthDialogLabels {\n title: string;\n description: string;\n /** Label of the password field. */\n password: string;\n submit: string;\n cancel: string;\n}\n\nexport const DEFAULT_REAUTH_DIALOG_LABELS: ReauthDialogLabels = {\n title: \"Confirm it’s you\",\n description: \"Enter your current password to continue.\",\n password: \"Current password\",\n submit: \"Continue\",\n cancel: \"Cancel\",\n};\n\nexport interface ReauthDialogProps {\n /**\n * Verifies the password — the app's call, e.g. the request the password guards.\n *\n * Return a promise and the dialog manages itself: busy until it settles, closes\n * (`onClose(true)`) when it resolves, STAYS OPEN with the password kept and selected\n * when it rejects — a wrong password is a typo to correct, not a sign-out. Pass the\n * reason as `error`. Return nothing and the app owns the rest: `busy` for the\n * pending state, unmounting the dialog once it is done.\n */\n onSubmit: (password: string) => void | Promise<unknown>;\n /**\n * The dialog has closed: `true` after a resolved `onSubmit`, `false` for Cancel,\n * Escape, the backdrop and Back. A dismissal while `onSubmit` is still running also\n * answers `false`, and that call's late result is then ignored.\n */\n onClose: (confirmed: boolean) => void;\n /**\n * Why the last attempt failed (\"Wrong password\"), shown under the field, which turns\n * invalid (`aria-invalid`, `--danger-border-strong`). The app decides the wording —\n * a 400 is a wrong password, a 500 is not. Hidden again as soon as the user edits\n * the field (the message is about the password they had typed), and shown again by\n * the next failed submit.\n */\n error?: ReactNode;\n /** The check is running, for a caller that tracks it itself (a mutation's\n * `isPending`); a promise returned from `onSubmit` does the same on its own. */\n busy?: boolean;\n /** Default: `labels.title` — \"Confirm it’s you\". */\n title?: ReactNode;\n /** The line under the title. Default: `labels.description`. */\n description?: ReactNode;\n /** Text of the submit button. Default: `labels.submit` — \"Continue\". */\n submitLabel?: ReactNode;\n /** `\"danger\"` when what the password unlocks cannot be undone: the submit is then the\n * destructive button. Default `\"neutral\"`. */\n tone?: \"neutral\" | \"danger\";\n /** Extra body above the field — what is about to happen, a warning. */\n children?: ReactNode;\n /** Kept-mounted mode, as on `Modal`: left out, the dialog is open while mounted. A\n * flip to `false` animates it out and wipes the password. */\n open?: boolean;\n /** User-facing strings; see {@link ReauthDialogLabels}. */\n labels?: Partial<ReauthDialogLabels>;\n}\n\n/**\n * \"Enter your current password to continue\" — the step-up check in front of an\n * action that changes how an account signs in (adding a passkey) or what it can\n * reach. Kurvenschmiede asked for the password before registering a passkey in a\n * dialog of its own; keksdose asks for it inline in `DangerConfirm`'s\n * `requirePassword` tile. Both are the same three facts — the field is the CURRENT\n * password (the password manager fills it, never generates one), a wrong one stays in\n * the dialog with its error, and the APP verifies it — so the promise handling is\n * `DangerConfirm`'s own, shared, the field is its `CurrentPasswordInput` with an\n * `error`, and this is the modal form of them.\n *\n * A component rather than a `useReauth()` promise: the step that follows the check\n * (Kurvenschmiede's WebAuthn ceremony) needs what the server answered, and the error\n * wording needs the app's own status codes — both already live in the app's mutation,\n * which this dialog only has to be handed (`onSubmit={(pw) => begin.mutateAsync(pw)}`,\n * `error={…}`).\n *\n * The field is a `<form>`, so Enter submits once something is typed. Focus starts in\n * the field and returns to the trigger through `Modal`'s trap. While a check runs,\n * the field is read-only, Cancel is disabled and Escape is held: a request that has\n * left cannot be cancelled by closing the dialog, and the dialog should not pretend it\n * can.\n */\nexport function ReauthDialog({\n onSubmit,\n onClose,\n error,\n busy: busyProp,\n title,\n description,\n submitLabel,\n tone = \"neutral\",\n children,\n open,\n labels: labelsProp,\n}: ReauthDialogProps) {\n const labels = useKitLabels(\"reauthDialog\", DEFAULT_REAUTH_DIALOG_LABELS, labelsProp);\n const [password, setPassword] = useState(\"\");\n // The error describes the password that was submitted; once the user edits it, it\n // describes nothing on screen. Cleared by the next submit, so a second failure with\n // the same message shows again.\n const [edited, setEdited] = useState(false);\n const { pending, run } = usePromisePending();\n const busy = Boolean(busyProp) || pending;\n const inputRef = useRef<HTMLInputElement>(null);\n // `onSubmit` resolved: the actions row lowers the panel (state, so it re-renders),\n // and `onClose` answers true (a ref, because `Modal` may call the `onClose` it saw\n // BEFORE that re-render — its latest-callback ref is refreshed in an effect that runs\n // after the actions row's).\n const [done, setDone] = useState(false);\n const confirmed = useRef(false);\n const formId = useId();\n const isOpen = open ?? true;\n\n // A close (kept-mounted) wipes the password: it must not sit in a hidden dialog.\n // During render, like DangerConfirm's disarm, so no frame holds it.\n const [wasOpen, setWasOpen] = useState(isOpen);\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n if (!isOpen) {\n setPassword(\"\");\n setEdited(false);\n setDone(false);\n }\n }\n\n // After `Modal`'s own effect (a parent's effects run after its children's), so the\n // trap has recorded the trigger as its restore target before focus moves in.\n useEffect(() => {\n if (!isOpen) return;\n confirmed.current = false;\n inputRef.current?.focus();\n }, [isOpen]);\n\n const submit = () => {\n if (busy || password === \"\") return;\n setEdited(false);\n run(onSubmit(password), () => {\n confirmed.current = true;\n setDone(true);\n });\n };\n\n // A rejection leaves focus where it was (the field, or the submit button) — select\n // the password there so the retry replaces it rather than appending to it.\n const wasPending = useRef(false);\n useEffect(() => {\n if (wasPending.current && !pending && !done) inputRef.current?.select();\n wasPending.current = pending;\n }, [pending, done]);\n\n const shownError = edited ? undefined : error;\n\n return (\n <DialogFrame\n open={open}\n title={title ?? labels.title}\n description={description ?? labels.description}\n onClose={() => onClose(confirmed.current)}\n onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => {\n if (busy && e.key === \"Escape\") e.preventDefault();\n }}\n aria-busy={busy || undefined}\n actions={\n <ReauthActions\n formId={formId}\n done={done}\n busy={busy}\n canSubmit={password !== \"\"}\n danger={tone === \"danger\"}\n cancelLabel={labels.cancel}\n submitLabel={submitLabel ?? labels.submit}\n />\n }\n >\n {children}\n <form\n id={formId}\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n {/* The kit's CurrentPasswordInput (0.23; it drew the same field by hand until\n then): the password manager fills it with the CURRENT password and never\n offers to generate one; `busy` makes it `readOnly`, not `disabled`, because\n disabling the field that has focus drops focus to <body>. Labelled by this\n namespace's `password` (\"Current password\"), not `dangerConfirm.password`.\n The `error` is Input's own: it paints the field, sits under it and is its\n `aria-describedby`; passing the prop on every render keeps the field's box\n steady, so the message going away on the first keystroke of a retry does not\n remount the <input> under the typist. */}\n <CurrentPasswordInput\n ref={inputRef}\n label={labels.password}\n value={password}\n busy={busy}\n error={shownError}\n onValueChange={(next) => {\n setPassword(next);\n setEdited(true);\n }}\n />\n </form>\n </DialogFrame>\n );\n}\n\n/**\n * The actions row. A component of its own because it renders INSIDE the `Modal`, where\n * the panel's animated close is in context: a resolved `onSubmit` lowers the panel\n * through it, the way Cancel and Escape do, and `Modal` then calls `onClose`.\n */\nfunction ReauthActions({\n formId,\n done,\n busy,\n canSubmit,\n danger,\n cancelLabel,\n submitLabel,\n}: {\n formId: string;\n done: boolean;\n busy: boolean;\n canSubmit: boolean;\n danger: boolean;\n cancelLabel: ReactNode;\n submitLabel: ReactNode;\n}) {\n const close = useContext(ModalCloseContext);\n useEffect(() => {\n if (done) close?.();\n }, [done, close]);\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => close?.()}>\n {cancelLabel}\n </Button>\n <Button\n type=\"submit\"\n form={formId}\n variant={danger ? \"danger\" : \"primary\"}\n disabled={!canSubmit || busy}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {submitLabel}\n </Button>\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -5,18 +5,22 @@ import { LegendEntry } from './toggle-legend.js';
5
5
  import { SeriesChartLabels } from './series-chart-labels.js';
6
6
  import { TimeTickUnit } from './series-chart-ticks.js';
7
7
  import { SeriesChartAxisBudget } from './series-chart-budget.js';
8
- import { x as ChartTooltipPlacement } from '../kit-labels-D1J2_jyD.js';
8
+ import { y as ChartTooltipPlacement } from '../feedback-BxeQVzwq.js';
9
9
  import 'recharts';
10
- import './data-table-labels.js';
10
+ import '../feedback-attachment-fGAzZPf0.js';
11
+ import '../feedback/feedback-inbox.js';
12
+ import 'lucide-react';
13
+ import './tooltip.js';
14
+ import '../hooks/use-anchored-rect.js';
15
+ import '../lib/clipping.js';
16
+ import '../data-table-labels-B7OdnM0S.js';
17
+ import './data-table-sort.js';
18
+ import './swipeable-row.js';
11
19
  import './mini-calendar.js';
12
20
  import './calendar-heatmap.js';
13
21
  import './popover.js';
14
22
  import './chip.js';
15
- import 'lucide-react';
16
23
  import './field-sync.js';
17
- import './tooltip.js';
18
- import '../hooks/use-anchored-rect.js';
19
- import '../lib/clipping.js';
20
24
  import '../wizard/types.js';
21
25
  import '../tour/tour.js';
22
26
  import '../search/command-palette.js';
@@ -29,7 +33,6 @@ import './sparkline.js';
29
33
  import './stat-tile.js';
30
34
  import './signature-pad.js';
31
35
  import './password-strength.js';
32
- import './danger-confirm.js';
33
36
  import './swatch-picker.js';
34
37
  import './tile-radio.js';
35
38
  import './icon-picker.js';
@@ -37,11 +40,7 @@ import './dialog-frame.js';
37
40
  import './modal.js';
38
41
  import '../hooks/use-search-param-state.js';
39
42
  import './measured-grid.js';
40
- import '../feedback-attachment-WFiXJ8We.js';
41
- import '../feedback/feedback-thread.js';
42
- import '../feedback/feedback-inbox.js';
43
43
  import './account-settings-labels.js';
44
- import './confirm-dialog.js';
45
44
  import './floating-panel.js';
46
45
  import './bulk-action-bar.js';
47
46
  import './list.js';
@@ -60,14 +59,20 @@ import './write-lock.js';
60
59
  import './legal.js';
61
60
  import './account-chips.js';
62
61
  import '../lib/format.js';
63
- import '../data-table-3mi2R3so.js';
64
- import './data-table-sort.js';
65
- import './swipeable-row.js';
66
62
  import './reauth-dialog.js';
67
63
  import './server-wake.js';
68
64
  import '../lib/server-wake.js';
69
65
  import './translation-review-labels.js';
70
66
  import '../lib/translation-review.js';
67
+ import './country-select.js';
68
+ import './inline-edit-field.js';
69
+ import '../lib/iban.js';
70
+ import '../lib/phone.js';
71
+ import './sign-chip.js';
72
+ import './column-mapper.js';
73
+ import '../lib/column-mapping.js';
74
+ import '../lib/table-text.js';
75
+ import './field-parts.js';
71
76
 
72
77
  interface SeriesChartSeries {
73
78
  /** The row key this line reads — and the `--color-<key>` it is painted with, so it
@@ -1,17 +1,21 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { aQ as SelectProps } from '../kit-labels-D1J2_jyD.js';
3
+ import { bw as SelectProps } from '../feedback-BxeQVzwq.js';
4
4
  import { ThemePreference } from '../theme/theme-store.js';
5
- import './data-table-labels.js';
5
+ import '../feedback-attachment-fGAzZPf0.js';
6
+ import '../feedback/feedback-inbox.js';
7
+ import 'lucide-react';
8
+ import './tooltip.js';
9
+ import '../hooks/use-anchored-rect.js';
10
+ import '../lib/clipping.js';
11
+ import '../data-table-labels-B7OdnM0S.js';
12
+ import './data-table-sort.js';
13
+ import './swipeable-row.js';
6
14
  import './mini-calendar.js';
7
15
  import './calendar-heatmap.js';
8
16
  import './popover.js';
9
17
  import './chip.js';
10
- import 'lucide-react';
11
18
  import './field-sync.js';
12
- import './tooltip.js';
13
- import '../hooks/use-anchored-rect.js';
14
- import '../lib/clipping.js';
15
19
  import '../wizard/types.js';
16
20
  import '../tour/tour.js';
17
21
  import '../search/command-palette.js';
@@ -25,7 +29,6 @@ import './sparkline.js';
25
29
  import './stat-tile.js';
26
30
  import './signature-pad.js';
27
31
  import './password-strength.js';
28
- import './danger-confirm.js';
29
32
  import './swatch-picker.js';
30
33
  import './tile-radio.js';
31
34
  import './icon-picker.js';
@@ -33,11 +36,7 @@ import './dialog-frame.js';
33
36
  import './modal.js';
34
37
  import '../hooks/use-search-param-state.js';
35
38
  import './measured-grid.js';
36
- import '../feedback-attachment-WFiXJ8We.js';
37
- import '../feedback/feedback-thread.js';
38
- import '../feedback/feedback-inbox.js';
39
39
  import './account-settings-labels.js';
40
- import './confirm-dialog.js';
41
40
  import './floating-panel.js';
42
41
  import './bulk-action-bar.js';
43
42
  import './list.js';
@@ -56,14 +55,20 @@ import './write-lock.js';
56
55
  import './legal.js';
57
56
  import './account-chips.js';
58
57
  import '../lib/format.js';
59
- import '../data-table-3mi2R3so.js';
60
- import './data-table-sort.js';
61
- import './swipeable-row.js';
62
58
  import './reauth-dialog.js';
63
59
  import './server-wake.js';
64
60
  import '../lib/server-wake.js';
65
61
  import './translation-review-labels.js';
66
62
  import '../lib/translation-review.js';
63
+ import './country-select.js';
64
+ import './inline-edit-field.js';
65
+ import '../lib/iban.js';
66
+ import '../lib/phone.js';
67
+ import './sign-chip.js';
68
+ import './column-mapper.js';
69
+ import '../lib/column-mapping.js';
70
+ import '../lib/table-text.js';
71
+ import './field-parts.js';
67
72
  import 'zustand/middleware';
68
73
  import 'zustand';
69
74
 
@@ -1,23 +1,24 @@
1
1
  import 'react';
2
2
  import './account-chips.js';
3
3
  import './dialog-frame.js';
4
- export { U as DEFAULT_SHARE_CARD_LABELS, aR as ShareAddRequest, aS as ShareCandidate, aT as ShareCard, aU as ShareCardLabels, aV as ShareCardProps, aW as ShareDialog, aX as ShareDialogProps, aY as ShareGrantee, aZ as SharePanel, a_ as SharePanelProps, a$ as SharePendingGrant, b0 as ShareRole } from '../kit-labels-D1J2_jyD.js';
4
+ export { ad as DEFAULT_SHARE_CARD_LABELS, bx as ShareAddRequest, by as ShareCandidate, bz as ShareCard, bA as ShareCardLabels, bB as ShareCardProps, bC as ShareDialog, bD as ShareDialogProps, bE as ShareGrantee, bF as SharePanel, bG as SharePanelProps, bH as SharePendingGrant, bI as ShareRole } from '../feedback-BxeQVzwq.js';
5
5
  import 'lucide-react';
6
6
  import '../lib/format.js';
7
7
  import './chip.js';
8
- import '../data-table-3mi2R3so.js';
8
+ import '../data-table-labels-B7OdnM0S.js';
9
9
  import './data-table-sort.js';
10
10
  import './swipeable-row.js';
11
- import './data-table-labels.js';
12
11
  import './modal.js';
13
12
  import '../hooks/use-search-param-state.js';
13
+ import '../feedback-attachment-fGAzZPf0.js';
14
+ import '../feedback/feedback-inbox.js';
15
+ import './tooltip.js';
16
+ import '../hooks/use-anchored-rect.js';
17
+ import '../lib/clipping.js';
14
18
  import './mini-calendar.js';
15
19
  import './calendar-heatmap.js';
16
20
  import './popover.js';
17
21
  import './field-sync.js';
18
- import './tooltip.js';
19
- import '../hooks/use-anchored-rect.js';
20
- import '../lib/clipping.js';
21
22
  import '../wizard/types.js';
22
23
  import '../tour/tour.js';
23
24
  import '../search/command-palette.js';
@@ -31,16 +32,11 @@ import './sparkline.js';
31
32
  import './stat-tile.js';
32
33
  import './signature-pad.js';
33
34
  import './password-strength.js';
34
- import './danger-confirm.js';
35
35
  import './swatch-picker.js';
36
36
  import './tile-radio.js';
37
37
  import './icon-picker.js';
38
38
  import './measured-grid.js';
39
- import '../feedback-attachment-WFiXJ8We.js';
40
- import '../feedback/feedback-thread.js';
41
- import '../feedback/feedback-inbox.js';
42
39
  import './account-settings-labels.js';
43
- import './confirm-dialog.js';
44
40
  import './floating-panel.js';
45
41
  import './bulk-action-bar.js';
46
42
  import './list.js';
@@ -62,3 +58,12 @@ import './server-wake.js';
62
58
  import '../lib/server-wake.js';
63
59
  import './translation-review-labels.js';
64
60
  import '../lib/translation-review.js';
61
+ import './country-select.js';
62
+ import './inline-edit-field.js';
63
+ import '../lib/iban.js';
64
+ import '../lib/phone.js';
65
+ import './sign-chip.js';
66
+ import './column-mapper.js';
67
+ import '../lib/column-mapping.js';
68
+ import '../lib/table-text.js';
69
+ import './field-parts.js';
@@ -0,0 +1,66 @@
1
+ import * as react from 'react';
2
+
3
+ /** The words a {@link SignChip} says — the `signChip` namespace of
4
+ * `<UiKitProvider labels>`, and per chip through its `labels` prop. */
5
+ interface SignChipLabels {
6
+ /** The chip's text while the figure is negative — money going out. */
7
+ outflow: string;
8
+ /** The chip's text while the figure is positive — money coming in. */
9
+ inflow: string;
10
+ /** The chip's accessible name and its tooltip: the state it is in, and what a press
11
+ * turns it into — `current` and `next` are the two words above. The other option is
12
+ * nowhere on screen to be read, so the name has to say both. */
13
+ direction: (current: string, next: string) => string;
14
+ }
15
+ declare const DEFAULT_SIGN_CHIP_LABELS: SignChipLabels;
16
+ interface SignChipProps {
17
+ /** The figure is negative (an outflow) — the same flag {@link AmountInput}'s
18
+ * `negative` is, so one state feeds both: `<AmountInput negative={neg}
19
+ * onNegativeChange={setNeg} />` and `<SignChip negative={neg}
20
+ * onNegativeChange={setNeg} />`. */
21
+ negative: boolean;
22
+ /** A press: the other direction. */
23
+ onNegativeChange: (negative: boolean) => void;
24
+ disabled?: boolean;
25
+ /** Per-chip overrides of the `signChip` namespace. */
26
+ labels?: Partial<SignChipLabels>;
27
+ /** On the chip. Place it in its row from here — keksdose's form passes `self-end`
28
+ * to sit it on the display figure's baseline strip. */
29
+ className?: string;
30
+ }
31
+ /**
32
+ * The SIGN of an amount, as one chip that shows only the state it is in — keksdose
33
+ * K15, its `DirectionToggle` (feedback #417, #430, #431, dev#434) made a kit piece,
34
+ * so the figure and its direction come from one package and wear one palette.
35
+ *
36
+ * WHY A CHIP, NOT A SEGMENTED CONTROL. A two-option toggle spends half its width
37
+ * telling you what the figure is NOT, and at a phone's width it was the widest thing on
38
+ * the form after the figure itself (#417). This sits beside the amount and reads as one
39
+ * phrase with it — "−42,50 Outflow" — and a press flips it. Hiding the alternative costs
40
+ * discoverability, so two things pay it back: the ± glyph and the swap arrows say "this
41
+ * is a control", not a badge; and the name and tooltip spell out both the current state
42
+ * and what a press will do.
43
+ *
44
+ * NOT A PRESSED/UNPRESSED TOGGLE. A {@link Chip} toggle has a FIXED label naming its
45
+ * on-state, with `aria-pressed` carrying the state. Here the word IS the state, so it
46
+ * is an action button with no `aria-pressed`: "Direction: Inflow, not pressed" would
47
+ * name no on-state for "not pressed" to be the absence of. The name changing under
48
+ * focus is the echo.
49
+ *
50
+ * THE COLOUR is the money pairing — the chip's `expense` / `income` tones, the same
51
+ * `--money-expense` / `--money-income` that {@link AmountInput}'s `tone` paints the
52
+ * figure with (dev#434): only the text and the border carry the tint, on a neutral
53
+ * surface, because a filled pill next to a tinted figure reads as one smear.
54
+ *
55
+ * SIZE. `lg` on a phone — the 44px touch target beside a display figure that has no
56
+ * field height to match — and from `md` up exactly a labelled field's 42px, so it never
57
+ * stands taller than the fields in its grid row (#431).
58
+ *
59
+ * An exported chip rather than an `AmountInput signControl` slot: the slot would have
60
+ * to own the row the two share, and every form lays that row out differently (beside
61
+ * the figure on a phone, in a grid column on a desktop). The chip is the part that was
62
+ * being re-written; the row stays the form's.
63
+ */
64
+ declare function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }: SignChipProps): react.JSX.Element;
65
+
66
+ export { DEFAULT_SIGN_CHIP_LABELS, SignChip, type SignChipLabels, type SignChipProps };
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ArrowUpDown, Minus, Plus } from "lucide-react";
4
+ import { Chip } from "./chip.js";
5
+ import { Tooltip } from "./tooltip.js";
6
+ import { cn } from "../lib/cn.js";
7
+ import { useKitLabels } from "../i18n/kit-labels.js";
8
+ const DEFAULT_SIGN_CHIP_LABELS = {
9
+ outflow: "Outflow",
10
+ inflow: "Inflow",
11
+ direction: (current, next) => `Direction: ${current} \u2014 switch to ${next}`
12
+ };
13
+ function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }) {
14
+ const labels = useKitLabels("signChip", DEFAULT_SIGN_CHIP_LABELS, labelsProp);
15
+ const current = negative ? labels.outflow : labels.inflow;
16
+ const next = negative ? labels.inflow : labels.outflow;
17
+ const name = labels.direction(current, next);
18
+ return /* @__PURE__ */ jsx(Tooltip, { label: name, lazy: true, children: /* @__PURE__ */ jsxs(
19
+ Chip,
20
+ {
21
+ size: "lg",
22
+ tone: negative ? "expense" : "income",
23
+ icon: negative ? Minus : Plus,
24
+ onClick: () => onNegativeChange(!negative),
25
+ disabled,
26
+ "aria-label": name,
27
+ className: cn("shrink-0 gap-1.5 whitespace-nowrap px-3 font-medium md:min-h-0 md:py-2.5", className),
28
+ children: [
29
+ current,
30
+ /* @__PURE__ */ jsx(ArrowUpDown, { className: "ms-1.5 inline size-3.5 align-[-0.2em] opacity-60", "aria-hidden": true })
31
+ ]
32
+ }
33
+ ) });
34
+ }
35
+ export {
36
+ DEFAULT_SIGN_CHIP_LABELS,
37
+ SignChip
38
+ };
39
+ //# sourceMappingURL=sign-chip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/sign-chip.tsx"],"sourcesContent":["import { ArrowUpDown, Minus, Plus } from \"lucide-react\";\nimport { Chip } from \"./chip\";\nimport { Tooltip } from \"./tooltip\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\n/** The words a {@link SignChip} says — the `signChip` namespace of\n * `<UiKitProvider labels>`, and per chip through its `labels` prop. */\nexport interface SignChipLabels {\n /** The chip's text while the figure is negative — money going out. */\n outflow: string;\n /** The chip's text while the figure is positive — money coming in. */\n inflow: string;\n /** The chip's accessible name and its tooltip: the state it is in, and what a press\n * turns it into — `current` and `next` are the two words above. The other option is\n * nowhere on screen to be read, so the name has to say both. */\n direction: (current: string, next: string) => string;\n}\n\nexport const DEFAULT_SIGN_CHIP_LABELS: SignChipLabels = {\n outflow: \"Outflow\",\n inflow: \"Inflow\",\n direction: (current, next) => `Direction: ${current} — switch to ${next}`,\n};\n\nexport interface SignChipProps {\n /** The figure is negative (an outflow) — the same flag {@link AmountInput}'s\n * `negative` is, so one state feeds both: `<AmountInput negative={neg}\n * onNegativeChange={setNeg} />` and `<SignChip negative={neg}\n * onNegativeChange={setNeg} />`. */\n negative: boolean;\n /** A press: the other direction. */\n onNegativeChange: (negative: boolean) => void;\n disabled?: boolean;\n /** Per-chip overrides of the `signChip` namespace. */\n labels?: Partial<SignChipLabels>;\n /** On the chip. Place it in its row from here — keksdose's form passes `self-end`\n * to sit it on the display figure's baseline strip. */\n className?: string;\n}\n\n/**\n * The SIGN of an amount, as one chip that shows only the state it is in — keksdose\n * K15, its `DirectionToggle` (feedback #417, #430, #431, dev#434) made a kit piece,\n * so the figure and its direction come from one package and wear one palette.\n *\n * WHY A CHIP, NOT A SEGMENTED CONTROL. A two-option toggle spends half its width\n * telling you what the figure is NOT, and at a phone's width it was the widest thing on\n * the form after the figure itself (#417). This sits beside the amount and reads as one\n * phrase with it — \"−42,50 Outflow\" — and a press flips it. Hiding the alternative costs\n * discoverability, so two things pay it back: the ± glyph and the swap arrows say \"this\n * is a control\", not a badge; and the name and tooltip spell out both the current state\n * and what a press will do.\n *\n * NOT A PRESSED/UNPRESSED TOGGLE. A {@link Chip} toggle has a FIXED label naming its\n * on-state, with `aria-pressed` carrying the state. Here the word IS the state, so it\n * is an action button with no `aria-pressed`: \"Direction: Inflow, not pressed\" would\n * name no on-state for \"not pressed\" to be the absence of. The name changing under\n * focus is the echo.\n *\n * THE COLOUR is the money pairing — the chip's `expense` / `income` tones, the same\n * `--money-expense` / `--money-income` that {@link AmountInput}'s `tone` paints the\n * figure with (dev#434): only the text and the border carry the tint, on a neutral\n * surface, because a filled pill next to a tinted figure reads as one smear.\n *\n * SIZE. `lg` on a phone — the 44px touch target beside a display figure that has no\n * field height to match — and from `md` up exactly a labelled field's 42px, so it never\n * stands taller than the fields in its grid row (#431).\n *\n * An exported chip rather than an `AmountInput signControl` slot: the slot would have\n * to own the row the two share, and every form lays that row out differently (beside\n * the figure on a phone, in a grid column on a desktop). The chip is the part that was\n * being re-written; the row stays the form's.\n */\nexport function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }: SignChipProps) {\n const labels = useKitLabels(\"signChip\", DEFAULT_SIGN_CHIP_LABELS, labelsProp);\n const current = negative ? labels.outflow : labels.inflow;\n const next = negative ? labels.inflow : labels.outflow;\n const name = labels.direction(current, next);\n return (\n <Tooltip label={name} lazy>\n <Chip\n size=\"lg\"\n tone={negative ? \"expense\" : \"income\"}\n icon={negative ? Minus : Plus}\n onClick={() => onNegativeChange(!negative)}\n disabled={disabled}\n aria-label={name}\n // `gap-1.5 px-3`: the chip's narrower body, because on a 375px screen every\n // pixel of it is taken from the figure beside it (#430). `md:py-2.5` with the\n // hairline border is the 42px a labelled field measures (#431).\n className={cn(\"shrink-0 gap-1.5 whitespace-nowrap px-3 font-medium md:min-h-0 md:py-2.5\", className)}\n >\n {current}\n {/* The swap arrows after the word, in the label's own row: decoration (the\n name already says what a press does), set a little apart from the word. */}\n <ArrowUpDown className=\"ms-1.5 inline size-3.5 align-[-0.2em] opacity-60\" aria-hidden />\n </Chip>\n </Tooltip>\n );\n}\n"],"mappings":";AAmFM,SAeE,KAfF;AAnFN,SAAS,aAAa,OAAO,YAAY;AACzC,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAiBtB,MAAM,2BAA2C;AAAA,EACtD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW,CAAC,SAAS,SAAS,cAAc,OAAO,qBAAgB,IAAI;AACzE;AAmDO,SAAS,SAAS,EAAE,UAAU,kBAAkB,UAAU,QAAQ,YAAY,UAAU,GAAkB;AAC/G,QAAM,SAAS,aAAa,YAAY,0BAA0B,UAAU;AAC5E,QAAM,UAAU,WAAW,OAAO,UAAU,OAAO;AACnD,QAAM,OAAO,WAAW,OAAO,SAAS,OAAO;AAC/C,QAAM,OAAO,OAAO,UAAU,SAAS,IAAI;AAC3C,SACE,oBAAC,WAAQ,OAAO,MAAM,MAAI,MACxB;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAM,WAAW,YAAY;AAAA,MAC7B,MAAM,WAAW,QAAQ;AAAA,MACzB,SAAS,MAAM,iBAAiB,CAAC,QAAQ;AAAA,MACzC;AAAA,MACA,cAAY;AAAA,MAIZ,WAAW,GAAG,4EAA4E,SAAS;AAAA,MAElG;AAAA;AAAA,QAGD,oBAAC,eAAY,WAAU,oDAAmD,eAAW,MAAC;AAAA;AAAA;AAAA,EACxF,GACF;AAEJ;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ComponentPropsWithoutRef } from 'react';
2
+ import { ReactNode, ComponentPropsWithoutRef } from 'react';
3
3
  import { TileSize } from './tile-radio.js';
4
4
 
5
5
  interface SwatchPickerLabels {
@@ -27,7 +27,23 @@ interface SwatchOption<T extends string> {
27
27
  /** A remark read after the name and shown under it in the bubble ("used by
28
28
  * Groceries"). The tile wears a dot while it has one. */
29
29
  note?: string;
30
+ /** Out of the choice and out of reach — skipped by the arrow keys and Tab. With a
31
+ * `disabledReason` the tile stays reachable instead and says why. */
30
32
  disabled?: boolean;
33
+ /**
34
+ * Why THIS colour cannot be chosen right now — the whole picker's `disabledReason`,
35
+ * one tile at a time (the kit's later list after keksdose K3: a colour another
36
+ * category already wears, a flag only an owner may set). {@link Button}'s rule: a tile
37
+ * that is merely `disabled` is a dead square a keyboard never lands on, so nobody
38
+ * learns why it is dimmed.
39
+ *
40
+ * The tile looks disabled but is `aria-disabled`, not `disabled`: Tab and the arrow
41
+ * keys still reach it, its bubble shows the reason under its name, and the reason
42
+ * describes it. Choosing it — a click, Space or Enter, or an arrow under automatic
43
+ * activation — does nothing: the focus moves onto it and the checked colour stays
44
+ * where it was, as under the whole-picker lock. Wins over `disabled`.
45
+ */
46
+ disabledReason?: ReactNode;
31
47
  }
32
48
  /**
33
49
  * `onChange` is the picker's own — the chosen VALUE, not a DOM event — so the div's
@@ -62,6 +78,31 @@ interface SwatchPickerProps<T extends string> extends Omit<ComponentPropsWithout
62
78
  tileClassName?: string;
63
79
  disabled?: boolean;
64
80
  labels?: Partial<SwatchPickerLabels>;
81
+ /**
82
+ * An 11px static label in a strip over the swatches, named by it (keksdose K2): the
83
+ * transaction row's flag picker stands between labelled fields and hand-builds this
84
+ * (`FieldLabel` + `pt-5`, transaction-flags.tsx:155) because the picker had no label.
85
+ * The label sits on the same line as a field's own (`top-1`, `inset-x-3`); the
86
+ * swatches start 20px down, below its whole line box — see `LABEL_STRIP_PAD`. With a
87
+ * label, `className` styles the wrapper, as on a labelled {@link ToggleGroup}; an
88
+ * explicit `aria-label` / `aria-labelledby` still names the group instead.
89
+ */
90
+ label?: ReactNode;
91
+ /** A {@link FieldHint} beside the label. Only with `label`. */
92
+ hint?: ReactNode;
93
+ /**
94
+ * Why the colour cannot be changed right now — {@link Button}'s `disabledReason`, for
95
+ * a picker that saves itself on change (keksdose K3, the flag picker in a row a
96
+ * read-only viewer may see). The swatches stay reachable and the arrow keys still
97
+ * walk them, but nothing reaches `onChange`; the reason is shown in the kit
98
+ * {@link Tooltip} under the row (each swatch's own name bubble sits above it) and
99
+ * describes the group. Wins over `disabled`.
100
+ */
101
+ disabledReason?: ReactNode;
102
+ /** This picker COMMITS — choosing a colour saves it. Under a locked
103
+ * {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
104
+ * reason, as {@link Button}'s `commit` does. */
105
+ commit?: boolean;
65
106
  }
66
107
  /**
67
108
  * A row of colour swatches that is one radio group.
@@ -75,6 +116,6 @@ interface SwatchPickerProps<T extends string> extends Omit<ComponentPropsWithout
75
116
  * choose as they go; Home and End jump to the ends. Left and right follow the page's
76
117
  * direction.
77
118
  */
78
- declare function SwatchPicker<T extends string>({ options, value, onChange, allowNone, mixed, activation, size, tileClassName, disabled, labels, className, ...rest }: SwatchPickerProps<T>): react.JSX.Element;
119
+ declare function SwatchPicker<T extends string>({ options, value, onChange, allowNone, mixed, activation, size, tileClassName, disabled, labels, label, hint, disabledReason: ownDisabledReason, commit, className, ...rest }: SwatchPickerProps<T>): react.JSX.Element;
79
120
 
80
121
  export { DEFAULT_SWATCH_PICKER_LABELS, type SwatchOption, SwatchPicker, type SwatchPickerLabels, type SwatchPickerProps };
@@ -1,9 +1,12 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useId, useMemo } from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { useKitLabels } from "../i18n/kit-labels.js";
6
6
  import { TILE_SIZE, TileRadioGroup } from "./tile-radio.js";
7
+ import { Tooltip } from "./tooltip.js";
8
+ import { useCommitReason } from "./write-lock.js";
9
+ import { hasContent, LabelStrip } from "./field-parts.js";
7
10
  const DEFAULT_SWATCH_PICKER_LABELS = {
8
11
  none: "No colour",
9
12
  mixed: "Mixed: the selected items have different colours"
@@ -19,56 +22,101 @@ function SwatchPicker({
19
22
  tileClassName,
20
23
  disabled = false,
21
24
  labels,
25
+ label,
26
+ hint,
27
+ disabledReason: ownDisabledReason,
28
+ commit,
22
29
  className,
23
30
  ...rest
24
31
  }) {
25
32
  const text = useKitLabels("swatchPicker", DEFAULT_SWATCH_PICKER_LABELS, labels);
26
33
  const mixedId = useId();
34
+ const labelId = useId();
35
+ const reasonId = useId();
36
+ const disabledReason = useCommitReason(commit, ownDisabledReason);
37
+ const locked = hasContent(disabledReason);
38
+ const labelled = hasContent(label);
27
39
  const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);
28
40
  const items = [
29
41
  ...allowNone ? [{ key: null, label: text.none }] : [],
30
- ...options.map((o) => ({ key: o.value, label: o.label, note: o.note, disabled: o.disabled }))
42
+ ...options.map((o) => ({
43
+ key: o.value,
44
+ label: o.label,
45
+ note: o.note,
46
+ disabled: o.disabled,
47
+ disabledReason: o.disabledReason
48
+ }))
31
49
  ];
32
- return /* @__PURE__ */ jsxs(
50
+ const tiles = /* @__PURE__ */ jsx(
51
+ TileRadioGroup,
52
+ {
53
+ items,
54
+ checked: mixed ? void 0 : value,
55
+ onSelect: (next) => {
56
+ if (!locked && !hasContent(next === null ? void 0 : byValue.get(next)?.disabledReason)) onChange(next);
57
+ },
58
+ activation,
59
+ disabled: disabled && !locked,
60
+ size,
61
+ tileClassName: tileClassName || locked ? (_item, selected) => cn(
62
+ // The disabled tile's look, without its `disabled`: dimmed, a
63
+ // not-allowed cursor, and no hover offer on a tile that cannot be
64
+ // chosen. Not on the selected tile, whose frame IS its state.
65
+ locked && "cursor-not-allowed opacity-50",
66
+ locked && !selected && "hover:border-[var(--border)] hover:bg-[var(--bg-surface)]",
67
+ tileClassName
68
+ ) : void 0,
69
+ renderTile: (item) => {
70
+ const opt = item.key === null ? void 0 : byValue.get(item.key);
71
+ return /* @__PURE__ */ jsx(
72
+ "span",
73
+ {
74
+ "aria-hidden": true,
75
+ className: cn(
76
+ "rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15",
77
+ TILE_SIZE[size].swatch,
78
+ opt?.swatchClassName
79
+ ),
80
+ style: { background: opt?.color }
81
+ }
82
+ );
83
+ }
84
+ }
85
+ );
86
+ const group = /* @__PURE__ */ jsxs(
33
87
  "div",
34
88
  {
35
89
  ...rest,
36
90
  role: "radiogroup",
37
- "aria-disabled": disabled || void 0,
38
- "aria-describedby": [rest["aria-describedby"], mixed && mixedId].filter(Boolean).join(" ") || void 0,
39
- className: cn("flex flex-wrap items-center gap-1.5", className),
91
+ "aria-labelledby": labelled && rest["aria-label"] === void 0 && rest["aria-labelledby"] === void 0 ? labelId : rest["aria-labelledby"],
92
+ "aria-disabled": disabled || locked || void 0,
93
+ "aria-describedby": [rest["aria-describedby"], mixed && mixedId, locked && reasonId].filter(Boolean).join(" ") || void 0,
94
+ className: cn("flex flex-wrap items-center gap-1.5", !labelled && className),
40
95
  children: [
41
- /* @__PURE__ */ jsx(
42
- TileRadioGroup,
43
- {
44
- items,
45
- checked: mixed ? void 0 : value,
46
- onSelect: onChange,
47
- activation,
48
- disabled,
49
- size,
50
- tileClassName: tileClassName ? () => tileClassName : void 0,
51
- renderTile: (item) => {
52
- const opt = item.key === null ? void 0 : byValue.get(item.key);
53
- return /* @__PURE__ */ jsx(
54
- "span",
55
- {
56
- "aria-hidden": true,
57
- className: cn(
58
- "rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15",
59
- TILE_SIZE[size].swatch,
60
- opt?.swatchClassName
61
- ),
62
- style: { background: opt?.color }
63
- }
64
- );
96
+ locked ? (
97
+ // Inside the group, round the tiles — not round the group, whose box is the
98
+ // caller's row item. This row inherits the group's gap, wrap and alignment, so
99
+ // the tiles sit exactly where they sit unlocked. Below the row: each tile's own
100
+ // bubble (its colour's name) is above it.
101
+ /* @__PURE__ */ jsx(
102
+ Tooltip,
103
+ {
104
+ label: disabledReason,
105
+ side: "bottom",
106
+ className: "flex min-w-0 flex-1 gap-[inherit] [align-items:inherit] [flex-wrap:inherit] [justify-content:inherit]",
107
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
108
+ tiles,
109
+ /* @__PURE__ */ jsx("span", { id: reasonId, hidden: true, children: disabledReason })
110
+ ] })
65
111
  }
66
- }
67
- ),
112
+ )
113
+ ) : tiles,
68
114
  mixed && /* @__PURE__ */ jsx("span", { id: mixedId, hidden: true, children: text.mixed })
69
115
  ]
70
116
  }
71
117
  );
118
+ if (!labelled) return group;
119
+ return /* @__PURE__ */ jsx(LabelStrip, { labelId, label, hint, disabled: disabled || locked, pad: "clear", className, children: group });
72
120
  }
73
121
  export {
74
122
  DEFAULT_SWATCH_PICKER_LABELS,