@eifi1/ui-kit 0.7.0 → 0.8.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 (233) hide show
  1. package/README.md +32 -17
  2. package/dist/chart.d.ts +3 -2
  3. package/dist/components/alert-banner.d.ts +59 -10
  4. package/dist/components/alert-banner.js +114 -8
  5. package/dist/components/alert-banner.js.map +1 -1
  6. package/dist/components/amount-input.d.ts +5 -0
  7. package/dist/components/button-group.d.ts +26 -0
  8. package/dist/components/button-group.js +49 -0
  9. package/dist/components/button-group.js.map +1 -0
  10. package/dist/components/calculator.d.ts +5 -0
  11. package/dist/components/chart-zoom.d.ts +63 -14
  12. package/dist/components/chart-zoom.js +82 -16
  13. package/dist/components/chart-zoom.js.map +1 -1
  14. package/dist/components/chip.d.ts +67 -2
  15. package/dist/components/chip.js +73 -8
  16. package/dist/components/chip.js.map +1 -1
  17. package/dist/components/confirm-dialog.d.ts +120 -0
  18. package/dist/components/confirm-dialog.js +96 -0
  19. package/dist/components/confirm-dialog.js.map +1 -0
  20. package/dist/components/copy-button.d.ts +62 -0
  21. package/dist/components/copy-button.js +95 -0
  22. package/dist/components/copy-button.js.map +1 -0
  23. package/dist/components/data-table-filter-popover.d.ts +1 -1
  24. package/dist/components/data-table-filters.d.ts +1 -1
  25. package/dist/components/data-table-pagination.d.ts +5 -1
  26. package/dist/components/data-table-pagination.js +71 -58
  27. package/dist/components/data-table-pagination.js.map +1 -1
  28. package/dist/components/data-table-sort.d.ts +28 -5
  29. package/dist/components/data-table-sort.js +11 -7
  30. package/dist/components/data-table-sort.js.map +1 -1
  31. package/dist/components/data-table.d.ts +1 -1
  32. package/dist/components/data-table.js +544 -423
  33. package/dist/components/data-table.js.map +1 -1
  34. package/dist/components/date-picker.d.ts +51 -5
  35. package/dist/components/date-picker.js +152 -40
  36. package/dist/components/date-picker.js.map +1 -1
  37. package/dist/components/description-list.d.ts +60 -0
  38. package/dist/components/description-list.js +112 -0
  39. package/dist/components/description-list.js.map +1 -0
  40. package/dist/components/disclosure.d.ts +37 -2
  41. package/dist/components/disclosure.js +14 -5
  42. package/dist/components/disclosure.js.map +1 -1
  43. package/dist/components/facing-pair.d.ts +2 -0
  44. package/dist/components/file-button.d.ts +19 -1
  45. package/dist/components/file-button.js +8 -1
  46. package/dist/components/file-button.js.map +1 -1
  47. package/dist/components/file-dropzone.d.ts +56 -9
  48. package/dist/components/file-dropzone.js +100 -70
  49. package/dist/components/file-dropzone.js.map +1 -1
  50. package/dist/components/floating-panel.d.ts +107 -0
  51. package/dist/components/floating-panel.js +206 -0
  52. package/dist/components/floating-panel.js.map +1 -0
  53. package/dist/components/full-bleed-dialog.d.ts +12 -1
  54. package/dist/components/full-bleed-dialog.js +26 -5
  55. package/dist/components/full-bleed-dialog.js.map +1 -1
  56. package/dist/components/modal.d.ts +6 -2
  57. package/dist/components/modal.js +2 -1
  58. package/dist/components/modal.js.map +1 -1
  59. package/dist/components/number-field.d.ts +5 -0
  60. package/dist/components/number-input.d.ts +5 -0
  61. package/dist/components/numpad-sheet.d.ts +5 -0
  62. package/dist/components/progress-bar.d.ts +54 -0
  63. package/dist/components/progress-bar.js +94 -0
  64. package/dist/components/progress-bar.js.map +1 -0
  65. package/dist/components/scroll-area.d.ts +53 -0
  66. package/dist/components/scroll-area.js +86 -0
  67. package/dist/components/scroll-area.js.map +1 -0
  68. package/dist/components/separator.d.ts +23 -0
  69. package/dist/components/separator.js +24 -0
  70. package/dist/components/separator.js.map +1 -0
  71. package/dist/components/series-chart-ticks.d.ts +34 -1
  72. package/dist/components/series-chart-ticks.js +83 -1
  73. package/dist/components/series-chart-ticks.js.map +1 -1
  74. package/dist/components/series-chart.d.ts +314 -19
  75. package/dist/components/series-chart.js +510 -120
  76. package/dist/components/series-chart.js.map +1 -1
  77. package/dist/components/settings-fields.d.ts +2 -0
  78. package/dist/components/skeleton.d.ts +36 -0
  79. package/dist/components/skeleton.js +35 -0
  80. package/dist/components/skeleton.js.map +1 -0
  81. package/dist/components/stat-tile.d.ts +60 -6
  82. package/dist/components/stat-tile.js +60 -26
  83. package/dist/components/stat-tile.js.map +1 -1
  84. package/dist/components/table.d.ts +83 -0
  85. package/dist/components/table.js +166 -0
  86. package/dist/components/table.js.map +1 -0
  87. package/dist/components/time-input.d.ts +2 -0
  88. package/dist/components/toggle-legend.d.ts +3 -2
  89. package/dist/components/toggle-legend.js +1 -1
  90. package/dist/components/toggle-legend.js.map +1 -1
  91. package/dist/components/tooltip.d.ts +10 -4
  92. package/dist/components/tooltip.js +23 -6
  93. package/dist/components/tooltip.js.map +1 -1
  94. package/dist/components/tree-view.d.ts +129 -0
  95. package/dist/components/tree-view.js +376 -0
  96. package/dist/components/tree-view.js.map +1 -0
  97. package/dist/components/treemap.d.ts +9 -2
  98. package/dist/components/treemap.js +2 -1
  99. package/dist/components/treemap.js.map +1 -1
  100. package/dist/components/ui.d.ts +92 -14
  101. package/dist/components/ui.js +112 -30
  102. package/dist/components/ui.js.map +1 -1
  103. package/dist/components/use-table-state.d.ts +1 -1
  104. package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
  105. package/dist/data-table.d.ts +2 -2
  106. package/dist/data-table.js.map +1 -1
  107. package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
  108. package/dist/hooks/use-copy-to-clipboard.js +81 -0
  109. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  110. package/dist/hooks/use-debounce.d.ts +43 -0
  111. package/dist/hooks/use-debounce.js +56 -0
  112. package/dist/hooks/use-debounce.js.map +1 -0
  113. package/dist/hooks/use-file-drop.d.ts +72 -0
  114. package/dist/hooks/use-file-drop.js +57 -0
  115. package/dist/hooks/use-file-drop.js.map +1 -0
  116. package/dist/i18n/defaults.d.ts +5 -0
  117. package/dist/i18n/defaults.js +7 -1
  118. package/dist/i18n/defaults.js.map +1 -1
  119. package/dist/i18n/kit-labels.d.ts +17 -0
  120. package/dist/i18n/kit-labels.js +6 -2
  121. package/dist/i18n/kit-labels.js.map +1 -1
  122. package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
  123. package/dist/i18n/locales/de-CH-informal.js +8 -0
  124. package/dist/i18n/locales/de-CH-informal.js.map +1 -0
  125. package/dist/i18n/locales/de-CH.d.ts +5 -0
  126. package/dist/i18n/locales/de-CH.js +1 -14
  127. package/dist/i18n/locales/de-CH.js.map +1 -1
  128. package/dist/i18n/locales/de-informal.d.ts +64 -0
  129. package/dist/i18n/locales/de-informal.js +37 -0
  130. package/dist/i18n/locales/de-informal.js.map +1 -0
  131. package/dist/i18n/locales/de.d.ts +5 -0
  132. package/dist/i18n/locales/de.js +25 -0
  133. package/dist/i18n/locales/de.js.map +1 -1
  134. package/dist/i18n/locales/es.d.ts +5 -0
  135. package/dist/i18n/locales/es.js +25 -0
  136. package/dist/i18n/locales/es.js.map +1 -1
  137. package/dist/i18n/locales/fr.d.ts +5 -0
  138. package/dist/i18n/locales/fr.js +25 -0
  139. package/dist/i18n/locales/fr.js.map +1 -1
  140. package/dist/i18n/locales/hu.d.ts +5 -0
  141. package/dist/i18n/locales/hu.js +25 -0
  142. package/dist/i18n/locales/hu.js.map +1 -1
  143. package/dist/i18n/locales/it.d.ts +5 -0
  144. package/dist/i18n/locales/it.js +25 -0
  145. package/dist/i18n/locales/it.js.map +1 -1
  146. package/dist/i18n/locales/zh.d.ts +5 -0
  147. package/dist/i18n/locales/zh.js +25 -0
  148. package/dist/i18n/locales/zh.js.map +1 -1
  149. package/dist/i18n/swiss.d.ts +13 -0
  150. package/dist/i18n/swiss.js +19 -0
  151. package/dist/i18n/swiss.js.map +1 -0
  152. package/dist/index.d.ts +26 -11
  153. package/dist/index.js +22 -0
  154. package/dist/index.js.map +1 -1
  155. package/dist/lib/dates.d.ts +37 -1
  156. package/dist/lib/dates.js +19 -0
  157. package/dist/lib/dates.js.map +1 -1
  158. package/dist/rhf/form.d.ts +2 -0
  159. package/dist/rhf.d.ts +2 -0
  160. package/dist/search/command-palette.d.ts +15 -1
  161. package/dist/search/command-palette.js +10 -2
  162. package/dist/search/command-palette.js.map +1 -1
  163. package/dist/shell/app-shell.js +2 -2
  164. package/dist/shell/app-shell.js.map +1 -1
  165. package/dist/wizard/stepper-nav.d.ts +35 -2
  166. package/dist/wizard/stepper-nav.js +54 -22
  167. package/dist/wizard/stepper-nav.js.map +1 -1
  168. package/dist/wizard/types.d.ts +107 -5
  169. package/dist/wizard/types.js +2 -1
  170. package/dist/wizard/types.js.map +1 -1
  171. package/dist/wizard/use-wizard.js +141 -21
  172. package/dist/wizard/use-wizard.js.map +1 -1
  173. package/dist/wizard/wizard-summary.d.ts +10 -2
  174. package/dist/wizard/wizard-summary.js +3 -1
  175. package/dist/wizard/wizard-summary.js.map +1 -1
  176. package/dist/wizard.d.ts +4 -1
  177. package/dist/wizard.js.map +1 -1
  178. package/package.json +1 -1
  179. package/src/components/alert-banner.tsx +211 -20
  180. package/src/components/button-group.tsx +75 -0
  181. package/src/components/chart-zoom.tsx +158 -20
  182. package/src/components/chip.tsx +160 -9
  183. package/src/components/confirm-dialog.tsx +242 -0
  184. package/src/components/copy-button.tsx +158 -0
  185. package/src/components/data-table-pagination.tsx +19 -6
  186. package/src/components/data-table-sort.ts +49 -10
  187. package/src/components/data-table.tsx +293 -24
  188. package/src/components/date-picker.tsx +262 -57
  189. package/src/components/description-list.tsx +174 -0
  190. package/src/components/disclosure.tsx +58 -5
  191. package/src/components/file-button.tsx +22 -2
  192. package/src/components/file-dropzone.tsx +198 -100
  193. package/src/components/floating-panel.tsx +341 -0
  194. package/src/components/full-bleed-dialog.tsx +48 -5
  195. package/src/components/modal.tsx +9 -2
  196. package/src/components/progress-bar.tsx +163 -0
  197. package/src/components/scroll-area.tsx +129 -0
  198. package/src/components/separator.tsx +37 -0
  199. package/src/components/series-chart-ticks.ts +135 -0
  200. package/src/components/series-chart.tsx +934 -67
  201. package/src/components/skeleton.tsx +65 -0
  202. package/src/components/stat-tile.tsx +160 -27
  203. package/src/components/table.tsx +263 -0
  204. package/src/components/toggle-legend.tsx +4 -3
  205. package/src/components/tooltip.tsx +40 -12
  206. package/src/components/tree-view.tsx +589 -0
  207. package/src/components/treemap.tsx +10 -2
  208. package/src/components/ui.tsx +268 -42
  209. package/src/data-table.ts +2 -0
  210. package/src/hooks/use-copy-to-clipboard.ts +122 -0
  211. package/src/hooks/use-debounce.ts +105 -0
  212. package/src/hooks/use-file-drop.ts +123 -0
  213. package/src/i18n/defaults.ts +6 -0
  214. package/src/i18n/kit-labels.tsx +19 -0
  215. package/src/i18n/locales/de-CH-informal.ts +10 -0
  216. package/src/i18n/locales/de-CH.ts +1 -16
  217. package/src/i18n/locales/de-informal.ts +61 -0
  218. package/src/i18n/locales/de.ts +25 -0
  219. package/src/i18n/locales/es.ts +25 -0
  220. package/src/i18n/locales/fr.ts +25 -0
  221. package/src/i18n/locales/hu.ts +25 -0
  222. package/src/i18n/locales/it.ts +25 -0
  223. package/src/i18n/locales/zh.ts +25 -0
  224. package/src/i18n/swiss.ts +25 -0
  225. package/src/index.ts +29 -0
  226. package/src/lib/dates.ts +48 -0
  227. package/src/search/command-palette.tsx +29 -3
  228. package/src/shell/app-shell.tsx +2 -2
  229. package/src/wizard/stepper-nav.tsx +102 -28
  230. package/src/wizard/types.ts +106 -4
  231. package/src/wizard/use-wizard.ts +211 -31
  232. package/src/wizard/wizard-summary.tsx +24 -12
  233. package/src/wizard.ts +3 -2
@@ -81,21 +81,67 @@ interface DatePickerProps extends DatePickerBaseProps {
81
81
  /** Single-date picker: a field showing the formatted date, opening a calendar. */
82
82
  declare function DatePicker({ value, onChange, locale: localeProp, placeholder, min, max, formatOptions, formatValue, step, stepLabels, today, todayLabel, className, calendarLabels, label, clearable, clearLabel, disabled, invalid, ...rest }: DatePickerProps): react.JSX.Element;
83
83
  interface DateRangePickerPreset {
84
+ /**
85
+ * The preset's identity, independent of its dates — `"last_3_months"`, not two ISO
86
+ * strings. Optional for the 0.x callers that pass anonymous pairs.
87
+ *
88
+ * keksdose's report range field (report-range-field.tsx, "Why this is app-level")
89
+ * is why it exists: its URL stores `?preset=last_3_months` and recomputes the dates
90
+ * from today on every read, so a preset that is only its dates froze "last 3 months"
91
+ * at whatever day the panel rendered, and two presets that happen to cover the same
92
+ * days (this week and today, on a Monday) could not be told apart. With an `id` the
93
+ * choice is reported to `onChange` and shown as selected for as long as the range
94
+ * still matches it. Recompute `from`/`to` on every render (`calendarMonthPresets()`,
95
+ * `dateRangePresets()`) and pass the key as the id.
96
+ */
97
+ id?: string;
84
98
  label: ReactNode;
85
99
  from: string;
86
100
  to: string;
87
101
  }
102
+ /**
103
+ * When the picker hands its range to `onChange`.
104
+ *
105
+ * - `"immediate"` (the default, and all of 0.7): a preset click commits and closes,
106
+ * and each calendar click is reported as it happens — the half-made `(from, "")`
107
+ * included. The right shape for quick ranges on a filter, where one click is one
108
+ * answer.
109
+ * - `"apply"`: the panel works on a DRAFT. A preset ARMS the calendar instead of
110
+ * committing, days can then be nudged ("last 3 months, but end it last Friday"),
111
+ * and nothing reaches `onChange` until Apply — which stays disabled until both ends
112
+ * exist. Cancel, Escape and an outside click discard the draft, and every open
113
+ * starts again from the committed value. keksdose's report range asked for exactly
114
+ * this: each commit there re-runs a whole page of charts, so an intermediate state
115
+ * must never escape the panel.
116
+ */
117
+ type DateRangeCommit = "immediate" | "apply";
88
118
  interface DateRangePickerProps extends DatePickerBaseProps {
89
119
  from: string;
90
120
  to: string;
91
- onChange: (from: string, to: string) => void;
121
+ /** The committed range. `presetId` is the chosen preset's `id` when a preset
122
+ * produced it (and the range was not nudged since), `undefined` for days picked by
123
+ * hand. A 0.x two-argument handler simply ignores it. */
124
+ onChange: (from: string, to: string, presetId?: string) => void;
92
125
  /** Separator between the two formatted dates in the trigger (default " – "). */
93
126
  separator?: string;
94
- /** Optional named ranges rendered as a left column (last month / YTD / …). */
127
+ /** Optional named ranges rendered as a start-side column (last month / YTD / …). */
95
128
  presets?: DateRangePickerPreset[];
129
+ /**
130
+ * The active preset's `id`, controlled — e.g. straight from keksdose's `?preset=`.
131
+ * `null` (or an id no preset has, such as `"custom"`) says "no preset": nothing in
132
+ * the column is marked even if a preset happens to cover the same days. A preset is
133
+ * marked only while its dates still equal `from`/`to`.
134
+ *
135
+ * Leave it `undefined` and the picker remembers the last preset it committed
136
+ * itself; with no memory at all it falls back to the first preset whose dates match.
137
+ */
138
+ preset?: string | null;
139
+ /** See {@link DateRangeCommit}. Default `"immediate"`. */
140
+ commit?: DateRangeCommit;
96
141
  }
97
142
  /** Two-date range picker: click a start then an end; closes once both are set.
98
- * With `presets`, a left column of named ranges is shown beside the calendar. */
99
- declare function DateRangePicker({ from, to, onChange, locale: localeProp, placeholder, min, max, formatOptions, formatValue, separator, presets, calendarLabels, label, clearable, clearLabel, disabled, invalid, ...rest }: DateRangePickerProps): react.JSX.Element;
143
+ * With `presets`, a column of named ranges is shown beside the calendar; with
144
+ * `commit="apply"` the panel drafts and commits on Apply (see {@link DateRangeCommit}). */
145
+ declare function DateRangePicker({ from, to, onChange, locale: localeProp, placeholder, min, max, formatOptions, formatValue, separator, presets, preset, commit, calendarLabels, label, clearable, clearLabel, disabled, invalid, ...rest }: DateRangePickerProps): react.JSX.Element;
100
146
 
101
- export { DatePicker, type DatePickerProps, DateRangePicker, type DateRangePickerPreset, type DateRangePickerProps };
147
+ export { DatePicker, type DatePickerProps, type DateRangeCommit, DateRangePicker, type DateRangePickerPreset, type DateRangePickerProps };
@@ -11,7 +11,7 @@ import {
11
11
  useKitLocale
12
12
  } from "../i18n/kit-labels.js";
13
13
  import { splitTriggerAria } from "./trigger-aria.js";
14
- import { FieldLabel, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui.js";
14
+ import { Button, FieldLabel, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui.js";
15
15
  import { MiniCalendar } from "./mini-calendar.js";
16
16
  import { Popover } from "./popover.js";
17
17
  import { Tooltip } from "./tooltip.js";
@@ -324,6 +324,131 @@ function DatePicker({
324
324
  )
325
325
  );
326
326
  }
327
+ function markedPreset(presets, from, to, activeId, controlled) {
328
+ const matches = (p) => p.from === from && p.to === to;
329
+ if (activeId != null) {
330
+ const byId = presets.findIndex((p) => p.id === activeId && matches(p));
331
+ if (byId !== -1 || controlled) return byId;
332
+ } else if (controlled) {
333
+ return -1;
334
+ }
335
+ return presets.findIndex(matches);
336
+ }
337
+ function PresetColumn({
338
+ presets,
339
+ marked,
340
+ label,
341
+ onPick
342
+ }) {
343
+ return (
344
+ // `border-e`/`pe`, not `-r`: the preset column is on the START side, and the rule
345
+ // between it and the calendar has to follow it in a right-to-left UI.
346
+ /* @__PURE__ */ jsx(
347
+ "div",
348
+ {
349
+ role: "group",
350
+ "aria-label": label,
351
+ className: "flex w-32 shrink-0 flex-col gap-0.5 border-e border-[var(--border)] pe-2",
352
+ children: presets.map((p, i) => {
353
+ const selected = i === marked;
354
+ return /* @__PURE__ */ jsx(
355
+ "button",
356
+ {
357
+ type: "button",
358
+ "aria-pressed": selected,
359
+ "data-preset": p.id,
360
+ onClick: () => onPick(p),
361
+ className: cn(
362
+ "rounded px-2 py-1.5 text-start text-xs",
363
+ selected ? "bg-[var(--bg-active)] font-medium text-[var(--text-primary)]" : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
364
+ ),
365
+ children: p.label
366
+ },
367
+ p.id ?? i
368
+ );
369
+ })
370
+ }
371
+ )
372
+ );
373
+ }
374
+ function RangePanel({
375
+ from,
376
+ to,
377
+ presets,
378
+ activeId,
379
+ controlled,
380
+ commit,
381
+ close,
382
+ commitRange,
383
+ calendarProps,
384
+ labels
385
+ }) {
386
+ const drafting = commit === "apply";
387
+ const [draft, setDraft] = useState({
388
+ from,
389
+ to,
390
+ presetId: activeId ?? void 0
391
+ });
392
+ const shown = drafting ? draft : { from, to, presetId: activeId ?? void 0 };
393
+ const marked = presets ? markedPreset(presets, shown.from, shown.to, drafting ? draft.presetId : activeId, drafting || controlled) : -1;
394
+ const complete = Boolean(draft.from && draft.to);
395
+ const calendar = /* @__PURE__ */ jsx(
396
+ MiniCalendar,
397
+ {
398
+ ...calendarProps,
399
+ focusOnOpen: true,
400
+ from: shown.from,
401
+ to: shown.to,
402
+ onSelect: (f, t) => {
403
+ if (drafting) {
404
+ setDraft({ from: f, to: t, presetId: void 0 });
405
+ return;
406
+ }
407
+ commitRange(f, t, void 0);
408
+ if (f && t) close();
409
+ }
410
+ }
411
+ );
412
+ const body = presets && presets.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
413
+ /* @__PURE__ */ jsx(
414
+ PresetColumn,
415
+ {
416
+ presets,
417
+ marked,
418
+ label: labels.presets,
419
+ onPick: (p) => {
420
+ if (drafting) {
421
+ setDraft({ from: p.from, to: p.to, presetId: p.id });
422
+ return;
423
+ }
424
+ commitRange(p.from, p.to, p.id);
425
+ close();
426
+ }
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: calendar })
430
+ ] }) : calendar;
431
+ if (!drafting) return body;
432
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
433
+ body,
434
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-[var(--border)] pt-2", children: [
435
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: close, children: labels.cancel }),
436
+ /* @__PURE__ */ jsx(
437
+ Button,
438
+ {
439
+ type: "button",
440
+ disabled: !complete,
441
+ onClick: () => {
442
+ if (!complete) return;
443
+ commitRange(draft.from, draft.to, draft.presetId);
444
+ close();
445
+ },
446
+ children: labels.apply
447
+ }
448
+ )
449
+ ] })
450
+ ] });
451
+ }
327
452
  function DateRangePicker({
328
453
  from,
329
454
  to,
@@ -336,6 +461,8 @@ function DateRangePicker({
336
461
  formatValue,
337
462
  separator = " \u2013 ",
338
463
  presets,
464
+ preset,
465
+ commit = "immediate",
339
466
  calendarLabels,
340
467
  label,
341
468
  clearable,
@@ -347,26 +474,18 @@ function DateRangePicker({
347
474
  const locale = useKitLocale(localeProp);
348
475
  const text = useDatePickerLabels(clearLabel, void 0, void 0);
349
476
  const render = formatValue ?? ((iso) => formatDate(iso, locale, formatOptions));
477
+ const [ownPreset, setOwnPreset] = useState(void 0);
478
+ const controlled = preset !== void 0;
479
+ const activeId = controlled ? preset : ownPreset;
350
480
  const a = from ? render(from) : "";
351
481
  const b = to ? render(to) : "";
352
482
  const triggerText = from ? to ? `${a}${separator}${b}` : `${a}${separator}\u2026` : placeholder ?? "";
353
483
  const hasPresets = Boolean(presets && presets.length > 0);
354
- const calendar = (close) => /* @__PURE__ */ jsx(
355
- MiniCalendar,
356
- {
357
- focusOnOpen: true,
358
- from,
359
- to,
360
- locale,
361
- min,
362
- max,
363
- labels: calendarLabels,
364
- onSelect: (f, t) => {
365
- onChange(f, t);
366
- if (f && t) close();
367
- }
368
- }
369
- );
484
+ const commitRange = (f, t, presetId) => {
485
+ setOwnPreset(presetId);
486
+ if (presetId === void 0) onChange(f, t);
487
+ else onChange(f, t, presetId);
488
+ };
370
489
  return /* @__PURE__ */ jsx(
371
490
  DateField,
372
491
  {
@@ -380,29 +499,22 @@ function DateRangePicker({
380
499
  width: hasPresets ? 440 : void 0,
381
500
  hasValue: Boolean(from || to),
382
501
  triggerText,
383
- onClear: () => onChange("", ""),
384
- children: (close) => hasPresets ? /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
385
- /* @__PURE__ */ jsx("div", { className: "flex w-32 shrink-0 flex-col gap-0.5 border-e border-[var(--border)] pe-2", children: presets.map((p, i) => {
386
- const selected = p.from === from && p.to === to;
387
- return /* @__PURE__ */ jsx(
388
- "button",
389
- {
390
- type: "button",
391
- onClick: () => {
392
- onChange(p.from, p.to);
393
- close();
394
- },
395
- className: cn(
396
- "rounded px-2 py-1.5 text-start text-xs",
397
- selected ? "bg-[var(--bg-active)] font-medium text-[var(--text-primary)]" : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
398
- ),
399
- children: p.label
400
- },
401
- i
402
- );
403
- }) }),
404
- /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: calendar(close) })
405
- ] }) : calendar(close)
502
+ onClear: () => commitRange("", "", void 0),
503
+ children: (close) => /* @__PURE__ */ jsx(
504
+ RangePanel,
505
+ {
506
+ from,
507
+ to,
508
+ presets,
509
+ activeId,
510
+ controlled,
511
+ commit,
512
+ close,
513
+ commitRange,
514
+ calendarProps: { locale, min, max, labels: calendarLabels },
515
+ labels: text
516
+ }
517
+ )
406
518
  }
407
519
  );
408
520
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/date-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ComponentType, ReactNode, RefObject } from \"react\";\nimport { Calendar, CalendarClock, ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { addDaysIso, parseIsoDate } from \"../lib/dates\";\nimport {\n DEFAULT_DATE_PICKER_LABELS,\n useKitLabels,\n useKitLocale,\n type DatePickerLabels,\n} from \"../i18n/kit-labels\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { FieldLabel, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { MiniCalendar, type MiniCalendarProps } from \"./mini-calendar\";\nimport { Popover } from \"./popover\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * What both pickers share — and, through `ComponentPropsWithoutRef<\"div\">`, everything a\n * `<div>` takes, so a `data-tour` anchor, a test id or an `aria-describedby` reaches the\n * field's root element instead of being dropped (audit §api-design).\n *\n * `onChange` is omitted from those attributes: the DOM event of that name carries a\n * `FormEvent`, while both pickers answer with ISO date strings. Each declares its own\n * below.\n */\ninterface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /** BCP 47 tag for the trigger's date and the calendar. Falls back to\n * `<UiKitProvider locale>`, then to the runtime's default. */\n locale?: string;\n /** Embedded top-boundary label, matching the native Input/Select fields. */\n label?: ReactNode;\n placeholder?: string;\n /** Inclusive ISO bounds for selectable days. */\n min?: string;\n max?: string;\n /** Show an X to clear the value (emits `\"\"`). */\n clearable?: boolean;\n /** Accessible name of the clear button. Wins over `datePicker.clear` from\n * `<UiKitProvider labels>`; kept as a prop because it predates the provider. */\n clearLabel?: string;\n disabled?: boolean;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n className?: string;\n /** Intl options for the trigger's formatted date (default: locale short date). */\n formatOptions?: Intl.DateTimeFormatOptions;\n /** Render the trigger's text for an ISO date yourself; wins over `formatOptions`.\n * For a host whose date rendering is more than one `Intl` call can say — Keksdose\n * puts the weekday's name in the UI language beside digits ordered by a separate\n * format preference, two locales in one string. `locale` still drives the calendar. */\n formatValue?: (iso: string) => string;\n /** Passed through to the {@link MiniCalendar} this opens. Its month arrows are\n * icon-only, so their `aria-label` is the only name they have — and the package\n * ships English defaults, which a translating host has to be able to replace.\n * Usually unnecessary now: the calendar reads `miniCalendar` from the provider. */\n calendarLabels?: MiniCalendarProps[\"labels\"];\n}\n\n/** Format an ISO date for the trigger. `lib/dates`' `formatIsoDate` insists on a\n * locale, and here there may be none — no prop and no provider — which `Intl` reads\n * as \"the runtime's default\", as every other kit formatter does. */\nfunction formatDate(iso: string, locale: string | undefined, options?: Intl.DateTimeFormatOptions) {\n return parseIsoDate(iso)?.toLocaleDateString(locale, options) ?? \"\";\n}\n\n/**\n * The field-shaped button that opens the calendar.\n *\n * A component of its own rather than JSX inline in `Popover`'s `trigger` render prop,\n * because handing focus back to it is an effect and a render prop is not somewhere a\n * hook can go.\n */\nfunction DateFieldTrigger({\n open,\n toggle,\n triggerRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n padded,\n disabled,\n invalid,\n panelId,\n aria,\n}: {\n /** The caller's naming/description attributes, routed here from the field. */\n aria: TriggerAria;\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n /** The panel's id — `role=\"combobox\"` requires `aria-controls`. */\n panelId: string;\n triggerText: string;\n hasValue: boolean;\n /** ids of the hidden label twin + the value span, in the order they are spoken. */\n labelledBy?: string;\n valueId: string;\n padded: boolean;\n disabled?: boolean;\n invalid?: boolean;\n}) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when the panel still HAD focus. The day the user was standing on has just\n // been unmounted with the panel, which drops focus to <body> and leaves a\n // keyboard user back at the top of the document with no idea where they were.\n // But an outside click closes this panel too, and that click has already given\n // focus to whatever it landed on — dragging it back here would be worse than the\n // bug, so a focus that is somewhere real is left alone.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n // Here rather than through `useFocusTrap`'s `restoreFocus`, and not only because\n // `Popover` runs no trap to carry it: that option restores to whatever held focus\n // when the panel opened, and on Safari — and on Firefox on macOS — clicking a\n // button does not focus it, so \"whatever held focus\" is the page. The requirement\n // is this trigger, every time, however the panel was opened.\n }, [open, triggerRef]);\n\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n // The name has to carry the VALUE. A bare `aria-label={label}` — which is what\n // this was — replaces the button's text outright, so the one field in the form\n // whose entire job is to show a date announced \"Due date\" and stopped there,\n // and a screen-reader user could not read back what they had picked without\n // opening the calendar and hunting for the selected day.\n //\n // Two ids rather than one composed string (`common.fieldValue`): \"label: value\"\n // is a sentence, and a name reference is spoken as the two texts in order with\n // no punctuation to translate at all. The hidden twin of the FieldLabel lives\n // in DateField, next to the label it copies.\n id={aria.id}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n // `role=\"combobox\"` on a button that opens a calendar is the APG date-picker\n // shape, and it is what makes the next two lines legal: `button` supports\n // neither `aria-expanded` nor `aria-invalid`, so the previous markup set an\n // invalid state that announced nothing (ESLint's `role-supports-aria-props`).\n //\n // `aria-haspopup=\"dialog\"` is now honest — it was not when this comment first\n // said so. `Popover` gained a real `role=\"dialog\"` in the same wave, so the\n // trigger's promise and the panel's role finally agree.\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span id={valueId} className={cn(\"truncate\", !hasValue && \"text-[var(--text-placeholder)]\")}>\n {/* `|| \" \"` (a NON-BREAKING space) — triggerText is \"\" when there is no value and no\n placeholder was passed. An empty span has no line box, so the\n trigger collapses to its padding and sits shorter than every\n other field beside it.\n\n It has to be a NON-BREAKING space. This read `|| \" \"` first, and\n that fix did nothing at all: an ordinary space is collapsible\n white space, and white space at the start and end of a line is\n removed — so the span rendered with height 0 and the trigger\n stayed 22px against the 42px of the fields either side of it\n (Keksdose live #294, measured in the browser: `span.textContent`\n WAS `\" \"` and `getBoundingClientRect().height` was 0). U+00A0 is\n not collapsible, so it holds the line. */}\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n/** A field-styled trigger that opens a portalled MiniCalendar. Shared by the\n * single- and range-date pickers below. */\nfunction DateField({\n triggerText,\n hasValue,\n label,\n disabled,\n clearable,\n clearLabel,\n panelLabel,\n className,\n width,\n onClear,\n invalid,\n children,\n ...rest\n}: Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> & {\n triggerText: string;\n hasValue: boolean;\n label?: ReactNode;\n disabled?: boolean;\n clearable?: boolean;\n /** Already resolved by the picker (prop > provider > English). */\n clearLabel: string;\n /** The popover panel's accessible name, resolved likewise. */\n panelLabel: string;\n invalid?: boolean;\n className?: string;\n /** Popover panel width; omit for the default (a bare calendar). */\n width?: number;\n onClear: () => void;\n children: (close: () => void) => ReactNode;\n}) {\n const showClear = Boolean(clearable && hasValue && !disabled);\n const [aria, wrapperRest] = splitTriggerAria(rest);\n // The panel is portalled to <body> and leaves the subtree whose `dir` it inherited,\n // so a calendar in an RTL form opened LTR — the grid's columns, its arrow keys and\n // its chevrons all the wrong way round. Read at the moment of opening (the only way\n // the popover opens is through `toggle`) and put back on the panel.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const id = useId();\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can be reused as a name; a ReactNode may be a whole row with\n // its own interactive \"?\" hint in it, and naming a button with that reads the hint\n // out as part of the field's name.\n const named = typeof label === \"string\";\n return (\n // The caller's attributes land here, on the field's own box — the trigger inside is\n // named by `aria-labelledby` and must keep the id pair it is given.\n <div {...wrapperRest} ref={rootRef} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n {/* The visible FieldLabel is a plain span, not a `<label htmlFor>`, so it names\n nothing on its own — this hidden twin is what the trigger is named by.\n `aria-hidden` because a name reference reads a hidden element deliberately\n (accname §4.3.1), and without it the label would be announced twice: once as\n loose text before the button, once inside the button's own name.\n `sr-only-fixed` rather than `sr-only` — see the note on the class in\n tokens.css. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width={width}\n panelId={panelId}\n // Named for what it is. Unnamed, it fell back to `popover.panel`, and a date\n // field announced its calendar as \"Popover\" — in English, in every language.\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <DateFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n triggerText={triggerText}\n hasValue={hasValue}\n // ALWAYS a name reference, never `undefined`. A `<button>` takes its\n // accessible name from its contents, so an unlabelled trigger used to\n // announce its own date text for free. `role=\"combobox\"` does not —\n // content is excluded from name computation for that role — so leaving\n // this undefined made an unlabelled date field announce nothing at all.\n // Named: \"label, value\". Unnamed: the value alone, which is what the\n // button was saying before.\n //\n // A caller's own reference comes first. And when the caller gives the field\n // an `id` (so an external `<label htmlFor>` can point at it), the trigger\n // lists ITSELF first: a self-reference in `aria-labelledby` is resolved\n // from the element's native label (accname 2B → 2D), so \"Due date\" from\n // that <label> is spoken before the value, instead of being overridden.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n panelId={panelId}\n valueId={valueId}\n padded={label !== undefined}\n disabled={disabled}\n invalid={invalid}\n />\n )}\n >\n {(close) => children(close)}\n </Popover>\n {showClear ? (\n <button\n type=\"button\"\n aria-label={clearLabel}\n onClick={onClear}\n className=\"absolute end-1.5 top-1/2 -translate-y-1/2 rounded p-1 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </button>\n ) : (\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]\"\n />\n )}\n </div>\n );\n}\n\n/** One ‹ / › day-step button, sized to sit flush beside the field. */\nfunction StepButton({\n icon: Icon,\n label,\n disabled,\n onClick,\n mirror,\n className,\n}: {\n icon: ComponentType<{ className?: string }>;\n label: string;\n disabled: boolean;\n onClick: () => void;\n /** Flip the icon in RTL — for the ‹ › day steps, whose \"previous\" points to the\n * reading start. Not for the today icon, which has no direction. */\n mirror?: boolean;\n /** The button's corners in the joined group (see DatePicker). */\n className?: string;\n}) {\n return (\n <Tooltip label={label} portal>\n <button\n type=\"button\"\n aria-label={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n FIELD_BASE,\n // FIELD_BASE is `block w-full` for text inputs; a step button is neither, and\n // px-3 would make it wider than it needs to be. twMerge lets these win.\n \"relative flex h-full w-10 shrink-0 items-center justify-center px-0 text-[var(--text-muted)]\",\n \"hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]\",\n // Raised while hovered or focused, so the shared border and the focus ring are\n // drawn on top of the neighbour they overlap by a pixel.\n \"hover:z-10 focus-visible:z-10\",\n // Dimmed ICON, not a dimmed box: at 40% opacity the whole button — border\n // included — faded out of the group and read as a broken, detached control.\n \"disabled:cursor-not-allowed disabled:text-[var(--text-placeholder)] disabled:opacity-60 disabled:hover:bg-[var(--bg-surface)]\",\n className,\n )}\n >\n <Icon className={cn(\"size-4\", mirror && \"rtl:-scale-x-100\")} />\n </button>\n </Tooltip>\n );\n}\n\nexport interface DatePickerProps extends DatePickerBaseProps {\n /** ISO \"YYYY-MM-DD\", or \"\" for empty. */\n value: string;\n onChange: (iso: string) => void;\n /**\n * Flank the field with ‹ › buttons that move the value one day. For fields whose\n * typical edit is a day or two — opening a calendar to go \"yesterday\" is three\n * interactions for what should be one.\n *\n * Off by default: on a filter or a far-away date the buttons are dead weight, and\n * they cost ~5rem of width that a narrow layout may not have.\n */\n step?: boolean;\n /** Accessible names for the step buttons. Optional: they resolve from\n * `datePicker.previousDay` / `nextDay` in `<UiKitProvider labels>`, then from the\n * English defaults. This prop wins over both, for the one field that says it\n * differently. */\n stepLabels?: { prev: string; next: string };\n /**\n * Today's date as ISO \"YYYY-MM-DD\". Adds a jump-to-today button beside the field,\n * disabled once the value already is that day.\n *\n * Passed in rather than read from a clock here: a component that decides what \"today\"\n * is cannot be tested at a fixed date, and the consumer already has one definition of\n * it. The button is always RENDERED — going flat instead of disappearing — because a\n * control that comes and goes with the value reflows whatever sits beside it, which\n * is the report this exists to answer.\n */\n today?: string;\n /** Accessible name for the today button; wins over `datePicker.today`. */\n todayLabel?: string;\n}\n\n/**\n * The picker's own label props, folded into one `datePicker` override so they sit at\n * the top of the same precedence chain as everything else: prop > provider > English.\n * They predate the provider and stay public API, which is why they are not simply\n * replaced by a `labels` prop.\n */\nfunction useDatePickerLabels(\n clearLabel: string | undefined,\n stepLabels: { prev: string; next: string } | undefined,\n todayLabel: string | undefined,\n): DatePickerLabels {\n const prev = stepLabels?.prev;\n const next = stepLabels?.next;\n const fromProps = useMemo(() => {\n const out: Partial<DatePickerLabels> = {};\n if (clearLabel !== undefined) out.clear = clearLabel;\n if (prev !== undefined) out.previousDay = prev;\n if (next !== undefined) out.nextDay = next;\n if (todayLabel !== undefined) out.today = todayLabel;\n return out;\n }, [clearLabel, prev, next, todayLabel]);\n return useKitLabels(\"datePicker\", DEFAULT_DATE_PICKER_LABELS, fromProps);\n}\n\n/** Single-date picker: a field showing the formatted date, opening a calendar. */\nexport function DatePicker({\n value,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n step,\n stepLabels,\n today,\n todayLabel,\n className,\n calendarLabels,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n ...rest\n}: DatePickerProps) {\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, stepLabels, todayLabel);\n const render = formatValue ?? ((iso: string) => formatDate(iso, locale, formatOptions));\n // With a step or a today button beside it the field is no longer the outermost\n // element — the flex row at the bottom is, and a caller's attributes belong on\n // whichever of the two is actually the root. Naming the shared props rather than\n // letting `...rest` carry them is what makes that choice possible.\n const wrapped = Boolean(step || today);\n const [triggerAria, wrapperRest] = splitTriggerAria(rest);\n const field = (\n <DateField\n // With step/today buttons the flex row is the root and takes the caller's props —\n // except the ones that name the field, which still belong on its trigger.\n {...(wrapped ? triggerAria : rest)}\n label={label}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.panel}\n disabled={disabled}\n invalid={invalid}\n className={step ? \"min-w-0 flex-1\" : className}\n hasValue={Boolean(value)}\n triggerText={value ? render(value) : (placeholder ?? \"\")}\n onClear={() => onChange(\"\")}\n >\n {(close) => (\n <MiniCalendar\n mode=\"single\"\n focusOnOpen\n from={value}\n to={value}\n locale={locale}\n min={min}\n max={max}\n labels={calendarLabels}\n onSelect={(iso) => {\n onChange(iso);\n close();\n }}\n />\n )}\n </DateField>\n );\n if (!wrapped) return field;\n\n // An empty field has nothing to step from, so both buttons are dead until a date\n // is picked. Bounds are compared as strings: \"YYYY-MM-DD\" sorts chronologically.\n const target = (days: number) => (value ? addDaysIso(value, days) : \"\");\n const outOfBounds = (iso: string) => Boolean((min && iso < min) || (max && iso > max));\n const blocked = (days: number) => {\n const next = target(days);\n if (!next || disabled) return true;\n return outOfBounds(next);\n };\n return (\n // ONE joined control, not three boxes in a row: the buttons and the field share\n // their borders (each piece overlaps the previous by a pixel) and only the outer\n // ends are rounded — the button-group shape, so ‹ date › reads as one field with\n // controls rather than a field with loose buttons parked beside it, which is what\n // the `gap-1` version looked like.\n //\n // items-stretch, not items-center: the buttons match the field's height, which\n // varies with whether it carries a floating label. The field's trigger is nested\n // inside DateField, so its inner corners are squared from here.\n <div\n {...wrapperRest}\n className={cn(\n \"flex items-stretch [&>*:not(:first-child)]:-ms-px\",\n step && \"[&_[role=combobox]]:rounded-s-none\",\n (step || today) && \"[&_[role=combobox]]:rounded-e-none\",\n \"[&_[role=combobox]:focus-visible]:relative [&_[role=combobox]:focus-visible]:z-10\",\n className,\n )}\n >\n {step && (\n <StepButton\n icon={ChevronLeft}\n mirror\n label={text.previousDay}\n disabled={blocked(-1)}\n onClick={() => onChange(target(-1))}\n className=\"rounded-e-none\"\n />\n )}\n {field}\n {step && (\n <StepButton\n icon={ChevronRight}\n mirror\n label={text.nextDay}\n disabled={blocked(1)}\n onClick={() => onChange(target(1))}\n className={cn(\"rounded-s-none\", today && \"rounded-e-none\")}\n />\n )}\n {today && (\n <StepButton\n icon={CalendarClock}\n label={text.today}\n disabled={Boolean(disabled) || value === today || outOfBounds(today)}\n onClick={() => onChange(today)}\n className=\"rounded-s-none\"\n />\n )}\n </div>\n );\n}\n\nexport interface DateRangePickerPreset {\n label: ReactNode;\n from: string;\n to: string;\n}\n\nexport interface DateRangePickerProps extends DatePickerBaseProps {\n from: string;\n to: string;\n onChange: (from: string, to: string) => void;\n /** Separator between the two formatted dates in the trigger (default \" – \"). */\n separator?: string;\n /** Optional named ranges rendered as a left column (last month / YTD / …). */\n presets?: DateRangePickerPreset[];\n}\n\n/** Two-date range picker: click a start then an end; closes once both are set.\n * With `presets`, a left column of named ranges is shown beside the calendar. */\nexport function DateRangePicker({\n from,\n to,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n separator = \" – \",\n presets,\n calendarLabels,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n ...rest\n}: DateRangePickerProps) {\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, undefined, undefined);\n const render = formatValue ?? ((iso: string) => formatDate(iso, locale, formatOptions));\n const a = from ? render(from) : \"\";\n const b = to ? render(to) : \"\";\n const triggerText = from\n ? to\n ? `${a}${separator}${b}`\n : `${a}${separator}…`\n : (placeholder ?? \"\");\n const hasPresets = Boolean(presets && presets.length > 0);\n const calendar = (close: () => void) => (\n <MiniCalendar\n focusOnOpen\n from={from}\n to={to}\n locale={locale}\n min={min}\n max={max}\n labels={calendarLabels}\n onSelect={(f, t) => {\n onChange(f, t);\n // Two-click range: only dismiss once both ends are chosen.\n if (f && t) close();\n }}\n />\n );\n return (\n <DateField\n {...rest}\n label={label}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.rangePanel}\n disabled={disabled}\n invalid={invalid}\n // Widen so the preset column sits beside the calendar (default otherwise).\n width={hasPresets ? 440 : undefined}\n hasValue={Boolean(from || to)}\n triggerText={triggerText}\n onClear={() => onChange(\"\", \"\")}\n >\n {(close) =>\n hasPresets ? (\n <div className=\"flex gap-3\">\n {/* `border-e`/`pe`, not `-r`: the preset column is on the START side, and the\n rule between it and the calendar has to follow it in a right-to-left UI. */}\n <div className=\"flex w-32 shrink-0 flex-col gap-0.5 border-e border-[var(--border)] pe-2\">\n {presets!.map((p, i) => {\n const selected = p.from === from && p.to === to;\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => {\n onChange(p.from, p.to);\n close();\n }}\n className={cn(\n \"rounded px-2 py-1.5 text-start text-xs\",\n selected\n ? \"bg-[var(--bg-active)] font-medium text-[var(--text-primary)]\"\n : \"text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\",\n )}\n >\n {p.label}\n </button>\n );\n })}\n </div>\n {/* `flex-1`: the calendar takes the rest of the panel. Content-sized it was\n seven tiny cells beside a wide preset column. */}\n <div className=\"min-w-0 flex-1\">{calendar(close)}</div>\n </div>\n ) : (\n calendar(close)\n )\n }\n </DateField>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/date-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ComponentType, ReactNode, RefObject } from \"react\";\nimport { Calendar, CalendarClock, ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { addDaysIso, parseIsoDate } from \"../lib/dates\";\nimport {\n DEFAULT_DATE_PICKER_LABELS,\n useKitLabels,\n useKitLocale,\n type DatePickerLabels,\n} from \"../i18n/kit-labels\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { Button, FieldLabel, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { MiniCalendar, type MiniCalendarProps } from \"./mini-calendar\";\nimport { Popover } from \"./popover\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * What both pickers share — and, through `ComponentPropsWithoutRef<\"div\">`, everything a\n * `<div>` takes, so a `data-tour` anchor, a test id or an `aria-describedby` reaches the\n * field's root element instead of being dropped (audit §api-design).\n *\n * `onChange` is omitted from those attributes: the DOM event of that name carries a\n * `FormEvent`, while both pickers answer with ISO date strings. Each declares its own\n * below.\n */\ninterface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /** BCP 47 tag for the trigger's date and the calendar. Falls back to\n * `<UiKitProvider locale>`, then to the runtime's default. */\n locale?: string;\n /** Embedded top-boundary label, matching the native Input/Select fields. */\n label?: ReactNode;\n placeholder?: string;\n /** Inclusive ISO bounds for selectable days. */\n min?: string;\n max?: string;\n /** Show an X to clear the value (emits `\"\"`). */\n clearable?: boolean;\n /** Accessible name of the clear button. Wins over `datePicker.clear` from\n * `<UiKitProvider labels>`; kept as a prop because it predates the provider. */\n clearLabel?: string;\n disabled?: boolean;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n className?: string;\n /** Intl options for the trigger's formatted date (default: locale short date). */\n formatOptions?: Intl.DateTimeFormatOptions;\n /** Render the trigger's text for an ISO date yourself; wins over `formatOptions`.\n * For a host whose date rendering is more than one `Intl` call can say — Keksdose\n * puts the weekday's name in the UI language beside digits ordered by a separate\n * format preference, two locales in one string. `locale` still drives the calendar. */\n formatValue?: (iso: string) => string;\n /** Passed through to the {@link MiniCalendar} this opens. Its month arrows are\n * icon-only, so their `aria-label` is the only name they have — and the package\n * ships English defaults, which a translating host has to be able to replace.\n * Usually unnecessary now: the calendar reads `miniCalendar` from the provider. */\n calendarLabels?: MiniCalendarProps[\"labels\"];\n}\n\n/** Format an ISO date for the trigger. `lib/dates`' `formatIsoDate` insists on a\n * locale, and here there may be none — no prop and no provider — which `Intl` reads\n * as \"the runtime's default\", as every other kit formatter does. */\nfunction formatDate(iso: string, locale: string | undefined, options?: Intl.DateTimeFormatOptions) {\n return parseIsoDate(iso)?.toLocaleDateString(locale, options) ?? \"\";\n}\n\n/**\n * The field-shaped button that opens the calendar.\n *\n * A component of its own rather than JSX inline in `Popover`'s `trigger` render prop,\n * because handing focus back to it is an effect and a render prop is not somewhere a\n * hook can go.\n */\nfunction DateFieldTrigger({\n open,\n toggle,\n triggerRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n padded,\n disabled,\n invalid,\n panelId,\n aria,\n}: {\n /** The caller's naming/description attributes, routed here from the field. */\n aria: TriggerAria;\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n /** The panel's id — `role=\"combobox\"` requires `aria-controls`. */\n panelId: string;\n triggerText: string;\n hasValue: boolean;\n /** ids of the hidden label twin + the value span, in the order they are spoken. */\n labelledBy?: string;\n valueId: string;\n padded: boolean;\n disabled?: boolean;\n invalid?: boolean;\n}) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when the panel still HAD focus. The day the user was standing on has just\n // been unmounted with the panel, which drops focus to <body> and leaves a\n // keyboard user back at the top of the document with no idea where they were.\n // But an outside click closes this panel too, and that click has already given\n // focus to whatever it landed on — dragging it back here would be worse than the\n // bug, so a focus that is somewhere real is left alone.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n // Here rather than through `useFocusTrap`'s `restoreFocus`, and not only because\n // `Popover` runs no trap to carry it: that option restores to whatever held focus\n // when the panel opened, and on Safari — and on Firefox on macOS — clicking a\n // button does not focus it, so \"whatever held focus\" is the page. The requirement\n // is this trigger, every time, however the panel was opened.\n }, [open, triggerRef]);\n\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n // The name has to carry the VALUE. A bare `aria-label={label}` — which is what\n // this was — replaces the button's text outright, so the one field in the form\n // whose entire job is to show a date announced \"Due date\" and stopped there,\n // and a screen-reader user could not read back what they had picked without\n // opening the calendar and hunting for the selected day.\n //\n // Two ids rather than one composed string (`common.fieldValue`): \"label: value\"\n // is a sentence, and a name reference is spoken as the two texts in order with\n // no punctuation to translate at all. The hidden twin of the FieldLabel lives\n // in DateField, next to the label it copies.\n id={aria.id}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n // `role=\"combobox\"` on a button that opens a calendar is the APG date-picker\n // shape, and it is what makes the next two lines legal: `button` supports\n // neither `aria-expanded` nor `aria-invalid`, so the previous markup set an\n // invalid state that announced nothing (ESLint's `role-supports-aria-props`).\n //\n // `aria-haspopup=\"dialog\"` is now honest — it was not when this comment first\n // said so. `Popover` gained a real `role=\"dialog\"` in the same wave, so the\n // trigger's promise and the panel's role finally agree.\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span id={valueId} className={cn(\"truncate\", !hasValue && \"text-[var(--text-placeholder)]\")}>\n {/* `|| \" \"` (a NON-BREAKING space) — triggerText is \"\" when there is no value and no\n placeholder was passed. An empty span has no line box, so the\n trigger collapses to its padding and sits shorter than every\n other field beside it.\n\n It has to be a NON-BREAKING space. This read `|| \" \"` first, and\n that fix did nothing at all: an ordinary space is collapsible\n white space, and white space at the start and end of a line is\n removed — so the span rendered with height 0 and the trigger\n stayed 22px against the 42px of the fields either side of it\n (Keksdose live #294, measured in the browser: `span.textContent`\n WAS `\" \"` and `getBoundingClientRect().height` was 0). U+00A0 is\n not collapsible, so it holds the line. */}\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n/** A field-styled trigger that opens a portalled MiniCalendar. Shared by the\n * single- and range-date pickers below. */\nfunction DateField({\n triggerText,\n hasValue,\n label,\n disabled,\n clearable,\n clearLabel,\n panelLabel,\n className,\n width,\n onClear,\n invalid,\n children,\n ...rest\n}: Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> & {\n triggerText: string;\n hasValue: boolean;\n label?: ReactNode;\n disabled?: boolean;\n clearable?: boolean;\n /** Already resolved by the picker (prop > provider > English). */\n clearLabel: string;\n /** The popover panel's accessible name, resolved likewise. */\n panelLabel: string;\n invalid?: boolean;\n className?: string;\n /** Popover panel width; omit for the default (a bare calendar). */\n width?: number;\n onClear: () => void;\n children: (close: () => void) => ReactNode;\n}) {\n const showClear = Boolean(clearable && hasValue && !disabled);\n const [aria, wrapperRest] = splitTriggerAria(rest);\n // The panel is portalled to <body> and leaves the subtree whose `dir` it inherited,\n // so a calendar in an RTL form opened LTR — the grid's columns, its arrow keys and\n // its chevrons all the wrong way round. Read at the moment of opening (the only way\n // the popover opens is through `toggle`) and put back on the panel.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const id = useId();\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can be reused as a name; a ReactNode may be a whole row with\n // its own interactive \"?\" hint in it, and naming a button with that reads the hint\n // out as part of the field's name.\n const named = typeof label === \"string\";\n return (\n // The caller's attributes land here, on the field's own box — the trigger inside is\n // named by `aria-labelledby` and must keep the id pair it is given.\n <div {...wrapperRest} ref={rootRef} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n {/* The visible FieldLabel is a plain span, not a `<label htmlFor>`, so it names\n nothing on its own — this hidden twin is what the trigger is named by.\n `aria-hidden` because a name reference reads a hidden element deliberately\n (accname §4.3.1), and without it the label would be announced twice: once as\n loose text before the button, once inside the button's own name.\n `sr-only-fixed` rather than `sr-only` — see the note on the class in\n tokens.css. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width={width}\n panelId={panelId}\n // Named for what it is. Unnamed, it fell back to `popover.panel`, and a date\n // field announced its calendar as \"Popover\" — in English, in every language.\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <DateFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n triggerText={triggerText}\n hasValue={hasValue}\n // ALWAYS a name reference, never `undefined`. A `<button>` takes its\n // accessible name from its contents, so an unlabelled trigger used to\n // announce its own date text for free. `role=\"combobox\"` does not —\n // content is excluded from name computation for that role — so leaving\n // this undefined made an unlabelled date field announce nothing at all.\n // Named: \"label, value\". Unnamed: the value alone, which is what the\n // button was saying before.\n //\n // A caller's own reference comes first. And when the caller gives the field\n // an `id` (so an external `<label htmlFor>` can point at it), the trigger\n // lists ITSELF first: a self-reference in `aria-labelledby` is resolved\n // from the element's native label (accname 2B → 2D), so \"Due date\" from\n // that <label> is spoken before the value, instead of being overridden.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n panelId={panelId}\n valueId={valueId}\n padded={label !== undefined}\n disabled={disabled}\n invalid={invalid}\n />\n )}\n >\n {(close) => children(close)}\n </Popover>\n {showClear ? (\n <button\n type=\"button\"\n aria-label={clearLabel}\n onClick={onClear}\n className=\"absolute end-1.5 top-1/2 -translate-y-1/2 rounded p-1 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </button>\n ) : (\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]\"\n />\n )}\n </div>\n );\n}\n\n/** One ‹ / › day-step button, sized to sit flush beside the field. */\nfunction StepButton({\n icon: Icon,\n label,\n disabled,\n onClick,\n mirror,\n className,\n}: {\n icon: ComponentType<{ className?: string }>;\n label: string;\n disabled: boolean;\n onClick: () => void;\n /** Flip the icon in RTL — for the ‹ › day steps, whose \"previous\" points to the\n * reading start. Not for the today icon, which has no direction. */\n mirror?: boolean;\n /** The button's corners in the joined group (see DatePicker). */\n className?: string;\n}) {\n return (\n <Tooltip label={label} portal>\n <button\n type=\"button\"\n aria-label={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n FIELD_BASE,\n // FIELD_BASE is `block w-full` for text inputs; a step button is neither, and\n // px-3 would make it wider than it needs to be. twMerge lets these win.\n \"relative flex h-full w-10 shrink-0 items-center justify-center px-0 text-[var(--text-muted)]\",\n \"hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]\",\n // Raised while hovered or focused, so the shared border and the focus ring are\n // drawn on top of the neighbour they overlap by a pixel.\n \"hover:z-10 focus-visible:z-10\",\n // Dimmed ICON, not a dimmed box: at 40% opacity the whole button — border\n // included — faded out of the group and read as a broken, detached control.\n \"disabled:cursor-not-allowed disabled:text-[var(--text-placeholder)] disabled:opacity-60 disabled:hover:bg-[var(--bg-surface)]\",\n className,\n )}\n >\n <Icon className={cn(\"size-4\", mirror && \"rtl:-scale-x-100\")} />\n </button>\n </Tooltip>\n );\n}\n\nexport interface DatePickerProps extends DatePickerBaseProps {\n /** ISO \"YYYY-MM-DD\", or \"\" for empty. */\n value: string;\n onChange: (iso: string) => void;\n /**\n * Flank the field with ‹ › buttons that move the value one day. For fields whose\n * typical edit is a day or two — opening a calendar to go \"yesterday\" is three\n * interactions for what should be one.\n *\n * Off by default: on a filter or a far-away date the buttons are dead weight, and\n * they cost ~5rem of width that a narrow layout may not have.\n */\n step?: boolean;\n /** Accessible names for the step buttons. Optional: they resolve from\n * `datePicker.previousDay` / `nextDay` in `<UiKitProvider labels>`, then from the\n * English defaults. This prop wins over both, for the one field that says it\n * differently. */\n stepLabels?: { prev: string; next: string };\n /**\n * Today's date as ISO \"YYYY-MM-DD\". Adds a jump-to-today button beside the field,\n * disabled once the value already is that day.\n *\n * Passed in rather than read from a clock here: a component that decides what \"today\"\n * is cannot be tested at a fixed date, and the consumer already has one definition of\n * it. The button is always RENDERED — going flat instead of disappearing — because a\n * control that comes and goes with the value reflows whatever sits beside it, which\n * is the report this exists to answer.\n */\n today?: string;\n /** Accessible name for the today button; wins over `datePicker.today`. */\n todayLabel?: string;\n}\n\n/**\n * The picker's own label props, folded into one `datePicker` override so they sit at\n * the top of the same precedence chain as everything else: prop > provider > English.\n * They predate the provider and stay public API, which is why they are not simply\n * replaced by a `labels` prop.\n */\nfunction useDatePickerLabels(\n clearLabel: string | undefined,\n stepLabels: { prev: string; next: string } | undefined,\n todayLabel: string | undefined,\n): DatePickerLabels {\n const prev = stepLabels?.prev;\n const next = stepLabels?.next;\n const fromProps = useMemo(() => {\n const out: Partial<DatePickerLabels> = {};\n if (clearLabel !== undefined) out.clear = clearLabel;\n if (prev !== undefined) out.previousDay = prev;\n if (next !== undefined) out.nextDay = next;\n if (todayLabel !== undefined) out.today = todayLabel;\n return out;\n }, [clearLabel, prev, next, todayLabel]);\n return useKitLabels(\"datePicker\", DEFAULT_DATE_PICKER_LABELS, fromProps);\n}\n\n/** Single-date picker: a field showing the formatted date, opening a calendar. */\nexport function DatePicker({\n value,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n step,\n stepLabels,\n today,\n todayLabel,\n className,\n calendarLabels,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n ...rest\n}: DatePickerProps) {\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, stepLabels, todayLabel);\n const render = formatValue ?? ((iso: string) => formatDate(iso, locale, formatOptions));\n // With a step or a today button beside it the field is no longer the outermost\n // element — the flex row at the bottom is, and a caller's attributes belong on\n // whichever of the two is actually the root. Naming the shared props rather than\n // letting `...rest` carry them is what makes that choice possible.\n const wrapped = Boolean(step || today);\n const [triggerAria, wrapperRest] = splitTriggerAria(rest);\n const field = (\n <DateField\n // With step/today buttons the flex row is the root and takes the caller's props —\n // except the ones that name the field, which still belong on its trigger.\n {...(wrapped ? triggerAria : rest)}\n label={label}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.panel}\n disabled={disabled}\n invalid={invalid}\n className={step ? \"min-w-0 flex-1\" : className}\n hasValue={Boolean(value)}\n triggerText={value ? render(value) : (placeholder ?? \"\")}\n onClear={() => onChange(\"\")}\n >\n {(close) => (\n <MiniCalendar\n mode=\"single\"\n focusOnOpen\n from={value}\n to={value}\n locale={locale}\n min={min}\n max={max}\n labels={calendarLabels}\n onSelect={(iso) => {\n onChange(iso);\n close();\n }}\n />\n )}\n </DateField>\n );\n if (!wrapped) return field;\n\n // An empty field has nothing to step from, so both buttons are dead until a date\n // is picked. Bounds are compared as strings: \"YYYY-MM-DD\" sorts chronologically.\n const target = (days: number) => (value ? addDaysIso(value, days) : \"\");\n const outOfBounds = (iso: string) => Boolean((min && iso < min) || (max && iso > max));\n const blocked = (days: number) => {\n const next = target(days);\n if (!next || disabled) return true;\n return outOfBounds(next);\n };\n return (\n // ONE joined control, not three boxes in a row: the buttons and the field share\n // their borders (each piece overlaps the previous by a pixel) and only the outer\n // ends are rounded — the button-group shape, so ‹ date › reads as one field with\n // controls rather than a field with loose buttons parked beside it, which is what\n // the `gap-1` version looked like.\n //\n // items-stretch, not items-center: the buttons match the field's height, which\n // varies with whether it carries a floating label. The field's trigger is nested\n // inside DateField, so its inner corners are squared from here.\n <div\n {...wrapperRest}\n className={cn(\n \"flex items-stretch [&>*:not(:first-child)]:-ms-px\",\n step && \"[&_[role=combobox]]:rounded-s-none\",\n (step || today) && \"[&_[role=combobox]]:rounded-e-none\",\n \"[&_[role=combobox]:focus-visible]:relative [&_[role=combobox]:focus-visible]:z-10\",\n className,\n )}\n >\n {step && (\n <StepButton\n icon={ChevronLeft}\n mirror\n label={text.previousDay}\n disabled={blocked(-1)}\n onClick={() => onChange(target(-1))}\n className=\"rounded-e-none\"\n />\n )}\n {field}\n {step && (\n <StepButton\n icon={ChevronRight}\n mirror\n label={text.nextDay}\n disabled={blocked(1)}\n onClick={() => onChange(target(1))}\n className={cn(\"rounded-s-none\", today && \"rounded-e-none\")}\n />\n )}\n {today && (\n <StepButton\n icon={CalendarClock}\n label={text.today}\n disabled={Boolean(disabled) || value === today || outOfBounds(today)}\n onClick={() => onChange(today)}\n className=\"rounded-s-none\"\n />\n )}\n </div>\n );\n}\n\nexport interface DateRangePickerPreset {\n /**\n * The preset's identity, independent of its dates — `\"last_3_months\"`, not two ISO\n * strings. Optional for the 0.x callers that pass anonymous pairs.\n *\n * keksdose's report range field (report-range-field.tsx, \"Why this is app-level\")\n * is why it exists: its URL stores `?preset=last_3_months` and recomputes the dates\n * from today on every read, so a preset that is only its dates froze \"last 3 months\"\n * at whatever day the panel rendered, and two presets that happen to cover the same\n * days (this week and today, on a Monday) could not be told apart. With an `id` the\n * choice is reported to `onChange` and shown as selected for as long as the range\n * still matches it. Recompute `from`/`to` on every render (`calendarMonthPresets()`,\n * `dateRangePresets()`) and pass the key as the id.\n */\n id?: string;\n label: ReactNode;\n from: string;\n to: string;\n}\n\n/**\n * When the picker hands its range to `onChange`.\n *\n * - `\"immediate\"` (the default, and all of 0.7): a preset click commits and closes,\n * and each calendar click is reported as it happens — the half-made `(from, \"\")`\n * included. The right shape for quick ranges on a filter, where one click is one\n * answer.\n * - `\"apply\"`: the panel works on a DRAFT. A preset ARMS the calendar instead of\n * committing, days can then be nudged (\"last 3 months, but end it last Friday\"),\n * and nothing reaches `onChange` until Apply — which stays disabled until both ends\n * exist. Cancel, Escape and an outside click discard the draft, and every open\n * starts again from the committed value. keksdose's report range asked for exactly\n * this: each commit there re-runs a whole page of charts, so an intermediate state\n * must never escape the panel.\n */\nexport type DateRangeCommit = \"immediate\" | \"apply\";\n\nexport interface DateRangePickerProps extends DatePickerBaseProps {\n from: string;\n to: string;\n /** The committed range. `presetId` is the chosen preset's `id` when a preset\n * produced it (and the range was not nudged since), `undefined` for days picked by\n * hand. A 0.x two-argument handler simply ignores it. */\n onChange: (from: string, to: string, presetId?: string) => void;\n /** Separator between the two formatted dates in the trigger (default \" – \"). */\n separator?: string;\n /** Optional named ranges rendered as a start-side column (last month / YTD / …). */\n presets?: DateRangePickerPreset[];\n /**\n * The active preset's `id`, controlled — e.g. straight from keksdose's `?preset=`.\n * `null` (or an id no preset has, such as `\"custom\"`) says \"no preset\": nothing in\n * the column is marked even if a preset happens to cover the same days. A preset is\n * marked only while its dates still equal `from`/`to`.\n *\n * Leave it `undefined` and the picker remembers the last preset it committed\n * itself; with no memory at all it falls back to the first preset whose dates match.\n */\n preset?: string | null;\n /** See {@link DateRangeCommit}. Default `\"immediate\"`. */\n commit?: DateRangeCommit;\n}\n\ninterface RangeDraft {\n from: string;\n to: string;\n presetId: string | undefined;\n}\n\n/** Which preset (by index) the column marks, or -1. See `DateRangePickerProps.preset`. */\nfunction markedPreset(\n presets: readonly DateRangePickerPreset[],\n from: string,\n to: string,\n activeId: string | null | undefined,\n controlled: boolean,\n): number {\n const matches = (p: DateRangePickerPreset) => p.from === from && p.to === to;\n if (activeId != null) {\n const byId = presets.findIndex((p) => p.id === activeId && matches(p));\n if (byId !== -1 || controlled) return byId;\n } else if (controlled) {\n return -1;\n }\n return presets.findIndex(matches);\n}\n\n/**\n * The preset column: a named group of toggle buttons, `aria-pressed` on the marked one.\n * Pressed-state rather than colour alone, because \"which range is this\" is the one\n * thing a screen-reader user opening the panel needs to hear.\n */\nfunction PresetColumn({\n presets,\n marked,\n label,\n onPick,\n}: {\n presets: readonly DateRangePickerPreset[];\n marked: number;\n label: string;\n onPick: (p: DateRangePickerPreset) => void;\n}) {\n return (\n // `border-e`/`pe`, not `-r`: the preset column is on the START side, and the rule\n // between it and the calendar has to follow it in a right-to-left UI.\n <div\n role=\"group\"\n aria-label={label}\n className=\"flex w-32 shrink-0 flex-col gap-0.5 border-e border-[var(--border)] pe-2\"\n >\n {presets.map((p, i) => {\n const selected = i === marked;\n return (\n <button\n key={p.id ?? i}\n type=\"button\"\n aria-pressed={selected}\n data-preset={p.id}\n onClick={() => onPick(p)}\n className={cn(\n \"rounded px-2 py-1.5 text-start text-xs\",\n selected\n ? \"bg-[var(--bg-active)] font-medium text-[var(--text-primary)]\"\n : \"text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\",\n )}\n >\n {p.label}\n </button>\n );\n })}\n </div>\n );\n}\n\n/**\n * The panel's body. A component of its own so `commit=\"apply\"` can keep its draft in\n * state that is born when the panel opens: `Popover` unmounts its children on close,\n * so the draft is re-seeded from the committed value on every open, and an abandoned\n * half-selection cannot survive into the next one.\n */\nfunction RangePanel({\n from,\n to,\n presets,\n activeId,\n controlled,\n commit,\n close,\n commitRange,\n calendarProps,\n labels,\n}: {\n from: string;\n to: string;\n presets: readonly DateRangePickerPreset[] | undefined;\n activeId: string | null | undefined;\n controlled: boolean;\n commit: DateRangeCommit;\n close: () => void;\n commitRange: (from: string, to: string, presetId: string | undefined) => void;\n calendarProps: Pick<MiniCalendarProps, \"locale\" | \"min\" | \"max\" | \"labels\">;\n labels: DatePickerLabels;\n}) {\n const drafting = commit === \"apply\";\n const [draft, setDraft] = useState<RangeDraft>({\n from,\n to,\n presetId: activeId ?? undefined,\n });\n // What the panel SHOWS: the draft while drafting, the committed value otherwise.\n const shown = drafting ? draft : { from, to, presetId: activeId ?? undefined };\n const marked = presets\n ? markedPreset(presets, shown.from, shown.to, drafting ? draft.presetId : activeId, drafting || controlled)\n : -1;\n const complete = Boolean(draft.from && draft.to);\n\n const calendar = (\n <MiniCalendar\n {...calendarProps}\n focusOnOpen\n from={shown.from}\n to={shown.to}\n onSelect={(f, t) => {\n if (drafting) {\n setDraft({ from: f, to: t, presetId: undefined });\n return;\n }\n commitRange(f, t, undefined);\n // Two-click range: only dismiss once both ends are chosen.\n if (f && t) close();\n }}\n />\n );\n\n const body =\n presets && presets.length > 0 ? (\n <div className=\"flex gap-3\">\n <PresetColumn\n presets={presets}\n marked={marked}\n label={labels.presets}\n onPick={(p) => {\n if (drafting) {\n setDraft({ from: p.from, to: p.to, presetId: p.id });\n return;\n }\n commitRange(p.from, p.to, p.id);\n close();\n }}\n />\n {/* `flex-1`: the calendar takes the rest of the panel. Content-sized it was\n seven tiny cells beside a wide preset column. */}\n <div className=\"min-w-0 flex-1\">{calendar}</div>\n </div>\n ) : (\n calendar\n );\n\n if (!drafting) return body;\n return (\n <div className=\"flex flex-col gap-2\">\n {body}\n <div className=\"flex items-center justify-end gap-2 border-t border-[var(--border)] pt-2\">\n <Button type=\"button\" variant=\"ghost\" onClick={close}>\n {labels.cancel}\n </Button>\n <Button\n type=\"button\"\n // A half-made `(from, \"\")` would commit a window with no end — a blank\n // report, one click into a two-click gesture.\n disabled={!complete}\n onClick={() => {\n if (!complete) return;\n commitRange(draft.from, draft.to, draft.presetId);\n close();\n }}\n >\n {labels.apply}\n </Button>\n </div>\n </div>\n );\n}\n\n/** Two-date range picker: click a start then an end; closes once both are set.\n * With `presets`, a column of named ranges is shown beside the calendar; with\n * `commit=\"apply\"` the panel drafts and commits on Apply (see {@link DateRangeCommit}). */\nexport function DateRangePicker({\n from,\n to,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n separator = \" – \",\n presets,\n preset,\n commit = \"immediate\",\n calendarLabels,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n ...rest\n}: DateRangePickerProps) {\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, undefined, undefined);\n const render = formatValue ?? ((iso: string) => formatDate(iso, locale, formatOptions));\n // The preset this picker committed itself, for a caller that does not control\n // `preset`. Cleared by a hand-picked range or a clear.\n const [ownPreset, setOwnPreset] = useState<string | undefined>(undefined);\n const controlled = preset !== undefined;\n const activeId = controlled ? preset : ownPreset;\n const a = from ? render(from) : \"\";\n const b = to ? render(to) : \"\";\n const triggerText = from\n ? to\n ? `${a}${separator}${b}`\n : `${a}${separator}…`\n : (placeholder ?? \"\");\n const hasPresets = Boolean(presets && presets.length > 0);\n const commitRange = (f: string, t: string, presetId: string | undefined) => {\n setOwnPreset(presetId);\n // Two arguments when no preset is involved — exactly the 0.7 call, so a caller's\n // `toHaveBeenCalledWith(from, to)` and a variadic handler see no difference.\n if (presetId === undefined) onChange(f, t);\n else onChange(f, t, presetId);\n };\n return (\n <DateField\n {...rest}\n label={label}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.rangePanel}\n disabled={disabled}\n invalid={invalid}\n // Widen so the preset column sits beside the calendar (default otherwise).\n width={hasPresets ? 440 : undefined}\n hasValue={Boolean(from || to)}\n triggerText={triggerText}\n onClear={() => commitRange(\"\", \"\", undefined)}\n >\n {(close) => (\n <RangePanel\n from={from}\n to={to}\n presets={presets}\n activeId={activeId}\n controlled={controlled}\n commit={commit}\n close={close}\n commitRange={commitRange}\n calendarProps={{ locale, min, max, labels: calendarLabels }}\n labels={text}\n />\n )}\n </DateField>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,60 @@
1
+ import * as react from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+
4
+ type DescriptionListLayout = "rows" | "cards";
5
+ type DescriptionListDensity = "comfortable" | "compact";
6
+ interface DescriptionListProps extends ComponentPropsWithoutRef<"dl"> {
7
+ /**
8
+ * `rows` (default): term beside detail, a two-column list that stacks the pair
9
+ * when the LIST is narrower than 24rem — a container query, so a detail panel
10
+ * beside a sidebar stacks where it is actually cramped, not where the viewport is.
11
+ * `cards`: a grid of small bordered cards, term above body — keksdose's overview
12
+ * figures, lenkbank's account facts.
13
+ */
14
+ layout?: DescriptionListLayout;
15
+ /** Every detail is a figure: tabular digits, end-aligned. A `numeric` list of
16
+ * rows does not stack on a phone — a figure is short, and putting it under its
17
+ * term would double the list's height to gain no width. Per item: see
18
+ * {@link DescriptionItemProps.numeric}. */
19
+ numeric?: boolean;
20
+ /** `compact` tightens the rhythm for a dense side panel. */
21
+ density?: DescriptionListDensity;
22
+ /** `cards` only: the narrowest a card may get before the grid drops a column. */
23
+ minCardWidth?: string;
24
+ }
25
+ /**
26
+ * A real `<dl>` — terms and their details, which is what keksdose's eight and
27
+ * lenkbank's four hand-built "label: value" blocks were, and none of them said so.
28
+ * As a `<dl>` a reader announces "list, 6 items" and pairs each term with its detail;
29
+ * as a stack of `<div>`s it read one undifferentiated run of text.
30
+ *
31
+ * Composed from {@link DescriptionItem} children rather than taking an `items`
32
+ * array. The call sites this replaces are mostly CONDITIONAL — "IBAN, if there is
33
+ * one", "closing date, once closed" — and `{iban && <DescriptionItem …>}` reads as
34
+ * what it means, where an array wants `.filter(Boolean)` and a cast. Children also
35
+ * give each item its own props (a numeric figure in an otherwise textual list, a
36
+ * `data-private` detail) with no parallel config object. A data-driven list loses
37
+ * nothing: `items.map((i) => <DescriptionItem key={i.term} {...i} />)`.
38
+ *
39
+ * Each pair is wrapped in a `<div>`, which HTML permits inside a `<dl>` for exactly
40
+ * this: styling a pair as a unit (a row, a card) without breaking the list.
41
+ */
42
+ declare function DescriptionList({ layout, numeric, density, minCardWidth, className, style, ...rest }: DescriptionListProps): react.JSX.Element;
43
+ interface DescriptionItemProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
44
+ /** What is described: "Opened", "IBAN", "Net rent". */
45
+ term: ReactNode;
46
+ /** The description itself. */
47
+ children?: ReactNode;
48
+ /** The explanation behind a "?" beside the term (a {@link FieldHint}) — the same
49
+ * meaning `hint` has on `StatTile` and the labelled fields. */
50
+ hint?: string;
51
+ /** This detail is a figure (see {@link DescriptionListProps.numeric}); overrides
52
+ * the list's setting either way. */
53
+ numeric?: boolean;
54
+ /** Classes for the `<dd>`. */
55
+ detailClassName?: string;
56
+ }
57
+ /** One term and its detail inside a {@link DescriptionList}. */
58
+ declare function DescriptionItem({ term, children, hint, numeric: numericProp, detailClassName, className, ...rest }: DescriptionItemProps): react.JSX.Element;
59
+
60
+ export { DescriptionItem, type DescriptionItemProps, DescriptionList, type DescriptionListDensity, type DescriptionListLayout, type DescriptionListProps };