@eifi1/ui-kit 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -25
- package/dist/chart.d.ts +19 -14
- 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 +53 -14
- package/dist/components/amount-input.js +17 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +29 -0
- package/dist/components/autocomplete.js +134 -104
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +19 -14
- package/dist/components/calculator.d.ts +19 -14
- 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/column-mapper.d.ts +182 -0
- package/dist/components/column-mapper.js +375 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +91 -2
- package/dist/components/combobox.js +454 -308
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +69 -161
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +16 -11
- package/dist/components/country-select.d.ts +167 -0
- package/dist/components/country-select.js +293 -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 +69 -166
- package/dist/components/danger-confirm.js +144 -50
- 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 +66 -5
- package/dist/components/date-picker.js +378 -66
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +42 -1
- package/dist/components/entity-combobox.js +154 -113
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +19 -14
- package/dist/components/field-parts.d.ts +224 -0
- package/dist/components/field-parts.js +156 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +19 -14
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +41 -15
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +18 -13
- package/dist/components/form-actions.js +69 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +69 -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 +21 -2
- package/dist/components/icon-picker.js +19 -4
- 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 +130 -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 +32 -14
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +291 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +26 -1
- package/dist/components/multi-entity-combobox.js +143 -102
- package/dist/components/multi-entity-combobox.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 +34 -19
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +79 -19
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +19 -14
- 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 +69 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +19 -14
- package/dist/components/settings-fields.d.ts +19 -14
- package/dist/components/share-card.d.ts +16 -11
- 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 +43 -2
- package/dist/components/swatch-picker.js +80 -32
- 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 +18 -13
- package/dist/components/tile-radio.d.ts +88 -13
- package/dist/components/tile-radio.js +28 -11
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +24 -15
- 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/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +16 -11
- package/dist/components/ui.js +284 -88
- 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 +348 -41
- 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 +3 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +68 -253
- package/dist/feedback/feedback-thread.js +46 -10
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
- package/dist/feedback.d.ts +66 -2
- package/dist/hooks/use-file-drop.d.ts +19 -14
- package/dist/i18n/defaults.d.ts +19 -14
- package/dist/i18n/defaults.js +19 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +19 -14
- package/dist/i18n/german.js +84 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -10
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +19 -14
- package/dist/i18n/locales/de-CH.d.ts +19 -14
- package/dist/i18n/locales/en.d.ts +19 -14
- package/dist/i18n/locales/en.js +16 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +19 -14
- package/dist/i18n/locales/es.js +83 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +19 -14
- package/dist/i18n/locales/fr.js +82 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +19 -14
- package/dist/i18n/locales/hu.js +85 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +19 -14
- package/dist/i18n/locales/it.js +84 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +19 -14
- package/dist/i18n/locales/zh.js +80 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +19 -14
- package/dist/i18n/review.js +27 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +73 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- 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/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +233 -18
- package/dist/rhf/fields.js +456 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +19 -14
- package/dist/rhf.d.ts +22 -15
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +18 -13
- package/dist/shell/top-bar-brand.d.ts +19 -14
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +18 -13
- package/dist/table-text.d.ts +1 -1
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -16
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +19 -14
- 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 +68 -56
- package/src/components/autocomplete.tsx +198 -114
- 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/column-mapper.tsx +666 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +661 -340
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +556 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +429 -82
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +595 -71
- package/src/components/entity-combobox.tsx +235 -124
- package/src/components/field-parts.tsx +403 -0
- package/src/components/field-strip.tsx +149 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +147 -6
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +47 -6
- 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 +13 -0
- package/src/components/month-picker.tsx +400 -63
- package/src/components/multi-entity-combobox.tsx +207 -120
- 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/reauth-dialog.tsx +17 -18
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +143 -31
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +145 -29
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +479 -138
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +597 -46
- package/src/feedback/feedback-dialog.tsx +9 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +113 -9
- package/src/i18n/defaults.ts +18 -1
- package/src/i18n/german.ts +89 -0
- package/src/i18n/kit-labels.tsx +82 -2
- package/src/i18n/locales/en.ts +36 -4
- package/src/i18n/locales/es.ts +87 -0
- package/src/i18n/locales/fr.ts +87 -0
- package/src/i18n/locales/hu.ts +83 -0
- package/src/i18n/locales/it.ts +88 -0
- package/src/i18n/locales/zh.ts +76 -0
- package/src/i18n/review.ts +26 -0
- package/src/index.ts +71 -3
- package/src/lib/column-mapping.ts +234 -0
- 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/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +860 -9
- package/src/rhf.ts +3 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/tour/tour.tsx +66 -28
- package/src/wizard/stepper-nav.tsx +35 -1
|
@@ -4,11 +4,12 @@ import { Calendar, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { dirOf, horizontalStep, type Direction } from "../lib/direction";
|
|
6
6
|
import { monthKey, pad } from "../lib/dates";
|
|
7
|
-
import { Button,
|
|
7
|
+
import { Button, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from "./ui";
|
|
8
8
|
import { Popover } from "./popover";
|
|
9
9
|
import { splitTriggerAria } from "./trigger-aria";
|
|
10
10
|
import type { TriggerAria } from "./trigger-aria";
|
|
11
|
-
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
11
|
+
import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
12
|
+
import { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from "./field-parts";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Every string the month picker can speak. The month and year names are NOT here —
|
|
@@ -32,6 +33,15 @@ export interface MonthPickerLabels {
|
|
|
32
33
|
nextMonth: string;
|
|
33
34
|
/** `variant="stepper"`: the button that jumps to the current month. Visible text. */
|
|
34
35
|
today: string;
|
|
36
|
+
/** `mode="year"`: the popover's accessible name. */
|
|
37
|
+
yearPanel: string;
|
|
38
|
+
/** `mode="year"`: the icon-only arrows that page the year grid back and forward by a
|
|
39
|
+
* page of twelve years. */
|
|
40
|
+
earlierYears: string;
|
|
41
|
+
laterYears: string;
|
|
42
|
+
/** `mode="year"`, `variant="stepper"`: the button that jumps to the current year.
|
|
43
|
+
* Visible text. */
|
|
44
|
+
thisYear: string;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/** The English starting point every override is merged onto. Exported (unlike the
|
|
@@ -45,6 +55,10 @@ export const DEFAULT_MONTH_PICKER_LABELS: MonthPickerLabels = {
|
|
|
45
55
|
previousMonth: "Previous month",
|
|
46
56
|
nextMonth: "Next month",
|
|
47
57
|
today: "Today",
|
|
58
|
+
yearPanel: "Choose a year",
|
|
59
|
+
earlierYears: "Earlier years",
|
|
60
|
+
laterYears: "Later years",
|
|
61
|
+
thisYear: "This year",
|
|
48
62
|
};
|
|
49
63
|
|
|
50
64
|
/** Merge caller overrides onto the English defaults. */
|
|
@@ -89,7 +103,45 @@ export interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
89
103
|
disabled?: boolean;
|
|
90
104
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
91
105
|
invalid?: boolean;
|
|
92
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* What is wrong with the month, in the caller's words — {@link Input}'s `error`
|
|
108
|
+
* (keksdose K4): under the field, attached to the trigger with `aria-describedby`
|
|
109
|
+
* (merged, after any caption), and implies `invalid`. Passing the key keeps the
|
|
110
|
+
* field's box while there is no message, so the trigger is never remounted.
|
|
111
|
+
*/
|
|
112
|
+
error?: ReactNode;
|
|
113
|
+
/** Standing advice, as on a {@link Select}: text is a caption under the field; a
|
|
114
|
+
* {@link FieldHint} rides the label line. */
|
|
115
|
+
hint?: ReactNode;
|
|
116
|
+
/**
|
|
117
|
+
* `"md"` (default): the form field. `"sm"`: the toolbar trigger — 36px tall (an
|
|
118
|
+
* `IconButton`'s height) and as wide as the month it shows, for a picker that sits
|
|
119
|
+
* between two icon buttons in a page header (keksdose K13: the budget header wrote
|
|
120
|
+
* `triggerClassName="h-9 w-auto … py-0"` by hand, live #260 — 34px between two 36px
|
|
121
|
+
* buttons, *"the kind of 2px that reads as sloppy"*). Unlabelled pickers only, as on
|
|
122
|
+
* {@link Select}: a floating label needs the tall box to float in. The width follows
|
|
123
|
+
* the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it
|
|
124
|
+
* must not move as it changes.
|
|
125
|
+
*/
|
|
126
|
+
size?: "sm" | "md";
|
|
127
|
+
/**
|
|
128
|
+
* `"month"` (default): the value is a month, `"YYYY-MM"`.
|
|
129
|
+
*
|
|
130
|
+
* `"year"`: the value is a YEAR, `"YYYY"`, and the panel is a grid of years — kastlan's
|
|
131
|
+
* budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab
|
|
132
|
+
* ("this year and four before", tax-tab.tsx:33). `min` / `max` are read as their year
|
|
133
|
+
* (`"2022"`, or any longer key). A bounded span of twelve years or fewer is shown
|
|
134
|
+
* whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /
|
|
135
|
+
* PageDown, or the arrows), starting from `min` when there is one. The stepper
|
|
136
|
+
* variant steps a year, and its Today button reads "This year".
|
|
137
|
+
*
|
|
138
|
+
* A string rather than a number, for the reason the month key is one: it is the
|
|
139
|
+
* same type across both modes, it crosses a URL as is, and it sorts. A numeric
|
|
140
|
+
* caller converts with `String(year)` and `Number(key)`.
|
|
141
|
+
*/
|
|
142
|
+
mode?: "month" | "year";
|
|
143
|
+
/** Intl options for the trigger's text (default: long month + numeric year, or the
|
|
144
|
+
* numeric year in `mode="year"`). Win over the provider's `formatDate`. */
|
|
93
145
|
formatOptions?: Intl.DateTimeFormatOptions;
|
|
94
146
|
/**
|
|
95
147
|
* The month to ring as "now", as `"YYYY-MM"`. Defaults to the local calendar's
|
|
@@ -109,8 +161,9 @@ export interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
109
161
|
* `"stepper"`: the header of a month PAGE — the month as a heading that opens the
|
|
110
162
|
* grid, then Today and ‹ › stepping the value by one month. What a calendar page
|
|
111
163
|
* assembled by hand around the field (overriding its trigger to look like a
|
|
112
|
-
* heading). `label`, `placeholder`, `invalid`
|
|
113
|
-
* and are not drawn here; with no `value` the
|
|
164
|
+
* heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and
|
|
165
|
+
* `triggerClassName` are the field's and are not drawn here; with no `value` the
|
|
166
|
+
* arrows step from the current month (or year).
|
|
114
167
|
*/
|
|
115
168
|
variant?: "field" | "stepper";
|
|
116
169
|
/** `variant="stepper"`: the heading level the month is set in (`<h2>` by default), or
|
|
@@ -160,6 +213,17 @@ function firstOf({ year, month }: YearMonth): Date {
|
|
|
160
213
|
|
|
161
214
|
const DEFAULT_TRIGGER_FORMAT: Intl.DateTimeFormatOptions = { month: "long", year: "numeric" };
|
|
162
215
|
const MONTH_NAME_FORMAT: Intl.DateTimeFormatOptions = { month: "long", year: "numeric" };
|
|
216
|
+
const YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: "numeric" };
|
|
217
|
+
|
|
218
|
+
// ── Year keys ─────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
const YEAR = /^(\d{4})/;
|
|
221
|
+
|
|
222
|
+
/** The year of `"YYYY"`, `"YYYY-MM"` or `"YYYY-MM-DD"`, or null. */
|
|
223
|
+
function parseYear(key: string | undefined): number | null {
|
|
224
|
+
const m = key ? YEAR.exec(key) : null;
|
|
225
|
+
return m ? Number(m[1]) : null;
|
|
226
|
+
}
|
|
163
227
|
|
|
164
228
|
// ── Trigger ───────────────────────────────────────────────────────────────
|
|
165
229
|
|
|
@@ -247,6 +311,7 @@ function MonthFieldTrigger({
|
|
|
247
311
|
valueId,
|
|
248
312
|
panelId,
|
|
249
313
|
padded,
|
|
314
|
+
small,
|
|
250
315
|
disabled,
|
|
251
316
|
invalid,
|
|
252
317
|
className,
|
|
@@ -263,6 +328,8 @@ function MonthFieldTrigger({
|
|
|
263
328
|
valueId: string;
|
|
264
329
|
panelId: string;
|
|
265
330
|
padded: boolean;
|
|
331
|
+
/** `size="sm"`: the 36px, content-wide toolbar trigger. */
|
|
332
|
+
small: boolean;
|
|
266
333
|
disabled?: boolean;
|
|
267
334
|
invalid?: boolean;
|
|
268
335
|
className?: string;
|
|
@@ -281,6 +348,7 @@ function MonthFieldTrigger({
|
|
|
281
348
|
aria-expanded={open}
|
|
282
349
|
id={aria.id}
|
|
283
350
|
aria-invalid={invalid || aria["aria-invalid"] === true || aria["aria-invalid"] === "true" || undefined}
|
|
351
|
+
aria-required={aria["aria-required"]}
|
|
284
352
|
aria-labelledby={aria["aria-label"] && !aria["aria-labelledby"] ? undefined : labelledBy}
|
|
285
353
|
aria-label={aria["aria-label"]}
|
|
286
354
|
aria-describedby={aria["aria-describedby"]}
|
|
@@ -288,6 +356,10 @@ function MonthFieldTrigger({
|
|
|
288
356
|
FIELD_TRIGGER,
|
|
289
357
|
"pe-9",
|
|
290
358
|
padded && FIELD_FLOATING_PAD,
|
|
359
|
+
// keksdose K13 (live #260): an IconButton's 36px, and only as wide as the
|
|
360
|
+
// month — `py-0` with a fixed height so the box is 36px whatever the type's
|
|
361
|
+
// line height. `pe-9` stays: the calendar glyph still sits at the end.
|
|
362
|
+
small && "h-9 w-auto py-0",
|
|
291
363
|
disabled && "cursor-not-allowed opacity-50",
|
|
292
364
|
invalid && FIELD_INVALID,
|
|
293
365
|
className,
|
|
@@ -522,6 +594,196 @@ function MonthGrid({
|
|
|
522
594
|
);
|
|
523
595
|
}
|
|
524
596
|
|
|
597
|
+
/** Years on one page of the year grid: the month grid's 4×3. */
|
|
598
|
+
const YEAR_PAGE = 12;
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* `mode="year"`'s panel: a page of years between ‹ › arrows — {@link MonthGrid}'s shape
|
|
602
|
+
* and keyboard, one level up (kastlan 7, keksdose tax-tab.tsx:33).
|
|
603
|
+
*
|
|
604
|
+
* Which years a page holds:
|
|
605
|
+
* - Bounded on both sides by twelve years or fewer: exactly those years, and no
|
|
606
|
+
* arrows — keksdose's "this year and four before" is five cells, not a page of
|
|
607
|
+
* twelve with seven greyed out.
|
|
608
|
+
* - Otherwise twelve at a time, starting from `min` when there is one (so the first
|
|
609
|
+
* page opens on it), else ending at `max`, else on multiples of twelve.
|
|
610
|
+
*
|
|
611
|
+
* The arrow keys walk the years (up/down by a row of three), PageUp / PageDown a page,
|
|
612
|
+
* Home / End the row; walking off a page turns it. Focus stops at the bounds rather
|
|
613
|
+
* than walking into years that refuse selection: a page has no "next year" past the
|
|
614
|
+
* last selectable one worth landing on.
|
|
615
|
+
*/
|
|
616
|
+
function YearGrid({
|
|
617
|
+
value,
|
|
618
|
+
onPick,
|
|
619
|
+
locale,
|
|
620
|
+
minYear,
|
|
621
|
+
maxYear,
|
|
622
|
+
currentYear,
|
|
623
|
+
labels,
|
|
624
|
+
}: {
|
|
625
|
+
value: number | null;
|
|
626
|
+
onPick: (key: string) => void;
|
|
627
|
+
locale: string | undefined;
|
|
628
|
+
minYear: number | null;
|
|
629
|
+
maxYear: number | null;
|
|
630
|
+
currentYear: number;
|
|
631
|
+
labels: MonthPickerLabels;
|
|
632
|
+
}) {
|
|
633
|
+
const clamp = (y: number) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));
|
|
634
|
+
// A bounded span that fits one page is shown whole.
|
|
635
|
+
const whole =
|
|
636
|
+
minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE
|
|
637
|
+
? { first: minYear, count: maxYear - minYear + 1 }
|
|
638
|
+
: null;
|
|
639
|
+
// The year holding the tab order, and with it the page on screen — one piece of
|
|
640
|
+
// state, as in MonthGrid. Opens on the value, or on this year, inside the bounds.
|
|
641
|
+
const [active, setActive] = useState(() => clamp(value ?? currentYear));
|
|
642
|
+
const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);
|
|
643
|
+
const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;
|
|
644
|
+
const count = whole ? whole.count : YEAR_PAGE;
|
|
645
|
+
const years = Array.from({ length: count }, (_, i) => first + i);
|
|
646
|
+
const outOfBounds = (y: number) => (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);
|
|
647
|
+
|
|
648
|
+
const gridRef = useRef<HTMLDivElement>(null);
|
|
649
|
+
const captionId = `${useId()}-years`;
|
|
650
|
+
const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);
|
|
651
|
+
// "2016–2027" in the locale's own range punctuation and digits.
|
|
652
|
+
const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));
|
|
653
|
+
|
|
654
|
+
const pendingFocus = useRef<number | null>(null);
|
|
655
|
+
useEffect(() => {
|
|
656
|
+
const year = pendingFocus.current;
|
|
657
|
+
if (year === null) return;
|
|
658
|
+
pendingFocus.current = null;
|
|
659
|
+
gridRef.current?.querySelector<HTMLElement>(`[data-year="${year}"]`)?.focus();
|
|
660
|
+
});
|
|
661
|
+
// Opened to pick a year: land on it (before the popover's trap, which keeps it).
|
|
662
|
+
useEffect(() => {
|
|
663
|
+
gridRef.current?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
|
|
664
|
+
}, []);
|
|
665
|
+
|
|
666
|
+
const moveTo = (next: number, focus: boolean) => {
|
|
667
|
+
const year = clamp(next);
|
|
668
|
+
if (focus) pendingFocus.current = year;
|
|
669
|
+
setActive(year);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
|
|
673
|
+
const step = horizontalStep(e.key, e.currentTarget);
|
|
674
|
+
const col = (active - first) % COLUMNS;
|
|
675
|
+
let delta: number;
|
|
676
|
+
switch (step ? "horizontal" : e.key) {
|
|
677
|
+
case "horizontal":
|
|
678
|
+
delta = step;
|
|
679
|
+
break;
|
|
680
|
+
case "ArrowUp":
|
|
681
|
+
delta = -COLUMNS;
|
|
682
|
+
break;
|
|
683
|
+
case "ArrowDown":
|
|
684
|
+
delta = COLUMNS;
|
|
685
|
+
break;
|
|
686
|
+
case "Home":
|
|
687
|
+
delta = -col;
|
|
688
|
+
break;
|
|
689
|
+
case "End":
|
|
690
|
+
delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);
|
|
691
|
+
break;
|
|
692
|
+
case "PageUp":
|
|
693
|
+
delta = -YEAR_PAGE;
|
|
694
|
+
break;
|
|
695
|
+
case "PageDown":
|
|
696
|
+
delta = YEAR_PAGE;
|
|
697
|
+
break;
|
|
698
|
+
default:
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
if (delta !== 0) moveTo(active + delta, true);
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
const prevBlocked = minYear !== null && first - 1 < minYear;
|
|
706
|
+
const nextBlocked = maxYear !== null && first + count > maxYear;
|
|
707
|
+
const arrow =
|
|
708
|
+
"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";
|
|
709
|
+
|
|
710
|
+
return (
|
|
711
|
+
<div className="select-none">
|
|
712
|
+
<div className={cn("mb-1 flex items-center", whole ? "h-7 justify-center" : "justify-between")}>
|
|
713
|
+
{!whole && (
|
|
714
|
+
<button
|
|
715
|
+
type="button"
|
|
716
|
+
aria-label={labels.earlierYears}
|
|
717
|
+
disabled={prevBlocked}
|
|
718
|
+
onClick={() => moveTo(active - YEAR_PAGE, false)}
|
|
719
|
+
className={arrow}
|
|
720
|
+
>
|
|
721
|
+
<ChevronLeft className="size-4 rtl:-scale-x-100" />
|
|
722
|
+
</button>
|
|
723
|
+
)}
|
|
724
|
+
<div id={captionId} aria-live="polite" className="text-sm font-semibold tabular-nums text-[var(--text-primary)]">
|
|
725
|
+
{caption}
|
|
726
|
+
</div>
|
|
727
|
+
{!whole && (
|
|
728
|
+
<button
|
|
729
|
+
type="button"
|
|
730
|
+
aria-label={labels.laterYears}
|
|
731
|
+
disabled={nextBlocked}
|
|
732
|
+
onClick={() => moveTo(active + YEAR_PAGE, false)}
|
|
733
|
+
className={arrow}
|
|
734
|
+
>
|
|
735
|
+
<ChevronRight className="size-4 rtl:-scale-x-100" />
|
|
736
|
+
</button>
|
|
737
|
+
)}
|
|
738
|
+
</div>
|
|
739
|
+
<div ref={gridRef} role="grid" aria-labelledby={captionId} className="grid gap-1">
|
|
740
|
+
{Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => (
|
|
741
|
+
<div key={row} role="row" className="grid grid-cols-3 gap-1">
|
|
742
|
+
{years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {
|
|
743
|
+
const disabled = outOfBounds(year);
|
|
744
|
+
const selected = year === value;
|
|
745
|
+
const current = year === currentYear;
|
|
746
|
+
return (
|
|
747
|
+
<button
|
|
748
|
+
key={year}
|
|
749
|
+
type="button"
|
|
750
|
+
role="gridcell"
|
|
751
|
+
data-year={year}
|
|
752
|
+
tabIndex={year === active ? 0 : -1}
|
|
753
|
+
aria-selected={selected}
|
|
754
|
+
aria-disabled={disabled || undefined}
|
|
755
|
+
aria-current={current ? "date" : undefined}
|
|
756
|
+
onClick={() => {
|
|
757
|
+
if (disabled) return;
|
|
758
|
+
setActive(year);
|
|
759
|
+
onPick(String(year));
|
|
760
|
+
}}
|
|
761
|
+
onKeyDown={onCellKeyDown}
|
|
762
|
+
className={cn(
|
|
763
|
+
"rounded px-2 py-1.5 text-sm tabular-nums transition-colors",
|
|
764
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset",
|
|
765
|
+
selected && !disabled
|
|
766
|
+
? "focus-visible:ring-[var(--brand-contrast)]"
|
|
767
|
+
: "focus-visible:ring-[var(--brand)]",
|
|
768
|
+
disabled
|
|
769
|
+
? "cursor-not-allowed text-[var(--text-placeholder)]"
|
|
770
|
+
: selected
|
|
771
|
+
? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]"
|
|
772
|
+
: "text-[var(--text-primary)] hover:bg-[var(--bg-hover)]",
|
|
773
|
+
current && !selected && !disabled && "ring-1 ring-inset ring-[var(--border-strong)]",
|
|
774
|
+
)}
|
|
775
|
+
>
|
|
776
|
+
{yearFormat.format(new Date(year, 0, 1))}
|
|
777
|
+
</button>
|
|
778
|
+
);
|
|
779
|
+
})}
|
|
780
|
+
</div>
|
|
781
|
+
))}
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
525
787
|
// ── Field ─────────────────────────────────────────────────────────────────
|
|
526
788
|
|
|
527
789
|
/**
|
|
@@ -542,83 +804,140 @@ function upperFirst(text: string, locale: string | undefined): string {
|
|
|
542
804
|
return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);
|
|
543
805
|
}
|
|
544
806
|
|
|
545
|
-
export function MonthPicker({
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
807
|
+
export function MonthPicker(props: MonthPickerProps) {
|
|
808
|
+
const {
|
|
809
|
+
value,
|
|
810
|
+
onChange,
|
|
811
|
+
locale: localeProp,
|
|
812
|
+
min,
|
|
813
|
+
max,
|
|
814
|
+
label,
|
|
815
|
+
placeholder,
|
|
816
|
+
disabled,
|
|
817
|
+
invalid,
|
|
818
|
+
error,
|
|
819
|
+
hint,
|
|
820
|
+
size = "md",
|
|
821
|
+
mode = "month",
|
|
822
|
+
formatOptions,
|
|
823
|
+
currentMonth,
|
|
824
|
+
labels: labelsProp,
|
|
825
|
+
className,
|
|
826
|
+
triggerClassName,
|
|
827
|
+
variant = "field",
|
|
828
|
+
headingLevel = 2,
|
|
829
|
+
showToday = true,
|
|
830
|
+
...rest
|
|
831
|
+
} = props;
|
|
565
832
|
const labels = useMonthPickerLabels(labelsProp);
|
|
566
833
|
const locale = useKitLocale(localeProp);
|
|
567
|
-
const
|
|
834
|
+
const fromProvider = useKitDateFormatter();
|
|
835
|
+
const years = mode === "year";
|
|
836
|
+
const selected = years ? null : parseMonth(value);
|
|
837
|
+
const selectedYear = years ? parseYear(value) : null;
|
|
568
838
|
const minKey = boundKey(min);
|
|
569
839
|
const maxKey = boundKey(max);
|
|
840
|
+
const minYear = parseYear(min);
|
|
841
|
+
const maxYear = parseYear(max);
|
|
570
842
|
const currentKey = boundKey(currentMonth) ?? monthKey(new Date());
|
|
843
|
+
const currentYear = parseYear(currentKey)!;
|
|
844
|
+
// The key the trigger shows, in the mode's own shape: "YYYY-MM" or "YYYY".
|
|
845
|
+
const shownKey = years ? (selectedYear !== null ? String(selectedYear) : null) : selected ? toKey(selected) : null;
|
|
571
846
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
847
|
+
// The kit's order: `formatOptions` (this field's own Intl look), then the provider's
|
|
848
|
+
// `formatDate` (keksdose K12 — the app's one way of writing a month), then the default.
|
|
849
|
+
const triggerText = shownKey
|
|
850
|
+
? (formatOptions === undefined &&
|
|
851
|
+
fromProvider?.(shownKey, { unit: years ? "year" : "month", source: "monthPicker", locale, weekday: false })) ||
|
|
852
|
+
upperFirst(
|
|
853
|
+
new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(
|
|
854
|
+
years ? new Date(selectedYear!, 0, 1) : firstOf(selected!),
|
|
576
855
|
),
|
|
577
856
|
locale,
|
|
578
857
|
)
|
|
579
858
|
: (placeholder ?? "");
|
|
580
859
|
|
|
581
860
|
const id = useId();
|
|
582
|
-
|
|
861
|
+
// keksdose K4: the caption and the error, on the trigger after the caller's own
|
|
862
|
+
// description. Painted for `invalid` and a message — not for a bare `aria-invalid`,
|
|
863
|
+
// which this field has never painted for.
|
|
864
|
+
const messages = useFieldMessages({ hint, error, describedBy: rest["aria-describedby"] });
|
|
865
|
+
const [ownAria, wrapperRest] = splitTriggerAria(rest);
|
|
866
|
+
// The field's trigger is described by the caption and the error too; the stepper
|
|
867
|
+
// draws neither, so its heading keeps the caller's own description.
|
|
868
|
+
const aria: TriggerAria = { ...ownAria, "aria-describedby": messages.describedBy };
|
|
583
869
|
const labelId = `${id}-label`;
|
|
584
870
|
const valueId = `${id}-value`;
|
|
585
871
|
const panelId = `${id}-panel`;
|
|
586
872
|
// Only a STRING label can double as a name; a ReactNode may hold its own controls.
|
|
587
873
|
const named = typeof label === "string";
|
|
874
|
+
// `sm` is the toolbar trigger, for an unlabelled picker only (see `size`).
|
|
875
|
+
const small = size === "sm" && label === undefined;
|
|
588
876
|
// The panel is portalled to <body>, out of the subtree whose `dir` it inherited — an
|
|
589
877
|
// RTL form got an LTR grid. Read when opening (`toggle` is the popover's only way
|
|
590
878
|
// open) and put back on the panel. Also what the arrow keys above read.
|
|
591
879
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
592
880
|
const [dir, setDir] = useState<Direction>("ltr");
|
|
881
|
+
const panelLabel = years ? labels.yearPanel : labels.panel;
|
|
593
882
|
|
|
594
|
-
const grid = (close: () => void) =>
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
883
|
+
const grid = (close: () => void) =>
|
|
884
|
+
years ? (
|
|
885
|
+
<YearGrid
|
|
886
|
+
value={selectedYear}
|
|
887
|
+
locale={locale}
|
|
888
|
+
minYear={minYear}
|
|
889
|
+
maxYear={maxYear}
|
|
890
|
+
currentYear={currentYear}
|
|
891
|
+
labels={labels}
|
|
892
|
+
onPick={(key) => {
|
|
893
|
+
onChange(key);
|
|
894
|
+
close();
|
|
895
|
+
}}
|
|
896
|
+
/>
|
|
897
|
+
) : (
|
|
898
|
+
<MonthGrid
|
|
899
|
+
value={selected}
|
|
900
|
+
locale={locale}
|
|
901
|
+
minKey={minKey}
|
|
902
|
+
maxKey={maxKey}
|
|
903
|
+
currentKey={currentKey}
|
|
904
|
+
labels={labels}
|
|
905
|
+
onPick={(key) => {
|
|
906
|
+
onChange(key);
|
|
907
|
+
close();
|
|
908
|
+
}}
|
|
909
|
+
/>
|
|
910
|
+
);
|
|
608
911
|
|
|
609
912
|
if (variant === "stepper") {
|
|
610
913
|
// The arrows step from the value, or from "now" when there is none; each is dead
|
|
611
|
-
// once the month it leads to is out of bounds, and Today once "now" is.
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
914
|
+
// once the month (or year) it leads to is out of bounds, and Today once "now" is.
|
|
915
|
+
let prevKey: string;
|
|
916
|
+
let nextKey: string;
|
|
917
|
+
let nowKey: string;
|
|
918
|
+
let outside: (key: string) => boolean;
|
|
919
|
+
if (years) {
|
|
920
|
+
const base = selectedYear ?? currentYear;
|
|
921
|
+
prevKey = String(base - 1);
|
|
922
|
+
nextKey = String(base + 1);
|
|
923
|
+
nowKey = String(currentYear);
|
|
924
|
+
outside = (key) => {
|
|
925
|
+
const y = Number(key);
|
|
926
|
+
return (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);
|
|
927
|
+
};
|
|
928
|
+
} else {
|
|
929
|
+
const base = selected ?? parseMonth(currentKey)!;
|
|
930
|
+
prevKey = toKey(shiftMonth(base, -1));
|
|
931
|
+
nextKey = toKey(shiftMonth(base, 1));
|
|
932
|
+
nowKey = currentKey;
|
|
933
|
+
outside = (key) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));
|
|
934
|
+
}
|
|
616
935
|
const Heading = headingLevel ? (`h${headingLevel}` as const) : null;
|
|
617
936
|
const trigger = (
|
|
618
937
|
<Popover
|
|
619
938
|
width={256}
|
|
620
939
|
panelId={panelId}
|
|
621
|
-
labels={{ panel:
|
|
940
|
+
labels={{ panel: panelLabel }}
|
|
622
941
|
dir={dir}
|
|
623
942
|
trigger={({ open, toggle, ref }) => (
|
|
624
943
|
<MonthHeadingTrigger
|
|
@@ -631,7 +950,7 @@ export function MonthPicker({
|
|
|
631
950
|
text={triggerText}
|
|
632
951
|
panelId={panelId}
|
|
633
952
|
disabled={disabled}
|
|
634
|
-
aria={
|
|
953
|
+
aria={ownAria}
|
|
635
954
|
/>
|
|
636
955
|
)}
|
|
637
956
|
>
|
|
@@ -646,16 +965,16 @@ export function MonthPicker({
|
|
|
646
965
|
<Button
|
|
647
966
|
variant="secondary"
|
|
648
967
|
size="sm"
|
|
649
|
-
disabled={disabled || outside(
|
|
968
|
+
disabled={disabled || outside(nowKey)}
|
|
650
969
|
onClick={() => {
|
|
651
|
-
if (
|
|
970
|
+
if (nowKey !== shownKey) onChange(nowKey);
|
|
652
971
|
}}
|
|
653
972
|
>
|
|
654
|
-
{labels.today}
|
|
973
|
+
{years ? labels.thisYear : labels.today}
|
|
655
974
|
</Button>
|
|
656
975
|
)}
|
|
657
976
|
<IconButton
|
|
658
|
-
aria-label={labels.previousMonth}
|
|
977
|
+
aria-label={years ? labels.previousYear : labels.previousMonth}
|
|
659
978
|
disabled={disabled || outside(prevKey)}
|
|
660
979
|
onClick={() => onChange(prevKey)}
|
|
661
980
|
>
|
|
@@ -663,7 +982,7 @@ export function MonthPicker({
|
|
|
663
982
|
<ChevronLeft className="rtl:-scale-x-100" />
|
|
664
983
|
</IconButton>
|
|
665
984
|
<IconButton
|
|
666
|
-
aria-label={labels.nextMonth}
|
|
985
|
+
aria-label={years ? labels.nextYear : labels.nextMonth}
|
|
667
986
|
disabled={disabled || outside(nextKey)}
|
|
668
987
|
onClick={() => onChange(nextKey)}
|
|
669
988
|
>
|
|
@@ -674,9 +993,11 @@ export function MonthPicker({
|
|
|
674
993
|
);
|
|
675
994
|
}
|
|
676
995
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
996
|
+
const field = (
|
|
997
|
+
// `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
|
|
998
|
+
// full-width box would park it at the far end of the row, away from its trigger.
|
|
999
|
+
<div {...wrapperRest} ref={rootRef} className={cn("relative", small && "w-fit max-w-full", className)}>
|
|
1000
|
+
{label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}
|
|
680
1001
|
{/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,
|
|
681
1002
|
and inside this `relative` root either way. */}
|
|
682
1003
|
{named && (
|
|
@@ -687,7 +1008,7 @@ export function MonthPicker({
|
|
|
687
1008
|
<Popover
|
|
688
1009
|
width={256}
|
|
689
1010
|
panelId={panelId}
|
|
690
|
-
labels={{ panel:
|
|
1011
|
+
labels={{ panel: panelLabel }}
|
|
691
1012
|
dir={dir}
|
|
692
1013
|
trigger={({ open, toggle, ref }) => (
|
|
693
1014
|
<MonthFieldTrigger
|
|
@@ -698,7 +1019,7 @@ export function MonthPicker({
|
|
|
698
1019
|
}}
|
|
699
1020
|
triggerRef={ref}
|
|
700
1021
|
triggerText={triggerText}
|
|
701
|
-
hasValue={
|
|
1022
|
+
hasValue={shownKey !== null}
|
|
702
1023
|
// As DatePicker: a caller's reference first; an `id` without a label of our
|
|
703
1024
|
// own references the trigger itself, so an external <label htmlFor> names it.
|
|
704
1025
|
labelledBy={[
|
|
@@ -712,8 +1033,9 @@ export function MonthPicker({
|
|
|
712
1033
|
valueId={valueId}
|
|
713
1034
|
panelId={panelId}
|
|
714
1035
|
padded={label !== undefined}
|
|
1036
|
+
small={small}
|
|
715
1037
|
disabled={disabled}
|
|
716
|
-
invalid={invalid}
|
|
1038
|
+
invalid={invalid || hasContent(error)}
|
|
717
1039
|
className={triggerClassName}
|
|
718
1040
|
/>
|
|
719
1041
|
)}
|
|
@@ -726,4 +1048,19 @@ export function MonthPicker({
|
|
|
726
1048
|
/>
|
|
727
1049
|
</div>
|
|
728
1050
|
);
|
|
1051
|
+
// An unlabelled picker has no label line: a FieldHint goes at its end, outside the
|
|
1052
|
+
// box, as on an unlabelled Select.
|
|
1053
|
+
const endHint = label === undefined ? messages.labelHint : undefined;
|
|
1054
|
+
return (
|
|
1055
|
+
<FieldBox below={messages.below} reserve={"error" in props || "hint" in props}>
|
|
1056
|
+
{endHint === undefined ? (
|
|
1057
|
+
field
|
|
1058
|
+
) : (
|
|
1059
|
+
<div className="flex items-center gap-1.5">
|
|
1060
|
+
<div className={cn("min-w-0", !small && "flex-1")}>{field}</div>
|
|
1061
|
+
<span className="flex shrink-0 items-center">{endHint}</span>
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
1064
|
+
</FieldBox>
|
|
1065
|
+
);
|
|
729
1066
|
}
|