@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
|
@@ -6,51 +6,119 @@ import { horizontalStep } from "../lib/direction";
|
|
|
6
6
|
import { Tooltip } from "./tooltip";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* A row of square tiles that behaves as ONE radio group — the machinery
|
|
10
|
+
* {@link SwatchPicker} and {@link IconPicker} are built on, public since 0.22.0 for the
|
|
11
|
+
* one-of-n pickers those two do not cover (kastlan's input audit: a choice whose tiles
|
|
12
|
+
* show something of the app's own — a mark, a pattern, a symbol set the kit does not
|
|
13
|
+
* ship).
|
|
12
14
|
*
|
|
13
15
|
* Keksdose had three of these (the category colours, the category symbols, the
|
|
14
16
|
* transaction flags) and all three were rows of `aria-pressed` buttons: a separate
|
|
15
17
|
* tab stop per tile, and "pressed" on a choice that is really one-of-n. The APG radio
|
|
16
18
|
* group is the shape a screen reader expects for that ("Red, radio, 2 of 7, checked")
|
|
17
19
|
* and the one a keyboard expects too — a single tab stop, arrows to move.
|
|
20
|
+
*
|
|
21
|
+
* ## The container is yours
|
|
22
|
+
*
|
|
23
|
+
* It renders the TILES only — no wrapper — so it can sit in a layout the caller owns
|
|
24
|
+
* (a wrapping row, a grid, a row beside a search box). That element must carry the
|
|
25
|
+
* group's semantics, as SwatchPicker's does:
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <div role="radiogroup" aria-labelledby={headingId} className="flex flex-wrap gap-1.5">
|
|
29
|
+
* <TileRadioGroup
|
|
30
|
+
* items={[{ key: "dots", label: "Dots" }, { key: "lines", label: "Lines" }]}
|
|
31
|
+
* checked={pattern}
|
|
32
|
+
* onSelect={(key) => key && setPattern(key)}
|
|
33
|
+
* renderTile={(item) => <PatternGlyph pattern={item.key} className={TILE_SIZE.md.glyph} />}
|
|
34
|
+
* />
|
|
35
|
+
* </div>
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* `role="radiogroup"` and a name (`aria-label` or `aria-labelledby`) — without them a
|
|
39
|
+
* screen reader hears radios that belong to nothing, and no "2 of 7". Set
|
|
40
|
+
* `aria-disabled` on it as well when the whole group is `disabled`.
|
|
41
|
+
*
|
|
42
|
+
* ## What a tile is
|
|
43
|
+
*
|
|
44
|
+
* A square (28 / 32 / 44px, {@link TILE_SIZE}) holding `renderTile`'s content, NAMED by
|
|
45
|
+
* the item's `label` (`aria-label`, so what is drawn inside is decoration) and showing
|
|
46
|
+
* that label in a bubble on hover and focus — a row of unlabelled squares is the
|
|
47
|
+
* complaint that put the bubble there. It is for choices that are SEEN: a colour, a
|
|
48
|
+
* glyph, a pattern. A choice that has to be READ — a plan with a price and three lines
|
|
49
|
+
* of features, an option with a sentence of explanation — is a {@link ChoiceCardGroup}
|
|
50
|
+
* (`type="radio"`): a card names its option by its title and describes it by its text,
|
|
51
|
+
* where a tile would hide the text behind a bubble and repeat it.
|
|
52
|
+
*
|
|
53
|
+
* No strings of its own: every name is an item's `label`, the "none" tile's included.
|
|
54
|
+
*
|
|
55
|
+
* ## A tile that cannot be chosen
|
|
56
|
+
*
|
|
57
|
+
* `disabled` takes a tile out of the choice AND out of reach: the arrow keys skip it
|
|
58
|
+
* and Tab never lands on it, so a dimmed square says nothing about why. Give it a
|
|
59
|
+
* `disabledReason` instead and it stays reachable, `aria-disabled`, with the reason in
|
|
60
|
+
* its bubble and its description — and choosing it does nothing (0.23, the kit's later
|
|
61
|
+
* list after keksdose K3; {@link Button}'s `disabledReason`, one tile at a time).
|
|
18
62
|
*/
|
|
19
63
|
|
|
64
|
+
/** A tile's size: 28 / 32 / 44px — see {@link TILE_SIZE}. */
|
|
20
65
|
export type TileSize = "sm" | "md" | "lg";
|
|
21
66
|
|
|
22
|
-
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`).
|
|
67
|
+
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`). `glyph`
|
|
68
|
+
* and `swatch` are the content sizes the kit's own pickers draw inside each — use them
|
|
69
|
+
* in `renderTile` so a tile of the app's own matches theirs. */
|
|
23
70
|
export const TILE_SIZE: Record<TileSize, { tile: string; glyph: string; swatch: string }> = {
|
|
24
71
|
sm: { tile: "size-7", glyph: "size-3.5", swatch: "size-4" },
|
|
25
72
|
md: { tile: "size-8", glyph: "size-4", swatch: "size-5" },
|
|
26
73
|
lg: { tile: "size-11", glyph: "size-5", swatch: "size-6" },
|
|
27
74
|
};
|
|
28
75
|
|
|
29
|
-
/** One tile, whatever it shows. `key` is `null` for the "none" entry
|
|
76
|
+
/** One tile, whatever it shows. `key` is `null` for the "none" entry — a tile of its
|
|
77
|
+
* own that leads back to nothing, since clicking a checked tile does not uncheck it. */
|
|
30
78
|
export interface TileItem<T extends string> {
|
|
31
79
|
key: T | null;
|
|
32
80
|
label: string;
|
|
33
81
|
/** Said after the name, and shown in the bubble under it. Marks the tile with a
|
|
34
82
|
* dot, so the note is visible without hovering every tile to find it. */
|
|
35
83
|
note?: string;
|
|
84
|
+
/** Out of the choice and out of reach: natively disabled, skipped by the arrow keys,
|
|
85
|
+
* never the tab stop. */
|
|
36
86
|
disabled?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Why this tile cannot be chosen right now — a colour another category already wears,
|
|
89
|
+
* a symbol only an owner may set. The tile LOOKS disabled but is `aria-disabled`, not
|
|
90
|
+
* `disabled`: Tab and the arrow keys still reach it, the reason shows in its bubble
|
|
91
|
+
* under its name (and its `note`) and describes it, and a click, Space / Enter or an
|
|
92
|
+
* arrow under automatic activation chooses nothing — the focus moves, the checked
|
|
93
|
+
* tile stays. Wins over `disabled`, as on {@link Button}; the whole group's
|
|
94
|
+
* `disabled` still wins over it.
|
|
95
|
+
*/
|
|
96
|
+
disabledReason?: ReactNode;
|
|
37
97
|
}
|
|
38
98
|
|
|
39
99
|
export interface TileRadioGroupProps<T extends string> {
|
|
100
|
+
/** The tiles, in reading order. Render them inside a `role="radiogroup"` element
|
|
101
|
+
* with a name — see the module note. */
|
|
40
102
|
items: TileItem<T>[];
|
|
41
103
|
/** The checked key, or `undefined` for "nothing is checked" (the mixed state, or
|
|
42
104
|
* a value that is not among the options). */
|
|
43
105
|
checked: T | null | undefined;
|
|
44
106
|
onSelect: (key: T | null) => void;
|
|
45
|
-
/** "automatic": an arrow key moves AND checks, the APG default. "manual":
|
|
46
|
-
* move focus only, Space or Enter checks — for a picker whose change is
|
|
47
|
-
* (a bulk edit that saves on every change). */
|
|
48
|
-
activation
|
|
49
|
-
disabled
|
|
50
|
-
|
|
51
|
-
/**
|
|
107
|
+
/** "automatic" (default): an arrow key moves AND checks, the APG default. "manual":
|
|
108
|
+
* arrows move focus only, Space or Enter checks — for a picker whose change is
|
|
109
|
+
* expensive (a bulk edit that saves on every change). */
|
|
110
|
+
activation?: "automatic" | "manual";
|
|
111
|
+
/** Every tile disabled. Mark the container `aria-disabled` too. Default `false`. */
|
|
112
|
+
disabled?: boolean;
|
|
113
|
+
/** 28 / 32 / 44px tiles ({@link TILE_SIZE}). Default "md"; "lg" is the touch-target
|
|
114
|
+
* size a phone surface wants. */
|
|
115
|
+
size?: TileSize;
|
|
116
|
+
/** What goes inside a tile — `aria-hidden` decoration, since the tile is named by
|
|
117
|
+
* `label`. Not called for the `null` ("none") item, which shows the kit's "no"
|
|
118
|
+
* glyph. The frame, the tick and the note's dot are drawn around it. */
|
|
52
119
|
renderTile: (item: TileItem<T>, selected: boolean) => ReactNode;
|
|
53
|
-
/** Extra classes for a tile, per item
|
|
120
|
+
/** Extra classes for a tile, per item, merged after the kit's — so a `size-*` here
|
|
121
|
+
* beats `size`'s (a tile that grows in a narrow card). */
|
|
54
122
|
tileClassName?: (item: TileItem<T>, selected: boolean) => string | undefined;
|
|
55
123
|
}
|
|
56
124
|
|
|
@@ -71,28 +139,53 @@ const TILE_IDLE =
|
|
|
71
139
|
"border-[var(--border)] bg-[var(--bg-surface)] hover:border-[var(--border-strong)] hover:bg-[var(--bg-hover)]";
|
|
72
140
|
const TILE_SELECTED =
|
|
73
141
|
"border-[var(--text-primary)] bg-[var(--bg-surface)] ring-1 ring-[var(--text-primary)]";
|
|
142
|
+
/** A locked tile: the disabled look without `disabled` — dimmed, a not-allowed cursor. */
|
|
143
|
+
const TILE_LOCKED = "cursor-not-allowed opacity-50";
|
|
144
|
+
/** …and no hover offer on an idle one, which cannot be chosen. Not on the selected
|
|
145
|
+
* tile, whose frame IS its state. */
|
|
146
|
+
const TILE_LOCKED_IDLE = "hover:border-[var(--border)] hover:bg-[var(--bg-surface)]";
|
|
147
|
+
|
|
148
|
+
/** `undefined`, `null`, `false` and `""` are no reason — what `cond && "…"` gives. */
|
|
149
|
+
function isLocked(item: TileItem<string>): boolean {
|
|
150
|
+
const r = item.disabledReason;
|
|
151
|
+
return r !== undefined && r !== null && r !== false && r !== "";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Out of reach: natively disabled. A reason keeps a `disabled` tile reachable. */
|
|
155
|
+
function isOff(item: TileItem<string>): boolean {
|
|
156
|
+
return Boolean(item.disabled) && !isLocked(item);
|
|
157
|
+
}
|
|
74
158
|
|
|
159
|
+
/**
|
|
160
|
+
* The tiles of one radio group, without the group element — render them inside a
|
|
161
|
+
* `role="radiogroup"` with a name (the module note above has the example). One tab stop
|
|
162
|
+
* (the checked tile, else the first that can be chosen), arrows to move — left and
|
|
163
|
+
* right follow the page's direction — Home and End to the ends, and by default the
|
|
164
|
+
* arrows choose as they go.
|
|
165
|
+
*/
|
|
75
166
|
export function TileRadioGroup<T extends string>({
|
|
76
167
|
items,
|
|
77
168
|
checked,
|
|
78
169
|
onSelect,
|
|
79
|
-
activation,
|
|
80
|
-
disabled,
|
|
81
|
-
size,
|
|
170
|
+
activation = "automatic",
|
|
171
|
+
disabled = false,
|
|
172
|
+
size = "md",
|
|
82
173
|
renderTile,
|
|
83
174
|
tileClassName,
|
|
84
175
|
}: TileRadioGroupProps<T>) {
|
|
85
176
|
const refs = useRef<Array<HTMLButtonElement | null>>([]);
|
|
86
177
|
const noteId = useId();
|
|
178
|
+
const reasonId = useId();
|
|
87
179
|
const s = TILE_SIZE[size];
|
|
88
180
|
const checkedIndex =
|
|
89
181
|
checked === undefined ? -1 : items.findIndex((it) => it.key === checked);
|
|
90
|
-
// The ONE tab stop: the checked tile, else the first that can be
|
|
91
|
-
// rule, so Tab into a group with nothing checked still lands somewhere.
|
|
182
|
+
// The ONE tab stop: the checked tile, else the first that can be reached — the APG
|
|
183
|
+
// rule, so Tab into a group with nothing checked still lands somewhere. A locked
|
|
184
|
+
// tile is reachable: landing on it is how its reason is read.
|
|
92
185
|
const tabStop =
|
|
93
|
-
checkedIndex >= 0 && !items[checkedIndex]
|
|
186
|
+
checkedIndex >= 0 && !isOff(items[checkedIndex])
|
|
94
187
|
? checkedIndex
|
|
95
|
-
: items.findIndex((it) => !it
|
|
188
|
+
: items.findIndex((it) => !isOff(it));
|
|
96
189
|
|
|
97
190
|
const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, index: number) => {
|
|
98
191
|
// Left and right are VISUAL directions: in a right-to-left page the row runs the
|
|
@@ -120,7 +213,8 @@ export function TileRadioGroup<T extends string>({
|
|
|
120
213
|
return;
|
|
121
214
|
}
|
|
122
215
|
e.preventDefault();
|
|
123
|
-
|
|
216
|
+
// Locked tiles are in the walk; natively disabled ones are not.
|
|
217
|
+
const enabled = items.map((it, i) => (isOff(it) ? -1 : i)).filter((i) => i >= 0);
|
|
124
218
|
if (!enabled.length) return;
|
|
125
219
|
const at = enabled.indexOf(index);
|
|
126
220
|
let target: number;
|
|
@@ -129,13 +223,17 @@ export function TileRadioGroup<T extends string>({
|
|
|
129
223
|
// Wrapping, as a native radio group does: the last tile's "next" is the first.
|
|
130
224
|
else target = enabled[(at + step + enabled.length) % enabled.length];
|
|
131
225
|
refs.current[target]?.focus();
|
|
132
|
-
|
|
226
|
+
// Onto a locked tile the focus moves and the choice does not: the checked tile
|
|
227
|
+
// stays checked, and the next arrow walks on from here.
|
|
228
|
+
if (activation === "automatic" && !isLocked(items[target])) onSelect(items[target].key);
|
|
133
229
|
};
|
|
134
230
|
|
|
135
231
|
return (
|
|
136
232
|
<>
|
|
137
233
|
{items.map((item, i) => {
|
|
138
234
|
const selected = i === checkedIndex;
|
|
235
|
+
const locked = isLocked(item);
|
|
236
|
+
const describedBy = [item.note && `${noteId}-${i}`, locked && `${reasonId}-${i}`].filter(Boolean).join(" ");
|
|
139
237
|
const button = (
|
|
140
238
|
<button
|
|
141
239
|
ref={(el) => {
|
|
@@ -146,18 +244,25 @@ export function TileRadioGroup<T extends string>({
|
|
|
146
244
|
aria-checked={selected}
|
|
147
245
|
aria-label={item.label}
|
|
148
246
|
// The note is a DESCRIPTION, read after the name and the state, not glued
|
|
149
|
-
// into the name — the punctuation between the two would be English's.
|
|
150
|
-
|
|
247
|
+
// into the name — the punctuation between the two would be English's. So
|
|
248
|
+
// is a lock's reason, after the note.
|
|
249
|
+
aria-describedby={describedBy || undefined}
|
|
151
250
|
tabIndex={i === tabStop ? 0 : -1}
|
|
152
|
-
disabled
|
|
251
|
+
// A reason wins over the tile's own `disabled` (as on Button): reachable,
|
|
252
|
+
// and refusing. The whole group's `disabled` still wins over both.
|
|
253
|
+
disabled={disabled || isOff(item)}
|
|
254
|
+
aria-disabled={(locked && !disabled) || undefined}
|
|
153
255
|
// Clicking the checked tile again does nothing: a radio is not a toggle.
|
|
154
|
-
// "None" is the way back to nothing, and it is a tile of its own.
|
|
155
|
-
|
|
256
|
+
// "None" is the way back to nothing, and it is a tile of its own. A locked
|
|
257
|
+
// tile is never chosen.
|
|
258
|
+
onClick={() => !selected && !locked && onSelect(item.key)}
|
|
156
259
|
onKeyDown={(e) => onKeyDown(e, i)}
|
|
157
260
|
className={cn(
|
|
158
261
|
TILE_BASE,
|
|
159
262
|
s.tile,
|
|
160
263
|
selected ? TILE_SELECTED : TILE_IDLE,
|
|
264
|
+
locked && TILE_LOCKED,
|
|
265
|
+
locked && !selected && TILE_LOCKED_IDLE,
|
|
161
266
|
tileClassName?.(item, selected),
|
|
162
267
|
)}
|
|
163
268
|
>
|
|
@@ -181,6 +286,14 @@ export function TileRadioGroup<T extends string>({
|
|
|
181
286
|
</span>
|
|
182
287
|
</>
|
|
183
288
|
)}
|
|
289
|
+
{/* The reason's description is a `hidden` copy, as on a locked Button: the
|
|
290
|
+
bubble exists only while hovered or focused once portalled, and a
|
|
291
|
+
description that comes and goes is read inconsistently. */}
|
|
292
|
+
{locked && (
|
|
293
|
+
<span id={`${reasonId}-${i}`} hidden>
|
|
294
|
+
{item.disabledReason}
|
|
295
|
+
</span>
|
|
296
|
+
)}
|
|
184
297
|
{selected && <SelectedTick />}
|
|
185
298
|
</button>
|
|
186
299
|
);
|
|
@@ -193,10 +306,13 @@ export function TileRadioGroup<T extends string>({
|
|
|
193
306
|
<Tooltip
|
|
194
307
|
key={item.key ?? "\u0000none"}
|
|
195
308
|
label={
|
|
196
|
-
item.note ? (
|
|
309
|
+
item.note || locked ? (
|
|
197
310
|
<>
|
|
198
311
|
<span className="block">{item.label}</span>
|
|
199
|
-
<span className="block font-normal text-[var(--text-muted)]">{item.note}</span>
|
|
312
|
+
{item.note && <span className="block font-normal text-[var(--text-muted)]">{item.note}</span>}
|
|
313
|
+
{/* The reason in the body colour: it is the news, the note is
|
|
314
|
+
standing context. */}
|
|
315
|
+
{locked && <span className="block font-normal">{item.disabledReason}</span>}
|
|
200
316
|
</>
|
|
201
317
|
) : (
|
|
202
318
|
item.label
|
|
@@ -31,9 +31,16 @@ import { cn } from "../lib/cn";
|
|
|
31
31
|
* {@link FIELD_INVALID} and `aria-invalid`. A reversed window (`min="22:00"
|
|
32
32
|
* max="06:00"`, a night shift, quiet hours) is read the way the HTML spec reads it:
|
|
33
33
|
* as wrapping past midnight.
|
|
34
|
+
*
|
|
35
|
+
* `hint` is Input's too (keksdose K4): text is a caption under the field ("Local time
|
|
36
|
+
* at the site"), a {@link FieldHint} rides the label line. `showCount` is typed out — a
|
|
37
|
+
* time has no length worth counting.
|
|
34
38
|
*/
|
|
35
39
|
export interface TimeInputProps
|
|
36
|
-
extends Omit<
|
|
40
|
+
extends Omit<
|
|
41
|
+
InputProps,
|
|
42
|
+
"type" | "value" | "defaultValue" | "min" | "max" | "step" | "variant" | "showCount" | "countLabels"
|
|
43
|
+
> {
|
|
37
44
|
/** `"HH:mm"` (or `"HH:mm:ss"` with a sub-minute `step`); `""` is "no time". */
|
|
38
45
|
value: string;
|
|
39
46
|
/** The normalised value on every change the browser reports — which for a time
|