@eifi1/ui-kit 0.34.0 → 0.35.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 (241) hide show
  1. package/README.md +82 -38
  2. package/dist/billing/billing-banner.d.ts +5 -2
  3. package/dist/billing/billing-banner.js.map +1 -1
  4. package/dist/billing/plan-limit-notice.d.ts +5 -2
  5. package/dist/billing/plan-limit-notice.js.map +1 -1
  6. package/dist/chart.js.map +1 -1
  7. package/dist/components/amount-input.d.ts +0 -3
  8. package/dist/components/amount-input.js +2 -2
  9. package/dist/components/amount-input.js.map +1 -1
  10. package/dist/components/calculator.d.ts +1 -4
  11. package/dist/components/calculator.js +1 -3
  12. package/dist/components/calculator.js.map +1 -1
  13. package/dist/components/chart-zoom.d.ts +0 -37
  14. package/dist/components/chart-zoom.js +1 -28
  15. package/dist/components/chart-zoom.js.map +1 -1
  16. package/dist/components/chip-input.d.ts +0 -4
  17. package/dist/components/chip-input.js +1 -5
  18. package/dist/components/chip-input.js.map +1 -1
  19. package/dist/components/combobox.d.ts +1 -1
  20. package/dist/components/combobox.js.map +1 -1
  21. package/dist/components/copy-button.d.ts +5 -1
  22. package/dist/components/copy-button.js.map +1 -1
  23. package/dist/components/currency-option-list.d.ts +3 -8
  24. package/dist/components/currency-option-list.js +1 -29
  25. package/dist/components/currency-option-list.js.map +1 -1
  26. package/dist/components/danger-confirm.d.ts +3 -3
  27. package/dist/components/danger-confirm.js.map +1 -1
  28. package/dist/components/data-table-filter-popover.d.ts +1 -1
  29. package/dist/components/data-table-filter-popover.js +1 -1
  30. package/dist/components/data-table-filter-popover.js.map +1 -1
  31. package/dist/components/data-table-labels.d.ts +1 -42
  32. package/dist/components/data-table-labels.js +2 -31
  33. package/dist/components/data-table-labels.js.map +1 -1
  34. package/dist/components/data-table-shared.d.ts +17 -1
  35. package/dist/components/data-table-shared.js +22 -0
  36. package/dist/components/data-table-shared.js.map +1 -1
  37. package/dist/components/data-table.d.ts +12 -2
  38. package/dist/components/data-table.js +18 -14
  39. package/dist/components/data-table.js.map +1 -1
  40. package/dist/components/dialog-frame.d.ts +3 -1
  41. package/dist/components/dialog-frame.js.map +1 -1
  42. package/dist/components/dropdown.d.ts +18 -15
  43. package/dist/components/dropdown.js +11 -3
  44. package/dist/components/dropdown.js.map +1 -1
  45. package/dist/components/entity-combobox.d.ts +1 -1
  46. package/dist/components/entity-combobox.js.map +1 -1
  47. package/dist/components/field-base.d.ts +0 -5
  48. package/dist/components/field-base.js +0 -5
  49. package/dist/components/field-base.js.map +1 -1
  50. package/dist/components/field-parts.d.ts +3 -11
  51. package/dist/components/field-parts.js +1 -9
  52. package/dist/components/field-parts.js.map +1 -1
  53. package/dist/components/field-strip.d.ts +8 -23
  54. package/dist/components/field-strip.js +24 -22
  55. package/dist/components/field-strip.js.map +1 -1
  56. package/dist/components/field-sync.d.ts +0 -4
  57. package/dist/components/field-sync.js +0 -4
  58. package/dist/components/field-sync.js.map +1 -1
  59. package/dist/components/field.d.ts +4 -4
  60. package/dist/components/field.js.map +1 -1
  61. package/dist/components/full-bleed-dialog.js +2 -2
  62. package/dist/components/full-bleed-dialog.js.map +1 -1
  63. package/dist/components/hover-menu.d.ts +6 -13
  64. package/dist/components/hover-menu.js +3 -3
  65. package/dist/components/hover-menu.js.map +1 -1
  66. package/dist/components/inline-entity-combobox.d.ts +1 -1
  67. package/dist/components/inline-entity-combobox.js.map +1 -1
  68. package/dist/components/modal.d.ts +9 -1
  69. package/dist/components/modal.js +7 -4
  70. package/dist/components/modal.js.map +1 -1
  71. package/dist/components/multi-select.d.ts +5 -16
  72. package/dist/components/multi-select.js +5 -10
  73. package/dist/components/multi-select.js.map +1 -1
  74. package/dist/components/number-input.d.ts +0 -3
  75. package/dist/components/number-input.js +2 -4
  76. package/dist/components/number-input.js.map +1 -1
  77. package/dist/components/popover.js +2 -2
  78. package/dist/components/popover.js.map +1 -1
  79. package/dist/components/series-chart.js.map +1 -1
  80. package/dist/components/server-wake.d.ts +0 -6
  81. package/dist/components/server-wake.js.map +1 -1
  82. package/dist/components/settings-fields.d.ts +4 -2
  83. package/dist/components/settings-fields.js.map +1 -1
  84. package/dist/components/stat-tile.d.ts +8 -2
  85. package/dist/components/stat-tile.js.map +1 -1
  86. package/dist/components/swipeable-row.d.ts +2 -17
  87. package/dist/components/swipeable-row.js +11 -16
  88. package/dist/components/swipeable-row.js.map +1 -1
  89. package/dist/components/tooltip.d.ts +9 -1
  90. package/dist/components/tooltip.js +20 -5
  91. package/dist/components/tooltip.js.map +1 -1
  92. package/dist/components/translation-review.d.ts +10 -2
  93. package/dist/components/translation-review.js +11 -3
  94. package/dist/components/translation-review.js.map +1 -1
  95. package/dist/components/use-table-state.d.ts +31 -3
  96. package/dist/components/use-table-state.js +53 -8
  97. package/dist/components/use-table-state.js.map +1 -1
  98. package/dist/components/user-avatar.d.ts +2 -2
  99. package/dist/components/user-avatar.js.map +1 -1
  100. package/dist/components/wizard-stepper.d.ts +1 -4
  101. package/dist/components/wizard-stepper.js +2 -3
  102. package/dist/components/wizard-stepper.js.map +1 -1
  103. package/dist/currencies.d.ts +23 -0
  104. package/dist/currencies.js +38 -0
  105. package/dist/currencies.js.map +1 -0
  106. package/dist/demo/demo-banner.d.ts +7 -2
  107. package/dist/demo/demo-banner.js.map +1 -1
  108. package/dist/eslint/index.d.ts +83 -0
  109. package/dist/eslint/index.js +69 -0
  110. package/dist/eslint/index.js.map +1 -0
  111. package/dist/eslint/role-utilities.d.ts +48 -0
  112. package/dist/eslint/role-utilities.generated.d.ts +2 -0
  113. package/dist/eslint/role-utilities.generated.js +143 -0
  114. package/dist/eslint/role-utilities.generated.js.map +1 -0
  115. package/dist/eslint/role-utilities.js +43 -0
  116. package/dist/eslint/role-utilities.js.map +1 -0
  117. package/dist/feedback/feedback-menu.d.ts +2 -2
  118. package/dist/feedback/feedback-menu.js.map +1 -1
  119. package/dist/feedback/feedback-thread.d.ts +3 -8
  120. package/dist/feedback/feedback-thread.js +0 -2
  121. package/dist/feedback/feedback-thread.js.map +1 -1
  122. package/dist/hooks/use-dismiss.d.ts +16 -3
  123. package/dist/hooks/use-dismiss.js +14 -2
  124. package/dist/hooks/use-dismiss.js.map +1 -1
  125. package/dist/i18n/kit-labels.d.ts +14 -4
  126. package/dist/i18n/kit-labels.js.map +1 -1
  127. package/dist/i18n/locales/en.js +1 -1
  128. package/dist/i18n/locales/en.js.map +2 -2
  129. package/dist/i18n/locales/fr.js +3 -2
  130. package/dist/i18n/locales/fr.js.map +1 -1
  131. package/dist/i18n/locales/it.js +3 -2
  132. package/dist/i18n/locales/it.js.map +1 -1
  133. package/dist/index.d.ts +2 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/refs.d.ts +13 -0
  137. package/dist/lib/refs.js +15 -0
  138. package/dist/lib/refs.js.map +1 -0
  139. package/dist/rhf/fields.d.ts +10 -10
  140. package/dist/rhf/fields.js.map +1 -1
  141. package/dist/rhf/form-core.d.ts +5 -4
  142. package/dist/rhf/form-core.js.map +1 -1
  143. package/dist/rhf/form.d.ts +7 -36
  144. package/dist/rhf/form.js +1 -16
  145. package/dist/rhf/form.js.map +1 -1
  146. package/dist/rhf/line-items.d.ts +1 -1
  147. package/dist/rhf/line-items.js.map +1 -1
  148. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  149. package/dist/rhf.js.map +1 -1
  150. package/dist/shell/option-switcher-menu.d.ts +5 -17
  151. package/dist/shell/option-switcher-menu.js +1 -3
  152. package/dist/shell/option-switcher-menu.js.map +1 -1
  153. package/dist/shell/role-switcher.d.ts +1 -4
  154. package/dist/shell/role-switcher.js +2 -3
  155. package/dist/shell/role-switcher.js.map +1 -1
  156. package/dist/shell/topbar-action-menu.d.ts +7 -19
  157. package/dist/shell/topbar-action-menu.js +1 -3
  158. package/dist/shell/topbar-action-menu.js.map +1 -1
  159. package/dist/shell/topbar-controls.d.ts +3 -12
  160. package/dist/shell/topbar-controls.js +6 -9
  161. package/dist/shell/topbar-controls.js.map +1 -1
  162. package/dist/testing/index.d.ts +1 -1
  163. package/dist/testing/index.js +2 -0
  164. package/dist/testing/index.js.map +1 -1
  165. package/dist/testing/viewport.d.ts +25 -2
  166. package/dist/testing/viewport.js +61 -10
  167. package/dist/testing/viewport.js.map +1 -1
  168. package/dist/theme/palette-presets.d.ts +0 -7
  169. package/dist/theme/palette-presets.js +0 -279
  170. package/dist/theme/palette-presets.js.map +1 -1
  171. package/dist/wizard/types.d.ts +0 -5
  172. package/dist/wizard/types.js +1 -5
  173. package/dist/wizard/types.js.map +1 -1
  174. package/package.json +9 -1
  175. package/src/billing/billing-banner.tsx +5 -2
  176. package/src/billing/plan-limit-notice.tsx +5 -2
  177. package/src/chart.ts +5 -3
  178. package/src/components/amount-input.tsx +2 -6
  179. package/src/components/calculator.tsx +2 -7
  180. package/src/components/chart-zoom.tsx +4 -47
  181. package/src/components/chip-input.tsx +0 -7
  182. package/src/components/combobox.tsx +1 -1
  183. package/src/components/copy-button.tsx +5 -1
  184. package/src/components/currency-option-list.tsx +8 -42
  185. package/src/components/danger-confirm.tsx +3 -3
  186. package/src/components/data-table-filter-popover.tsx +2 -1
  187. package/src/components/data-table-labels.ts +2 -84
  188. package/src/components/data-table-shared.tsx +55 -2
  189. package/src/components/data-table.tsx +29 -17
  190. package/src/components/dialog-frame.tsx +3 -1
  191. package/src/components/dropdown.tsx +42 -18
  192. package/src/components/entity-combobox.tsx +1 -1
  193. package/src/components/field-base.tsx +0 -11
  194. package/src/components/field-parts.tsx +5 -19
  195. package/src/components/field-strip.tsx +9 -25
  196. package/src/components/field-sync.tsx +0 -7
  197. package/src/components/field.tsx +4 -4
  198. package/src/components/full-bleed-dialog.tsx +4 -3
  199. package/src/components/hover-menu.tsx +12 -16
  200. package/src/components/inline-entity-combobox.tsx +1 -1
  201. package/src/components/modal.tsx +20 -4
  202. package/src/components/multi-select.tsx +13 -30
  203. package/src/components/number-input.tsx +3 -8
  204. package/src/components/popover.tsx +5 -3
  205. package/src/components/series-chart.tsx +1 -1
  206. package/src/components/server-wake.tsx +0 -6
  207. package/src/components/settings-fields.tsx +4 -2
  208. package/src/components/stat-tile.tsx +8 -2
  209. package/src/components/swipeable-row.tsx +15 -37
  210. package/src/components/tooltip.tsx +37 -6
  211. package/src/components/translation-review.tsx +25 -7
  212. package/src/components/use-table-state.ts +91 -10
  213. package/src/components/user-avatar.tsx +2 -2
  214. package/src/components/wizard-stepper.tsx +2 -6
  215. package/src/currencies.ts +70 -0
  216. package/src/demo/demo-banner.tsx +7 -2
  217. package/src/eslint/index.ts +161 -0
  218. package/src/eslint/role-utilities.generated.ts +146 -0
  219. package/src/eslint/role-utilities.ts +99 -0
  220. package/src/feedback/feedback-menu.tsx +2 -2
  221. package/src/feedback/feedback-thread.tsx +3 -8
  222. package/src/hooks/use-dismiss.ts +39 -4
  223. package/src/i18n/kit-labels.tsx +15 -5
  224. package/src/i18n/locales/fr.ts +3 -2
  225. package/src/i18n/locales/it.ts +3 -2
  226. package/src/index.ts +3 -1
  227. package/src/lib/refs.ts +22 -0
  228. package/src/rhf/fields.tsx +10 -10
  229. package/src/rhf/form-core.tsx +5 -4
  230. package/src/rhf/form.tsx +7 -43
  231. package/src/rhf/line-items.tsx +1 -1
  232. package/src/rhf/use-rhf-wizard-step.ts +4 -4
  233. package/src/rhf.ts +3 -2
  234. package/src/shell/option-switcher-menu.tsx +5 -22
  235. package/src/shell/role-switcher.tsx +2 -6
  236. package/src/shell/topbar-action-menu.tsx +7 -24
  237. package/src/shell/topbar-controls.tsx +6 -18
  238. package/src/testing/index.ts +3 -1
  239. package/src/testing/viewport.ts +125 -19
  240. package/src/theme/palette-presets.ts +7 -293
  241. package/src/wizard/types.ts +0 -8
package/README.md CHANGED
@@ -94,6 +94,12 @@ currency picker and the language menu. It was a 5.6 MB hard dependency through 0
94
94
  installed into every consumer and imported by nothing — so it may still be in your tree
95
95
  transitively after upgrading. Depend on it explicitly if you render any of the three.
96
96
 
97
+ **Shipping only the flags you render.** `@eifi1/ui-kit/currencies` (0.34.1) is the
98
+ currency list as plain data — `CURRENCIES`, and `CURRENCY_COUNTRIES` (code → the lowercase
99
+ country flag-icons names the flag by: `CHF` → `"ch"`, `EUR` → `"eu"`). It imports nothing,
100
+ so a build script or a Vite plugin can import it in Node to generate a flag subset. Read
101
+ it there, never out of a file under `dist/`: those move between versions.
102
+
97
103
  ## Wiring
98
104
 
99
105
  ### 1. Vite — dedupe shared singletons
@@ -226,8 +232,10 @@ name with the property's own prefix dropped (`--bg-surface` → `bg-surface`,
226
232
  The names are **property-scoped**: a text role makes a `text-*` utility and nothing else,
227
233
  so `bg-muted` does not exist and a role cannot land on the wrong property by accident. An
228
234
  app's own `--color-*` names (shadcn-style) do not collide with them. Keep `[var(--…)]` for a
229
- role with no utility: the chart colours, `--status-*`, a ring offset, and a money colour
230
- that needs a variant (the money classes below are plain and take none). The kit's own
235
+ role with no utility: the chart colours and `--status-*`. The ring offsets
236
+ (`ring-offset-surface`, `ring-offset-page`) and the money colours (`text-money-income`,
237
+ `bg-money-expense` …, the table below) are utilities since 0.34 and take variants, unlike
238
+ the plain money classes further down. The kit's own
231
239
  markup follows this rule with no exceptions: `check:tokens` allows no `[var(--role)]` where
232
240
  a utility exists.
233
241
 
@@ -329,6 +337,35 @@ Theme values: `text-micro` (font size 0.625rem), `text-caption` (font size 0.687
329
337
 
330
338
  <!-- END GENERATED: styling -->
331
339
 
340
+ ### Lint: the role utilities in an app
341
+
342
+ `@eifi1/ui-kit/eslint` (0.35) is `check:tokens`' role rule for an app's own markup: an ESLint
343
+ flat-config plugin whose `role-utilities` rule reports a `[var(--role)]` class wherever that
344
+ property has a utility for the role, and names it. It reads every string literal and
345
+ template-literal part, so a JSX `className`, a `cn()` / `clsx()` argument and a class constant
346
+ alike. Where one utility fits, `eslint --fix` writes it and keeps the variants and the opacity:
347
+ `hover:text-[var(--text-muted)]/70` → `hover:text-muted/70`. A token with no utility on that
348
+ property is left alone (`text-[var(--chart-1)]`, `--status-*`, `bg-[var(--border)]` as a
349
+ divider), and so are comments. The rule reads the table above, so a role that gains a utility is
350
+ flagged from the release that adds it.
351
+
352
+ ```js
353
+ // eslint.config.js
354
+ import uiKit from "@eifi1/ui-kit/eslint";
355
+
356
+ export default defineConfig([ // or tseslint.config(…): either takes it
357
+ // … the app's own entries, which say which files are linted …
358
+ uiKit.configs.recommended,
359
+ ]);
360
+ ```
361
+
362
+ `configs.recommended` registers the plugin as `@eifi1/ui-kit` and turns
363
+ `@eifi1/ui-kit/role-utilities` on as an error for every linted file except tests
364
+ (`**/__tests__/**`, `*.test.*`, `*.spec.*`), which may name an old class to assert it is gone.
365
+ To scope it yourself, register the plugin and set the rule:
366
+ `{ files: ["src/**/*.tsx"], plugins: { "@eifi1/ui-kit": uiKit }, rules: { "@eifi1/ui-kit/role-utilities": "error" } }`.
367
+ It runs on ESLint 9 and 10 and imports no package of its own.
368
+
332
369
  ### Text size and More contrast
333
370
 
334
371
  ONE scale on `<html>`: `data-text-size="normal" | "large" | "xlarge"` is 100 / 125 / 150 %
@@ -505,25 +542,27 @@ columns, strings and permissions (see the note at the top of `src/feedback/feedb
505
542
 
506
543
  <!-- BEGIN GENERATED: exports — node scripts/gen-export-inventory.mjs -->
507
544
 
508
- **1899 names from 302 modules** — 888 values and 1011 types. _Italic_ is a type-only export.
545
+ **1878 names from 302 modules** — 868 values and 1010 types. _Italic_ is a type-only export.
509
546
 
510
547
  Generated from `dist/index.d.ts` by `node scripts/gen-export-inventory.mjs`; the count
511
548
  is pinned by `src/__tests__/public-surface.test.ts`. Do not edit between the markers.
512
549
 
513
550
  | Entry point | Names |
514
551
  |---|---|
515
- | `@eifi1/ui-kit` | 1899 |
516
- | `@eifi1/ui-kit/chart` | 105 |
552
+ | `@eifi1/ui-kit` | 1878 |
553
+ | `@eifi1/ui-kit/chart` | 92 |
517
554
  | `@eifi1/ui-kit/shell` | 77 |
518
- | `@eifi1/ui-kit/data-table` | 53 |
519
- | `@eifi1/ui-kit/wizard` | 25 |
555
+ | `@eifi1/ui-kit/data-table` | 51 |
556
+ | `@eifi1/ui-kit/wizard` | 24 |
520
557
  | `@eifi1/ui-kit/tour` | 7 |
521
- | `@eifi1/ui-kit/feedback` | 165 |
558
+ | `@eifi1/ui-kit/feedback` | 164 |
522
559
  | `@eifi1/ui-kit/search` | 23 |
523
560
  | `@eifi1/ui-kit/dates` | 22 |
524
561
  | `@eifi1/ui-kit/table-text` | 14 |
525
- | `@eifi1/ui-kit/testing` | 10 |
526
- | `@eifi1/ui-kit/rhf` | 60 |
562
+ | `@eifi1/ui-kit/currencies` | 3 |
563
+ | `@eifi1/ui-kit/eslint` | 1 |
564
+ | `@eifi1/ui-kit/testing` | 11 |
565
+ | `@eifi1/ui-kit/rhf` | 48 |
527
566
 
528
567
  Everything below is reachable from the main `@eifi1/ui-kit` barrel. The subpaths are a
529
568
  re-slicing of it, never a second API.
@@ -584,7 +623,7 @@ re-slicing of it, never a second API.
584
623
  |---|---|
585
624
  | `theme/color` | `contrast`, `deltaE`, `hexToOklch`, `luminance`, `oklchToHex`, `oklchToRgb`, `parseHex`, `rgbToOklch`, `simulateCvd`, `solveLightness`, `toHex`, _`CvdType`_, _`Oklch`_, _`Rgb`_ |
586
625
  | `theme/palette-derive` | `auditChartRamp`, `auditPalette`, `deriveChartRamp`, `derivePalette`, `describe`, _`AnchorRole`_, _`ChartRampReport`_, _`ContrastCheck`_, _`ContrastReport`_, _`DerivedPalette`_, _`DeriveOptions`_, _`PaletteAnchors`_, _`SemanticTokens`_ |
587
- | `theme/palette-presets` | `ALTERNATIVE_PRESETS`, `applyTokenSet`, `DEFAULT_PRESET`, `DERIVED_PRESETS`, `IMPRINT_PRESET`, `PALETTES`, `presetById`, _`PalettePreset`_, _`TokenSet`_ |
626
+ | `theme/palette-presets` | `applyTokenSet`, `DEFAULT_PRESET`, `DERIVED_PRESETS`, `IMPRINT_PRESET`, `PALETTES`, `presetById`, _`PalettePreset`_, _`TokenSet`_ |
588
627
  | `theme/theme-store` | `applyPersistedTheme`, `createThemeStore`, _`ThemeMode`_, _`ThemePreference`_, _`ThemeState`_, _`ThemeStore`_ |
589
628
  | `theme/palette-store` | `applyPersistedPalette`, `createPaletteStore`, _`PaletteStore`_ |
590
629
  | `theme/text-size` | `applyPersistedTextSize`, `applyTextSize`, `createTextSizeStore`, `isTextSize`, `readPersistedTextSize`, `readTextSize`, `resolveTextSize`, `TEXT_SCALE`, `TEXT_SIZE_INLINE_SCRIPT`, `TEXT_SIZES`, `useTextSize`, _`ApplyTextSizeOptions`_, _`PersistedAppearanceOptions`_, _`PersistedTextSize`_, _`TextSize`_, _`TextSizeInfo`_, _`TextSizeState`_, _`TextSizeStore`_ |
@@ -608,7 +647,7 @@ re-slicing of it, never a second API.
608
647
  | `components/button` | `Button`, _`ButtonLinkProps`_, _`ButtonProps`_ |
609
648
  | `components/button-look` | `buttonClasses`, `TOUCH_TARGET_LARGE`, _`ButtonClassesOptions`_, _`ButtonSize`_, _`ButtonTone`_, _`ButtonVariant`_ |
610
649
  | `components/icon-button` | `IconButton`, _`IconButtonGlyphSize`_, _`IconButtonLinkProps`_, _`IconButtonProps`_, _`IconButtonSize`_, _`IconButtonTone`_, _`IconButtonToneColor`_, _`IconButtonVariant`_ |
611
- | `components/field-base` | `DEFAULT_CHARACTER_COUNT_LABELS`, `DEFAULT_PASSWORD_REVEAL_LABELS`, `FIELD_BASE`, `FIELD_DISPLAY`, `FIELD_FLOATING_PAD`, `FIELD_INVALID`, `FIELD_TOUCH_TEXT`, `FIELD_TRIGGER`, `FIELD_WRITABLE_LOOK`, `FieldChevron`, `FLOATING_INPUT_CLASS`, `FLOATING_LABEL_CLASS`, `FloatingField`, `Label`, `resolvePasswordRevealLabels`, _`CharacterCountLabels`_, _`FieldChevronProps`_, _`FloatingFieldProps`_, _`LabelProps`_, _`PasswordRevealLabels`_ |
650
+ | `components/field-base` | `DEFAULT_CHARACTER_COUNT_LABELS`, `DEFAULT_PASSWORD_REVEAL_LABELS`, `FIELD_BASE`, `FIELD_DISPLAY`, `FIELD_FLOATING_PAD`, `FIELD_INVALID`, `FIELD_TOUCH_TEXT`, `FIELD_TRIGGER`, `FIELD_WRITABLE_LOOK`, `FieldChevron`, `FLOATING_INPUT_CLASS`, `FLOATING_LABEL_CLASS`, `FloatingField`, `Label`, _`CharacterCountLabels`_, _`FieldChevronProps`_, _`FloatingFieldProps`_, _`LabelProps`_, _`PasswordRevealLabels`_ |
612
651
  | `components/input` | `Input`, _`InputProps`_ |
613
652
  | `components/select` | `Select`, _`SelectProps`_ |
614
653
  | `components/textarea` | `Textarea`, _`TextareaProps`_ |
@@ -638,7 +677,7 @@ re-slicing of it, never a second API.
638
677
  | `components/tooltip` | `placeTooltip`, `Tooltip`, _`TooltipPlacement`_, _`TooltipProps`_, _`TooltipSide`_, _`TooltipSize`_, _`TooltipTap`_, _`TooltipViewport`_ |
639
678
  | `components/user-avatar` | `avatarInitials`, `UserAvatar`, _`UserAvatarBadge`_, _`UserAvatarProps`_ |
640
679
  | `components/settings-fields` | `ContrastSetting`, `LanguageSetting`, `TextSizeSetting`, `ThemeSetting`, _`ContrastSettingProps`_, _`LanguageSettingProps`_, _`TextSizeSettingProps`_, _`ThemeSettingProps`_, _`ThemeSettingSelectProps`_, _`ThemeSettingToggleProps`_ |
641
- | `components/field-sync` | `DEFAULT_FIELD_SYNC_LABELS`, `FIELD_SYNC_FRAME`, `FIELD_SYNC_SAVED_MS`, `FieldSyncIndicator`, `FieldSyncRow`, `resolveFieldSyncLabels`, `useFieldSync`, _`FieldSyncIndicatorProps`_, _`FieldSyncLabels`_, _`FieldSyncRowProps`_, _`FieldSyncState`_, _`UseFieldSyncOptions`_, _`UseFieldSyncReturn`_ |
680
+ | `components/field-sync` | `DEFAULT_FIELD_SYNC_LABELS`, `FIELD_SYNC_FRAME`, `FIELD_SYNC_SAVED_MS`, `FieldSyncIndicator`, `FieldSyncRow`, `useFieldSync`, _`FieldSyncIndicatorProps`_, _`FieldSyncLabels`_, _`FieldSyncRowProps`_, _`FieldSyncState`_, _`UseFieldSyncOptions`_, _`UseFieldSyncReturn`_ |
642
681
  | `components/month-picker` | `DEFAULT_MONTH_PICKER_LABELS`, `MonthPicker`, _`MonthPickerLabels`_, _`MonthPickerProps`_ |
643
682
  | `components/checkbox` | `Checkbox`, _`CheckboxProps`_ |
644
683
  | `components/switch` | `Switch`, _`SwitchProps`_, _`SwitchSize`_ |
@@ -667,7 +706,6 @@ re-slicing of it, never a second API.
667
706
  | `components/treemap` | `fitLabel`, `Treemap`, `TreemapCell`, _`TreemapCellProps`_, _`TreemapNode`_, _`TreemapProps`_ |
668
707
  | `components/series-chart` | `resolveTooltipPlacement`, `SeriesChart`, `seriesLegendEntries`, `StaticSeriesChart`, _`SeriesChartAxis`_, _`SeriesChartHit`_, _`SeriesChartMarker`_, _`SeriesChartPoint`_, _`SeriesChartProps`_, _`SeriesChartReference`_, _`SeriesChartRow`_, _`SeriesChartSeries`_, _`SeriesChartSpan`_, _`SeriesChartTickValues`_, _`SeriesChartTone`_, _`SeriesChartTooltip`_, _`SeriesChartTooltipPlacement`_, _`SeriesChartType`_, _`SeriesChartX`_, _`SeriesChartXTick`_, _`SeriesChartXValue`_, _`SeriesLegendAxes`_ |
669
708
  | `components/chart-height` | _`ChartHeight`_ |
670
- | `components/chart-zoom` | `axisExtent`, `DEFAULT_Y_AXIS`, `defaultZoomAxes`, `fitXToY`, `fitYToX`, `NO_ZOOM`, `selectionFromDrag`, `withChartZoom`, `ZOOM_MIN_DRAG`, `ZOOM_SQUARE_ENOUGH`, `zoomAfter`, `zoomAxesFor`, `zoomDomains` |
671
709
  | `components/chart-zoom-core` | `SharedXZoom`, _`ZoomAxes`_, _`ZoomAxesSetting`_, _`ZoomBinding`_, _`ZoomDrag`_, _`ZoomFitSeries`_, _`ZoomFitSource`_, _`ZoomRow`_, _`ZoomSelection`_, _`ZoomState`_, _`ZoomTarget`_ |
672
710
  | `components/toggle-legend` | `LegendColumn`, `LegendGroup`, `StaticLegend`, `STEP_DASH`, `STROKE_PATTERNS`, `strokeDash`, `toggleHidden`, `ToggleLegend`, _`LegendEntry`_, _`StaticLegendProps`_, _`ToggleLegendProps`_ |
673
711
  | `components/facing-pair` | `FACING_SIDES`, `facingAxes`, `facingBand`, `facingHeadingPad`, _`FacingSide`_ |
@@ -679,7 +717,7 @@ re-slicing of it, never a second API.
679
717
  | `components/toggle-group` | `ToggleGroup`, _`ToggleGroupBaseProps`_, _`ToggleGroupClearableProps`_, _`ToggleGroupProps`_, _`ToggleGroupRequiredProps`_, _`ToggleOption`_ |
680
718
  | `components/nav-pills` | `NavPills`, _`NavPillItem`_, _`NavPillLinkProps`_, _`NavPillsCurrent`_, _`NavPillsProps`_ |
681
719
  | `components/chip` | `Chip`, _`ChipHue`_, _`ChipLinkProps`_, _`ChipProps`_, _`ChipShape`_, _`ChipSize`_, _`ChipTone`_, _`ChipVariant`_ |
682
- | `components/chip-input` | `ChipInput`, `DEFAULT_CHIP_INPUT_LABELS`, `resolveChipInputLabels`, _`ChipInputLabels`_, _`ChipInputProps`_ |
720
+ | `components/chip-input` | `ChipInput`, `DEFAULT_CHIP_INPUT_LABELS`, _`ChipInputLabels`_, _`ChipInputProps`_ |
683
721
  | `components/wizard-stepper` | `WizardStepper` |
684
722
  | `components/hover-menu` | `HoverMenu`, _`HoverMenuProps`_ |
685
723
  | `components/modal` | `Modal`, `ModalCloseContext`, `useBackdropClose`, _`ModalProps`_ |
@@ -732,9 +770,9 @@ re-slicing of it, never a second API.
732
770
  | `components/country-select` | `CountrySelect`, `DEFAULT_COUNTRY_SELECT_LABELS`, _`CountrySelectLabels`_, _`CountrySelectProps`_ |
733
771
  | `components/iban-input` | `DEFAULT_IBAN_INPUT_LABELS`, `IbanInput`, _`IbanInputLabels`_, _`IbanInputProps`_ |
734
772
  | `components/phone-input` | `DEFAULT_PHONE_INPUT_LABELS`, `PhoneInput`, _`PhoneInputLabels`_, _`PhoneInputProps`_ |
735
- | `components/field-strip` | `FieldStrip`, _`FieldStripIds`_, _`FieldStripPad`_, _`FieldStripProps`_ |
773
+ | `components/field-strip` | `FieldStrip`, _`FieldStripIds`_, _`FieldStripProps`_ |
736
774
  | `components/column-mapper` | `ColumnMapper`, `ColumnRoleTable`, `DEFAULT_COLUMN_MAPPER_LABELS`, _`ColumnMapperLabels`_, _`ColumnMapperProps`_, _`ColumnRoleTableBodyProps`_, _`ColumnRoleTableProps`_, _`ColumnRoleTableRowProps`_ |
737
- | `components/data-table-labels` | `DEFAULT_DATA_TABLE_LABELS`, `missingDataTableLabels`, `resolveDataTableLabels`, _`DataTableLabels`_ |
775
+ | `components/data-table-labels` | `DEFAULT_DATA_TABLE_LABELS`, _`DataTableLabels`_ |
738
776
  | `components/data-table-sort` | `decodeSorts`, `encodeSorts`, `nextSorts`, `normalizeSorts`, _`SortCycle`_, _`SortDir`_, _`SortState`_, _`SortStepOptions`_ |
739
777
  | `components/data-table-filters` | `dateFilter`, `decodeFilterValue`, `decodeFilterValueOfType`, `defaultFilterState`, `encodeFilterValue`, `filterHref`, `isFilterActive`, `numberFilter`, `resolveFilter`, `rowMatches`, `selectFilter`, `textFilter`, _`ColumnFilter`_, _`FilterValue`_ |
740
778
  | `components/data-table-pagination` | `PAGE_SIZE_OPTIONS`, `Pagination` |
@@ -801,7 +839,7 @@ re-slicing of it, never a second API.
801
839
  | `feedback/feedback-attachment` | `DEFAULT_ATTACHMENT_ACCEPT`, `DEFAULT_FEEDBACK_ATTACHMENT_LABELS`, `DEFAULT_MAX_ATTACHMENT_BYTES`, `DEFAULT_MAX_ATTACHMENTS`, `FeedbackAttachmentField`, `pastedName`, _`FeedbackAttachmentError`_, _`FeedbackAttachmentErrorInfo`_, _`FeedbackAttachmentFieldLabels`_, _`FeedbackAttachmentFieldMultipleProps`_, _`FeedbackAttachmentFieldProps`_, _`FeedbackAttachmentFieldRefsProps`_, _`FeedbackAttachmentFieldSingleProps`_, _`FeedbackAttachmentRef`_, _`FeedbackAttachmentRefsError`_ |
802
840
  | `feedback/feedback-dialog` | `DEFAULT_FEEDBACK_DIALOG_LABELS`, `FeedbackDialog`, _`FeedbackAttachmentLabels`_, _`FeedbackCategoryOption`_, _`FeedbackDialogLabels`_, _`FeedbackDialogMultipleProps`_, _`FeedbackDialogProps`_, _`FeedbackDialogSingleProps`_, _`FeedbackDialogTextLabels`_, _`FeedbackMultipleSubmission`_, _`FeedbackSubmission`_, _`FeedbackSubmitHint`_ |
803
841
  | `feedback/feedback-inbox` | `FeedbackCategoryBadge`, `FeedbackDetail`, `FeedbackDetailSection`, `FeedbackNoteEditor`, `FeedbackProse`, `FeedbackStatusBadge`, `FeedbackStatusTransitions`, _`FeedbackNoteAttachment`_ |
804
- | `feedback/feedback-thread` | `ChatComposer`, `DEFAULT_FEEDBACK_COMPOSER_LABELS`, `DEFAULT_FEEDBACK_THREAD_LABELS`, `FeedbackComposer`, `FeedbackThread`, _`ChatComposerHandle`_, _`ChatComposerLabels`_, _`ChatComposerProps`_, _`ChatComposerSlotContext`_, _`FeedbackComposerAttachment`_, _`FeedbackComposerHandle`_, _`FeedbackComposerLabels`_, _`FeedbackComposerProps`_, _`FeedbackComposerSlotContext`_, _`FeedbackThreadAttachment`_, _`FeedbackThreadEvent`_, _`FeedbackThreadItem`_, _`FeedbackThreadLabels`_, _`FeedbackThreadMessage`_, _`FeedbackThreadProps`_ |
842
+ | `feedback/feedback-thread` | `ChatComposer`, `DEFAULT_FEEDBACK_COMPOSER_LABELS`, `DEFAULT_FEEDBACK_THREAD_LABELS`, `FeedbackThread`, _`ChatComposerHandle`_, _`ChatComposerLabels`_, _`ChatComposerProps`_, _`ChatComposerSlotContext`_, _`FeedbackComposerAttachment`_, _`FeedbackComposerHandle`_, _`FeedbackComposerLabels`_, _`FeedbackComposerProps`_, _`FeedbackComposerSlotContext`_, _`FeedbackThreadAttachment`_, _`FeedbackThreadEvent`_, _`FeedbackThreadItem`_, _`FeedbackThreadLabels`_, _`FeedbackThreadMessage`_, _`FeedbackThreadProps`_ |
805
843
  | `feedback/feedback-record` | `appendRework`, `attachmentName`, `FEEDBACK_ATTACHMENT_ACCEPT`, `FEEDBACK_AUTHOR_EDITABLE_STATUSES`, `FEEDBACK_AWAITING_STATUSES`, `FEEDBACK_PICKABLE_CATEGORIES`, `FEEDBACK_REWORKABLE_STATUSES`, `feedbackAttachmentLine`, `isImageAttachment`, `replaceDescription`, `reworkCount`, `splitBodyAttachments`, `splitDescription`, _`FeedbackContext`_, _`FeedbackEnvironment`_, _`FeedbackRecord`_ |
806
844
  | `feedback/feedback-labels` | `DEFAULT_FEEDBACK_CATEGORY_LABELS`, `DEFAULT_FEEDBACK_STATUS_LABELS`, `DEFAULT_FEEDBACK_TOAST_LABELS`, `useFeedbackCategoryLabels`, `useFeedbackStatusLabels`, `useFeedbackToastLabels`, _`FeedbackCategoryLabels`_, _`FeedbackStatusLabels`_, _`FeedbackToastLabels`_ |
807
845
  | `feedback/feedback-status-undo` | `FEEDBACK_UNDO_DURATION`, `useFeedbackStatusUndo`, _`FeedbackStatusChange`_, _`FeedbackStatusMutate`_, _`FeedbackStatusPatch`_, _`FeedbackStatusRow`_, _`FeedbackStatusUndoOptions`_ |
@@ -819,7 +857,7 @@ re-slicing of it, never a second API.
819
857
 
820
858
  | Module | Exports |
821
859
  |---|---|
822
- | `wizard/types` | `DEFAULT_WIZARD_LABELS`, `resolveWizardLabels`, _`FieldErrors`_, _`StepStatus`_, _`SummaryItem`_, _`SummarySection`_, _`UseWizardOptions`_, _`UseWizardReturn`_, _`ValidateResult`_, _`WizardLabels`_, _`WizardStepConfig`_, _`WizardUrlSync`_ |
860
+ | `wizard/types` | `DEFAULT_WIZARD_LABELS`, _`FieldErrors`_, _`StepStatus`_, _`SummaryItem`_, _`SummarySection`_, _`UseWizardOptions`_, _`UseWizardReturn`_, _`ValidateResult`_, _`WizardLabels`_, _`WizardStepConfig`_, _`WizardUrlSync`_ |
823
861
  | `wizard/use-wizard` | `useWizard` |
824
862
  | `wizard/wizard-context` | `useOptionalWizardContext`, `useWizardContext`, `useWizardNextGate`, `useWizardStepValidate`, `WizardContextProvider`, _`WizardContextValue`_ |
825
863
  | `wizard/validation` | `requiredFieldsValidator`, _`RequiredFieldSpec`_ |
@@ -915,6 +953,12 @@ re-slicing of it, never a second API.
915
953
  | `demo/demo-banner` | `DemoBanner`, _`DemoBannerProps`_ |
916
954
  | `demo/demo-ended` | `DemoEnded`, _`DemoEndedProps`_ |
917
955
 
956
+ ### root
957
+
958
+ | Module | Exports |
959
+ |---|---|
960
+ | `currencies` | `CURRENCY_COUNTRIES` |
961
+
918
962
  <!-- END GENERATED: exports -->
919
963
 
920
964
  ## Forms / refs
@@ -926,36 +970,36 @@ element that takes focus: the `<input>` of `Input`, `NumberField`, `AmountInput`
926
970
  on its checked option. So they work directly with react-hook-form:
927
971
  `<Input {...field} />` (from `Controller`/`register`) attaches RHF's ref, giving
928
972
  focus-and-scroll-to-error for free. A component that spreads its props onto an element
929
- (`Card`, `EmptyState`, `PageHeader`, …) takes that element's `ref` too. For the full
930
- `FormField`/`FormControl` scaffolding, see `@eifi1/ui-kit/rhf` below.
973
+ (`Card`, `EmptyState`, `PageHeader`, …) takes that element's `ref` too. For fields bound
974
+ to a react-hook-form form in one line each, see `@eifi1/ui-kit/rhf` below.
931
975
 
932
976
  ### react-hook-form — `@eifi1/ui-kit/rhf` (optional)
933
977
 
934
- A thin adapter in shadcn's `form.tsx` shape, and the only entry that needs
935
- `react-hook-form` (an **optional** peer, `^7.55.0`). The main barrel does not import it,
936
- so apps without a form library install nothing.
978
+ The react-hook-form adapter: `Form`, the bound fields (`RhfTextField`, `RhfNumberField`,
979
+ `RhfMoneyField`, `RhfDateField`, `RhfSelect`, `RhfCombobox`, `RhfCheckbox`, …) and
980
+ `RhfField` for a control of your own, and the only entry that needs `react-hook-form` (an
981
+ **optional** peer, `^7.55.0`). The main barrel does not import it, so apps without a form
982
+ library install nothing.
937
983
 
938
984
  ```tsx
939
- import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@eifi1/ui-kit/rhf";
985
+ import { Form, RhfField, RhfNumberField, RhfTextField } from "@eifi1/ui-kit/rhf";
940
986
 
941
987
  <Form {...form}>
942
- <FormField control={form.control} name="amount" render={({ field }) => (
943
- <FormItem>
944
- <FormLabel required>Amount</FormLabel>
945
- <FormControl>
946
- <NumberField value={field.value} onCommit={field.onChange} />
947
- </FormControl>
948
- <FormMessage />
949
- </FormItem>
988
+ <RhfTextField name="title" label="Title" required rules={{ required: "Give it a title." }} />
989
+ <RhfNumberField name="amount" label="Amount" unit="EUR" hint="Net of VAT." />
990
+ <RhfField name="street" label="Street" render={({ field, invalid }) => (
991
+ <AddressAutocomplete {...field} invalid={invalid} />
950
992
  )} />
951
993
  </Form>
952
994
  ```
953
995
 
954
- `FormControl` gives its one child `id`, `aria-describedby` (only the description and
955
- message actually rendered, merged after the child's own) and `aria-invalid` — which every
956
- kit field now also PAINTS from, so no extra `invalid` prop is needed. `FormMessage` shows
957
- the form's own message; the kit adds no text. `useFormField()` exposes the ids and field
958
- state for custom parts.
996
+ Each bound field is a label, the control, a hint and the form's message wired to each
997
+ other: the control's `aria-describedby` names only the hint and message actually rendered
998
+ (merged after the control's own), `aria-invalid` and `invalid` come from the field state,
999
+ and `field.ref` reaches the focusable element, so a failed submit focuses the first field
1000
+ in error. The message is the form's own; the kit adds no text. The shadcn scaffold the
1001
+ adapter started as (`FormField`, `FormItem`, `FormLabel`, `FormControl`,
1002
+ `FormDescription`, `FormMessage`, `useFormField`) left the public surface in 0.35.
959
1003
 
960
1004
  ### Pasted and imported tables — `@eifi1/ui-kit/table-text`
961
1005
 
@@ -1,4 +1,4 @@
1
- import type { ComponentPropsWithoutRef } from "react";
1
+ import type { ComponentPropsWithRef } from "react";
2
2
  import type { LabelOverride } from "../i18n/kit-labels.js";
3
3
  import type { AlertSize, AlertTone } from "../components/alert-banner.js";
4
4
  import type { BillingLabels } from "./billing-labels.js";
@@ -15,7 +15,10 @@ import type { BillingLabels } from "./billing-labels.js";
15
15
  * - `processing` — back from checkout before the webhook landed (§12.21).
16
16
  */
17
17
  export type BillingBannerKind = "trial-ending" | "grant-ending" | "payment-failed" | "plan-ended" | "guest" | "processing";
18
- interface BillingBannerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "onClick"> {
18
+ /** A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2): they
19
+ * go on to the {@link AlertBanner}, so the ref is the banner's `<div>` — with an × too,
20
+ * since the banner has no `href` or `onClick` that would make its row a link or a button. */
21
+ interface BillingBannerBaseProps extends Omit<ComponentPropsWithRef<"div">, "children" | "onClick"> {
19
22
  /**
20
23
  * Over the kind's tone: `trial-ending` and `grant-ending` are `info` until the last
21
24
  * three days and `warning` in them; `payment-failed` is `danger` (money did not
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/billing/billing-banner.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { CreditCard, Hourglass, PencilOff } from \"lucide-react\";\n\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport type { AlertSize, AlertTone } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Spinner } from \"../components/spinner\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\n\n/**\n * The billing banners (docs/billing-harmonization.md §3.3, §7, §12.6, §12.21):\n *\n * - `trial-ending` — from 7 days before `trial_ends_at` (§3.3);\n * - `grant-ending` — from 7 days before `comped_until`: a beta user's 12 months, an\n * operator's grant;\n * - `payment-failed` — from the first failed payment (`past_due`), while the provider\n * retries;\n * - `plan-ended` — out of good standing: read-only, never locked (§2.3);\n * - `guest` — a guest in someone else's read-only item (§12.6);\n * - `processing` — back from checkout before the webhook landed (§12.21).\n */\nexport type BillingBannerKind =\n | \"trial-ending\"\n | \"grant-ending\"\n | \"payment-failed\"\n | \"plan-ended\"\n | \"guest\"\n | \"processing\";\n\ninterface BillingBannerBaseProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\" | \"onClick\"> {\n /**\n * Over the kind's tone: `trial-ending` and `grant-ending` are `info` until the last\n * three days and `warning` in them; `payment-failed` is `danger` (money did not\n * arrive — kastlan's past-due strip was red); `plan-ended` is `warning`, a state with\n * everything still there rather than an error; `guest` and `processing` are `info`.\n */\n tone?: AlertTone;\n /** `strip` (default): the band under the top bar, on every page. `box`: the same\n * message on the subscription page itself. */\n variant?: \"strip\" | \"box\";\n /** Default `sm` for a strip (the demo's banner), `md` for a box. */\n size?: AlertSize;\n /**\n * Where the action goes — the subscription page, say. The app's action, as an `href`\n * or as {@link onAction}; without either there is no button. Never a write-locked\n * commit: billing itself always works (§3.3).\n */\n actionHref?: string;\n /** What the action does — the app's portal call (`POST /billing/portal`, then off to\n * its `url`): for `payment-failed` with `{target: \"payment_method\"}`, the portal's\n * page for the card (§14.5); for `processing`, `useCheckoutProcessing`'s\n * `checkAgain`. The kit sends no request. */\n onAction?: () => void;\n /**\n * Over the kind's action words: \"Choose a plan\" (trial, grant, plan ended), \"Update\n * payment method\" (payment failed), \"Check again\" (processing). The guest banner has\n * none of its own: an action there needs this.\n */\n actionLabel?: string;\n /** The action is under way: the button's spinner, no second press. */\n actionPending?: boolean;\n /** An × that calls this; the app removes the banner and remembers that it did. */\n onDismiss?: () => void;\n dismissLabel?: string;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport type BillingBannerProps = BillingBannerBaseProps &\n (\n | {\n kind: \"trial-ending\" | \"grant-ending\";\n /** Calendar days left — `billingDaysLeft(trial_ends_at)`. 0 is today. */\n daysLeft: number;\n item?: never;\n }\n | {\n kind: \"guest\";\n /** The item's own name (\"Household 2026\"), or nothing. NEVER the owner's status:\n * the banner says \"for now\" and nothing more (§12.6). */\n item?: string;\n daysLeft?: never;\n }\n | {\n kind: \"payment-failed\" | \"plan-ended\" | \"processing\";\n daysLeft?: never;\n item?: never;\n }\n );\n\n/** The last days, in which an ending trial or grant turns from news into a warning. */\nconst LAST_DAYS = 3;\n\nfunction defaultTone(kind: BillingBannerKind, daysLeft: number | undefined): AlertTone {\n switch (kind) {\n case \"trial-ending\":\n case \"grant-ending\":\n return (daysLeft ?? 0) > LAST_DAYS ? \"info\" : \"warning\";\n case \"payment-failed\":\n return \"danger\";\n case \"plan-ended\":\n return \"warning\";\n default:\n return \"info\";\n }\n}\n\n/**\n * One of the billing banners, as a kit `AlertBanner` strip (§7): the kind's sentence, its\n * glyph and tone, and the app's action. Carries `data-billing-banner` with the kind.\n *\n * The app decides WHICH banner shows, from `GET /billing/overview` (and hides all of them\n * while billing is off, §4); the kit only says it. A guest's banner takes no status at\n * all — the type does not let one in — so the owner's payment state cannot leak into it\n * by a wrong prop (§12.6).\n *\n * `processing` is a live region (`role=\"status\"`): it appears when the person comes back\n * from checkout and goes when the overview catches up (§12.21), and a screen reader\n * should hear both. The others are page furniture, role-less as the strip is.\n */\nexport function BillingBanner(props: BillingBannerProps) {\n const {\n kind,\n daysLeft,\n item,\n tone,\n variant = \"strip\",\n size,\n actionHref,\n onAction,\n actionLabel,\n actionPending,\n onDismiss,\n dismissLabel,\n labels: labelsProp,\n role,\n ...rest\n } = props;\n const labels = useBillingLabels(labelsProp);\n const days = Math.max(0, Math.floor(daysLeft ?? 0));\n\n let text: string;\n let icon: ReactNode;\n let ownAction: string | undefined;\n switch (kind) {\n case \"trial-ending\":\n text = labels.trialEnding(days);\n icon = <Hourglass />;\n ownAction = labels.choosePlan;\n break;\n case \"grant-ending\":\n text = labels.grantEnding(days);\n icon = <Hourglass />;\n ownAction = labels.choosePlan;\n break;\n case \"payment-failed\":\n text = labels.paymentFailed;\n icon = <CreditCard />;\n ownAction = labels.updatePayment;\n break;\n case \"plan-ended\":\n text = labels.planEnded;\n icon = <PencilOff />;\n ownAction = labels.choosePlan;\n break;\n case \"guest\":\n text = labels.guestReadOnly(item);\n icon = <PencilOff />;\n ownAction = undefined;\n break;\n case \"processing\":\n text = labels.processing;\n icon = <Spinner label={null} />;\n ownAction = labels.checkAgain;\n break;\n }\n\n const words = actionLabel ?? ownAction;\n const action =\n words === undefined || (actionHref === undefined && onAction === undefined) ? undefined : actionHref !== undefined ? (\n <Button href={actionHref} variant=\"secondary\" size=\"sm\">\n {words}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={onAction} pending={actionPending}>\n {words}\n </Button>\n );\n\n return (\n <AlertBanner\n {...rest}\n role={role ?? (kind === \"processing\" ? \"status\" : undefined)}\n variant={variant}\n size={size ?? (variant === \"strip\" ? \"sm\" : \"md\")}\n tone={tone ?? defaultTone(kind, days)}\n icon={icon}\n action={action}\n onDismiss={onDismiss}\n dismissLabel={dismissLabel}\n data-billing-banner={kind}\n >\n {text}\n </AlertBanner>\n );\n}\n"],"mappings":";AAqJa;AApJb,SAAS,YAAY,WAAW,iBAAiB;AAGjD,SAAS,mBAAmB;AAE5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AAqFjC,MAAM,YAAY;AAElB,SAAS,YAAY,MAAyB,UAAyC;AACrF,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,YAAY,KAAK,YAAY,SAAS;AAAA,IAChD,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAeO,SAAS,cAAc,OAA2B;AACvD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,CAAC,CAAC;AAElD,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,OAAO,YAAY,IAAI;AAC9B,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO,YAAY,IAAI;AAC9B,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,cAAW;AACnB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO,cAAc,IAAI;AAChC,aAAO,oBAAC,aAAU;AAClB,kBAAY;AACZ;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,WAAQ,OAAO,MAAM;AAC7B,kBAAY,OAAO;AACnB;AAAA,EACJ;AAEA,QAAM,QAAQ,eAAe;AAC7B,QAAM,SACJ,UAAU,UAAc,eAAe,UAAa,aAAa,SAAa,SAAY,eAAe,SACvG,oBAAC,UAAO,MAAM,YAAY,SAAQ,aAAY,MAAK,MAChD,iBACH,IAEA,oBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,MAAK,MAAK,SAAS,UAAU,SAAS,eAC7E,iBACH;AAGJ,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAM,SAAS,SAAS,eAAe,WAAW;AAAA,MAClD;AAAA,MACA,MAAM,SAAS,YAAY,UAAU,OAAO;AAAA,MAC5C,MAAM,QAAQ,YAAY,MAAM,IAAI;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAqB;AAAA,MAEpB;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/billing/billing-banner.tsx"],"sourcesContent":["import type { ComponentPropsWithRef, ReactNode } from \"react\";\nimport { CreditCard, Hourglass, PencilOff } from \"lucide-react\";\n\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport type { AlertSize, AlertTone } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Spinner } from \"../components/spinner\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\n\n/**\n * The billing banners (docs/billing-harmonization.md §3.3, §7, §12.6, §12.21):\n *\n * - `trial-ending` — from 7 days before `trial_ends_at` (§3.3);\n * - `grant-ending` — from 7 days before `comped_until`: a beta user's 12 months, an\n * operator's grant;\n * - `payment-failed` — from the first failed payment (`past_due`), while the provider\n * retries;\n * - `plan-ended` — out of good standing: read-only, never locked (§2.3);\n * - `guest` — a guest in someone else's read-only item (§12.6);\n * - `processing` — back from checkout before the webhook landed (§12.21).\n */\nexport type BillingBannerKind =\n | \"trial-ending\"\n | \"grant-ending\"\n | \"payment-failed\"\n | \"plan-ended\"\n | \"guest\"\n | \"processing\";\n\n/** A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2): they\n * go on to the {@link AlertBanner}, so the ref is the banner's `<div>` — with an × too,\n * since the banner has no `href` or `onClick` that would make its row a link or a button. */\ninterface BillingBannerBaseProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\" | \"onClick\"> {\n /**\n * Over the kind's tone: `trial-ending` and `grant-ending` are `info` until the last\n * three days and `warning` in them; `payment-failed` is `danger` (money did not\n * arrive — kastlan's past-due strip was red); `plan-ended` is `warning`, a state with\n * everything still there rather than an error; `guest` and `processing` are `info`.\n */\n tone?: AlertTone;\n /** `strip` (default): the band under the top bar, on every page. `box`: the same\n * message on the subscription page itself. */\n variant?: \"strip\" | \"box\";\n /** Default `sm` for a strip (the demo's banner), `md` for a box. */\n size?: AlertSize;\n /**\n * Where the action goes — the subscription page, say. The app's action, as an `href`\n * or as {@link onAction}; without either there is no button. Never a write-locked\n * commit: billing itself always works (§3.3).\n */\n actionHref?: string;\n /** What the action does — the app's portal call (`POST /billing/portal`, then off to\n * its `url`): for `payment-failed` with `{target: \"payment_method\"}`, the portal's\n * page for the card (§14.5); for `processing`, `useCheckoutProcessing`'s\n * `checkAgain`. The kit sends no request. */\n onAction?: () => void;\n /**\n * Over the kind's action words: \"Choose a plan\" (trial, grant, plan ended), \"Update\n * payment method\" (payment failed), \"Check again\" (processing). The guest banner has\n * none of its own: an action there needs this.\n */\n actionLabel?: string;\n /** The action is under way: the button's spinner, no second press. */\n actionPending?: boolean;\n /** An × that calls this; the app removes the banner and remembers that it did. */\n onDismiss?: () => void;\n dismissLabel?: string;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport type BillingBannerProps = BillingBannerBaseProps &\n (\n | {\n kind: \"trial-ending\" | \"grant-ending\";\n /** Calendar days left — `billingDaysLeft(trial_ends_at)`. 0 is today. */\n daysLeft: number;\n item?: never;\n }\n | {\n kind: \"guest\";\n /** The item's own name (\"Household 2026\"), or nothing. NEVER the owner's status:\n * the banner says \"for now\" and nothing more (§12.6). */\n item?: string;\n daysLeft?: never;\n }\n | {\n kind: \"payment-failed\" | \"plan-ended\" | \"processing\";\n daysLeft?: never;\n item?: never;\n }\n );\n\n/** The last days, in which an ending trial or grant turns from news into a warning. */\nconst LAST_DAYS = 3;\n\nfunction defaultTone(kind: BillingBannerKind, daysLeft: number | undefined): AlertTone {\n switch (kind) {\n case \"trial-ending\":\n case \"grant-ending\":\n return (daysLeft ?? 0) > LAST_DAYS ? \"info\" : \"warning\";\n case \"payment-failed\":\n return \"danger\";\n case \"plan-ended\":\n return \"warning\";\n default:\n return \"info\";\n }\n}\n\n/**\n * One of the billing banners, as a kit `AlertBanner` strip (§7): the kind's sentence, its\n * glyph and tone, and the app's action. Carries `data-billing-banner` with the kind.\n *\n * The app decides WHICH banner shows, from `GET /billing/overview` (and hides all of them\n * while billing is off, §4); the kit only says it. A guest's banner takes no status at\n * all — the type does not let one in — so the owner's payment state cannot leak into it\n * by a wrong prop (§12.6).\n *\n * `processing` is a live region (`role=\"status\"`): it appears when the person comes back\n * from checkout and goes when the overview catches up (§12.21), and a screen reader\n * should hear both. The others are page furniture, role-less as the strip is.\n */\nexport function BillingBanner(props: BillingBannerProps) {\n const {\n kind,\n daysLeft,\n item,\n tone,\n variant = \"strip\",\n size,\n actionHref,\n onAction,\n actionLabel,\n actionPending,\n onDismiss,\n dismissLabel,\n labels: labelsProp,\n role,\n ...rest\n } = props;\n const labels = useBillingLabels(labelsProp);\n const days = Math.max(0, Math.floor(daysLeft ?? 0));\n\n let text: string;\n let icon: ReactNode;\n let ownAction: string | undefined;\n switch (kind) {\n case \"trial-ending\":\n text = labels.trialEnding(days);\n icon = <Hourglass />;\n ownAction = labels.choosePlan;\n break;\n case \"grant-ending\":\n text = labels.grantEnding(days);\n icon = <Hourglass />;\n ownAction = labels.choosePlan;\n break;\n case \"payment-failed\":\n text = labels.paymentFailed;\n icon = <CreditCard />;\n ownAction = labels.updatePayment;\n break;\n case \"plan-ended\":\n text = labels.planEnded;\n icon = <PencilOff />;\n ownAction = labels.choosePlan;\n break;\n case \"guest\":\n text = labels.guestReadOnly(item);\n icon = <PencilOff />;\n ownAction = undefined;\n break;\n case \"processing\":\n text = labels.processing;\n icon = <Spinner label={null} />;\n ownAction = labels.checkAgain;\n break;\n }\n\n const words = actionLabel ?? ownAction;\n const action =\n words === undefined || (actionHref === undefined && onAction === undefined) ? undefined : actionHref !== undefined ? (\n <Button href={actionHref} variant=\"secondary\" size=\"sm\">\n {words}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={onAction} pending={actionPending}>\n {words}\n </Button>\n );\n\n return (\n <AlertBanner\n {...rest}\n role={role ?? (kind === \"processing\" ? \"status\" : undefined)}\n variant={variant}\n size={size ?? (variant === \"strip\" ? \"sm\" : \"md\")}\n tone={tone ?? defaultTone(kind, days)}\n icon={icon}\n action={action}\n onDismiss={onDismiss}\n dismissLabel={dismissLabel}\n data-billing-banner={kind}\n >\n {text}\n </AlertBanner>\n );\n}\n"],"mappings":";AAwJa;AAvJb,SAAS,YAAY,WAAW,iBAAiB;AAGjD,SAAS,mBAAmB;AAE5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AAwFjC,MAAM,YAAY;AAElB,SAAS,YAAY,MAAyB,UAAyC;AACrF,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,YAAY,KAAK,YAAY,SAAS;AAAA,IAChD,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAeO,SAAS,cAAc,OAA2B;AACvD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,CAAC,CAAC;AAElD,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,OAAO,YAAY,IAAI;AAC9B,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO,YAAY,IAAI;AAC9B,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,cAAW;AACnB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,aAAU;AAClB,kBAAY,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,OAAO,cAAc,IAAI;AAChC,aAAO,oBAAC,aAAU;AAClB,kBAAY;AACZ;AAAA,IACF,KAAK;AACH,aAAO,OAAO;AACd,aAAO,oBAAC,WAAQ,OAAO,MAAM;AAC7B,kBAAY,OAAO;AACnB;AAAA,EACJ;AAEA,QAAM,QAAQ,eAAe;AAC7B,QAAM,SACJ,UAAU,UAAc,eAAe,UAAa,aAAa,SAAa,SAAY,eAAe,SACvG,oBAAC,UAAO,MAAM,YAAY,SAAQ,aAAY,MAAK,MAChD,iBACH,IAEA,oBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,MAAK,MAAK,SAAS,UAAU,SAAS,eAC7E,iBACH;AAGJ,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAM,SAAS,SAAS,eAAe,WAAW;AAAA,MAClD;AAAA,MACA,MAAM,SAAS,YAAY,UAAU,OAAO;AAAA,MAC5C,MAAM,QAAQ,YAAY,MAAM,IAAI;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAqB;AAAA,MAEpB;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -1,10 +1,13 @@
1
- import type { ComponentPropsWithoutRef } from "react";
1
+ import type { ComponentPropsWithRef } from "react";
2
2
  import type { LabelOverride } from "../i18n/kit-labels.js";
3
3
  import type { AlertTone } from "../components/alert-banner.js";
4
4
  import type { BillingLabels } from "./billing-labels.js";
5
5
  /** The `contact` mode's mail: to support, its subject naming the dimension. */
6
6
  export declare function planLimitMailto(email: string, subject: string): string;
7
- interface PlanLimitNoticeBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "onClick"> {
7
+ /** A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2): they
8
+ * go on to the {@link AlertBanner}, so the ref is the notice's `<div>` — its `href` is the
9
+ * action button's, never the banner's, so the frame is never a link. */
10
+ interface PlanLimitNoticeBaseProps extends Omit<ComponentPropsWithRef<"div">, "children" | "onClick"> {
8
11
  /** The dimension as the refusal names it (`"budgets"`, `"seats"`) — `isPlanLimit(err)`'s
9
12
  * `dimension`. Carried as `data-dimension`. */
10
13
  dimension: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/billing/plan-limit-notice.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef } from \"react\";\n\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { formatNumber } from \"../lib/format\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport type { AlertTone } from \"../components/alert-banner\";\nimport { ProgressBar } from \"../components/progress-bar\";\nimport { Button } from \"../components/button\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\n\n/** RFC 3986 percent-encoding, `!'()*` too — a mail client that splits on `'` is the one\n * that breaks \"Plan limit: Ada's plots\" (landing/access.ts keeps the same rule). */\nfunction encodeStrict(value: string): string {\n return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);\n}\n\n/** The `contact` mode's mail: to support, its subject naming the dimension. */\nexport function planLimitMailto(email: string, subject: string): string {\n const to = encodeStrict(email).replace(/%40/g, \"@\");\n return `mailto:${to}?subject=${encodeStrict(subject)}`;\n}\n\ninterface PlanLimitNoticeBaseProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\" | \"onClick\"> {\n /** The dimension as the refusal names it (`\"budgets\"`, `\"seats\"`) — `isPlanLimit(err)`'s\n * `dimension`. Carried as `data-dimension`. */\n dimension: string;\n /** The app's name for it, in the reader's language: \"Budgets\". Default: the key. */\n dimensionLabel?: string;\n /** How many there are. Left out (keksdose's legacy `plan_budget_limit` body has no\n * `used`), it is read as `limit`: the create was refused for being at it. */\n used?: number;\n /** The plan's limit for the dimension. Left out — a body without one — the notice says\n * its words and draws no meter. */\n limit?: number;\n /** The payer's plan code, as `isPlanLimit` reads it — carried as `data-plan`, so the\n * refusal can be spread in whole: `<PlanLimitNotice {...isPlanLimit(err)} …/>`. */\n plan?: string;\n /** How a figure is written — kastlan's storage limit is bytes (`formatFileSize`).\n * Default: a number in the kit's locale. */\n formatValue?: (value: number) => string;\n /** Over the provider's locale, for the default figures. */\n locale?: string;\n /** Default `warning`. */\n tone?: AlertTone;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport type PlanLimitNoticeProps = PlanLimitNoticeBaseProps &\n (\n | {\n /** Plans are bought: \"Choose a plan\" → the app's subscription page. */\n mode: \"upgrade\";\n /** The subscription page. */\n href: string;\n email?: never;\n }\n | {\n /** The operator grants plans by hand (keksdose's today): \"Ask for more\" → a mail. */\n mode: \"contact\";\n /** The support address: `support@` the app's domain. */\n email: string;\n href?: never;\n }\n );\n\n/**\n * A create refused at the plan's limit (docs/billing-harmonization.md §3.4): \"Plan limit\n * reached\", what to do, the dimension's meter (\"Budgets — 3 of 3\") and the app's way to\n * more — `upgrade`, a link to the subscription page, or `contact`, a mail to support.\n *\n * A limit gates CREATION only: a downgrade never deletes or hides what is over the new\n * limit (§3.4), so `used` may be past `limit`; the meter then fills and turns red. Feed it\n * the refusal: `const hit = isPlanLimit(err); hit && <PlanLimitNotice {...hit} …/>`. The\n * kit sends nothing — the mail opens the reader's own client.\n */\nexport function PlanLimitNotice(props: PlanLimitNoticeProps) {\n const {\n dimension,\n dimensionLabel,\n used: usedProp,\n limit,\n plan,\n formatValue,\n locale: localeProp,\n tone = \"warning\",\n labels: labelsProp,\n mode,\n href,\n email,\n className,\n ...rest\n } = props;\n const labels = useBillingLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const used = usedProp ?? limit ?? 0;\n const name = dimensionLabel ?? dimension;\n const figure = (value: number) => (formatValue ? formatValue(value) : formatNumber(value, { locale }));\n\n const action =\n mode === \"upgrade\" ? (\n <Button href={href} variant=\"secondary\" size=\"sm\">\n {labels.choosePlan}\n </Button>\n ) : (\n <Button href={planLimitMailto(email, labels.contactSubject(name))} variant=\"secondary\" size=\"sm\">\n {labels.askForMore}\n </Button>\n );\n\n return (\n <AlertBanner\n {...rest}\n tone={tone}\n action={action}\n data-plan-limit-mode={mode}\n data-dimension={dimension}\n data-plan={plan}\n className={className}\n >\n <span className=\"block font-medium\">{labels.limitReached}</span>\n <span className=\"block\">{mode === \"upgrade\" ? labels.limitUpgrade : labels.limitContact}</span>\n {limit !== undefined && (\n <ProgressBar\n as=\"span\"\n variant=\"meter\"\n size=\"slim\"\n value={used}\n max={limit}\n label={name}\n showValue\n // The bar is clamped at `max` and hands its formatter the clamped value; the\n // figures are the real ones, so \"5 of 3\" after a downgrade says what the bar\n // cannot draw.\n formatValue={() => labels.usage(figure(used), figure(limit))}\n tone={used > limit ? \"danger\" : \"warning\"}\n className=\"mt-2\"\n locale={locale}\n />\n )}\n </AlertBanner>\n );\n}\n"],"mappings":";AAuGM,cAUF,YAVE;AArGN,SAAS,oBAAoB;AAE7B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAE5B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,wBAAwB;AAKjC,SAAS,aAAa,OAAuB;AAC3C,SAAO,mBAAmB,KAAK,EAAE,QAAQ,YAAY,CAAC,MAAM,IAAI,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,YAAY,CAAC,EAAE;AAC9G;AAGO,SAAS,gBAAgB,OAAe,SAAyB;AACtE,QAAM,KAAK,aAAa,KAAK,EAAE,QAAQ,QAAQ,GAAG;AAClD,SAAO,UAAU,EAAE,YAAY,aAAa,OAAO,CAAC;AACtD;AAwDO,SAAS,gBAAgB,OAA6B;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,OAAO,YAAY,SAAS;AAClC,QAAM,OAAO,kBAAkB;AAC/B,QAAM,SAAS,CAAC,UAAmB,cAAc,YAAY,KAAK,IAAI,aAAa,OAAO,EAAE,OAAO,CAAC;AAEpG,QAAM,SACJ,SAAS,YACP,oBAAC,UAAO,MAAY,SAAQ,aAAY,MAAK,MAC1C,iBAAO,YACV,IAEA,oBAAC,UAAO,MAAM,gBAAgB,OAAO,OAAO,eAAe,IAAI,CAAC,GAAG,SAAQ,aAAY,MAAK,MACzF,iBAAO,YACV;AAGJ,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA,wBAAsB;AAAA,MACtB,kBAAgB;AAAA,MAChB,aAAW;AAAA,MACX;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,qBAAqB,iBAAO,cAAa;AAAA,QACzD,oBAAC,UAAK,WAAU,SAAS,mBAAS,YAAY,OAAO,eAAe,OAAO,cAAa;AAAA,QACvF,UAAU,UACT;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,OAAO;AAAA,YACP,KAAK;AAAA,YACL,OAAO;AAAA,YACP,WAAS;AAAA,YAIT,aAAa,MAAM,OAAO,MAAM,OAAO,IAAI,GAAG,OAAO,KAAK,CAAC;AAAA,YAC3D,MAAM,OAAO,QAAQ,WAAW;AAAA,YAChC,WAAU;AAAA,YACV;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/billing/plan-limit-notice.tsx"],"sourcesContent":["import type { ComponentPropsWithRef } from \"react\";\n\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { formatNumber } from \"../lib/format\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport type { AlertTone } from \"../components/alert-banner\";\nimport { ProgressBar } from \"../components/progress-bar\";\nimport { Button } from \"../components/button\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\n\n/** RFC 3986 percent-encoding, `!'()*` too — a mail client that splits on `'` is the one\n * that breaks \"Plan limit: Ada's plots\" (landing/access.ts keeps the same rule). */\nfunction encodeStrict(value: string): string {\n return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);\n}\n\n/** The `contact` mode's mail: to support, its subject naming the dimension. */\nexport function planLimitMailto(email: string, subject: string): string {\n const to = encodeStrict(email).replace(/%40/g, \"@\");\n return `mailto:${to}?subject=${encodeStrict(subject)}`;\n}\n\n/** A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2): they\n * go on to the {@link AlertBanner}, so the ref is the notice's `<div>` — its `href` is the\n * action button's, never the banner's, so the frame is never a link. */\ninterface PlanLimitNoticeBaseProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\" | \"onClick\"> {\n /** The dimension as the refusal names it (`\"budgets\"`, `\"seats\"`) — `isPlanLimit(err)`'s\n * `dimension`. Carried as `data-dimension`. */\n dimension: string;\n /** The app's name for it, in the reader's language: \"Budgets\". Default: the key. */\n dimensionLabel?: string;\n /** How many there are. Left out (keksdose's legacy `plan_budget_limit` body has no\n * `used`), it is read as `limit`: the create was refused for being at it. */\n used?: number;\n /** The plan's limit for the dimension. Left out — a body without one — the notice says\n * its words and draws no meter. */\n limit?: number;\n /** The payer's plan code, as `isPlanLimit` reads it — carried as `data-plan`, so the\n * refusal can be spread in whole: `<PlanLimitNotice {...isPlanLimit(err)} …/>`. */\n plan?: string;\n /** How a figure is written — kastlan's storage limit is bytes (`formatFileSize`).\n * Default: a number in the kit's locale. */\n formatValue?: (value: number) => string;\n /** Over the provider's locale, for the default figures. */\n locale?: string;\n /** Default `warning`. */\n tone?: AlertTone;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\nexport type PlanLimitNoticeProps = PlanLimitNoticeBaseProps &\n (\n | {\n /** Plans are bought: \"Choose a plan\" → the app's subscription page. */\n mode: \"upgrade\";\n /** The subscription page. */\n href: string;\n email?: never;\n }\n | {\n /** The operator grants plans by hand (keksdose's today): \"Ask for more\" → a mail. */\n mode: \"contact\";\n /** The support address: `support@` the app's domain. */\n email: string;\n href?: never;\n }\n );\n\n/**\n * A create refused at the plan's limit (docs/billing-harmonization.md §3.4): \"Plan limit\n * reached\", what to do, the dimension's meter (\"Budgets — 3 of 3\") and the app's way to\n * more — `upgrade`, a link to the subscription page, or `contact`, a mail to support.\n *\n * A limit gates CREATION only: a downgrade never deletes or hides what is over the new\n * limit (§3.4), so `used` may be past `limit`; the meter then fills and turns red. Feed it\n * the refusal: `const hit = isPlanLimit(err); hit && <PlanLimitNotice {...hit} …/>`. The\n * kit sends nothing — the mail opens the reader's own client.\n */\nexport function PlanLimitNotice(props: PlanLimitNoticeProps) {\n const {\n dimension,\n dimensionLabel,\n used: usedProp,\n limit,\n plan,\n formatValue,\n locale: localeProp,\n tone = \"warning\",\n labels: labelsProp,\n mode,\n href,\n email,\n className,\n ...rest\n } = props;\n const labels = useBillingLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const used = usedProp ?? limit ?? 0;\n const name = dimensionLabel ?? dimension;\n const figure = (value: number) => (formatValue ? formatValue(value) : formatNumber(value, { locale }));\n\n const action =\n mode === \"upgrade\" ? (\n <Button href={href} variant=\"secondary\" size=\"sm\">\n {labels.choosePlan}\n </Button>\n ) : (\n <Button href={planLimitMailto(email, labels.contactSubject(name))} variant=\"secondary\" size=\"sm\">\n {labels.askForMore}\n </Button>\n );\n\n return (\n <AlertBanner\n {...rest}\n tone={tone}\n action={action}\n data-plan-limit-mode={mode}\n data-dimension={dimension}\n data-plan={plan}\n className={className}\n >\n <span className=\"block font-medium\">{labels.limitReached}</span>\n <span className=\"block\">{mode === \"upgrade\" ? labels.limitUpgrade : labels.limitContact}</span>\n {limit !== undefined && (\n <ProgressBar\n as=\"span\"\n variant=\"meter\"\n size=\"slim\"\n value={used}\n max={limit}\n label={name}\n showValue\n // The bar is clamped at `max` and hands its formatter the clamped value; the\n // figures are the real ones, so \"5 of 3\" after a downgrade says what the bar\n // cannot draw.\n formatValue={() => labels.usage(figure(used), figure(limit))}\n tone={used > limit ? \"danger\" : \"warning\"}\n className=\"mt-2\"\n locale={locale}\n />\n )}\n </AlertBanner>\n );\n}\n"],"mappings":";AA0GM,cAUF,YAVE;AAxGN,SAAS,oBAAoB;AAE7B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAE5B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,wBAAwB;AAKjC,SAAS,aAAa,OAAuB;AAC3C,SAAO,mBAAmB,KAAK,EAAE,QAAQ,YAAY,CAAC,MAAM,IAAI,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,YAAY,CAAC,EAAE;AAC9G;AAGO,SAAS,gBAAgB,OAAe,SAAyB;AACtE,QAAM,KAAK,aAAa,KAAK,EAAE,QAAQ,QAAQ,GAAG;AAClD,SAAO,UAAU,EAAE,YAAY,aAAa,OAAO,CAAC;AACtD;AA2DO,SAAS,gBAAgB,OAA6B;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,OAAO,YAAY,SAAS;AAClC,QAAM,OAAO,kBAAkB;AAC/B,QAAM,SAAS,CAAC,UAAmB,cAAc,YAAY,KAAK,IAAI,aAAa,OAAO,EAAE,OAAO,CAAC;AAEpG,QAAM,SACJ,SAAS,YACP,oBAAC,UAAO,MAAY,SAAQ,aAAY,MAAK,MAC1C,iBAAO,YACV,IAEA,oBAAC,UAAO,MAAM,gBAAgB,OAAO,OAAO,eAAe,IAAI,CAAC,GAAG,SAAQ,aAAY,MAAK,MACzF,iBAAO,YACV;AAGJ,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA,wBAAsB;AAAA,MACtB,kBAAgB;AAAA,MAChB,aAAW;AAAA,MACX;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,qBAAqB,iBAAO,cAAa;AAAA,QACzD,oBAAC,UAAK,WAAU,SAAS,mBAAS,YAAY,OAAO,eAAe,OAAO,cAAa;AAAA,QACvF,UAAU,UACT;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,OAAO;AAAA,YACP,KAAK;AAAA,YACL,OAAO;AAAA,YACP,WAAS;AAAA,YAIT,aAAa,MAAM,OAAO,MAAM,OAAO,IAAI,GAAG,OAAO,KAAK,CAAC;AAAA,YAC3D,MAAM,OAAO,QAAQ,WAAW;AAAA,YAChC,WAAU;AAAA,YACV;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
package/dist/chart.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chart.ts"],"sourcesContent":["// `@eifi1/ui-kit/chart` — the chart shell and its colour system.\n//\n// A re-slicing of what the main barrel already exports, not a new API. It exists so a\n// consumer can reach the chart kit WITHOUT the barrel: `import(\"@eifi1/ui-kit/chart\")`\n// is a 31KB chunk, where the same lazy import through the barrel pulls 221KB, because\n// a dynamic import of the barrel cannot be tree-shaken down to one of its members.\n//\n// It is about chunk size, not about the peer: `recharts` is a REQUIRED peer (since 0.5.0),\n// because the main barrel re-exports everything here too, so `import { Button } from\n// \"@eifi1/ui-kit\"` reaches recharts as well (packaging-contract.test.ts asserts it).\nexport * from \"./components/chart\";\nexport * from \"./theme/chart-palette\";\n\n// The tile chart (treemap) the apps share — moved here from keksdose so every consumer\n// derives it from the package rather than from a copy.\nexport * from \"./components/treemap\";\n\n// lenkbank's zoomable series chart, its synced x-zoom, the toggle legend and the\n// mirrored-pair axis geometry — moved here so the apps derive them from the package.\nexport * from \"./components/series-chart\";\n// 0.32.1 (keksdose's 0.32 report): the height every chart takes, for an app's wrappers.\nexport type { ChartHeight } from \"./components/chart-height\";\nexport * from \"./components/chart-zoom\";\n// The zoom's own module since 0.34: SharedXZoom and the drag layer (recharts) live here,\n// ./chart-zoom is their public face. Named, so the arithmetic it also holds stays the\n// kit's (refactor audit 2026-10, API-6b) — the same binding the line above re-exports.\nexport { SharedXZoom } from \"./components/chart-zoom-core\";\nexport * from \"./components/toggle-legend\";\nexport * from \"./components/facing-pair\";\nexport * from \"./components/series-chart-labels\";\n\n// The part-to-whole chart (donut / pie) — moved here from kastlan's dashboard and reports.\nexport * from \"./components/pie-chart\";\nexport * from \"./components/pie-chart-labels\";\n"],"mappings":";AAUA,cAAc;AACd,cAAc;AAId,cAAc;AAId,cAAc;AAGd,cAAc;AAId,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/chart.ts"],"sourcesContent":["// `@eifi1/ui-kit/chart` — the chart shell and its colour system.\n//\n// A re-slicing of what the main barrel already exports, not a new API. It exists so a\n// consumer can reach the chart kit WITHOUT the barrel: `import(\"@eifi1/ui-kit/chart\")`\n// is a 31KB chunk, where the same lazy import through the barrel pulls 221KB, because\n// a dynamic import of the barrel cannot be tree-shaken down to one of its members.\n//\n// It is about chunk size, not about the peer: `recharts` is a REQUIRED peer (since 0.5.0),\n// because the main barrel re-exports everything here too, so `import { Button } from\n// \"@eifi1/ui-kit\"` reaches recharts as well (packaging-contract.test.ts asserts it).\nexport * from \"./components/chart\";\nexport * from \"./theme/chart-palette\";\n\n// The tile chart (treemap) the apps share — moved here from keksdose so every consumer\n// derives it from the package rather than from a copy.\nexport * from \"./components/treemap\";\n\n// lenkbank's zoomable series chart, its synced x-zoom, the toggle legend and the\n// mirrored-pair axis geometry — moved here so the apps derive them from the package.\nexport * from \"./components/series-chart\";\n// 0.32.1 (keksdose's 0.32 report): the height every chart takes, for an app's wrappers.\nexport type { ChartHeight } from \"./components/chart-height\";\n// SharedXZoom and the zoom's types: the public face of ./chart-zoom-core, whose arithmetic\n// stays the kit's (refactor audit 2026-10, API-6b; off the surface in 0.35).\nexport * from \"./components/chart-zoom\";\n// The same binding, named from the module that holds it and the drag layer (recharts): the\n// packaging contract reads this entry's own imports to know which modules may import\n// recharts, so chart-zoom-core has to be one of them (MOD-6).\nexport { SharedXZoom } from \"./components/chart-zoom-core\";\nexport * from \"./components/toggle-legend\";\nexport * from \"./components/facing-pair\";\nexport * from \"./components/series-chart-labels\";\n\n// The part-to-whole chart (donut / pie) — moved here from kastlan's dashboard and reports.\nexport * from \"./components/pie-chart\";\nexport * from \"./components/pie-chart-labels\";\n"],"mappings":";AAUA,cAAc;AACd,cAAc;AAId,cAAc;AAId,cAAc;AAKd,cAAc;AAId,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
@@ -62,9 +62,6 @@ export interface AmountInputProps {
62
62
  id?: string;
63
63
  /** Names the field when it has no visible `label` (a table cell, the display shape). */
64
64
  "aria-label"?: string;
65
- /** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
66
- * Names the field only when `aria-label` is absent. */
67
- ariaLabel?: string;
68
65
  /**
69
66
  * Set on the `<input>`, with `id` above. Declared so `Field`'s render-prop spread —
70
67
  * `{(ids) => <AmountInput {...ids} … />}` — is typed and reaches the input: the
@@ -62,7 +62,7 @@ function isResultOf(previous, text, settle) {
62
62
  return n !== null && settle(formatResult(n)) === text.trim();
63
63
  }
64
64
  function AmountInput(props) {
65
- const { ref, value, onChange, currency, onCurrencyChange, placeholder, label, className, inputClassName, id, "aria-label": ariaLabelAttr, ariaLabel: ariaLabelProp, "aria-required": ariaRequired, autoFocus, tone = "neutral", negative = false, onNegativeChange, align = "start", labels, currencyNames, currencyOptions, digits: digitsProp, min, max, onCommit, disabledReasonDisplay } = props;
65
+ const { ref, value, onChange, currency, onCurrencyChange, placeholder, label, className, inputClassName, id, "aria-label": ariaLabel, "aria-required": ariaRequired, autoFocus, tone = "neutral", negative = false, onNegativeChange, align = "start", labels, currencyNames, currencyOptions, digits: digitsProp, min, max, onCommit, disabledReasonDisplay } = props;
66
66
  const generatedId = useId();
67
67
  const fieldId = id ?? generatedId;
68
68
  const signOwned = onNegativeChange !== void 0;
@@ -135,7 +135,7 @@ function AmountInput(props) {
135
135
  {
136
136
  ref: setRefs,
137
137
  id: fieldId,
138
- "aria-label": ariaLabelAttr ?? ariaLabelProp,
138
+ "aria-label": ariaLabel,
139
139
  type: "text",
140
140
  autoFocus,
141
141
  inputMode: isMobile ? "none" : "decimal",