@eifi1/ui-kit 0.20.1 → 0.22.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.
- package/dist/chart.d.ts +10 -8
- package/dist/components/account-chips.d.ts +10 -3
- package/dist/components/account-chips.js +5 -2
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings.d.ts +33 -2
- package/dist/components/account-settings.js +43 -21
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +27 -8
- package/dist/components/amount-input.js +15 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +8 -0
- package/dist/components/autocomplete.js +9 -4
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +10 -8
- package/dist/components/calculator.d.ts +10 -8
- package/dist/components/checkbox-group.d.ts +104 -0
- package/dist/components/checkbox-group.js +111 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.ts +19 -0
- package/dist/components/checkbox.js +30 -7
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +18 -0
- package/dist/components/chip.js +47 -10
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox.d.ts +13 -2
- package/dist/components/combobox.js +17 -8
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +66 -161
- package/dist/components/copy-button.d.ts +10 -8
- package/dist/components/country-select.d.ts +124 -0
- package/dist/components/country-select.js +237 -0
- package/dist/components/country-select.js.map +1 -0
- package/dist/components/currency-select.d.ts +17 -1
- package/dist/components/currency-select.js +56 -23
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +66 -166
- package/dist/components/danger-confirm.js +133 -49
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -2
- package/dist/components/data-table-filter-popover.d.ts +1 -2
- package/dist/components/data-table-filters.d.ts +1 -2
- package/dist/components/data-table-labels.d.ts +4 -97
- package/dist/components/data-table-labels.js +9 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table.d.ts +1 -2
- package/dist/components/data-table.js +98 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -5
- package/dist/components/date-picker.js +110 -59
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +8 -1
- package/dist/components/entity-combobox.js +9 -4
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +10 -8
- package/dist/components/field-anatomy.d.ts +95 -0
- package/dist/components/field-anatomy.js +84 -0
- package/dist/components/field-anatomy.js.map +1 -0
- package/dist/components/field-parts.d.ts +114 -0
- package/dist/components/field-parts.js +72 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/file-button.d.ts +10 -8
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +32 -9
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +10 -8
- package/dist/components/form-actions.js +61 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +2 -2
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/iban-input.d.ts +66 -0
- package/dist/components/iban-input.js +125 -0
- package/dist/components/iban-input.js.map +1 -0
- package/dist/components/icon-picker.d.ts +12 -2
- package/dist/components/icon-picker.js +9 -2
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-edit-field.d.ts +167 -0
- package/dist/components/inline-edit-field.js +293 -0
- package/dist/components/inline-edit-field.js.map +1 -0
- package/dist/components/language-select.d.ts +127 -0
- package/dist/components/language-select.js +18 -0
- package/dist/components/language-select.js.map +1 -0
- package/dist/components/line-items.d.ts +25 -3
- package/dist/components/line-items.js +55 -46
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +25 -0
- package/dist/components/list.js +16 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +20 -8
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +290 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +8 -1
- package/dist/components/multi-select.js +9 -4
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +25 -13
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +70 -13
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +10 -8
- package/dist/components/one-time-code-input.d.ts +100 -0
- package/dist/components/one-time-code-input.js +137 -0
- package/dist/components/one-time-code-input.js.map +1 -0
- package/dist/components/phone-input.d.ts +66 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/series-chart.d.ts +10 -8
- package/dist/components/settings-fields.d.ts +10 -8
- package/dist/components/share-card.d.ts +8 -6
- package/dist/components/sign-chip.d.ts +66 -0
- package/dist/components/sign-chip.js +39 -0
- package/dist/components/sign-chip.js.map +1 -0
- package/dist/components/swatch-picker.d.ts +27 -2
- package/dist/components/swatch-picker.js +73 -31
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/switch.d.ts +19 -0
- package/dist/components/switch.js +24 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/text-link.d.ts +10 -8
- package/dist/components/tile-radio.d.ts +68 -13
- package/dist/components/tile-radio.js +3 -3
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +15 -9
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toggle-group.d.ts +47 -1
- package/dist/components/toggle-group.js +110 -69
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/ui.d.ts +10 -8
- package/dist/components/ui.js +280 -78
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -2
- package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +210 -3
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +37 -3
- package/dist/feedback/feedback-thread.js +38 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-NhUm7Zga.d.ts} +70 -5
- package/dist/feedback.d.ts +2 -2
- package/dist/hooks/use-file-drop.d.ts +10 -8
- package/dist/i18n/defaults.d.ts +10 -8
- package/dist/i18n/defaults.js +17 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +10 -8
- package/dist/i18n/german.js +50 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +10 -8
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +10 -8
- package/dist/i18n/locales/de-CH.d.ts +10 -8
- package/dist/i18n/locales/en.d.ts +10 -8
- package/dist/i18n/locales/en.js +9 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +10 -8
- package/dist/i18n/locales/es.js +48 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +10 -8
- package/dist/i18n/locales/fr.js +48 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +10 -8
- package/dist/i18n/locales/hu.js +49 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +10 -8
- package/dist/i18n/locales/it.js +49 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +10 -8
- package/dist/i18n/locales/zh.js +48 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +10 -8
- package/dist/i18n/review.js +8 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -10
- package/dist/index.js +57 -2
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → kit-labels-R8mIAc9N.d.ts} +811 -8
- package/dist/lib/countries.d.ts +43 -0
- package/dist/lib/countries.js +39 -0
- package/dist/lib/countries.js.map +1 -0
- package/dist/lib/decimal-marks.d.ts +46 -0
- package/dist/lib/decimal-marks.js +40 -0
- package/dist/lib/decimal-marks.js.map +1 -0
- package/dist/lib/iban.d.ts +98 -0
- package/dist/lib/iban.js +154 -0
- package/dist/lib/iban.js.map +1 -0
- package/dist/lib/isin.d.ts +30 -0
- package/dist/lib/isin.js +36 -0
- package/dist/lib/isin.js.map +1 -0
- package/dist/lib/phone.d.ts +69 -0
- package/dist/lib/phone.js +121 -0
- package/dist/lib/phone.js.map +1 -0
- package/dist/rhf/fields.d.ts +148 -11
- package/dist/rhf/fields.js +332 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +10 -8
- package/dist/rhf.d.ts +13 -9
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +10 -8
- package/dist/shell/top-bar-brand.d.ts +10 -8
- package/dist/shell/topbar-action-menu.d.ts +13 -1
- package/dist/shell/topbar-action-menu.js +31 -28
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +10 -8
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +10 -8
- package/dist/wizard.d.ts +10 -8
- package/package.json +1 -1
- package/src/components/account-chips.tsx +17 -3
- package/src/components/account-settings.tsx +92 -26
- package/src/components/amount-input.tsx +49 -56
- package/src/components/autocomplete.tsx +133 -110
- package/src/components/checkbox-group.tsx +229 -0
- package/src/components/checkbox.tsx +72 -8
- package/src/components/chip.tsx +80 -10
- package/src/components/combobox.tsx +292 -253
- package/src/components/country-select.tsx +443 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +345 -77
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +173 -62
- package/src/components/entity-combobox.tsx +107 -87
- package/src/components/field-anatomy.tsx +190 -0
- package/src/components/field-parts.tsx +184 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +115 -2
- package/src/components/hover-menu.tsx +7 -4
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +26 -4
- package/src/components/inline-edit-field.tsx +533 -0
- package/src/components/language-select.tsx +78 -0
- package/src/components/line-items.tsx +35 -2
- package/src/components/list.tsx +55 -6
- package/src/components/money-field.tsx +10 -0
- package/src/components/month-picker.tsx +399 -63
- package/src/components/multi-select.tsx +80 -58
- package/src/components/number-field.tsx +28 -46
- package/src/components/number-input.tsx +210 -43
- package/src/components/one-time-code-input.tsx +288 -0
- package/src/components/phone-input.tsx +299 -0
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +118 -30
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +71 -16
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/ui.tsx +472 -105
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +313 -9
- package/src/feedback/feedback-dialog.tsx +2 -0
- package/src/feedback/feedback-thread.tsx +66 -5
- package/src/i18n/defaults.ts +16 -1
- package/src/i18n/german.ts +47 -0
- package/src/i18n/kit-labels.tsx +78 -2
- package/src/i18n/locales/en.ts +10 -0
- package/src/i18n/locales/es.ts +46 -0
- package/src/i18n/locales/fr.ts +45 -0
- package/src/i18n/locales/hu.ts +46 -0
- package/src/i18n/locales/it.ts +47 -0
- package/src/i18n/locales/zh.ts +44 -0
- package/src/i18n/review.ts +7 -0
- package/src/index.ts +53 -3
- package/src/lib/countries.ts +91 -0
- package/src/lib/decimal-marks.ts +99 -0
- package/src/lib/iban.ts +171 -0
- package/src/lib/isin.ts +62 -0
- package/src/lib/phone.ts +211 -0
- package/src/rhf/fields.tsx +606 -9
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +53 -29
- package/src/tour/tour.tsx +66 -28
|
@@ -2,8 +2,10 @@ import * as react from 'react';
|
|
|
2
2
|
import { ComponentProps, Ref } from 'react';
|
|
3
3
|
import { AmountInput } from './amount-input.js';
|
|
4
4
|
import './calculator.js';
|
|
5
|
-
import '../kit-labels-
|
|
6
|
-
import '
|
|
5
|
+
import '../kit-labels-R8mIAc9N.js';
|
|
6
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
7
|
+
import './data-table-sort.js';
|
|
8
|
+
import './swipeable-row.js';
|
|
7
9
|
import './mini-calendar.js';
|
|
8
10
|
import './calendar-heatmap.js';
|
|
9
11
|
import './popover.js';
|
|
@@ -26,7 +28,6 @@ import './sparkline.js';
|
|
|
26
28
|
import './stat-tile.js';
|
|
27
29
|
import './signature-pad.js';
|
|
28
30
|
import './password-strength.js';
|
|
29
|
-
import './danger-confirm.js';
|
|
30
31
|
import './swatch-picker.js';
|
|
31
32
|
import './tile-radio.js';
|
|
32
33
|
import './icon-picker.js';
|
|
@@ -34,11 +35,10 @@ import './dialog-frame.js';
|
|
|
34
35
|
import './modal.js';
|
|
35
36
|
import '../hooks/use-search-param-state.js';
|
|
36
37
|
import './measured-grid.js';
|
|
37
|
-
import '../feedback-attachment-
|
|
38
|
+
import '../feedback-attachment-NhUm7Zga.js';
|
|
38
39
|
import '../feedback/feedback-thread.js';
|
|
39
40
|
import '../feedback/feedback-inbox.js';
|
|
40
41
|
import './account-settings-labels.js';
|
|
41
|
-
import './confirm-dialog.js';
|
|
42
42
|
import './floating-panel.js';
|
|
43
43
|
import './bulk-action-bar.js';
|
|
44
44
|
import './list.js';
|
|
@@ -57,14 +57,16 @@ import './write-lock.js';
|
|
|
57
57
|
import './legal.js';
|
|
58
58
|
import './account-chips.js';
|
|
59
59
|
import '../lib/format.js';
|
|
60
|
-
import '../data-table-3mi2R3so.js';
|
|
61
|
-
import './data-table-sort.js';
|
|
62
|
-
import './swipeable-row.js';
|
|
63
60
|
import './reauth-dialog.js';
|
|
64
61
|
import './server-wake.js';
|
|
65
62
|
import '../lib/server-wake.js';
|
|
66
63
|
import './translation-review-labels.js';
|
|
67
64
|
import '../lib/translation-review.js';
|
|
65
|
+
import './country-select.js';
|
|
66
|
+
import './inline-edit-field.js';
|
|
67
|
+
import '../lib/iban.js';
|
|
68
|
+
import '../lib/phone.js';
|
|
69
|
+
import './sign-chip.js';
|
|
68
70
|
import './numpad-sheet.js';
|
|
69
71
|
|
|
70
72
|
type AmountInputProps = ComponentProps<typeof AmountInput>;
|
|
@@ -93,6 +95,16 @@ interface MoneyFieldProps extends Omit<AmountInputProps, "value" | "onChange" |
|
|
|
93
95
|
* rounding to the currency's minor unit and `min` / `max` are AmountInput's. A caller-
|
|
94
96
|
* owned sign (`negative`) is not offered: the figure here carries its own minus.
|
|
95
97
|
*
|
|
98
|
+
* So are two props a table cell needs (keksdose K4, K6): `error`, the message under the
|
|
99
|
+
* field that paints and describes it like {@link Input}'s, and `calculator={false}`,
|
|
100
|
+
* for an amount in a close-on-blur inline editor, where a click on the calculator icon
|
|
101
|
+
* blurs the field and tears the editor down before the popover can open (typed
|
|
102
|
+
* calculations still evaluate).
|
|
103
|
+
*
|
|
104
|
+
* This, not a `NumberField` with `digits={2}`, is the number-valued field for money:
|
|
105
|
+
* it settles to the CURRENCY's minor unit (JPY 0, CHF 2) and `digits` takes a unit
|
|
106
|
+
* price's finer scale (kastlan 5).
|
|
107
|
+
*
|
|
96
108
|
* ```tsx
|
|
97
109
|
* <MoneyField ariaLabel="Amount" currency="CHF" value={row.amount}
|
|
98
110
|
* onCommit={(amount) => updateRow(row.id, { amount })} />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/money-field.tsx"],"sourcesContent":["import { useState, type ComponentProps, type Ref } from \"react\";\nimport { AmountInput } from \"./amount-input\";\n\ntype AmountInputProps = ComponentProps<typeof AmountInput>;\n\nexport interface MoneyFieldProps\n extends Omit<AmountInputProps, \"value\" | \"onChange\" | \"onCommit\" | \"negative\" | \"onNegativeChange\" | \"ref\"> {\n /** The amount, or `null` for an empty field. */\n value: number | null;\n /**\n * The settled amount — on blur, on Enter, on the numpad's \"=\" and on a calculator\n * result; never a half-typed \"1200+\". Rounded to the currency's minor unit (or\n * `digits`) and clamped to `min` / `max`, like {@link AmountInput}. `null` when the\n * field was emptied. Not fired when the settled amount equals `value`, so an\n * Enter-then-blur saves once.\n */\n onCommit: (value: number | null) => void;\n ref?: Ref<HTMLInputElement>;\n}\n\n/** The number a settled text reads as: `null` for empty, `undefined` for no number\n * (a lone \"-\" or \".\"). */\nfunction parseSettled(text: string): number | null | undefined {\n const t = text.trim();\n if (t === \"\") return null;\n const n = Number(t);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * {@link AmountInput} with a NUMBER in and a number out, for money edited outside a\n * form — a budget row, a table cell (kastlan 52: new-budget-page edited a CHF amount per\n * row with `NumberField` because AmountInput is string-valued and reports every\n * keystroke, \"1200+\" included, so a number-state row needed its own parse-and-settle\n * glue). This is that glue, the same one `RhfMoneyField` has: the text being typed\n * lives here, and only a settled figure reaches `onCommit`.\n *\n * The currency chip, the calculator, the numpad, the locale's decimal mark, the\n * rounding to the currency's minor unit and `min` / `max` are AmountInput's. A caller-\n * owned sign (`negative`) is not offered: the figure here carries its own minus.\n *\n * ```tsx\n * <MoneyField ariaLabel=\"Amount\" currency=\"CHF\" value={row.amount}\n * onCommit={(amount) => updateRow(row.id, { amount })} />\n * ```\n */\nexport function MoneyField({ value, onCommit, ref, ...props }: MoneyFieldProps) {\n const external = value === null ? \"\" : String(value);\n // The draft is re-read from `value` only when `value` moves away from what the draft\n // already says (a reset, another row's save), so a commit's own echo keeps the\n // typist's spelling (\"12.50\" stays \"12.50\" when 12.5 comes back).\n const [draft, setDraft] = useState(external);\n const [seen, setSeen] = useState(value);\n if (seen !== value) {\n setSeen(value);\n if (parseSettled(draft) !== value) setDraft(external);\n }\n return (\n <AmountInput\n {...props}\n ref={ref}\n value={draft}\n onChange={setDraft}\n onCommit={(text) => {\n const next = parseSettled(text);\n // No number (\"-\" alone): back to the stored amount rather than a guess.\n if (next === undefined) {\n setDraft(external);\n return;\n }\n if (next !== value) onCommit(next);\n }}\n />\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/money-field.tsx"],"sourcesContent":["import { useState, type ComponentProps, type Ref } from \"react\";\nimport { AmountInput } from \"./amount-input\";\n\ntype AmountInputProps = ComponentProps<typeof AmountInput>;\n\nexport interface MoneyFieldProps\n extends Omit<AmountInputProps, \"value\" | \"onChange\" | \"onCommit\" | \"negative\" | \"onNegativeChange\" | \"ref\"> {\n /** The amount, or `null` for an empty field. */\n value: number | null;\n /**\n * The settled amount — on blur, on Enter, on the numpad's \"=\" and on a calculator\n * result; never a half-typed \"1200+\". Rounded to the currency's minor unit (or\n * `digits`) and clamped to `min` / `max`, like {@link AmountInput}. `null` when the\n * field was emptied. Not fired when the settled amount equals `value`, so an\n * Enter-then-blur saves once.\n */\n onCommit: (value: number | null) => void;\n ref?: Ref<HTMLInputElement>;\n}\n\n/** The number a settled text reads as: `null` for empty, `undefined` for no number\n * (a lone \"-\" or \".\"). */\nfunction parseSettled(text: string): number | null | undefined {\n const t = text.trim();\n if (t === \"\") return null;\n const n = Number(t);\n return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * {@link AmountInput} with a NUMBER in and a number out, for money edited outside a\n * form — a budget row, a table cell (kastlan 52: new-budget-page edited a CHF amount per\n * row with `NumberField` because AmountInput is string-valued and reports every\n * keystroke, \"1200+\" included, so a number-state row needed its own parse-and-settle\n * glue). This is that glue, the same one `RhfMoneyField` has: the text being typed\n * lives here, and only a settled figure reaches `onCommit`.\n *\n * The currency chip, the calculator, the numpad, the locale's decimal mark, the\n * rounding to the currency's minor unit and `min` / `max` are AmountInput's. A caller-\n * owned sign (`negative`) is not offered: the figure here carries its own minus.\n *\n * So are two props a table cell needs (keksdose K4, K6): `error`, the message under the\n * field that paints and describes it like {@link Input}'s, and `calculator={false}`,\n * for an amount in a close-on-blur inline editor, where a click on the calculator icon\n * blurs the field and tears the editor down before the popover can open (typed\n * calculations still evaluate).\n *\n * This, not a `NumberField` with `digits={2}`, is the number-valued field for money:\n * it settles to the CURRENCY's minor unit (JPY 0, CHF 2) and `digits` takes a unit\n * price's finer scale (kastlan 5).\n *\n * ```tsx\n * <MoneyField ariaLabel=\"Amount\" currency=\"CHF\" value={row.amount}\n * onCommit={(amount) => updateRow(row.id, { amount })} />\n * ```\n */\nexport function MoneyField({ value, onCommit, ref, ...props }: MoneyFieldProps) {\n const external = value === null ? \"\" : String(value);\n // The draft is re-read from `value` only when `value` moves away from what the draft\n // already says (a reset, another row's save), so a commit's own echo keeps the\n // typist's spelling (\"12.50\" stays \"12.50\" when 12.5 comes back).\n const [draft, setDraft] = useState(external);\n const [seen, setSeen] = useState(value);\n if (seen !== value) {\n setSeen(value);\n if (parseSettled(draft) !== value) setDraft(external);\n }\n return (\n <AmountInput\n {...props}\n ref={ref}\n value={draft}\n onChange={setDraft}\n onCommit={(text) => {\n const next = parseSettled(text);\n // No number (\"-\" alone): back to the stored amount rather than a guess.\n if (next === undefined) {\n setDraft(external);\n return;\n }\n if (next !== value) onCommit(next);\n }}\n />\n );\n}\n"],"mappings":";AAoEI;AApEJ,SAAS,gBAA+C;AACxD,SAAS,mBAAmB;AAqB5B,SAAS,aAAa,MAAyC;AAC7D,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,MAAM,GAAI,QAAO;AACrB,QAAM,IAAI,OAAO,CAAC;AAClB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AA6BO,SAAS,WAAW,EAAE,OAAO,UAAU,KAAK,GAAG,MAAM,GAAoB;AAC9E,QAAM,WAAW,UAAU,OAAO,KAAK,OAAO,KAAK;AAInD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,MAAI,SAAS,OAAO;AAClB,YAAQ,KAAK;AACb,QAAI,aAAa,KAAK,MAAM,MAAO,UAAS,QAAQ;AAAA,EACtD;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU,CAAC,SAAS;AAClB,cAAM,OAAO,aAAa,IAAI;AAE9B,YAAI,SAAS,QAAW;AACtB,mBAAS,QAAQ;AACjB;AAAA,QACF;AACA,YAAI,SAAS,MAAO,UAAS,IAAI;AAAA,MACnC;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -23,6 +23,15 @@ interface MonthPickerLabels {
|
|
|
23
23
|
nextMonth: string;
|
|
24
24
|
/** `variant="stepper"`: the button that jumps to the current month. Visible text. */
|
|
25
25
|
today: string;
|
|
26
|
+
/** `mode="year"`: the popover's accessible name. */
|
|
27
|
+
yearPanel: string;
|
|
28
|
+
/** `mode="year"`: the icon-only arrows that page the year grid back and forward by a
|
|
29
|
+
* page of twelve years. */
|
|
30
|
+
earlierYears: string;
|
|
31
|
+
laterYears: string;
|
|
32
|
+
/** `mode="year"`, `variant="stepper"`: the button that jumps to the current year.
|
|
33
|
+
* Visible text. */
|
|
34
|
+
thisYear: string;
|
|
26
35
|
}
|
|
27
36
|
/** The English starting point every override is merged onto. Exported (unlike the
|
|
28
37
|
* calendar's) because it is asked for by name: a host building its own translation
|
|
@@ -64,7 +73,45 @@ interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<"div">, "onChan
|
|
|
64
73
|
disabled?: boolean;
|
|
65
74
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
66
75
|
invalid?: boolean;
|
|
67
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* What is wrong with the month, in the caller's words — {@link Input}'s `error`
|
|
78
|
+
* (keksdose K4): under the field, attached to the trigger with `aria-describedby`
|
|
79
|
+
* (merged, after any caption), and implies `invalid`. Passing the key keeps the
|
|
80
|
+
* field's box while there is no message, so the trigger is never remounted.
|
|
81
|
+
*/
|
|
82
|
+
error?: ReactNode;
|
|
83
|
+
/** Standing advice, as on a {@link Select}: text is a caption under the field; a
|
|
84
|
+
* {@link FieldHint} rides the label line. */
|
|
85
|
+
hint?: ReactNode;
|
|
86
|
+
/**
|
|
87
|
+
* `"md"` (default): the form field. `"sm"`: the toolbar trigger — 36px tall (an
|
|
88
|
+
* `IconButton`'s height) and as wide as the month it shows, for a picker that sits
|
|
89
|
+
* between two icon buttons in a page header (keksdose K13: the budget header wrote
|
|
90
|
+
* `triggerClassName="h-9 w-auto … py-0"` by hand, live #260 — 34px between two 36px
|
|
91
|
+
* buttons, *"the kind of 2px that reads as sloppy"*). Unlabelled pickers only, as on
|
|
92
|
+
* {@link Select}: a floating label needs the tall box to float in. The width follows
|
|
93
|
+
* the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it
|
|
94
|
+
* must not move as it changes.
|
|
95
|
+
*/
|
|
96
|
+
size?: "sm" | "md";
|
|
97
|
+
/**
|
|
98
|
+
* `"month"` (default): the value is a month, `"YYYY-MM"`.
|
|
99
|
+
*
|
|
100
|
+
* `"year"`: the value is a YEAR, `"YYYY"`, and the panel is a grid of years — kastlan's
|
|
101
|
+
* budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab
|
|
102
|
+
* ("this year and four before", tax-tab.tsx:33). `min` / `max` are read as their year
|
|
103
|
+
* (`"2022"`, or any longer key). A bounded span of twelve years or fewer is shown
|
|
104
|
+
* whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /
|
|
105
|
+
* PageDown, or the arrows), starting from `min` when there is one. The stepper
|
|
106
|
+
* variant steps a year, and its Today button reads "This year".
|
|
107
|
+
*
|
|
108
|
+
* A string rather than a number, for the reason the month key is one: it is the
|
|
109
|
+
* same type across both modes, it crosses a URL as is, and it sorts. A numeric
|
|
110
|
+
* caller converts with `String(year)` and `Number(key)`.
|
|
111
|
+
*/
|
|
112
|
+
mode?: "month" | "year";
|
|
113
|
+
/** Intl options for the trigger's text (default: long month + numeric year, or the
|
|
114
|
+
* numeric year in `mode="year"`). Win over the provider's `formatDate`. */
|
|
68
115
|
formatOptions?: Intl.DateTimeFormatOptions;
|
|
69
116
|
/**
|
|
70
117
|
* The month to ring as "now", as `"YYYY-MM"`. Defaults to the local calendar's
|
|
@@ -84,8 +131,9 @@ interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<"div">, "onChan
|
|
|
84
131
|
* `"stepper"`: the header of a month PAGE — the month as a heading that opens the
|
|
85
132
|
* grid, then Today and ‹ › stepping the value by one month. What a calendar page
|
|
86
133
|
* assembled by hand around the field (overriding its trigger to look like a
|
|
87
|
-
* heading). `label`, `placeholder`, `invalid`
|
|
88
|
-
* and are not drawn here; with no `value` the
|
|
134
|
+
* heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and
|
|
135
|
+
* `triggerClassName` are the field's and are not drawn here; with no `value` the
|
|
136
|
+
* arrows step from the current month (or year).
|
|
89
137
|
*/
|
|
90
138
|
variant?: "field" | "stepper";
|
|
91
139
|
/** `variant="stepper"`: the heading level the month is set in (`<h2>` by default), or
|
|
@@ -94,6 +142,6 @@ interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<"div">, "onChan
|
|
|
94
142
|
/** `variant="stepper"`: draw the Today button. Default `true`. */
|
|
95
143
|
showToday?: boolean;
|
|
96
144
|
}
|
|
97
|
-
declare function MonthPicker(
|
|
145
|
+
declare function MonthPicker(props: MonthPickerProps): react.JSX.Element;
|
|
98
146
|
|
|
99
147
|
export { DEFAULT_MONTH_PICKER_LABELS, MonthPicker, type MonthPickerLabels, type MonthPickerProps };
|
|
@@ -5,10 +5,11 @@ import { Calendar, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
6
|
import { dirOf, horizontalStep } from "../lib/direction.js";
|
|
7
7
|
import { monthKey, pad } from "../lib/dates.js";
|
|
8
|
-
import { Button,
|
|
8
|
+
import { Button, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from "./ui.js";
|
|
9
9
|
import { Popover } from "./popover.js";
|
|
10
10
|
import { splitTriggerAria } from "./trigger-aria.js";
|
|
11
|
-
import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
11
|
+
import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
12
|
+
import { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from "./field-anatomy.js";
|
|
12
13
|
const DEFAULT_MONTH_PICKER_LABELS = {
|
|
13
14
|
previousYear: "Previous year",
|
|
14
15
|
nextYear: "Next year",
|
|
@@ -16,7 +17,11 @@ const DEFAULT_MONTH_PICKER_LABELS = {
|
|
|
16
17
|
month: (monthYear) => monthYear,
|
|
17
18
|
previousMonth: "Previous month",
|
|
18
19
|
nextMonth: "Next month",
|
|
19
|
-
today: "Today"
|
|
20
|
+
today: "Today",
|
|
21
|
+
yearPanel: "Choose a year",
|
|
22
|
+
earlierYears: "Earlier years",
|
|
23
|
+
laterYears: "Later years",
|
|
24
|
+
thisYear: "This year"
|
|
20
25
|
};
|
|
21
26
|
function useMonthPickerLabels(partial) {
|
|
22
27
|
return useKitLabels("monthPicker", DEFAULT_MONTH_PICKER_LABELS, partial);
|
|
@@ -44,6 +49,12 @@ function firstOf({ year, month }) {
|
|
|
44
49
|
}
|
|
45
50
|
const DEFAULT_TRIGGER_FORMAT = { month: "long", year: "numeric" };
|
|
46
51
|
const MONTH_NAME_FORMAT = { month: "long", year: "numeric" };
|
|
52
|
+
const YEAR_FORMAT = { year: "numeric" };
|
|
53
|
+
const YEAR = /^(\d{4})/;
|
|
54
|
+
function parseYear(key) {
|
|
55
|
+
const m = key ? YEAR.exec(key) : null;
|
|
56
|
+
return m ? Number(m[1]) : null;
|
|
57
|
+
}
|
|
47
58
|
function useFocusBackOnClose(open, triggerRef) {
|
|
48
59
|
const wasOpen = useRef(false);
|
|
49
60
|
useEffect(() => {
|
|
@@ -100,6 +111,7 @@ function MonthFieldTrigger({
|
|
|
100
111
|
valueId,
|
|
101
112
|
panelId,
|
|
102
113
|
padded,
|
|
114
|
+
small,
|
|
103
115
|
disabled,
|
|
104
116
|
invalid,
|
|
105
117
|
className,
|
|
@@ -126,6 +138,10 @@ function MonthFieldTrigger({
|
|
|
126
138
|
FIELD_TRIGGER,
|
|
127
139
|
"pe-9",
|
|
128
140
|
padded && FIELD_FLOATING_PAD,
|
|
141
|
+
// keksdose K13 (live #260): an IconButton's 36px, and only as wide as the
|
|
142
|
+
// month — `py-0` with a fixed height so the box is 36px whatever the type's
|
|
143
|
+
// line height. `pe-9` stays: the calendar glyph still sits at the end.
|
|
144
|
+
small && "h-9 w-auto py-0",
|
|
129
145
|
disabled && "cursor-not-allowed opacity-50",
|
|
130
146
|
invalid && FIELD_INVALID,
|
|
131
147
|
className
|
|
@@ -291,50 +307,212 @@ function MonthGrid({
|
|
|
291
307
|
}) }, row)) })
|
|
292
308
|
] });
|
|
293
309
|
}
|
|
310
|
+
const YEAR_PAGE = 12;
|
|
311
|
+
function YearGrid({
|
|
312
|
+
value,
|
|
313
|
+
onPick,
|
|
314
|
+
locale,
|
|
315
|
+
minYear,
|
|
316
|
+
maxYear,
|
|
317
|
+
currentYear,
|
|
318
|
+
labels
|
|
319
|
+
}) {
|
|
320
|
+
const clamp = (y) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));
|
|
321
|
+
const whole = minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE ? { first: minYear, count: maxYear - minYear + 1 } : null;
|
|
322
|
+
const [active, setActive] = useState(() => clamp(value ?? currentYear));
|
|
323
|
+
const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);
|
|
324
|
+
const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;
|
|
325
|
+
const count = whole ? whole.count : YEAR_PAGE;
|
|
326
|
+
const years = Array.from({ length: count }, (_, i) => first + i);
|
|
327
|
+
const outOfBounds = (y) => minYear !== null && y < minYear || maxYear !== null && y > maxYear;
|
|
328
|
+
const gridRef = useRef(null);
|
|
329
|
+
const captionId = `${useId()}-years`;
|
|
330
|
+
const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);
|
|
331
|
+
const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));
|
|
332
|
+
const pendingFocus = useRef(null);
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const year = pendingFocus.current;
|
|
335
|
+
if (year === null) return;
|
|
336
|
+
pendingFocus.current = null;
|
|
337
|
+
gridRef.current?.querySelector(`[data-year="${year}"]`)?.focus();
|
|
338
|
+
});
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
gridRef.current?.querySelector('[tabindex="0"]')?.focus();
|
|
341
|
+
}, []);
|
|
342
|
+
const moveTo = (next, focus) => {
|
|
343
|
+
const year = clamp(next);
|
|
344
|
+
if (focus) pendingFocus.current = year;
|
|
345
|
+
setActive(year);
|
|
346
|
+
};
|
|
347
|
+
const onCellKeyDown = (e) => {
|
|
348
|
+
const step = horizontalStep(e.key, e.currentTarget);
|
|
349
|
+
const col = (active - first) % COLUMNS;
|
|
350
|
+
let delta;
|
|
351
|
+
switch (step ? "horizontal" : e.key) {
|
|
352
|
+
case "horizontal":
|
|
353
|
+
delta = step;
|
|
354
|
+
break;
|
|
355
|
+
case "ArrowUp":
|
|
356
|
+
delta = -COLUMNS;
|
|
357
|
+
break;
|
|
358
|
+
case "ArrowDown":
|
|
359
|
+
delta = COLUMNS;
|
|
360
|
+
break;
|
|
361
|
+
case "Home":
|
|
362
|
+
delta = -col;
|
|
363
|
+
break;
|
|
364
|
+
case "End":
|
|
365
|
+
delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);
|
|
366
|
+
break;
|
|
367
|
+
case "PageUp":
|
|
368
|
+
delta = -YEAR_PAGE;
|
|
369
|
+
break;
|
|
370
|
+
case "PageDown":
|
|
371
|
+
delta = YEAR_PAGE;
|
|
372
|
+
break;
|
|
373
|
+
default:
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
if (delta !== 0) moveTo(active + delta, true);
|
|
378
|
+
};
|
|
379
|
+
const prevBlocked = minYear !== null && first - 1 < minYear;
|
|
380
|
+
const nextBlocked = maxYear !== null && first + count > maxYear;
|
|
381
|
+
const arrow = "flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent";
|
|
382
|
+
return /* @__PURE__ */ jsxs("div", { className: "select-none", children: [
|
|
383
|
+
/* @__PURE__ */ jsxs("div", { className: cn("mb-1 flex items-center", whole ? "h-7 justify-center" : "justify-between"), children: [
|
|
384
|
+
!whole && /* @__PURE__ */ jsx(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
type: "button",
|
|
388
|
+
"aria-label": labels.earlierYears,
|
|
389
|
+
disabled: prevBlocked,
|
|
390
|
+
onClick: () => moveTo(active - YEAR_PAGE, false),
|
|
391
|
+
className: arrow,
|
|
392
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 rtl:-scale-x-100" })
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
/* @__PURE__ */ jsx("div", { id: captionId, "aria-live": "polite", className: "text-sm font-semibold tabular-nums text-[var(--text-primary)]", children: caption }),
|
|
396
|
+
!whole && /* @__PURE__ */ jsx(
|
|
397
|
+
"button",
|
|
398
|
+
{
|
|
399
|
+
type: "button",
|
|
400
|
+
"aria-label": labels.laterYears,
|
|
401
|
+
disabled: nextBlocked,
|
|
402
|
+
onClick: () => moveTo(active + YEAR_PAGE, false),
|
|
403
|
+
className: arrow,
|
|
404
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 rtl:-scale-x-100" })
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": captionId, className: "grid gap-1", children: Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-3 gap-1", children: years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {
|
|
409
|
+
const disabled = outOfBounds(year);
|
|
410
|
+
const selected = year === value;
|
|
411
|
+
const current = year === currentYear;
|
|
412
|
+
return /* @__PURE__ */ jsx(
|
|
413
|
+
"button",
|
|
414
|
+
{
|
|
415
|
+
type: "button",
|
|
416
|
+
role: "gridcell",
|
|
417
|
+
"data-year": year,
|
|
418
|
+
tabIndex: year === active ? 0 : -1,
|
|
419
|
+
"aria-selected": selected,
|
|
420
|
+
"aria-disabled": disabled || void 0,
|
|
421
|
+
"aria-current": current ? "date" : void 0,
|
|
422
|
+
onClick: () => {
|
|
423
|
+
if (disabled) return;
|
|
424
|
+
setActive(year);
|
|
425
|
+
onPick(String(year));
|
|
426
|
+
},
|
|
427
|
+
onKeyDown: onCellKeyDown,
|
|
428
|
+
className: cn(
|
|
429
|
+
"rounded px-2 py-1.5 text-sm tabular-nums transition-colors",
|
|
430
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset",
|
|
431
|
+
selected && !disabled ? "focus-visible:ring-[var(--brand-contrast)]" : "focus-visible:ring-[var(--brand)]",
|
|
432
|
+
disabled ? "cursor-not-allowed text-[var(--text-placeholder)]" : selected ? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]" : "text-[var(--text-primary)] hover:bg-[var(--bg-hover)]",
|
|
433
|
+
current && !selected && !disabled && "ring-1 ring-inset ring-[var(--border-strong)]"
|
|
434
|
+
),
|
|
435
|
+
children: yearFormat.format(new Date(year, 0, 1))
|
|
436
|
+
},
|
|
437
|
+
year
|
|
438
|
+
);
|
|
439
|
+
}) }, row)) })
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
294
442
|
function upperFirst(text, locale) {
|
|
295
443
|
return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);
|
|
296
444
|
}
|
|
297
|
-
function MonthPicker({
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
445
|
+
function MonthPicker(props) {
|
|
446
|
+
const {
|
|
447
|
+
value,
|
|
448
|
+
onChange,
|
|
449
|
+
locale: localeProp,
|
|
450
|
+
min,
|
|
451
|
+
max,
|
|
452
|
+
label,
|
|
453
|
+
placeholder,
|
|
454
|
+
disabled,
|
|
455
|
+
invalid,
|
|
456
|
+
error,
|
|
457
|
+
hint,
|
|
458
|
+
size = "md",
|
|
459
|
+
mode = "month",
|
|
460
|
+
formatOptions,
|
|
461
|
+
currentMonth,
|
|
462
|
+
labels: labelsProp,
|
|
463
|
+
className,
|
|
464
|
+
triggerClassName,
|
|
465
|
+
variant = "field",
|
|
466
|
+
headingLevel = 2,
|
|
467
|
+
showToday = true,
|
|
468
|
+
...rest
|
|
469
|
+
} = props;
|
|
317
470
|
const labels = useMonthPickerLabels(labelsProp);
|
|
318
471
|
const locale = useKitLocale(localeProp);
|
|
319
|
-
const
|
|
472
|
+
const fromProvider = useKitDateFormatter();
|
|
473
|
+
const years = mode === "year";
|
|
474
|
+
const selected = years ? null : parseMonth(value);
|
|
475
|
+
const selectedYear = years ? parseYear(value) : null;
|
|
320
476
|
const minKey = boundKey(min);
|
|
321
477
|
const maxKey = boundKey(max);
|
|
478
|
+
const minYear = parseYear(min);
|
|
479
|
+
const maxYear = parseYear(max);
|
|
322
480
|
const currentKey = boundKey(currentMonth) ?? monthKey(/* @__PURE__ */ new Date());
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
481
|
+
const currentYear = parseYear(currentKey);
|
|
482
|
+
const shownKey = years ? selectedYear !== null ? String(selectedYear) : null : selected ? toKey(selected) : null;
|
|
483
|
+
const triggerText = shownKey ? formatOptions === void 0 && fromProvider?.(shownKey, { unit: years ? "year" : "month", source: "monthPicker", locale, weekday: false }) || upperFirst(
|
|
484
|
+
new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(
|
|
485
|
+
years ? new Date(selectedYear, 0, 1) : firstOf(selected)
|
|
326
486
|
),
|
|
327
487
|
locale
|
|
328
488
|
) : placeholder ?? "";
|
|
329
489
|
const id = useId();
|
|
330
|
-
const
|
|
490
|
+
const messages = useFieldMessages({ hint, error, describedBy: rest["aria-describedby"] });
|
|
491
|
+
const [ownAria, wrapperRest] = splitTriggerAria(rest);
|
|
492
|
+
const aria = { ...ownAria, "aria-describedby": messages.describedBy };
|
|
331
493
|
const labelId = `${id}-label`;
|
|
332
494
|
const valueId = `${id}-value`;
|
|
333
495
|
const panelId = `${id}-panel`;
|
|
334
496
|
const named = typeof label === "string";
|
|
497
|
+
const small = size === "sm" && label === void 0;
|
|
335
498
|
const rootRef = useRef(null);
|
|
336
499
|
const [dir, setDir] = useState("ltr");
|
|
337
|
-
const
|
|
500
|
+
const panelLabel = years ? labels.yearPanel : labels.panel;
|
|
501
|
+
const grid = (close) => years ? /* @__PURE__ */ jsx(
|
|
502
|
+
YearGrid,
|
|
503
|
+
{
|
|
504
|
+
value: selectedYear,
|
|
505
|
+
locale,
|
|
506
|
+
minYear,
|
|
507
|
+
maxYear,
|
|
508
|
+
currentYear,
|
|
509
|
+
labels,
|
|
510
|
+
onPick: (key) => {
|
|
511
|
+
onChange(key);
|
|
512
|
+
close();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
) : /* @__PURE__ */ jsx(
|
|
338
516
|
MonthGrid,
|
|
339
517
|
{
|
|
340
518
|
value: selected,
|
|
@@ -350,17 +528,33 @@ function MonthPicker({
|
|
|
350
528
|
}
|
|
351
529
|
);
|
|
352
530
|
if (variant === "stepper") {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
531
|
+
let prevKey;
|
|
532
|
+
let nextKey;
|
|
533
|
+
let nowKey;
|
|
534
|
+
let outside;
|
|
535
|
+
if (years) {
|
|
536
|
+
const base = selectedYear ?? currentYear;
|
|
537
|
+
prevKey = String(base - 1);
|
|
538
|
+
nextKey = String(base + 1);
|
|
539
|
+
nowKey = String(currentYear);
|
|
540
|
+
outside = (key) => {
|
|
541
|
+
const y = Number(key);
|
|
542
|
+
return minYear !== null && y < minYear || maxYear !== null && y > maxYear;
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
const base = selected ?? parseMonth(currentKey);
|
|
546
|
+
prevKey = toKey(shiftMonth(base, -1));
|
|
547
|
+
nextKey = toKey(shiftMonth(base, 1));
|
|
548
|
+
nowKey = currentKey;
|
|
549
|
+
outside = (key) => Boolean(minKey && key < minKey || maxKey && key > maxKey);
|
|
550
|
+
}
|
|
357
551
|
const Heading = headingLevel ? `h${headingLevel}` : null;
|
|
358
552
|
const trigger = /* @__PURE__ */ jsx(
|
|
359
553
|
Popover,
|
|
360
554
|
{
|
|
361
555
|
width: 256,
|
|
362
556
|
panelId,
|
|
363
|
-
labels: { panel:
|
|
557
|
+
labels: { panel: panelLabel },
|
|
364
558
|
dir,
|
|
365
559
|
trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
|
|
366
560
|
MonthHeadingTrigger,
|
|
@@ -374,7 +568,7 @@ function MonthPicker({
|
|
|
374
568
|
text: triggerText,
|
|
375
569
|
panelId,
|
|
376
570
|
disabled,
|
|
377
|
-
aria
|
|
571
|
+
aria: ownAria
|
|
378
572
|
}
|
|
379
573
|
),
|
|
380
574
|
children: grid
|
|
@@ -388,17 +582,17 @@ function MonthPicker({
|
|
|
388
582
|
{
|
|
389
583
|
variant: "secondary",
|
|
390
584
|
size: "sm",
|
|
391
|
-
disabled: disabled || outside(
|
|
585
|
+
disabled: disabled || outside(nowKey),
|
|
392
586
|
onClick: () => {
|
|
393
|
-
if (
|
|
587
|
+
if (nowKey !== shownKey) onChange(nowKey);
|
|
394
588
|
},
|
|
395
|
-
children: labels.today
|
|
589
|
+
children: years ? labels.thisYear : labels.today
|
|
396
590
|
}
|
|
397
591
|
),
|
|
398
592
|
/* @__PURE__ */ jsx(
|
|
399
593
|
IconButton,
|
|
400
594
|
{
|
|
401
|
-
"aria-label": labels.previousMonth,
|
|
595
|
+
"aria-label": years ? labels.previousYear : labels.previousMonth,
|
|
402
596
|
disabled: disabled || outside(prevKey),
|
|
403
597
|
onClick: () => onChange(prevKey),
|
|
404
598
|
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:-scale-x-100" })
|
|
@@ -407,7 +601,7 @@ function MonthPicker({
|
|
|
407
601
|
/* @__PURE__ */ jsx(
|
|
408
602
|
IconButton,
|
|
409
603
|
{
|
|
410
|
-
"aria-label": labels.nextMonth,
|
|
604
|
+
"aria-label": years ? labels.nextYear : labels.nextMonth,
|
|
411
605
|
disabled: disabled || outside(nextKey),
|
|
412
606
|
onClick: () => onChange(nextKey),
|
|
413
607
|
children: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:-scale-x-100" })
|
|
@@ -416,52 +610,62 @@ function MonthPicker({
|
|
|
416
610
|
] })
|
|
417
611
|
] });
|
|
418
612
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
/* @__PURE__ */
|
|
423
|
-
|
|
424
|
-
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
613
|
+
const field = (
|
|
614
|
+
// `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
|
|
615
|
+
// full-width box would park it at the far end of the row, away from its trigger.
|
|
616
|
+
/* @__PURE__ */ jsxs("div", { ...wrapperRest, ref: rootRef, className: cn("relative", small && "w-fit max-w-full", className), children: [
|
|
617
|
+
label !== void 0 && /* @__PURE__ */ jsx(FieldLabelLine, { label, hint: messages.labelHint }),
|
|
618
|
+
named && /* @__PURE__ */ jsx("span", { id: labelId, "aria-hidden": true, className: "sr-only-fixed", children: label }),
|
|
619
|
+
/* @__PURE__ */ jsx(
|
|
620
|
+
Popover,
|
|
621
|
+
{
|
|
622
|
+
width: 256,
|
|
623
|
+
panelId,
|
|
624
|
+
labels: { panel: panelLabel },
|
|
625
|
+
dir,
|
|
626
|
+
trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
|
|
627
|
+
MonthFieldTrigger,
|
|
628
|
+
{
|
|
629
|
+
open,
|
|
630
|
+
toggle: () => {
|
|
631
|
+
setDir(dirOf(rootRef.current));
|
|
632
|
+
toggle();
|
|
633
|
+
},
|
|
634
|
+
triggerRef: ref,
|
|
635
|
+
triggerText,
|
|
636
|
+
hasValue: shownKey !== null,
|
|
637
|
+
labelledBy: [
|
|
638
|
+
aria["aria-labelledby"],
|
|
639
|
+
named ? labelId : !aria["aria-labelledby"] && aria.id ? aria.id : void 0,
|
|
640
|
+
valueId
|
|
641
|
+
].filter(Boolean).join(" "),
|
|
642
|
+
aria,
|
|
643
|
+
valueId,
|
|
644
|
+
panelId,
|
|
645
|
+
padded: label !== void 0,
|
|
646
|
+
small,
|
|
647
|
+
disabled,
|
|
648
|
+
invalid: invalid || hasContent(error),
|
|
649
|
+
className: triggerClassName
|
|
650
|
+
}
|
|
651
|
+
),
|
|
652
|
+
children: grid
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
/* @__PURE__ */ jsx(
|
|
656
|
+
Calendar,
|
|
657
|
+
{
|
|
658
|
+
"aria-hidden": true,
|
|
659
|
+
className: "pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
] })
|
|
663
|
+
);
|
|
664
|
+
const endHint = label === void 0 ? messages.labelHint : void 0;
|
|
665
|
+
return /* @__PURE__ */ jsx(FieldBox, { below: messages.below, reserve: "error" in props || "hint" in props, children: endHint === void 0 ? field : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
666
|
+
/* @__PURE__ */ jsx("div", { className: cn("min-w-0", !small && "flex-1"), children: field }),
|
|
667
|
+
/* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: endHint })
|
|
668
|
+
] }) });
|
|
465
669
|
}
|
|
466
670
|
export {
|
|
467
671
|
DEFAULT_MONTH_PICKER_LABELS,
|