@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactElement, ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, KeyboardEvent } from 'react';
|
|
3
3
|
import { LucideIcon } from 'lucide-react';
|
|
4
|
-
import {
|
|
5
|
-
import '
|
|
4
|
+
import { b2 as KitLinkComponent } from '../kit-labels-R8mIAc9N.js';
|
|
5
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
6
|
+
import './data-table-sort.js';
|
|
7
|
+
import './swipeable-row.js';
|
|
6
8
|
import './mini-calendar.js';
|
|
7
9
|
import './calendar-heatmap.js';
|
|
8
10
|
import './popover.js';
|
|
@@ -24,7 +26,6 @@ import './sparkline.js';
|
|
|
24
26
|
import './stat-tile.js';
|
|
25
27
|
import './signature-pad.js';
|
|
26
28
|
import './password-strength.js';
|
|
27
|
-
import './danger-confirm.js';
|
|
28
29
|
import './swatch-picker.js';
|
|
29
30
|
import './tile-radio.js';
|
|
30
31
|
import './icon-picker.js';
|
|
@@ -32,11 +33,10 @@ import './dialog-frame.js';
|
|
|
32
33
|
import './modal.js';
|
|
33
34
|
import '../hooks/use-search-param-state.js';
|
|
34
35
|
import './measured-grid.js';
|
|
35
|
-
import '../feedback-attachment-
|
|
36
|
+
import '../feedback-attachment-NhUm7Zga.js';
|
|
36
37
|
import '../feedback/feedback-thread.js';
|
|
37
38
|
import '../feedback/feedback-inbox.js';
|
|
38
39
|
import './account-settings-labels.js';
|
|
39
|
-
import './confirm-dialog.js';
|
|
40
40
|
import './floating-panel.js';
|
|
41
41
|
import './bulk-action-bar.js';
|
|
42
42
|
import './list.js';
|
|
@@ -55,14 +55,16 @@ import './write-lock.js';
|
|
|
55
55
|
import './legal.js';
|
|
56
56
|
import './account-chips.js';
|
|
57
57
|
import '../lib/format.js';
|
|
58
|
-
import '../data-table-3mi2R3so.js';
|
|
59
|
-
import './data-table-sort.js';
|
|
60
|
-
import './swipeable-row.js';
|
|
61
58
|
import './reauth-dialog.js';
|
|
62
59
|
import './server-wake.js';
|
|
63
60
|
import '../lib/server-wake.js';
|
|
64
61
|
import './translation-review-labels.js';
|
|
65
62
|
import '../lib/translation-review.js';
|
|
63
|
+
import './country-select.js';
|
|
64
|
+
import './inline-edit-field.js';
|
|
65
|
+
import '../lib/iban.js';
|
|
66
|
+
import '../lib/phone.js';
|
|
67
|
+
import './sign-chip.js';
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* An `href` that leaves the app: a scheme (`https:`, `mailto:`, `tel:`) or a
|
|
@@ -2,24 +2,64 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* A row of square tiles that behaves as ONE radio group — the machinery
|
|
6
|
+
* {@link SwatchPicker} and {@link IconPicker} are built on, public since 0.22.0 for the
|
|
7
|
+
* one-of-n pickers those two do not cover (kastlan's input audit: a choice whose tiles
|
|
8
|
+
* show something of the app's own — a mark, a pattern, a symbol set the kit does not
|
|
9
|
+
* ship).
|
|
8
10
|
*
|
|
9
11
|
* Keksdose had three of these (the category colours, the category symbols, the
|
|
10
12
|
* transaction flags) and all three were rows of `aria-pressed` buttons: a separate
|
|
11
13
|
* tab stop per tile, and "pressed" on a choice that is really one-of-n. The APG radio
|
|
12
14
|
* group is the shape a screen reader expects for that ("Red, radio, 2 of 7, checked")
|
|
13
15
|
* and the one a keyboard expects too — a single tab stop, arrows to move.
|
|
16
|
+
*
|
|
17
|
+
* ## The container is yours
|
|
18
|
+
*
|
|
19
|
+
* It renders the TILES only — no wrapper — so it can sit in a layout the caller owns
|
|
20
|
+
* (a wrapping row, a grid, a row beside a search box). That element must carry the
|
|
21
|
+
* group's semantics, as SwatchPicker's does:
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <div role="radiogroup" aria-labelledby={headingId} className="flex flex-wrap gap-1.5">
|
|
25
|
+
* <TileRadioGroup
|
|
26
|
+
* items={[{ key: "dots", label: "Dots" }, { key: "lines", label: "Lines" }]}
|
|
27
|
+
* checked={pattern}
|
|
28
|
+
* onSelect={(key) => key && setPattern(key)}
|
|
29
|
+
* renderTile={(item) => <PatternGlyph pattern={item.key} className={TILE_SIZE.md.glyph} />}
|
|
30
|
+
* />
|
|
31
|
+
* </div>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* `role="radiogroup"` and a name (`aria-label` or `aria-labelledby`) — without them a
|
|
35
|
+
* screen reader hears radios that belong to nothing, and no "2 of 7". Set
|
|
36
|
+
* `aria-disabled` on it as well when the whole group is `disabled`.
|
|
37
|
+
*
|
|
38
|
+
* ## What a tile is
|
|
39
|
+
*
|
|
40
|
+
* A square (28 / 32 / 44px, {@link TILE_SIZE}) holding `renderTile`'s content, NAMED by
|
|
41
|
+
* the item's `label` (`aria-label`, so what is drawn inside is decoration) and showing
|
|
42
|
+
* that label in a bubble on hover and focus — a row of unlabelled squares is the
|
|
43
|
+
* complaint that put the bubble there. It is for choices that are SEEN: a colour, a
|
|
44
|
+
* glyph, a pattern. A choice that has to be READ — a plan with a price and three lines
|
|
45
|
+
* of features, an option with a sentence of explanation — is a {@link ChoiceCardGroup}
|
|
46
|
+
* (`type="radio"`): a card names its option by its title and describes it by its text,
|
|
47
|
+
* where a tile would hide the text behind a bubble and repeat it.
|
|
48
|
+
*
|
|
49
|
+
* No strings of its own: every name is an item's `label`, the "none" tile's included.
|
|
14
50
|
*/
|
|
51
|
+
/** A tile's size: 28 / 32 / 44px — see {@link TILE_SIZE}. */
|
|
15
52
|
type TileSize = "sm" | "md" | "lg";
|
|
16
|
-
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`).
|
|
53
|
+
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`). `glyph`
|
|
54
|
+
* and `swatch` are the content sizes the kit's own pickers draw inside each — use them
|
|
55
|
+
* in `renderTile` so a tile of the app's own matches theirs. */
|
|
17
56
|
declare const TILE_SIZE: Record<TileSize, {
|
|
18
57
|
tile: string;
|
|
19
58
|
glyph: string;
|
|
20
59
|
swatch: string;
|
|
21
60
|
}>;
|
|
22
|
-
/** One tile, whatever it shows. `key` is `null` for the "none" entry
|
|
61
|
+
/** One tile, whatever it shows. `key` is `null` for the "none" entry — a tile of its
|
|
62
|
+
* own that leads back to nothing, since clicking a checked tile does not uncheck it. */
|
|
23
63
|
interface TileItem<T extends string> {
|
|
24
64
|
key: T | null;
|
|
25
65
|
label: string;
|
|
@@ -29,22 +69,37 @@ interface TileItem<T extends string> {
|
|
|
29
69
|
disabled?: boolean;
|
|
30
70
|
}
|
|
31
71
|
interface TileRadioGroupProps<T extends string> {
|
|
72
|
+
/** The tiles, in reading order. Render them inside a `role="radiogroup"` element
|
|
73
|
+
* with a name — see the module note. */
|
|
32
74
|
items: TileItem<T>[];
|
|
33
75
|
/** The checked key, or `undefined` for "nothing is checked" (the mixed state, or
|
|
34
76
|
* a value that is not among the options). */
|
|
35
77
|
checked: T | null | undefined;
|
|
36
78
|
onSelect: (key: T | null) => void;
|
|
37
|
-
/** "automatic": an arrow key moves AND checks, the APG default. "manual":
|
|
38
|
-
* move focus only, Space or Enter checks — for a picker whose change is
|
|
39
|
-
* (a bulk edit that saves on every change). */
|
|
40
|
-
activation
|
|
41
|
-
disabled
|
|
42
|
-
|
|
43
|
-
/**
|
|
79
|
+
/** "automatic" (default): an arrow key moves AND checks, the APG default. "manual":
|
|
80
|
+
* arrows move focus only, Space or Enter checks — for a picker whose change is
|
|
81
|
+
* expensive (a bulk edit that saves on every change). */
|
|
82
|
+
activation?: "automatic" | "manual";
|
|
83
|
+
/** Every tile disabled. Mark the container `aria-disabled` too. Default `false`. */
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
/** 28 / 32 / 44px tiles ({@link TILE_SIZE}). Default "md"; "lg" is the touch-target
|
|
86
|
+
* size a phone surface wants. */
|
|
87
|
+
size?: TileSize;
|
|
88
|
+
/** What goes inside a tile — `aria-hidden` decoration, since the tile is named by
|
|
89
|
+
* `label`. Not called for the `null` ("none") item, which shows the kit's "no"
|
|
90
|
+
* glyph. The frame, the tick and the note's dot are drawn around it. */
|
|
44
91
|
renderTile: (item: TileItem<T>, selected: boolean) => ReactNode;
|
|
45
|
-
/** Extra classes for a tile, per item
|
|
92
|
+
/** Extra classes for a tile, per item, merged after the kit's — so a `size-*` here
|
|
93
|
+
* beats `size`'s (a tile that grows in a narrow card). */
|
|
46
94
|
tileClassName?: (item: TileItem<T>, selected: boolean) => string | undefined;
|
|
47
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* The tiles of one radio group, without the group element — render them inside a
|
|
98
|
+
* `role="radiogroup"` with a name (the module note above has the example). One tab stop
|
|
99
|
+
* (the checked tile, else the first that can be chosen), arrows to move — left and
|
|
100
|
+
* right follow the page's direction — Home and End to the ends, and by default the
|
|
101
|
+
* arrows choose as they go.
|
|
102
|
+
*/
|
|
48
103
|
declare function TileRadioGroup<T extends string>({ items, checked, onSelect, activation, disabled, size, renderTile, tileClassName, }: TileRadioGroupProps<T>): react.JSX.Element;
|
|
49
104
|
|
|
50
105
|
export { TILE_SIZE, type TileItem, TileRadioGroup, type TileRadioGroupProps, type TileSize };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/tile-radio.tsx"],"sourcesContent":["import { useId, useRef } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { Ban, Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { horizontalStep } from \"../lib/direction\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * The machinery {@link SwatchPicker} and {@link IconPicker} share: a row of square\n * tiles that behaves as ONE radio group. Private to the package — not re-exported\n * from the barrel.\n *\n * Keksdose had three of these (the category colours, the category symbols, the\n * transaction flags) and all three were rows of `aria-pressed` buttons: a separate\n * tab stop per tile, and \"pressed\" on a choice that is really one-of-n. The APG radio\n * group is the shape a screen reader expects for that (\"Red, radio, 2 of 7, checked\")\n * and the one a keyboard expects too — a single tab stop, arrows to move.\n */\n\nexport type TileSize = \"sm\" | \"md\" | \"lg\";\n\n/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`). */\nexport const TILE_SIZE: Record<TileSize, { tile: string; glyph: string; swatch: string }> = {\n sm: { tile: \"size-7\", glyph: \"size-3.5\", swatch: \"size-4\" },\n md: { tile: \"size-8\", glyph: \"size-4\", swatch: \"size-5\" },\n lg: { tile: \"size-11\", glyph: \"size-5\", swatch: \"size-6\" },\n};\n\n/** One tile, whatever it shows. `key` is `null` for the \"none\" entry. */\nexport interface TileItem<T extends string> {\n key: T | null;\n label: string;\n /** Said after the name, and shown in the bubble under it. Marks the tile with a\n * dot, so the note is visible without hovering every tile to find it. */\n note?: string;\n disabled?: boolean;\n}\n\nexport interface TileRadioGroupProps<T extends string> {\n items: TileItem<T>[];\n /** The checked key, or `undefined` for \"nothing is checked\" (the mixed state, or\n * a value that is not among the options). */\n checked: T | null | undefined;\n onSelect: (key: T | null) => void;\n /** \"automatic\": an arrow key moves AND checks, the APG default. \"manual\": arrows\n * move focus only, Space or Enter checks — for a picker whose change is expensive\n * (a bulk edit that saves on every change). */\n activation: \"automatic\" | \"manual\";\n disabled: boolean;\n size: TileSize;\n /** What goes inside a tile. The frame, the tick and the note's dot are drawn here. */\n renderTile: (item: TileItem<T>, selected: boolean) => ReactNode;\n /** Extra classes for a tile, per item. */\n tileClassName?: (item: TileItem<T>, selected: boolean) => string | undefined;\n}\n\n/**\n * The tile's frame. Selection is a thicker, darker frame AND a tick in the corner —\n * never a colour change alone,\n * because the thing inside a swatch tile IS a colour, and a red frame round a red dot\n * says nothing to a reader who cannot see red.\n *\n * `focus-visible` with an offset in the surface colour, as the checkbox does: the ring\n * must not be painted in the colour of the frame it touches.\n */\nconst TILE_BASE =\n \"relative inline-flex shrink-0 items-center justify-center rounded-md border transition-colors \" +\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-surface)] \" +\n \"disabled:cursor-not-allowed disabled:opacity-50\";\nconst TILE_IDLE =\n \"border-[var(--border)] bg-[var(--bg-surface)] hover:border-[var(--border-strong)] hover:bg-[var(--bg-hover)]\";\nconst TILE_SELECTED =\n \"border-[var(--text-primary)] bg-[var(--bg-surface)] ring-1 ring-[var(--text-primary)]\";\n\nexport function TileRadioGroup<T extends string>({\n items,\n checked,\n onSelect,\n activation,\n disabled,\n size,\n renderTile,\n tileClassName,\n}: TileRadioGroupProps<T>) {\n const refs = useRef<Array<HTMLButtonElement | null>>([]);\n const noteId = useId();\n const s = TILE_SIZE[size];\n const checkedIndex =\n checked === undefined ? -1 : items.findIndex((it) => it.key === checked);\n // The ONE tab stop: the checked tile, else the first that can be chosen — the APG\n // rule, so Tab into a group with nothing checked still lands somewhere.\n const tabStop =\n checkedIndex >= 0 && !items[checkedIndex].disabled\n ? checkedIndex\n : items.findIndex((it) => !it.disabled);\n\n const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, index: number) => {\n // Left and right are VISUAL directions: in a right-to-left page the row runs the\n // other way. Up and down follow reading order either way — the APG's radio group\n // treats them as previous/next, and the tiles wrap, so there is no column to keep.\n const horizontal = horizontalStep(e.key, e.currentTarget);\n let step: number | \"first\" | \"last\";\n switch (horizontal ? \"horizontal\" : e.key) {\n case \"horizontal\":\n step = horizontal;\n break;\n case \"ArrowDown\":\n step = 1;\n break;\n case \"ArrowUp\":\n step = -1;\n break;\n case \"Home\":\n step = \"first\";\n break;\n case \"End\":\n step = \"last\";\n break;\n default:\n return;\n }\n e.preventDefault();\n const enabled = items.map((it, i) => (it.disabled ? -1 : i)).filter((i) => i >= 0);\n if (!enabled.length) return;\n const at = enabled.indexOf(index);\n let target: number;\n if (step === \"first\") target = enabled[0];\n else if (step === \"last\") target = enabled[enabled.length - 1];\n // Wrapping, as a native radio group does: the last tile's \"next\" is the first.\n else target = enabled[(at + step + enabled.length) % enabled.length];\n refs.current[target]?.focus();\n if (activation === \"automatic\") onSelect(items[target].key);\n };\n\n return (\n <>\n {items.map((item, i) => {\n const selected = i === checkedIndex;\n const button = (\n <button\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={item.label}\n // The note is a DESCRIPTION, read after the name and the state, not glued\n // into the name — the punctuation between the two would be English's.\n aria-describedby={item.note ? `${noteId}-${i}` : undefined}\n tabIndex={i === tabStop ? 0 : -1}\n disabled={disabled || item.disabled}\n // Clicking the checked tile again does nothing: a radio is not a toggle.\n // \"None\" is the way back to nothing, and it is a tile of its own.\n onClick={() => !selected && onSelect(item.key)}\n onKeyDown={(e) => onKeyDown(e, i)}\n className={cn(\n TILE_BASE,\n s.tile,\n selected ? TILE_SELECTED : TILE_IDLE,\n tileClassName?.(item, selected),\n )}\n >\n {item.key === null ? (\n <Ban aria-hidden className={cn(s.glyph, \"text-[var(--text-muted)]\")} />\n ) : (\n renderTile(item, selected)\n )}\n {item.note && (\n <>\n {/* A dot rather than a dimmed tile: dimming reads as \"disabled\", and a\n tile with a note is entirely available. */}\n <span\n aria-hidden\n className=\"absolute end-0.5 top-0.5 size-1.5 rounded-full bg-[var(--text-muted)]\"\n />\n {/* `hidden` still serves as a description: aria-describedby reads\n hidden text, which is what keeps it out of the layout. */}\n <span id={`${noteId}-${i}`} hidden>\n {item.note}\n </span>\n </>\n )}\n {selected && <SelectedTick />}\n </button>\n );\n return (\n // The bubble is the SIGHTED user's name for the tile — eight unlabelled\n // squares are the complaint that put a bubble on Keksdose's (live #289).\n // It wraps a span rather than the button, because a Tooltip hands its\n // child an `aria-describedby`, and a tile named \"Red\" and described as\n // \"Red\" is read out twice.\n <Tooltip\n key={item.key ?? \"\\u0000none\"}\n label={\n item.note ? (\n <>\n <span className=\"block\">{item.label}</span>\n <span className=\"block font-normal text-[var(--text-muted)]\">{item.note}</span>\n </>\n ) : (\n item.label\n )\n }\n portal\n >\n <span className=\"inline-flex\">{button}</span>\n </Tooltip>\n );\n })}\n </>\n );\n}\n\n/** The tick on a selected tile — a small badge in the corner, in the text colour on\n * the surface colour, so it reads the same over every swatch colour. */\nfunction SelectedTick() {\n return (\n <span\n aria-hidden\n className=\"absolute -bottom-1 -end-1 inline-flex size-3.5 items-center justify-center rounded-full bg-[var(--text-primary)] text-[var(--bg-surface)]\"\n >\n <Check strokeWidth={3} className=\"size-2.5\" />\n </span>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/tile-radio.tsx"],"sourcesContent":["import { useId, useRef } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { Ban, Check } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { horizontalStep } from \"../lib/direction\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * A row of square tiles that behaves as ONE radio group — the machinery\n * {@link SwatchPicker} and {@link IconPicker} are built on, public since 0.22.0 for the\n * one-of-n pickers those two do not cover (kastlan's input audit: a choice whose tiles\n * show something of the app's own — a mark, a pattern, a symbol set the kit does not\n * ship).\n *\n * Keksdose had three of these (the category colours, the category symbols, the\n * transaction flags) and all three were rows of `aria-pressed` buttons: a separate\n * tab stop per tile, and \"pressed\" on a choice that is really one-of-n. The APG radio\n * group is the shape a screen reader expects for that (\"Red, radio, 2 of 7, checked\")\n * and the one a keyboard expects too — a single tab stop, arrows to move.\n *\n * ## The container is yours\n *\n * It renders the TILES only — no wrapper — so it can sit in a layout the caller owns\n * (a wrapping row, a grid, a row beside a search box). That element must carry the\n * group's semantics, as SwatchPicker's does:\n *\n * ```tsx\n * <div role=\"radiogroup\" aria-labelledby={headingId} className=\"flex flex-wrap gap-1.5\">\n * <TileRadioGroup\n * items={[{ key: \"dots\", label: \"Dots\" }, { key: \"lines\", label: \"Lines\" }]}\n * checked={pattern}\n * onSelect={(key) => key && setPattern(key)}\n * renderTile={(item) => <PatternGlyph pattern={item.key} className={TILE_SIZE.md.glyph} />}\n * />\n * </div>\n * ```\n *\n * `role=\"radiogroup\"` and a name (`aria-label` or `aria-labelledby`) — without them a\n * screen reader hears radios that belong to nothing, and no \"2 of 7\". Set\n * `aria-disabled` on it as well when the whole group is `disabled`.\n *\n * ## What a tile is\n *\n * A square (28 / 32 / 44px, {@link TILE_SIZE}) holding `renderTile`'s content, NAMED by\n * the item's `label` (`aria-label`, so what is drawn inside is decoration) and showing\n * that label in a bubble on hover and focus — a row of unlabelled squares is the\n * complaint that put the bubble there. It is for choices that are SEEN: a colour, a\n * glyph, a pattern. A choice that has to be READ — a plan with a price and three lines\n * of features, an option with a sentence of explanation — is a {@link ChoiceCardGroup}\n * (`type=\"radio\"`): a card names its option by its title and describes it by its text,\n * where a tile would hide the text behind a bubble and repeat it.\n *\n * No strings of its own: every name is an item's `label`, the \"none\" tile's included.\n */\n\n/** A tile's size: 28 / 32 / 44px — see {@link TILE_SIZE}. */\nexport type TileSize = \"sm\" | \"md\" | \"lg\";\n\n/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`). `glyph`\n * and `swatch` are the content sizes the kit's own pickers draw inside each — use them\n * in `renderTile` so a tile of the app's own matches theirs. */\nexport const TILE_SIZE: Record<TileSize, { tile: string; glyph: string; swatch: string }> = {\n sm: { tile: \"size-7\", glyph: \"size-3.5\", swatch: \"size-4\" },\n md: { tile: \"size-8\", glyph: \"size-4\", swatch: \"size-5\" },\n lg: { tile: \"size-11\", glyph: \"size-5\", swatch: \"size-6\" },\n};\n\n/** One tile, whatever it shows. `key` is `null` for the \"none\" entry — a tile of its\n * own that leads back to nothing, since clicking a checked tile does not uncheck it. */\nexport interface TileItem<T extends string> {\n key: T | null;\n label: string;\n /** Said after the name, and shown in the bubble under it. Marks the tile with a\n * dot, so the note is visible without hovering every tile to find it. */\n note?: string;\n disabled?: boolean;\n}\n\nexport interface TileRadioGroupProps<T extends string> {\n /** The tiles, in reading order. Render them inside a `role=\"radiogroup\"` element\n * with a name — see the module note. */\n items: TileItem<T>[];\n /** The checked key, or `undefined` for \"nothing is checked\" (the mixed state, or\n * a value that is not among the options). */\n checked: T | null | undefined;\n onSelect: (key: T | null) => void;\n /** \"automatic\" (default): an arrow key moves AND checks, the APG default. \"manual\":\n * arrows move focus only, Space or Enter checks — for a picker whose change is\n * expensive (a bulk edit that saves on every change). */\n activation?: \"automatic\" | \"manual\";\n /** Every tile disabled. Mark the container `aria-disabled` too. Default `false`. */\n disabled?: boolean;\n /** 28 / 32 / 44px tiles ({@link TILE_SIZE}). Default \"md\"; \"lg\" is the touch-target\n * size a phone surface wants. */\n size?: TileSize;\n /** What goes inside a tile — `aria-hidden` decoration, since the tile is named by\n * `label`. Not called for the `null` (\"none\") item, which shows the kit's \"no\"\n * glyph. The frame, the tick and the note's dot are drawn around it. */\n renderTile: (item: TileItem<T>, selected: boolean) => ReactNode;\n /** Extra classes for a tile, per item, merged after the kit's — so a `size-*` here\n * beats `size`'s (a tile that grows in a narrow card). */\n tileClassName?: (item: TileItem<T>, selected: boolean) => string | undefined;\n}\n\n/**\n * The tile's frame. Selection is a thicker, darker frame AND a tick in the corner —\n * never a colour change alone,\n * because the thing inside a swatch tile IS a colour, and a red frame round a red dot\n * says nothing to a reader who cannot see red.\n *\n * `focus-visible` with an offset in the surface colour, as the checkbox does: the ring\n * must not be painted in the colour of the frame it touches.\n */\nconst TILE_BASE =\n \"relative inline-flex shrink-0 items-center justify-center rounded-md border transition-colors \" +\n \"focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-surface)] \" +\n \"disabled:cursor-not-allowed disabled:opacity-50\";\nconst TILE_IDLE =\n \"border-[var(--border)] bg-[var(--bg-surface)] hover:border-[var(--border-strong)] hover:bg-[var(--bg-hover)]\";\nconst TILE_SELECTED =\n \"border-[var(--text-primary)] bg-[var(--bg-surface)] ring-1 ring-[var(--text-primary)]\";\n\n/**\n * The tiles of one radio group, without the group element — render them inside a\n * `role=\"radiogroup\"` with a name (the module note above has the example). One tab stop\n * (the checked tile, else the first that can be chosen), arrows to move — left and\n * right follow the page's direction — Home and End to the ends, and by default the\n * arrows choose as they go.\n */\nexport function TileRadioGroup<T extends string>({\n items,\n checked,\n onSelect,\n activation = \"automatic\",\n disabled = false,\n size = \"md\",\n renderTile,\n tileClassName,\n}: TileRadioGroupProps<T>) {\n const refs = useRef<Array<HTMLButtonElement | null>>([]);\n const noteId = useId();\n const s = TILE_SIZE[size];\n const checkedIndex =\n checked === undefined ? -1 : items.findIndex((it) => it.key === checked);\n // The ONE tab stop: the checked tile, else the first that can be chosen — the APG\n // rule, so Tab into a group with nothing checked still lands somewhere.\n const tabStop =\n checkedIndex >= 0 && !items[checkedIndex].disabled\n ? checkedIndex\n : items.findIndex((it) => !it.disabled);\n\n const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, index: number) => {\n // Left and right are VISUAL directions: in a right-to-left page the row runs the\n // other way. Up and down follow reading order either way — the APG's radio group\n // treats them as previous/next, and the tiles wrap, so there is no column to keep.\n const horizontal = horizontalStep(e.key, e.currentTarget);\n let step: number | \"first\" | \"last\";\n switch (horizontal ? \"horizontal\" : e.key) {\n case \"horizontal\":\n step = horizontal;\n break;\n case \"ArrowDown\":\n step = 1;\n break;\n case \"ArrowUp\":\n step = -1;\n break;\n case \"Home\":\n step = \"first\";\n break;\n case \"End\":\n step = \"last\";\n break;\n default:\n return;\n }\n e.preventDefault();\n const enabled = items.map((it, i) => (it.disabled ? -1 : i)).filter((i) => i >= 0);\n if (!enabled.length) return;\n const at = enabled.indexOf(index);\n let target: number;\n if (step === \"first\") target = enabled[0];\n else if (step === \"last\") target = enabled[enabled.length - 1];\n // Wrapping, as a native radio group does: the last tile's \"next\" is the first.\n else target = enabled[(at + step + enabled.length) % enabled.length];\n refs.current[target]?.focus();\n if (activation === \"automatic\") onSelect(items[target].key);\n };\n\n return (\n <>\n {items.map((item, i) => {\n const selected = i === checkedIndex;\n const button = (\n <button\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={item.label}\n // The note is a DESCRIPTION, read after the name and the state, not glued\n // into the name — the punctuation between the two would be English's.\n aria-describedby={item.note ? `${noteId}-${i}` : undefined}\n tabIndex={i === tabStop ? 0 : -1}\n disabled={disabled || item.disabled}\n // Clicking the checked tile again does nothing: a radio is not a toggle.\n // \"None\" is the way back to nothing, and it is a tile of its own.\n onClick={() => !selected && onSelect(item.key)}\n onKeyDown={(e) => onKeyDown(e, i)}\n className={cn(\n TILE_BASE,\n s.tile,\n selected ? TILE_SELECTED : TILE_IDLE,\n tileClassName?.(item, selected),\n )}\n >\n {item.key === null ? (\n <Ban aria-hidden className={cn(s.glyph, \"text-[var(--text-muted)]\")} />\n ) : (\n renderTile(item, selected)\n )}\n {item.note && (\n <>\n {/* A dot rather than a dimmed tile: dimming reads as \"disabled\", and a\n tile with a note is entirely available. */}\n <span\n aria-hidden\n className=\"absolute end-0.5 top-0.5 size-1.5 rounded-full bg-[var(--text-muted)]\"\n />\n {/* `hidden` still serves as a description: aria-describedby reads\n hidden text, which is what keeps it out of the layout. */}\n <span id={`${noteId}-${i}`} hidden>\n {item.note}\n </span>\n </>\n )}\n {selected && <SelectedTick />}\n </button>\n );\n return (\n // The bubble is the SIGHTED user's name for the tile — eight unlabelled\n // squares are the complaint that put a bubble on Keksdose's (live #289).\n // It wraps a span rather than the button, because a Tooltip hands its\n // child an `aria-describedby`, and a tile named \"Red\" and described as\n // \"Red\" is read out twice.\n <Tooltip\n key={item.key ?? \"\\u0000none\"}\n label={\n item.note ? (\n <>\n <span className=\"block\">{item.label}</span>\n <span className=\"block font-normal text-[var(--text-muted)]\">{item.note}</span>\n </>\n ) : (\n item.label\n )\n }\n portal\n >\n <span className=\"inline-flex\">{button}</span>\n </Tooltip>\n );\n })}\n </>\n );\n}\n\n/** The tick on a selected tile — a small badge in the corner, in the text colour on\n * the surface colour, so it reads the same over every swatch colour. */\nfunction SelectedTick() {\n return (\n <span\n aria-hidden\n className=\"absolute -bottom-1 -end-1 inline-flex size-3.5 items-center justify-center rounded-full bg-[var(--text-primary)] text-[var(--bg-surface)]\"\n >\n <Check strokeWidth={3} className=\"size-2.5\" />\n </span>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import '
|
|
2
|
+
import { a$ as InputProps } from '../kit-labels-R8mIAc9N.js';
|
|
3
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
4
|
+
import './data-table-sort.js';
|
|
5
|
+
import './swipeable-row.js';
|
|
4
6
|
import './mini-calendar.js';
|
|
5
7
|
import './calendar-heatmap.js';
|
|
6
8
|
import './popover.js';
|
|
@@ -23,7 +25,6 @@ import './sparkline.js';
|
|
|
23
25
|
import './stat-tile.js';
|
|
24
26
|
import './signature-pad.js';
|
|
25
27
|
import './password-strength.js';
|
|
26
|
-
import './danger-confirm.js';
|
|
27
28
|
import './swatch-picker.js';
|
|
28
29
|
import './tile-radio.js';
|
|
29
30
|
import './icon-picker.js';
|
|
@@ -31,11 +32,10 @@ import './dialog-frame.js';
|
|
|
31
32
|
import './modal.js';
|
|
32
33
|
import '../hooks/use-search-param-state.js';
|
|
33
34
|
import './measured-grid.js';
|
|
34
|
-
import '../feedback-attachment-
|
|
35
|
+
import '../feedback-attachment-NhUm7Zga.js';
|
|
35
36
|
import '../feedback/feedback-thread.js';
|
|
36
37
|
import '../feedback/feedback-inbox.js';
|
|
37
38
|
import './account-settings-labels.js';
|
|
38
|
-
import './confirm-dialog.js';
|
|
39
39
|
import './floating-panel.js';
|
|
40
40
|
import './bulk-action-bar.js';
|
|
41
41
|
import './list.js';
|
|
@@ -54,14 +54,16 @@ import './write-lock.js';
|
|
|
54
54
|
import './legal.js';
|
|
55
55
|
import './account-chips.js';
|
|
56
56
|
import '../lib/format.js';
|
|
57
|
-
import '../data-table-3mi2R3so.js';
|
|
58
|
-
import './data-table-sort.js';
|
|
59
|
-
import './swipeable-row.js';
|
|
60
57
|
import './reauth-dialog.js';
|
|
61
58
|
import './server-wake.js';
|
|
62
59
|
import '../lib/server-wake.js';
|
|
63
60
|
import './translation-review-labels.js';
|
|
64
61
|
import '../lib/translation-review.js';
|
|
62
|
+
import './country-select.js';
|
|
63
|
+
import './inline-edit-field.js';
|
|
64
|
+
import '../lib/iban.js';
|
|
65
|
+
import '../lib/phone.js';
|
|
66
|
+
import './sign-chip.js';
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
69
|
* A time of day, as a `"HH:mm"` string (`"HH:mm:ss"` once `step` asks for seconds).
|
|
@@ -91,8 +93,12 @@ import '../lib/translation-review.js';
|
|
|
91
93
|
* {@link FIELD_INVALID} and `aria-invalid`. A reversed window (`min="22:00"
|
|
92
94
|
* max="06:00"`, a night shift, quiet hours) is read the way the HTML spec reads it:
|
|
93
95
|
* as wrapping past midnight.
|
|
96
|
+
*
|
|
97
|
+
* `hint` is Input's too (keksdose K4): text is a caption under the field ("Local time
|
|
98
|
+
* at the site"), a {@link FieldHint} rides the label line. `showCount` is typed out — a
|
|
99
|
+
* time has no length worth counting.
|
|
94
100
|
*/
|
|
95
|
-
interface TimeInputProps extends Omit<InputProps, "type" | "value" | "defaultValue" | "min" | "max" | "step" | "variant"> {
|
|
101
|
+
interface TimeInputProps extends Omit<InputProps, "type" | "value" | "defaultValue" | "min" | "max" | "step" | "variant" | "showCount" | "countLabels"> {
|
|
96
102
|
/** `"HH:mm"` (or `"HH:mm:ss"` with a sub-minute `step`); `""` is "no time". */
|
|
97
103
|
value: string;
|
|
98
104
|
/** The normalised value on every change the browser reports — which for a time
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/time-input.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { ChangeEvent } from \"react\";\nimport { Input, type InputProps } from \"./ui\";\nimport { cn } from \"../lib/cn\";\n\n/**\n * A time of day, as a `\"HH:mm\"` string (`\"HH:mm:ss\"` once `step` asks for seconds).\n *\n * NATIVE `<input type=\"time\">`, deliberately, rather than a pair of custom spinners.\n * What the browser's control gives for free is the part a hand-rolled one gets wrong:\n * 12- versus 24-hour display follows the user's OWN locale settings (not the page's —\n * which is the right authority for \"how do I read a clock\"), a phone opens its\n * platform wheel or dial, each segment is a spin button a screen reader already knows,\n * and typing \"1430\" fills both segments. The price is that the display cannot be\n * forced to 24h from here and the look of the segments is the browser's; neither is a\n * reason for three apps to ship an unaudited widget. So this is {@link Input} with\n * `type=\"time\"` and a string contract, and everything else — the floating label, the\n * `invalid`/`error` wiring, the click-anywhere `showPicker()` (feedback #224) — is\n * Input's own, inherited rather than copied.\n *\n * Two things it adds over `<Input type=\"time\">`:\n *\n * - **A normalised value.** A time input's value is any valid time string, so a\n * browser may leave off a zero seconds segment with `step={1}` or report\n * milliseconds, and a stored value may carry seconds the field does not show.\n * `onValueChange` always hands back exactly the shape `step` implies, so the value\n * a form stores compares and sorts as a plain string.\n * - **Out-of-range painted, not just flagged.** A native time input outside\n * `min`/`max` matches `:invalid` and nothing styles that selector (see Input's\n * `invalid`), so an out-of-window time looked fine. Here it wears\n * {@link FIELD_INVALID} and `aria-invalid`. A reversed window (`min=\"22:00\"\n * max=\"06:00\"`, a night shift, quiet hours) is read the way the HTML spec reads it:\n * as wrapping past midnight.\n */\nexport interface TimeInputProps\n extends Omit
|
|
1
|
+
{"version":3,"sources":["../../src/components/time-input.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { ChangeEvent } from \"react\";\nimport { Input, type InputProps } from \"./ui\";\nimport { cn } from \"../lib/cn\";\n\n/**\n * A time of day, as a `\"HH:mm\"` string (`\"HH:mm:ss\"` once `step` asks for seconds).\n *\n * NATIVE `<input type=\"time\">`, deliberately, rather than a pair of custom spinners.\n * What the browser's control gives for free is the part a hand-rolled one gets wrong:\n * 12- versus 24-hour display follows the user's OWN locale settings (not the page's —\n * which is the right authority for \"how do I read a clock\"), a phone opens its\n * platform wheel or dial, each segment is a spin button a screen reader already knows,\n * and typing \"1430\" fills both segments. The price is that the display cannot be\n * forced to 24h from here and the look of the segments is the browser's; neither is a\n * reason for three apps to ship an unaudited widget. So this is {@link Input} with\n * `type=\"time\"` and a string contract, and everything else — the floating label, the\n * `invalid`/`error` wiring, the click-anywhere `showPicker()` (feedback #224) — is\n * Input's own, inherited rather than copied.\n *\n * Two things it adds over `<Input type=\"time\">`:\n *\n * - **A normalised value.** A time input's value is any valid time string, so a\n * browser may leave off a zero seconds segment with `step={1}` or report\n * milliseconds, and a stored value may carry seconds the field does not show.\n * `onValueChange` always hands back exactly the shape `step` implies, so the value\n * a form stores compares and sorts as a plain string.\n * - **Out-of-range painted, not just flagged.** A native time input outside\n * `min`/`max` matches `:invalid` and nothing styles that selector (see Input's\n * `invalid`), so an out-of-window time looked fine. Here it wears\n * {@link FIELD_INVALID} and `aria-invalid`. A reversed window (`min=\"22:00\"\n * max=\"06:00\"`, a night shift, quiet hours) is read the way the HTML spec reads it:\n * as wrapping past midnight.\n *\n * `hint` is Input's too (keksdose K4): text is a caption under the field (\"Local time\n * at the site\"), a {@link FieldHint} rides the label line. `showCount` is typed out — a\n * time has no length worth counting.\n */\nexport interface TimeInputProps\n extends Omit<\n InputProps,\n \"type\" | \"value\" | \"defaultValue\" | \"min\" | \"max\" | \"step\" | \"variant\" | \"showCount\" | \"countLabels\"\n > {\n /** `\"HH:mm\"` (or `\"HH:mm:ss\"` with a sub-minute `step`); `\"\"` is \"no time\". */\n value: string;\n /** The normalised value on every change the browser reports — which for a time\n * input is only once every segment holds a digit, or when the field is cleared\n * (`\"\"`). Plain `onChange` still fires alongside it with the raw event. */\n onValueChange?: (value: string) => void;\n /** Earliest time, `\"HH:mm\"`. May be later than `max` for a window that wraps\n * midnight. */\n min?: string;\n /** Latest time, `\"HH:mm\"`. */\n max?: string;\n /** Granularity in SECONDS, as on the native input: 60 (the default) is minutes,\n * 900 is quarter hours, anything not a whole minute (e.g. 1) shows seconds. */\n step?: number;\n}\n\n/** Seconds shown when `step` is not a whole number of minutes — the browser's own rule\n * for whether the seconds segment appears. */\nfunction withSeconds(step: number | undefined): boolean {\n return step !== undefined && step % 60 !== 0;\n}\n\n/** Any `HH:mm[:ss[.sss]]` the browser reports → exactly the shape `step` implies.\n * Anything else (including `\"\"`) comes back as it went in. */\nexport function normalizeTime(raw: string, seconds: boolean): string {\n const m = /^(\\d{2}):(\\d{2})(?::(\\d{2}))?/.exec(raw);\n if (!m) return raw;\n return seconds ? `${m[1]}:${m[2]}:${m[3] ?? \"00\"}` : `${m[1]}:${m[2]}`;\n}\n\n/** Whether `value` lies inside `[min, max]`, wrapping midnight when `min > max`.\n * Zero-padded times compare as strings; both sides are padded to seconds first so\n * \"14:30\" and \"14:30:00\" are the same instant. */\nexport function isTimeInRange(value: string, min?: string, max?: string): boolean {\n if (value === \"\") return true;\n const v = normalizeTime(value, true);\n const lo = min ? normalizeTime(min, true) : undefined;\n const hi = max ? normalizeTime(max, true) : undefined;\n if (lo !== undefined && hi !== undefined && lo > hi) return v >= lo || v <= hi;\n return (lo === undefined || v >= lo) && (hi === undefined || v <= hi);\n}\n\nexport const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>(function TimeInput(\n { value, onValueChange, onChange, min, max, step, invalid, inputClassName, ...rest },\n ref,\n) {\n const seconds = withSeconds(step);\n const outOfRange = !isTimeInRange(value, min, max);\n return (\n <Input\n ref={ref}\n {...rest}\n type=\"time\"\n // Normalised on the way IN as well: a stored \"14:30:00\" handed to a minutes-only\n // field would otherwise make the browser show a seconds segment nobody asked for.\n value={normalizeTime(value, seconds)}\n min={min}\n max={max}\n step={step}\n invalid={invalid || outOfRange}\n onChange={(e: ChangeEvent<HTMLInputElement>) => {\n onChange?.(e);\n onValueChange?.(normalizeTime(e.target.value, seconds));\n }}\n // The segments are digits that change under the caret as you spin them;\n // proportional figures make the whole field jitter sideways on every step.\n inputClassName={cn(\"tabular-nums\", inputClassName)}\n />\n );\n});\nTimeInput.displayName = \"TimeInput\";\n"],"mappings":";AA4FI;AA5FJ,SAAS,kBAAkB;AAE3B,SAAS,aAA8B;AACvC,SAAS,UAAU;AA0DnB,SAAS,YAAY,MAAmC;AACtD,SAAO,SAAS,UAAa,OAAO,OAAO;AAC7C;AAIO,SAAS,cAAc,KAAa,SAA0B;AACnE,QAAM,IAAI,gCAAgC,KAAK,GAAG;AAClD,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,UAAU,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACtE;AAKO,SAAS,cAAc,OAAe,KAAc,KAAuB;AAChF,MAAI,UAAU,GAAI,QAAO;AACzB,QAAM,IAAI,cAAc,OAAO,IAAI;AACnC,QAAM,KAAK,MAAM,cAAc,KAAK,IAAI,IAAI;AAC5C,QAAM,KAAK,MAAM,cAAc,KAAK,IAAI,IAAI;AAC5C,MAAI,OAAO,UAAa,OAAO,UAAa,KAAK,GAAI,QAAO,KAAK,MAAM,KAAK;AAC5E,UAAQ,OAAO,UAAa,KAAK,QAAQ,OAAO,UAAa,KAAK;AACpE;AAEO,MAAM,YAAY,WAA6C,SAASA,WAC7E,EAAE,OAAO,eAAe,UAAU,KAAK,KAAK,MAAM,SAAS,gBAAgB,GAAG,KAAK,GACnF,KACA;AACA,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,aAAa,CAAC,cAAc,OAAO,KAAK,GAAG;AACjD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACC,GAAG;AAAA,MACJ,MAAK;AAAA,MAGL,OAAO,cAAc,OAAO,OAAO;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,WAAW;AAAA,MACpB,UAAU,CAAC,MAAqC;AAC9C,mBAAW,CAAC;AACZ,wBAAgB,cAAc,EAAE,OAAO,OAAO,OAAO,CAAC;AAAA,MACxD;AAAA,MAGA,gBAAgB,GAAG,gBAAgB,cAAc;AAAA;AAAA,EACnD;AAEJ,CAAC;AACD,UAAU,cAAc;","names":["TimeInput"]}
|
|
@@ -53,6 +53,9 @@ interface ToggleGroupBaseProps<T extends string> extends Omit<ComponentPropsWith
|
|
|
53
53
|
* `sm`: 12px options with `px-2 py-1` — the compact group keksdose's rule editor
|
|
54
54
|
* (rule-editor:204) writes as `optionClassName="px-2 py-1 text-xs"` beside a small
|
|
55
55
|
* caption. `md` (default) is the size every other group has.
|
|
56
|
+
*
|
|
57
|
+
* Honoured inside the field chrome and the strip too (keksdose K1): the 12px type
|
|
58
|
+
* stays, and the field is still 42px — see `CHROME_PAD`.
|
|
56
59
|
*/
|
|
57
60
|
size?: "sm" | "md";
|
|
58
61
|
/**
|
|
@@ -78,11 +81,32 @@ interface ToggleGroupBaseProps<T extends string> extends Omit<ComponentPropsWith
|
|
|
78
81
|
* cap pair sits in a `Field` column between two labelled-above inputs, where the
|
|
79
82
|
* chrome's inner label would be the only one of its kind).
|
|
80
83
|
*
|
|
84
|
+
* `"strip"`: the 11px static label (and `hint`) in a strip OVER the bare group, for a
|
|
85
|
+
* group that stands in a row of fields but is not one (keksdose K2 — the transaction
|
|
86
|
+
* row's cleared-status picker, live #288 / #431 / dev#468). No field border: the group
|
|
87
|
+
* keeps its own box, and its segments drop their vertical padding so a 16px strip
|
|
88
|
+
* plus the 26px group is exactly a labelled {@link Input}'s 42px, the label on the
|
|
89
|
+
* same line as theirs. `error` and `caption` go under the group, as in the other two.
|
|
90
|
+
*
|
|
81
91
|
* Inside a `Field`, pass no `label` at all and spread the render-prop instead —
|
|
82
92
|
* `{(ids, { labelId }) => <ToggleGroup {...ids} aria-labelledby={labelId} … />}` —
|
|
83
93
|
* so the Field's label names the group and its hint and error describe it.
|
|
84
94
|
*/
|
|
85
|
-
labelPlacement?: "field" | "above";
|
|
95
|
+
labelPlacement?: "field" | "above" | "strip";
|
|
96
|
+
/**
|
|
97
|
+
* Classes for the field chrome's own box — the bordered, padded box round the
|
|
98
|
+
* segments — merged last, so they win. `className` styles the wrapper the chrome sits
|
|
99
|
+
* in; this reaches the box itself. Only with `label` in the `"field"` placement.
|
|
100
|
+
*
|
|
101
|
+
* For a field JOINED flush to a neighbour (keksdose K1): reports-filters.tsx joins its
|
|
102
|
+
* "group by" field to the range select on its start edge from `md` up, and squares the
|
|
103
|
+
* shared corners by hand chrome today — here that is
|
|
104
|
+
* `chromeClassName="md:rounded-s-none"` with `className="md:-ms-px"` (the wrapper
|
|
105
|
+
* overlaps the neighbour by the one border they share). A class rather than a
|
|
106
|
+
* `joined="start"` because every such join keksdose has is responsive: on a phone the
|
|
107
|
+
* two stack and both keep their corners.
|
|
108
|
+
*/
|
|
109
|
+
chromeClassName?: string;
|
|
86
110
|
/** A {@link FieldHint} on the label line, as on a labelled {@link Select}. Only with
|
|
87
111
|
* `label`. */
|
|
88
112
|
hint?: ReactNode;
|
|
@@ -90,6 +114,28 @@ interface ToggleGroupBaseProps<T extends string> extends Omit<ComponentPropsWith
|
|
|
90
114
|
* `--danger`, marks the group `aria-invalid` and describes it with the message, as
|
|
91
115
|
* {@link Select}'s `error` does. Only with `label`. */
|
|
92
116
|
error?: ReactNode;
|
|
117
|
+
/**
|
|
118
|
+
* Why the group cannot be changed right now — {@link Button}'s `disabledReason`, for
|
|
119
|
+
* a group that saves itself on change (keksdose K3: the last `SaveGuard` sites are
|
|
120
|
+
* self-committing controls like this one, and SaveGuard's native `disabled` took
|
|
121
|
+
* them out of the tab order so the reason never reached a keyboard).
|
|
122
|
+
*
|
|
123
|
+
* The group stays reachable — its segments are `aria-disabled`, not `disabled`, and
|
|
124
|
+
* a radio group's arrow keys still walk them — but nothing reaches `onChange`, and
|
|
125
|
+
* the reason shows in the kit {@link Tooltip} over the segments on hover or focus and
|
|
126
|
+
* describes each segment. The chosen option keeps its fill, as under `disabled`.
|
|
127
|
+
* Wins over `disabled`, as on Button: passing both keeps the group reachable.
|
|
128
|
+
*/
|
|
129
|
+
disabledReason?: ReactNode;
|
|
130
|
+
/**
|
|
131
|
+
* This group COMMITS — choosing an option saves it. Under a locked
|
|
132
|
+
* {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
|
|
133
|
+
* reason (which wins over a `disabledReason` of its own): *"everything renders; only
|
|
134
|
+
* the commit is locked"* (keksdose dev#496/#497), and a control that is its own commit
|
|
135
|
+
* must render refused, never live-and-discarding. No provider, or an unlocked one:
|
|
136
|
+
* no effect.
|
|
137
|
+
*/
|
|
138
|
+
commit?: boolean;
|
|
93
139
|
}
|
|
94
140
|
/**
|
|
95
141
|
* A line under the group saying what the CHOSEN option means — lenkbank's ToggleField
|