@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
|
@@ -3,8 +3,10 @@ import type { CSSProperties, ReactNode, TdHTMLAttributes, ThHTMLAttributes } fro
|
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import {
|
|
5
5
|
ArrowDown,
|
|
6
|
+
ArrowDownWideNarrow,
|
|
6
7
|
ArrowUp,
|
|
7
8
|
ArrowUpDown,
|
|
9
|
+
ArrowUpNarrowWide,
|
|
8
10
|
ChevronDown,
|
|
9
11
|
ChevronRight,
|
|
10
12
|
Filter,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
Trash2,
|
|
13
15
|
X,
|
|
14
16
|
} from "lucide-react";
|
|
15
|
-
import { Card, IconButton, Spinner } from "./ui";
|
|
17
|
+
import { Card, IconButton, Select, Spinner } from "./ui";
|
|
16
18
|
import { cn } from "../lib/cn";
|
|
17
19
|
import {
|
|
18
20
|
defaultFilterState,
|
|
@@ -33,7 +35,7 @@ import { FullBleedDialog } from "./full-bleed-dialog";
|
|
|
33
35
|
import { Popover } from "./popover";
|
|
34
36
|
import { useMediaQuery } from "../hooks/use-media-query";
|
|
35
37
|
import { useAnnounce } from "../hooks/use-announce";
|
|
36
|
-
import { resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
|
|
38
|
+
import { DEFAULT_DATA_TABLE_LABELS, resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
|
|
37
39
|
import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels";
|
|
38
40
|
import { Tooltip } from "./tooltip";
|
|
39
41
|
import { dirOf, isRtl, type Direction } from "../lib/direction";
|
|
@@ -78,6 +80,14 @@ export interface DataTableColumn<T> {
|
|
|
78
80
|
* Default `"asc"`.
|
|
79
81
|
*/
|
|
80
82
|
firstSort?: SortDir;
|
|
83
|
+
/**
|
|
84
|
+
* The column's name as plain text, for the places a ReactNode `header` cannot go:
|
|
85
|
+
* the phone sort control's options ({@link DataTableProps.mobileSort}), the spoken
|
|
86
|
+
* "Sorted by …" and the column-settings checklist. Only needed when `header` is not
|
|
87
|
+
* a string (an icon, a unit suffix); without it those places fall back to the
|
|
88
|
+
* header string, then to `key`.
|
|
89
|
+
*/
|
|
90
|
+
headerText?: string;
|
|
81
91
|
/**
|
|
82
92
|
* Extra attributes for this column's `<td>` in a given row — a `data-*` hook, a
|
|
83
93
|
* `title`, an `aria-describedby`, a class. keksdose's accounts page
|
|
@@ -390,6 +400,18 @@ export interface DataTableProps<T> {
|
|
|
390
400
|
* wastes space (feedback #317). Desktop is unaffected.
|
|
391
401
|
*/
|
|
392
402
|
mobileCard?: (row: T) => ReactNode;
|
|
403
|
+
/**
|
|
404
|
+
* A "Sort by" select and a direction button above the phone card list (keksdose
|
|
405
|
+
* K10). The card list has no header row, so without it a phone user is stuck with
|
|
406
|
+
* whatever order the table opened in. It drives the same sort state as the column
|
|
407
|
+
* headers — `sorts`/`onSortsChange` when controlled, the URL and `storageKey` when
|
|
408
|
+
* not — and offers the columns a header click could sort (those with a `sortBy`).
|
|
409
|
+
* Each option is named by the column's `headerText`, else its string `header`.
|
|
410
|
+
*
|
|
411
|
+
* Off by default, so no existing phone layout gains a row. Strings:
|
|
412
|
+
* {@link DataTableSortLabels}. Phones only; the desktop table is unchanged.
|
|
413
|
+
*/
|
|
414
|
+
mobileSort?: boolean;
|
|
393
415
|
/**
|
|
394
416
|
* Swipe actions for a mobile row, revealed by dragging it horizontally. Return
|
|
395
417
|
* `null` (or empty sides) for rows that should not move — a locked row, one whose
|
|
@@ -683,7 +705,120 @@ function cleanAttrs(
|
|
|
683
705
|
*/
|
|
684
706
|
function columnLabel<T>(col: DataTableColumn<T>, labels?: DataTableLabels): string {
|
|
685
707
|
if (col.key === ROW_ACTIONS_KEY && labels) return labels.actions;
|
|
686
|
-
return typeof col.header === "string" ? col.header : col.key;
|
|
708
|
+
return col.headerText ?? (typeof col.header === "string" ? col.header : col.key);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ---------- Phone sort control (keksdose K10) ----------
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* The phone sort control's strings (keksdose K10). They are `dataTable` strings —
|
|
715
|
+
* read from the table's resolved labels, so the `labels` prop and the provider's
|
|
716
|
+
* `dataTable` namespace reach them — and are declared here, beside the control, with
|
|
717
|
+
* English defaults until `DataTableLabels` declares them itself.
|
|
718
|
+
*/
|
|
719
|
+
export interface DataTableSortLabels {
|
|
720
|
+
/** The control's visible label: "Sort by". */
|
|
721
|
+
sortBy: string;
|
|
722
|
+
/** The "no sort" option — the rows in the order they were given: "Default order". */
|
|
723
|
+
sortDefault: string;
|
|
724
|
+
/** The direction button's name while the sort ascends: "Ascending". */
|
|
725
|
+
sortAscending: string;
|
|
726
|
+
/** …and while it descends: "Descending". */
|
|
727
|
+
sortDescending: string;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** The phone sort control's words, as they sit in {@link DEFAULT_DATA_TABLE_LABELS}
|
|
731
|
+
* (DataTableLabels extends DataTableSortLabels; one source for the English). */
|
|
732
|
+
export const DEFAULT_DATA_TABLE_SORT_LABELS: DataTableSortLabels = {
|
|
733
|
+
sortBy: DEFAULT_DATA_TABLE_LABELS.sortBy,
|
|
734
|
+
sortDefault: DEFAULT_DATA_TABLE_LABELS.sortDefault,
|
|
735
|
+
sortAscending: DEFAULT_DATA_TABLE_LABELS.sortAscending,
|
|
736
|
+
sortDescending: DEFAULT_DATA_TABLE_LABELS.sortDescending,
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const flipDir = (d: SortDir): SortDir => (d === "asc" ? "desc" : "asc");
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* The card list's sort control: a "Sort by" select and a direction button.
|
|
743
|
+
*
|
|
744
|
+
* keksdose K10 (inputs audit 2026-10-02 §5; its aggregated report tables, #258): a
|
|
745
|
+
* table is sorted by its header row and the phone card list has none, so the two
|
|
746
|
+
* decisions the header carries — which column, which direction — were unreachable
|
|
747
|
+
* below the breakpoint. keksdose hand-built this row over its DataTable to answer
|
|
748
|
+
* "who did I pay the most" on the device it was asked on; this is that row, driven by
|
|
749
|
+
* the SAME sort state as the headers (controlled `sorts` included), so turning the
|
|
750
|
+
* phone sideways keeps the order and a sort chosen here is the one the URL and the
|
|
751
|
+
* storage key hold.
|
|
752
|
+
*
|
|
753
|
+
* The rules are the header's: a newly picked column starts in its `firstSort`, the
|
|
754
|
+
* direction button flips the primary sort (secondary ones stay), and "Default order"
|
|
755
|
+
* — the third click of a `"tri"` cycle — is offered only under `"tri"`, since a
|
|
756
|
+
* `"toggle"` table promises it is always ranked by something. The select's label is
|
|
757
|
+
* a real `<label>` beside it, and the button sits outside that label: a click inside
|
|
758
|
+
* a label is forwarded to the control it names, so a nested button would flip AND
|
|
759
|
+
* open the select.
|
|
760
|
+
*/
|
|
761
|
+
function MobileSort<T>({
|
|
762
|
+
columns,
|
|
763
|
+
sorts,
|
|
764
|
+
sortCycle,
|
|
765
|
+
compact,
|
|
766
|
+
labels,
|
|
767
|
+
onPick,
|
|
768
|
+
onFlip,
|
|
769
|
+
}: {
|
|
770
|
+
columns: DataTableColumn<T>[];
|
|
771
|
+
sorts: SortState[];
|
|
772
|
+
sortCycle: SortCycle;
|
|
773
|
+
compact: boolean;
|
|
774
|
+
labels: DataTableLabels;
|
|
775
|
+
onPick: (key: string | null) => void;
|
|
776
|
+
onFlip: () => void;
|
|
777
|
+
}) {
|
|
778
|
+
const selectId = useId();
|
|
779
|
+
const sortLabels: DataTableSortLabels = labels;
|
|
780
|
+
if (columns.length === 0) return null;
|
|
781
|
+
// A persisted or URL sort can name a column with no `sortBy` (or a hidden one): the
|
|
782
|
+
// sort chain drops it, and so does the control rather than claiming it.
|
|
783
|
+
const primary = sorts[0] && columns.some((c) => c.key === sorts[0].key) ? sorts[0] : null;
|
|
784
|
+
const offerDefault = sortCycle === "tri" || primary === null;
|
|
785
|
+
const desc = primary?.dir === "desc";
|
|
786
|
+
return (
|
|
787
|
+
<div
|
|
788
|
+
data-table-mobile-sort=""
|
|
789
|
+
className={cn(
|
|
790
|
+
"flex items-center gap-2 border-b border-[var(--border)]",
|
|
791
|
+
compact ? "px-3 py-1.5" : "px-4 py-2",
|
|
792
|
+
)}
|
|
793
|
+
>
|
|
794
|
+
<label htmlFor={selectId} className="shrink-0 text-xs text-[var(--text-muted)]">
|
|
795
|
+
{sortLabels.sortBy}
|
|
796
|
+
</label>
|
|
797
|
+
<Select
|
|
798
|
+
id={selectId}
|
|
799
|
+
size={compact ? "sm" : "md"}
|
|
800
|
+
className="min-w-0 flex-1"
|
|
801
|
+
value={primary?.key ?? ""}
|
|
802
|
+
onChange={(e) => onPick(e.target.value || null)}
|
|
803
|
+
>
|
|
804
|
+
{offerDefault && <option value="">{sortLabels.sortDefault}</option>}
|
|
805
|
+
{columns.map((col) => (
|
|
806
|
+
<option key={col.key} value={col.key}>
|
|
807
|
+
{columnLabel(col, labels)}
|
|
808
|
+
</option>
|
|
809
|
+
))}
|
|
810
|
+
</Select>
|
|
811
|
+
<IconButton
|
|
812
|
+
size={compact ? "xs" : "md"}
|
|
813
|
+
variant="secondary"
|
|
814
|
+
label={desc ? sortLabels.sortDescending : sortLabels.sortAscending}
|
|
815
|
+
disabled={primary === null}
|
|
816
|
+
onClick={onFlip}
|
|
817
|
+
>
|
|
818
|
+
{desc ? <ArrowDownWideNarrow /> : <ArrowUpNarrowWide />}
|
|
819
|
+
</IconButton>
|
|
820
|
+
</div>
|
|
821
|
+
);
|
|
687
822
|
}
|
|
688
823
|
|
|
689
824
|
/**
|
|
@@ -857,6 +992,7 @@ export function DataTable<T>({
|
|
|
857
992
|
mobileGroupBy,
|
|
858
993
|
mobileGroupLabel,
|
|
859
994
|
mobileCard,
|
|
995
|
+
mobileSort = false,
|
|
860
996
|
mobileSwipeActions,
|
|
861
997
|
leadingRow,
|
|
862
998
|
density = "comfortable",
|
|
@@ -1125,6 +1261,13 @@ export function DataTable<T>({
|
|
|
1125
1261
|
firstDir: col?.firstSort,
|
|
1126
1262
|
cycle: sortCycle,
|
|
1127
1263
|
});
|
|
1264
|
+
commitSorts(next, key);
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
// Header clicks and the phone sort control (`mobileSort`) land here alike, so both
|
|
1268
|
+
// go through the same owner and say the same thing.
|
|
1269
|
+
const commitSorts = (next: SortState[], key: string) => {
|
|
1270
|
+
const col = columns.find((c) => c.key === key);
|
|
1128
1271
|
if (onSortsChange) onSortsChange(next);
|
|
1129
1272
|
else setInternalSorts(next);
|
|
1130
1273
|
|
|
@@ -1405,9 +1548,9 @@ export function DataTable<T>({
|
|
|
1405
1548
|
|
|
1406
1549
|
// ---- Mobile card list (md:hidden) ----
|
|
1407
1550
|
// Renders the same paged/filtered/sorted slice but as stacked cards instead
|
|
1408
|
-
// of a horizontally-scrolling table.
|
|
1409
|
-
//
|
|
1410
|
-
//
|
|
1551
|
+
// of a horizontally-scrolling table. Column settings stay desktop-only; the
|
|
1552
|
+
// filters come through a bottom sheet (feedback #299) and the sort through the
|
|
1553
|
+
// opt-in `mobileSort` row (keksdose K10), both on the table's own state.
|
|
1411
1554
|
const mobileColumns = visibleColumns.filter((c) => !c.mobileHidden);
|
|
1412
1555
|
const mobilePrimaryCol =
|
|
1413
1556
|
mobileColumns.find((c) => c.mobilePrimary) ?? mobileColumns[0] ?? null;
|
|
@@ -1765,6 +1908,32 @@ export function DataTable<T>({
|
|
|
1765
1908
|
locale={locale}
|
|
1766
1909
|
/>
|
|
1767
1910
|
)}
|
|
1911
|
+
{mobileSort && (
|
|
1912
|
+
<MobileSort
|
|
1913
|
+
columns={visibleColumns.filter((c) => (!isServer || !!onSortsChange) && !!c.sortBy)}
|
|
1914
|
+
sorts={sorts}
|
|
1915
|
+
sortCycle={sortCycle}
|
|
1916
|
+
compact={compact}
|
|
1917
|
+
labels={labels}
|
|
1918
|
+
onPick={(key) => {
|
|
1919
|
+
if (key === null) {
|
|
1920
|
+
if (sorts.length > 0) commitSorts([], sorts[0].key);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
if (sorts[0]?.key === key) return;
|
|
1924
|
+
// A newly picked column starts where its header's first click would.
|
|
1925
|
+
const col = columns.find((c) => c.key === key);
|
|
1926
|
+
commitSorts([{ key, dir: col?.firstSort ?? "asc" }], key);
|
|
1927
|
+
}}
|
|
1928
|
+
onFlip={() => {
|
|
1929
|
+
if (sorts.length === 0) return;
|
|
1930
|
+
commitSorts(
|
|
1931
|
+
sorts.map((s, i) => (i === 0 ? { ...s, dir: flipDir(s.dir) } : s)),
|
|
1932
|
+
sorts[0].key,
|
|
1933
|
+
);
|
|
1934
|
+
}}
|
|
1935
|
+
/>
|
|
1936
|
+
)}
|
|
1768
1937
|
<ul
|
|
1769
1938
|
className={cn(
|
|
1770
1939
|
"divide-y divide-[var(--border)]",
|
|
@@ -7,13 +7,16 @@ import { addDaysIso, parseIsoDate } from "../lib/dates";
|
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_DATE_PICKER_LABELS,
|
|
9
9
|
DEFAULT_PICKER_SHEET_LABELS,
|
|
10
|
+
useKitDateFormatter,
|
|
10
11
|
useKitLabels,
|
|
11
12
|
useKitLocale,
|
|
12
13
|
type DatePickerLabels,
|
|
14
|
+
type KitDateFormatContext,
|
|
13
15
|
} from "../i18n/kit-labels";
|
|
14
16
|
import { splitTriggerAria } from "./trigger-aria";
|
|
15
17
|
import type { TriggerAria } from "./trigger-aria";
|
|
16
|
-
import { Button,
|
|
18
|
+
import { Button, FIELD_BASE, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID, PHONE_QUERY } from "./ui";
|
|
19
|
+
import { FieldBox, FieldLabelLine, useFieldMessages } from "./field-anatomy";
|
|
17
20
|
import { FullBleedDialog } from "./full-bleed-dialog";
|
|
18
21
|
import { useMediaQuery } from "../hooks/use-media-query";
|
|
19
22
|
import { MiniCalendar, type MiniCalendarProps } from "./mini-calendar";
|
|
@@ -47,13 +50,33 @@ interface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "onC
|
|
|
47
50
|
disabled?: boolean;
|
|
48
51
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
49
52
|
invalid?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Standing advice for the field (keksdose K4, "uniform field anatomy"), as on a
|
|
55
|
+
* {@link Select}: plain TEXT is a caption under the field, attached with
|
|
56
|
+
* `aria-describedby`; a {@link FieldHint} rides the label line (or, unlabelled, sits
|
|
57
|
+
* at the field's end, outside the box).
|
|
58
|
+
*/
|
|
59
|
+
hint?: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* What is wrong with the date, in the caller's words ("The due date is before the
|
|
62
|
+
* invoice date") — {@link Input}'s `error` (keksdose K4: DateField errors had no slot).
|
|
63
|
+
* Rendered under the field (under the whole row, with `step` / `today`), attached to
|
|
64
|
+
* the trigger with `aria-describedby` — merged with any the caller passed, after the
|
|
65
|
+
* caption — and implies `invalid`. Passing the key at all keeps the field's box while
|
|
66
|
+
* there is no message, so the trigger is never remounted as one comes and goes.
|
|
67
|
+
*/
|
|
68
|
+
error?: ReactNode;
|
|
50
69
|
className?: string;
|
|
51
|
-
/** Intl options for the trigger's formatted date (default: locale short date).
|
|
70
|
+
/** Intl options for the trigger's formatted date (default: locale short date). Win
|
|
71
|
+
* over the provider's `formatDate`. */
|
|
52
72
|
formatOptions?: Intl.DateTimeFormatOptions;
|
|
53
|
-
/** Render the trigger's text for an ISO date yourself; wins over `formatOptions
|
|
73
|
+
/** Render the trigger's text for an ISO date yourself; wins over `formatOptions` and
|
|
74
|
+
* over the provider's `formatDate`.
|
|
54
75
|
* For a host whose date rendering is more than one `Intl` call can say — Keksdose
|
|
55
76
|
* puts the weekday's name in the UI language beside digits ordered by a separate
|
|
56
|
-
* format preference, two locales in one string. `locale` still drives the calendar.
|
|
77
|
+
* format preference, two locales in one string. `locale` still drives the calendar.
|
|
78
|
+
* Set that rendering once with `<UiKitProvider formatDate>` (keksdose K12) and this
|
|
79
|
+
* is for the one field that differs. */
|
|
57
80
|
formatValue?: (iso: string) => string;
|
|
58
81
|
/** Passed through to the {@link MiniCalendar} this opens. Its month arrows are
|
|
59
82
|
* icon-only, so their `aria-label` is the only name they have — and the package
|
|
@@ -69,6 +92,30 @@ function formatDate(iso: string, locale: string | undefined, options?: Intl.Date
|
|
|
69
92
|
return parseIsoDate(iso)?.toLocaleDateString(locale, options) ?? "";
|
|
70
93
|
}
|
|
71
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The trigger's date renderer, in the kit's order of precedence: the picker's own
|
|
97
|
+
* `formatValue`, then its `formatOptions` (an explicit `Intl` look for this field),
|
|
98
|
+
* then the provider's `formatDate` (keksdose K12), then the locale's short date. A
|
|
99
|
+
* provider formatter that answers `""` falls through to the default for that date.
|
|
100
|
+
*/
|
|
101
|
+
function useTriggerFormat(
|
|
102
|
+
source: KitDateFormatContext["source"],
|
|
103
|
+
locale: string | undefined,
|
|
104
|
+
formatOptions: Intl.DateTimeFormatOptions | undefined,
|
|
105
|
+
formatValue: ((iso: string) => string) | undefined,
|
|
106
|
+
): (iso: string) => string {
|
|
107
|
+
const fromProvider = useKitDateFormatter();
|
|
108
|
+
if (formatValue) return formatValue;
|
|
109
|
+
return (iso) => {
|
|
110
|
+
if (formatOptions === undefined && fromProvider) {
|
|
111
|
+
// A single date stands alone, where the weekday helps; a range's two ends do not.
|
|
112
|
+
const text = fromProvider(iso, { unit: "day", source, locale, weekday: source === "datePicker" });
|
|
113
|
+
if (text) return text;
|
|
114
|
+
}
|
|
115
|
+
return formatDate(iso, locale, formatOptions);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
72
119
|
/**
|
|
73
120
|
* Everything the kit's own trigger button carries, handed to a custom one
|
|
74
121
|
* ({@link DateRangePickerProps.renderTrigger}) to spread onto its `<button>`. Spread
|
|
@@ -285,6 +332,7 @@ function DateField({
|
|
|
285
332
|
sheet = false,
|
|
286
333
|
sheetBackCloses = true,
|
|
287
334
|
renderTrigger,
|
|
335
|
+
labelHint,
|
|
288
336
|
children,
|
|
289
337
|
// The trigger's, not the wrapper's: `Field`'s render-prop spreads it with the id
|
|
290
338
|
// pair, and a required date read as optional while it sat on the role-less div.
|
|
@@ -295,6 +343,8 @@ function DateField({
|
|
|
295
343
|
triggerText: string;
|
|
296
344
|
hasValue: boolean;
|
|
297
345
|
label?: ReactNode;
|
|
346
|
+
/** A `FieldHint` for the label line, beside the label. */
|
|
347
|
+
labelHint?: ReactNode;
|
|
298
348
|
disabled?: boolean;
|
|
299
349
|
clearable?: boolean;
|
|
300
350
|
/** Already resolved by the picker (prop > provider > English). */
|
|
@@ -412,7 +462,7 @@ function DateField({
|
|
|
412
462
|
// The caller's attributes land here, on the field's own box — the trigger inside is
|
|
413
463
|
// named by `aria-labelledby` and must keep the id pair it is given.
|
|
414
464
|
<div {...wrapperRest} ref={rootRef} className={cn("relative", className)}>
|
|
415
|
-
{label !== undefined && <
|
|
465
|
+
{label !== undefined && <FieldLabelLine label={label} hint={labelHint} />}
|
|
416
466
|
{/* The visible FieldLabel is a plain span, not a `<label htmlFor>`, so it names
|
|
417
467
|
nothing on its own — this hidden twin is what the trigger is named by.
|
|
418
468
|
`aria-hidden` because a name reference reads a hidden element deliberately
|
|
@@ -568,35 +618,61 @@ function useDatePickerLabels(
|
|
|
568
618
|
return useKitLabels("datePicker", DEFAULT_DATE_PICKER_LABELS, fromProps);
|
|
569
619
|
}
|
|
570
620
|
|
|
621
|
+
/**
|
|
622
|
+
* The caller's `hint` when it has no label line to ride: at the end of the field,
|
|
623
|
+
* outside the box, as an unlabelled {@link Select} places its FieldHint (0.15.5 P8).
|
|
624
|
+
*/
|
|
625
|
+
function withEndHint(field: ReactNode, hint: ReactNode): ReactNode {
|
|
626
|
+
if (hint === undefined) return field;
|
|
627
|
+
return (
|
|
628
|
+
<div className="flex items-center gap-1.5">
|
|
629
|
+
<div className="min-w-0 flex-1">{field}</div>
|
|
630
|
+
<span className="flex shrink-0 items-center">{hint}</span>
|
|
631
|
+
</div>
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
571
635
|
/** Single-date picker: a field showing the formatted date, opening a calendar. */
|
|
572
|
-
export function DatePicker({
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
636
|
+
export function DatePicker(props: DatePickerProps) {
|
|
637
|
+
const {
|
|
638
|
+
value,
|
|
639
|
+
onChange,
|
|
640
|
+
locale: localeProp,
|
|
641
|
+
placeholder,
|
|
642
|
+
min,
|
|
643
|
+
max,
|
|
644
|
+
formatOptions,
|
|
645
|
+
formatValue,
|
|
646
|
+
step,
|
|
647
|
+
stepLabels,
|
|
648
|
+
today,
|
|
649
|
+
todayLabel,
|
|
650
|
+
className,
|
|
651
|
+
calendarLabels,
|
|
652
|
+
label,
|
|
653
|
+
clearable,
|
|
654
|
+
clearLabel,
|
|
655
|
+
disabled,
|
|
656
|
+
invalid,
|
|
657
|
+
hint,
|
|
658
|
+
error,
|
|
659
|
+
// Named so it reaches the field (and so its trigger) when a step/today row is the
|
|
660
|
+
// root and takes the rest of the caller's props.
|
|
661
|
+
"aria-required": ariaRequired,
|
|
662
|
+
...rest
|
|
663
|
+
} = props;
|
|
597
664
|
const locale = useKitLocale(localeProp);
|
|
598
665
|
const text = useDatePickerLabels(clearLabel, stepLabels, todayLabel);
|
|
599
|
-
const render =
|
|
666
|
+
const render = useTriggerFormat("datePicker", locale, formatOptions, formatValue);
|
|
667
|
+
// keksdose K4: the caption and the error under the field, attached to the TRIGGER
|
|
668
|
+
// (the element a screen reader is on) after any description of the caller's own.
|
|
669
|
+
const messages = useFieldMessages({
|
|
670
|
+
hint,
|
|
671
|
+
error,
|
|
672
|
+
invalid,
|
|
673
|
+
describedBy: rest["aria-describedby"],
|
|
674
|
+
ariaInvalid: rest["aria-invalid"],
|
|
675
|
+
});
|
|
600
676
|
// With a step or a today button beside it the field is no longer the outermost
|
|
601
677
|
// element — the flex row at the bottom is, and a caller's attributes belong on
|
|
602
678
|
// whichever of the two is actually the root. Naming the shared props rather than
|
|
@@ -608,13 +684,15 @@ export function DatePicker({
|
|
|
608
684
|
// With step/today buttons the flex row is the root and takes the caller's props —
|
|
609
685
|
// except the ones that name the field, which still belong on its trigger.
|
|
610
686
|
{...(wrapped ? triggerAria : rest)}
|
|
687
|
+
aria-describedby={messages.describedBy}
|
|
611
688
|
aria-required={ariaRequired}
|
|
612
689
|
label={label}
|
|
690
|
+
labelHint={messages.labelHint}
|
|
613
691
|
clearable={clearable}
|
|
614
692
|
clearLabel={text.clear}
|
|
615
693
|
panelLabel={text.panel}
|
|
616
694
|
disabled={disabled}
|
|
617
|
-
invalid={
|
|
695
|
+
invalid={messages.isInvalid}
|
|
618
696
|
className={step ? "min-w-0 flex-1" : className}
|
|
619
697
|
hasValue={Boolean(value)}
|
|
620
698
|
triggerText={value ? render(value) : (placeholder ?? "")}
|
|
@@ -638,7 +716,16 @@ export function DatePicker({
|
|
|
638
716
|
)}
|
|
639
717
|
</DateField>
|
|
640
718
|
);
|
|
641
|
-
|
|
719
|
+
// Passed at all — even as `undefined` — the field keeps its box; see FieldBox.
|
|
720
|
+
const reserve = "error" in props || "hint" in props;
|
|
721
|
+
const endHint = label === undefined ? messages.labelHint : undefined;
|
|
722
|
+
if (!wrapped) {
|
|
723
|
+
return (
|
|
724
|
+
<FieldBox below={messages.below} reserve={reserve}>
|
|
725
|
+
{withEndHint(field, endHint)}
|
|
726
|
+
</FieldBox>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
642
729
|
|
|
643
730
|
// An empty field has nothing to step from, so both buttons are dead until a date
|
|
644
731
|
// is picked. Bounds are compared as strings: "YYYY-MM-DD" sorts chronologically.
|
|
@@ -649,7 +736,7 @@ export function DatePicker({
|
|
|
649
736
|
if (!next || disabled) return true;
|
|
650
737
|
return outOfBounds(next);
|
|
651
738
|
};
|
|
652
|
-
|
|
739
|
+
const row = (
|
|
653
740
|
// ONE joined control, not three boxes in a row: the buttons and the field share
|
|
654
741
|
// their borders (each piece overlaps the previous by a pixel) and only the outer
|
|
655
742
|
// ends are rounded — the button-group shape, so ‹ date › reads as one field with
|
|
@@ -701,6 +788,13 @@ export function DatePicker({
|
|
|
701
788
|
)}
|
|
702
789
|
</div>
|
|
703
790
|
);
|
|
791
|
+
// Under the whole row, not under the field between the buttons: the message is about
|
|
792
|
+
// the date, and the steppers are part of how it is entered.
|
|
793
|
+
return (
|
|
794
|
+
<FieldBox below={messages.below} reserve={reserve}>
|
|
795
|
+
{withEndHint(row, endHint)}
|
|
796
|
+
</FieldBox>
|
|
797
|
+
);
|
|
704
798
|
}
|
|
705
799
|
|
|
706
800
|
export interface DateRangePickerPreset {
|
|
@@ -1052,34 +1146,44 @@ function RangePanel({
|
|
|
1052
1146
|
/** Two-date range picker: click a start then an end; closes once both are set.
|
|
1053
1147
|
* With `presets`, a column of named ranges is shown beside the calendar; with
|
|
1054
1148
|
* `commit="apply"` the panel drafts and commits on Apply (see {@link DateRangeCommit}). */
|
|
1055
|
-
export function DateRangePicker({
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1149
|
+
export function DateRangePicker(props: DateRangePickerProps) {
|
|
1150
|
+
const {
|
|
1151
|
+
from,
|
|
1152
|
+
to,
|
|
1153
|
+
onChange,
|
|
1154
|
+
locale: localeProp,
|
|
1155
|
+
placeholder,
|
|
1156
|
+
min,
|
|
1157
|
+
max,
|
|
1158
|
+
formatOptions,
|
|
1159
|
+
formatValue,
|
|
1160
|
+
separator = " – ",
|
|
1161
|
+
presets,
|
|
1162
|
+
preset,
|
|
1163
|
+
commit = "immediate",
|
|
1164
|
+
renderTrigger,
|
|
1165
|
+
sheetBackCloses,
|
|
1166
|
+
renderDraftSummary,
|
|
1167
|
+
calendarLabels,
|
|
1168
|
+
label,
|
|
1169
|
+
clearable,
|
|
1170
|
+
clearLabel,
|
|
1171
|
+
disabled,
|
|
1172
|
+
invalid,
|
|
1173
|
+
hint,
|
|
1174
|
+
error,
|
|
1175
|
+
...rest
|
|
1176
|
+
} = props;
|
|
1080
1177
|
const locale = useKitLocale(localeProp);
|
|
1081
1178
|
const text = useDatePickerLabels(clearLabel, undefined, undefined);
|
|
1082
|
-
const render =
|
|
1179
|
+
const render = useTriggerFormat("dateRangePicker", locale, formatOptions, formatValue);
|
|
1180
|
+
const messages = useFieldMessages({
|
|
1181
|
+
hint,
|
|
1182
|
+
error,
|
|
1183
|
+
invalid,
|
|
1184
|
+
describedBy: rest["aria-describedby"],
|
|
1185
|
+
ariaInvalid: rest["aria-invalid"],
|
|
1186
|
+
});
|
|
1083
1187
|
// The preset this picker committed itself, for a caller that does not control
|
|
1084
1188
|
// `preset`. Cleared by a hand-picked range or a clear.
|
|
1085
1189
|
const [ownPreset, setOwnPreset] = useState<string | undefined>(undefined);
|
|
@@ -1101,15 +1205,17 @@ export function DateRangePicker({
|
|
|
1101
1205
|
if (presetId === undefined) onChange(f, t);
|
|
1102
1206
|
else onChange(f, t, presetId);
|
|
1103
1207
|
};
|
|
1104
|
-
|
|
1208
|
+
const field = (
|
|
1105
1209
|
<DateField
|
|
1106
1210
|
{...rest}
|
|
1211
|
+
aria-describedby={messages.describedBy}
|
|
1107
1212
|
label={label}
|
|
1213
|
+
labelHint={messages.labelHint}
|
|
1108
1214
|
clearable={clearable}
|
|
1109
1215
|
clearLabel={text.clear}
|
|
1110
1216
|
panelLabel={text.rangePanel}
|
|
1111
1217
|
disabled={disabled}
|
|
1112
|
-
invalid={
|
|
1218
|
+
invalid={messages.isInvalid}
|
|
1113
1219
|
// Widen so the preset column sits beside the calendar (default otherwise).
|
|
1114
1220
|
width={hasPresets ? 440 : undefined}
|
|
1115
1221
|
hasValue={Boolean(from || to)}
|
|
@@ -1150,4 +1256,9 @@ export function DateRangePicker({
|
|
|
1150
1256
|
)}
|
|
1151
1257
|
</DateField>
|
|
1152
1258
|
);
|
|
1259
|
+
return (
|
|
1260
|
+
<FieldBox below={messages.below} reserve={"error" in props || "hint" in props}>
|
|
1261
|
+
{withEndHint(field, label === undefined ? messages.labelHint : undefined)}
|
|
1262
|
+
</FieldBox>
|
|
1263
|
+
);
|
|
1153
1264
|
}
|