@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ArrowUpDown, Minus, Plus } from "lucide-react";
|
|
2
|
+
import { Chip } from "./chip";
|
|
3
|
+
import { Tooltip } from "./tooltip";
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
import { useKitLabels } from "../i18n/kit-labels";
|
|
6
|
+
|
|
7
|
+
// ── Labels ────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/** The words a {@link SignChip} says — the `signChip` namespace of
|
|
10
|
+
* `<UiKitProvider labels>`, and per chip through its `labels` prop. */
|
|
11
|
+
export interface SignChipLabels {
|
|
12
|
+
/** The chip's text while the figure is negative — money going out. */
|
|
13
|
+
outflow: string;
|
|
14
|
+
/** The chip's text while the figure is positive — money coming in. */
|
|
15
|
+
inflow: string;
|
|
16
|
+
/** The chip's accessible name and its tooltip: the state it is in, and what a press
|
|
17
|
+
* turns it into — `current` and `next` are the two words above. The other option is
|
|
18
|
+
* nowhere on screen to be read, so the name has to say both. */
|
|
19
|
+
direction: (current: string, next: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_SIGN_CHIP_LABELS: SignChipLabels = {
|
|
23
|
+
outflow: "Outflow",
|
|
24
|
+
inflow: "Inflow",
|
|
25
|
+
direction: (current, next) => `Direction: ${current} — switch to ${next}`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface SignChipProps {
|
|
29
|
+
/** The figure is negative (an outflow) — the same flag {@link AmountInput}'s
|
|
30
|
+
* `negative` is, so one state feeds both: `<AmountInput negative={neg}
|
|
31
|
+
* onNegativeChange={setNeg} />` and `<SignChip negative={neg}
|
|
32
|
+
* onNegativeChange={setNeg} />`. */
|
|
33
|
+
negative: boolean;
|
|
34
|
+
/** A press: the other direction. */
|
|
35
|
+
onNegativeChange: (negative: boolean) => void;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Per-chip overrides of the `signChip` namespace. */
|
|
38
|
+
labels?: Partial<SignChipLabels>;
|
|
39
|
+
/** On the chip. Place it in its row from here — keksdose's form passes `self-end`
|
|
40
|
+
* to sit it on the display figure's baseline strip. */
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The SIGN of an amount, as one chip that shows only the state it is in — keksdose
|
|
46
|
+
* K15, its `DirectionToggle` (feedback #417, #430, #431, dev#434) made a kit piece,
|
|
47
|
+
* so the figure and its direction come from one package and wear one palette.
|
|
48
|
+
*
|
|
49
|
+
* WHY A CHIP, NOT A SEGMENTED CONTROL. A two-option toggle spends half its width
|
|
50
|
+
* telling you what the figure is NOT, and at a phone's width it was the widest thing on
|
|
51
|
+
* the form after the figure itself (#417). This sits beside the amount and reads as one
|
|
52
|
+
* phrase with it — "−42,50 Outflow" — and a press flips it. Hiding the alternative costs
|
|
53
|
+
* discoverability, so two things pay it back: the ± glyph and the swap arrows say "this
|
|
54
|
+
* is a control", not a badge; and the name and tooltip spell out both the current state
|
|
55
|
+
* and what a press will do.
|
|
56
|
+
*
|
|
57
|
+
* NOT A PRESSED/UNPRESSED TOGGLE. A {@link Chip} toggle has a FIXED label naming its
|
|
58
|
+
* on-state, with `aria-pressed` carrying the state. Here the word IS the state, so it
|
|
59
|
+
* is an action button with no `aria-pressed`: "Direction: Inflow, not pressed" would
|
|
60
|
+
* name no on-state for "not pressed" to be the absence of. The name changing under
|
|
61
|
+
* focus is the echo.
|
|
62
|
+
*
|
|
63
|
+
* THE COLOUR is the money pairing — the chip's `expense` / `income` tones, the same
|
|
64
|
+
* `--money-expense` / `--money-income` that {@link AmountInput}'s `tone` paints the
|
|
65
|
+
* figure with (dev#434): only the text and the border carry the tint, on a neutral
|
|
66
|
+
* surface, because a filled pill next to a tinted figure reads as one smear.
|
|
67
|
+
*
|
|
68
|
+
* SIZE. `lg` on a phone — the 44px touch target beside a display figure that has no
|
|
69
|
+
* field height to match — and from `md` up exactly a labelled field's 42px, so it never
|
|
70
|
+
* stands taller than the fields in its grid row (#431).
|
|
71
|
+
*
|
|
72
|
+
* An exported chip rather than an `AmountInput signControl` slot: the slot would have
|
|
73
|
+
* to own the row the two share, and every form lays that row out differently (beside
|
|
74
|
+
* the figure on a phone, in a grid column on a desktop). The chip is the part that was
|
|
75
|
+
* being re-written; the row stays the form's.
|
|
76
|
+
*/
|
|
77
|
+
export function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }: SignChipProps) {
|
|
78
|
+
const labels = useKitLabels("signChip", DEFAULT_SIGN_CHIP_LABELS, labelsProp);
|
|
79
|
+
const current = negative ? labels.outflow : labels.inflow;
|
|
80
|
+
const next = negative ? labels.inflow : labels.outflow;
|
|
81
|
+
const name = labels.direction(current, next);
|
|
82
|
+
return (
|
|
83
|
+
<Tooltip label={name} lazy>
|
|
84
|
+
<Chip
|
|
85
|
+
size="lg"
|
|
86
|
+
tone={negative ? "expense" : "income"}
|
|
87
|
+
icon={negative ? Minus : Plus}
|
|
88
|
+
onClick={() => onNegativeChange(!negative)}
|
|
89
|
+
disabled={disabled}
|
|
90
|
+
aria-label={name}
|
|
91
|
+
// `gap-1.5 px-3`: the chip's narrower body, because on a 375px screen every
|
|
92
|
+
// pixel of it is taken from the figure beside it (#430). `md:py-2.5` with the
|
|
93
|
+
// hairline border is the 42px a labelled field measures (#431).
|
|
94
|
+
className={cn("shrink-0 gap-1.5 whitespace-nowrap px-3 font-medium md:min-h-0 md:py-2.5", className)}
|
|
95
|
+
>
|
|
96
|
+
{current}
|
|
97
|
+
{/* The swap arrows after the word, in the label's own row: decoration (the
|
|
98
|
+
name already says what a press does), set a little apart from the word. */}
|
|
99
|
+
<ArrowUpDown className="ms-1.5 inline size-3.5 align-[-0.2em] opacity-60" aria-hidden />
|
|
100
|
+
</Chip>
|
|
101
|
+
</Tooltip>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { useId, useMemo } from "react";
|
|
2
|
-
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels";
|
|
5
5
|
import { TILE_SIZE, TileRadioGroup } from "./tile-radio";
|
|
6
6
|
import type { TileItem, TileSize } from "./tile-radio";
|
|
7
|
+
import { Tooltip } from "./tooltip";
|
|
8
|
+
import { useCommitReason } from "./write-lock";
|
|
9
|
+
import { hasContent, LabelStrip } from "./field-anatomy";
|
|
7
10
|
|
|
8
11
|
export interface SwatchPickerLabels {
|
|
9
12
|
/** The "no colour" tile's name, when `allowNone` is set. */
|
|
@@ -72,6 +75,31 @@ export interface SwatchPickerProps<T extends string>
|
|
|
72
75
|
tileClassName?: string;
|
|
73
76
|
disabled?: boolean;
|
|
74
77
|
labels?: Partial<SwatchPickerLabels>;
|
|
78
|
+
/**
|
|
79
|
+
* An 11px static label in a strip over the swatches, named by it (keksdose K2): the
|
|
80
|
+
* transaction row's flag picker stands between labelled fields and hand-builds this
|
|
81
|
+
* (`FieldLabel` + `pt-5`, transaction-flags.tsx:155) because the picker had no label.
|
|
82
|
+
* The label sits on the same line as a field's own (`top-1`, `inset-x-3`); the
|
|
83
|
+
* swatches start 20px down, below its whole line box — see `LABEL_STRIP_PAD`. With a
|
|
84
|
+
* label, `className` styles the wrapper, as on a labelled {@link ToggleGroup}; an
|
|
85
|
+
* explicit `aria-label` / `aria-labelledby` still names the group instead.
|
|
86
|
+
*/
|
|
87
|
+
label?: ReactNode;
|
|
88
|
+
/** A {@link FieldHint} beside the label. Only with `label`. */
|
|
89
|
+
hint?: ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* Why the colour cannot be changed right now — {@link Button}'s `disabledReason`, for
|
|
92
|
+
* a picker that saves itself on change (keksdose K3, the flag picker in a row a
|
|
93
|
+
* read-only viewer may see). The swatches stay reachable and the arrow keys still
|
|
94
|
+
* walk them, but nothing reaches `onChange`; the reason is shown in the kit
|
|
95
|
+
* {@link Tooltip} under the row (each swatch's own name bubble sits above it) and
|
|
96
|
+
* describes the group. Wins over `disabled`.
|
|
97
|
+
*/
|
|
98
|
+
disabledReason?: ReactNode;
|
|
99
|
+
/** This picker COMMITS — choosing a colour saves it. Under a locked
|
|
100
|
+
* {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
|
|
101
|
+
* reason, as {@link Button}'s `commit` does. */
|
|
102
|
+
commit?: boolean;
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
/**
|
|
@@ -97,51 +125,105 @@ export function SwatchPicker<T extends string>({
|
|
|
97
125
|
tileClassName,
|
|
98
126
|
disabled = false,
|
|
99
127
|
labels,
|
|
128
|
+
label,
|
|
129
|
+
hint,
|
|
130
|
+
disabledReason: ownDisabledReason,
|
|
131
|
+
commit,
|
|
100
132
|
className,
|
|
101
133
|
...rest
|
|
102
134
|
}: SwatchPickerProps<T>) {
|
|
103
135
|
const text = useKitLabels("swatchPicker", DEFAULT_SWATCH_PICKER_LABELS, labels);
|
|
104
136
|
const mixedId = useId();
|
|
137
|
+
const labelId = useId();
|
|
138
|
+
const reasonId = useId();
|
|
139
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
140
|
+
const locked = hasContent(disabledReason);
|
|
141
|
+
const labelled = hasContent(label);
|
|
105
142
|
const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);
|
|
106
143
|
const items: TileItem<T>[] = [
|
|
107
144
|
...(allowNone ? [{ key: null, label: text.none }] : []),
|
|
108
145
|
...options.map((o) => ({ key: o.value, label: o.label, note: o.note, disabled: o.disabled })),
|
|
109
146
|
];
|
|
110
|
-
|
|
147
|
+
const tiles = (
|
|
148
|
+
<TileRadioGroup
|
|
149
|
+
items={items}
|
|
150
|
+
checked={mixed ? undefined : value}
|
|
151
|
+
// A locked picker saves on change, so the change is swallowed — the arrow keys
|
|
152
|
+
// still move the focus (and, automatic, would choose), and nothing is chosen.
|
|
153
|
+
onSelect={(next) => {
|
|
154
|
+
if (!locked) onChange(next);
|
|
155
|
+
}}
|
|
156
|
+
activation={activation}
|
|
157
|
+
// A reason wins over `disabled`: the tiles stay in the tab order.
|
|
158
|
+
disabled={disabled && !locked}
|
|
159
|
+
size={size}
|
|
160
|
+
tileClassName={
|
|
161
|
+
tileClassName || locked
|
|
162
|
+
? (_item, selected) =>
|
|
163
|
+
cn(
|
|
164
|
+
// The disabled tile's look, without its `disabled`: dimmed, a
|
|
165
|
+
// not-allowed cursor, and no hover offer on a tile that cannot be
|
|
166
|
+
// chosen. Not on the selected tile, whose frame IS its state.
|
|
167
|
+
locked && "cursor-not-allowed opacity-50",
|
|
168
|
+
locked && !selected && "hover:border-[var(--border)] hover:bg-[var(--bg-surface)]",
|
|
169
|
+
tileClassName,
|
|
170
|
+
)
|
|
171
|
+
: undefined
|
|
172
|
+
}
|
|
173
|
+
renderTile={(item) => {
|
|
174
|
+
const opt = item.key === null ? undefined : byValue.get(item.key);
|
|
175
|
+
return (
|
|
176
|
+
<span
|
|
177
|
+
aria-hidden
|
|
178
|
+
// A hairline round the dot, drawn over whatever colour it is: a pale
|
|
179
|
+
// yellow swatch on a white surface is otherwise a hole in the row.
|
|
180
|
+
className={cn(
|
|
181
|
+
"rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15",
|
|
182
|
+
TILE_SIZE[size].swatch,
|
|
183
|
+
opt?.swatchClassName,
|
|
184
|
+
)}
|
|
185
|
+
style={{ background: opt?.color }}
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
const group = (
|
|
111
192
|
<div
|
|
112
193
|
{...rest}
|
|
113
194
|
role="radiogroup"
|
|
114
|
-
aria-
|
|
195
|
+
aria-labelledby={
|
|
196
|
+
labelled && rest["aria-label"] === undefined && rest["aria-labelledby"] === undefined
|
|
197
|
+
? labelId
|
|
198
|
+
: rest["aria-labelledby"]
|
|
199
|
+
}
|
|
200
|
+
aria-disabled={disabled || locked || undefined}
|
|
115
201
|
aria-describedby={
|
|
116
|
-
[rest["aria-describedby"], mixed && mixedId].filter(Boolean).join(" ") ||
|
|
202
|
+
[rest["aria-describedby"], mixed && mixedId, locked && reasonId].filter(Boolean).join(" ") ||
|
|
203
|
+
undefined
|
|
117
204
|
}
|
|
118
|
-
className={cn("flex flex-wrap items-center gap-1.5", className)}
|
|
205
|
+
className={cn("flex flex-wrap items-center gap-1.5", !labelled && className)}
|
|
119
206
|
>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
style={{ background: opt?.color }}
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
}}
|
|
144
|
-
/>
|
|
207
|
+
{locked ? (
|
|
208
|
+
// Inside the group, round the tiles — not round the group, whose box is the
|
|
209
|
+
// caller's row item. This row inherits the group's gap, wrap and alignment, so
|
|
210
|
+
// the tiles sit exactly where they sit unlocked. Below the row: each tile's own
|
|
211
|
+
// bubble (its colour's name) is above it.
|
|
212
|
+
<Tooltip
|
|
213
|
+
label={disabledReason}
|
|
214
|
+
side="bottom"
|
|
215
|
+
className="flex min-w-0 flex-1 gap-[inherit] [align-items:inherit] [flex-wrap:inherit] [justify-content:inherit]"
|
|
216
|
+
>
|
|
217
|
+
<>
|
|
218
|
+
{tiles}
|
|
219
|
+
<span id={reasonId} hidden>
|
|
220
|
+
{disabledReason}
|
|
221
|
+
</span>
|
|
222
|
+
</>
|
|
223
|
+
</Tooltip>
|
|
224
|
+
) : (
|
|
225
|
+
tiles
|
|
226
|
+
)}
|
|
145
227
|
{/* `hidden`, not `sr-only`: a description is read from hidden text, and this
|
|
146
228
|
way it takes no room in the row. */}
|
|
147
229
|
{mixed && (
|
|
@@ -151,4 +233,10 @@ export function SwatchPicker<T extends string>({
|
|
|
151
233
|
)}
|
|
152
234
|
</div>
|
|
153
235
|
);
|
|
236
|
+
if (!labelled) return group;
|
|
237
|
+
return (
|
|
238
|
+
<LabelStrip labelId={labelId} label={label} hint={hint} disabled={disabled || locked} pad="tiles" className={className}>
|
|
239
|
+
{group}
|
|
240
|
+
</LabelStrip>
|
|
241
|
+
);
|
|
154
242
|
}
|
|
@@ -2,6 +2,7 @@ import { forwardRef, useId } from "react";
|
|
|
2
2
|
import type { ChangeEvent, InputHTMLAttributes, ReactNode } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { hasMessage, mergeDescribedBy } from "./choice-parts";
|
|
5
|
+
import { LockedReason, useLockReason } from "./field-parts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* An on/off switch that is still `<input type="checkbox">`, with `role="switch"`.
|
|
@@ -79,6 +80,25 @@ export interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
|
|
|
79
80
|
* label WITHOUT a literal "*". No mark without a label.
|
|
80
81
|
*/
|
|
81
82
|
required?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Why the switch cannot be flipped — {@link Button}'s `disabledReason`. A switch is
|
|
85
|
+
* the control that "takes effect the moment it is flipped", so it is the first one
|
|
86
|
+
* keksdose K3 names: its settings rows sat behind a `SaveGuard` that forced a native
|
|
87
|
+
* `disabled`, out of the tab order, so the reason never reached a keyboard.
|
|
88
|
+
*
|
|
89
|
+
* With a reason the switch is `aria-disabled` instead: still focusable, a click or
|
|
90
|
+
* Space flips nothing and fires no `onChange` / `onCheckedChange`, the reason is in
|
|
91
|
+
* the kit {@link Tooltip} on the track and on its `aria-describedby`, and the row
|
|
92
|
+
* fades as a disabled one does. It wins over `disabled`. Changing the lock mounts the
|
|
93
|
+
* switch anew (it moves in or out of the Tooltip), as a locked Button is.
|
|
94
|
+
*/
|
|
95
|
+
disabledReason?: ReactNode;
|
|
96
|
+
/**
|
|
97
|
+
* This switch COMMITS — flipping it saves. Under a locked {@link WriteLockProvider} it
|
|
98
|
+
* is locked the `disabledReason` way with the lock's reason (which wins over its
|
|
99
|
+
* own). No provider, or an unlocked one: no effect. Button's `commit`, for K3.
|
|
100
|
+
*/
|
|
101
|
+
commit?: boolean;
|
|
82
102
|
}
|
|
83
103
|
|
|
84
104
|
/**
|
|
@@ -101,39 +121,70 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
|
|
|
101
121
|
id,
|
|
102
122
|
disabled,
|
|
103
123
|
required,
|
|
124
|
+
disabledReason,
|
|
125
|
+
commit,
|
|
126
|
+
onClick,
|
|
104
127
|
...rest
|
|
105
128
|
},
|
|
106
129
|
ref,
|
|
107
130
|
) {
|
|
108
131
|
const generated = useId();
|
|
109
132
|
const inputId = id ?? generated;
|
|
133
|
+
const lock = useLockReason(commit, disabledReason);
|
|
134
|
+
const locked = lock.locked;
|
|
135
|
+
// Locked is disabled the focusable way — the row fades and the words stop inviting a
|
|
136
|
+
// click, as for `disabled`.
|
|
137
|
+
const looksDisabled = Boolean(disabled) || locked;
|
|
110
138
|
const descriptionId = `${inputId}-description`;
|
|
111
139
|
const showDescription = hasMessage(description);
|
|
112
140
|
const bare = label === undefined && !showDescription;
|
|
113
141
|
const geometry = SIZES[size];
|
|
114
142
|
|
|
115
|
-
const
|
|
116
|
-
<span className={cn("relative inline-flex shrink-0", bare && className)}>
|
|
143
|
+
const track = (
|
|
144
|
+
<span className={cn("relative inline-flex shrink-0", bare && !locked && className)}>
|
|
117
145
|
<input
|
|
118
146
|
ref={ref}
|
|
119
147
|
id={bare ? id : inputId}
|
|
120
|
-
disabled
|
|
148
|
+
// A reason wins over `disabled`, as on Button: the switch stays reachable.
|
|
149
|
+
disabled={locked ? undefined : disabled}
|
|
121
150
|
required={required}
|
|
122
151
|
{...rest}
|
|
123
152
|
// After the spread: a caller's props object must not be able to turn this
|
|
124
153
|
// back into a plain checkbox, or into a text field.
|
|
125
154
|
type="checkbox"
|
|
126
155
|
role="switch"
|
|
127
|
-
aria-
|
|
156
|
+
aria-disabled={locked || rest["aria-disabled"] || undefined}
|
|
157
|
+
aria-describedby={mergeDescribedBy(
|
|
158
|
+
rest["aria-describedby"],
|
|
159
|
+
showDescription && descriptionId,
|
|
160
|
+
locked && lock.reasonId,
|
|
161
|
+
)}
|
|
162
|
+
// A locked switch's click is swallowed, as a locked Button's is.
|
|
163
|
+
onClick={locked ? undefined : onClick}
|
|
128
164
|
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
165
|
+
if (locked) {
|
|
166
|
+
// Put the thumb back and report nothing — see Checkbox for why here and not
|
|
167
|
+
// by cancelling the click. Label clicks and Space arrive as clicks too.
|
|
168
|
+
e.currentTarget.checked = !e.currentTarget.checked;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
129
171
|
onChange?.(e);
|
|
130
172
|
onCheckedChange?.(e.target.checked);
|
|
131
173
|
}}
|
|
132
|
-
className={cn(TRACK, geometry.track, inputClassName)}
|
|
174
|
+
className={cn(TRACK, geometry.track, locked && "cursor-not-allowed", inputClassName)}
|
|
133
175
|
/>
|
|
134
176
|
<span aria-hidden className={cn(THUMB, geometry.thumb)} />
|
|
135
177
|
</span>
|
|
136
178
|
);
|
|
179
|
+
// The Tooltip goes round the track, not the row — the row is the caller's layout. A
|
|
180
|
+
// bare switch hands its `className` to the wrapper, then the outermost element.
|
|
181
|
+
const control = locked ? (
|
|
182
|
+
<LockedReason lock={lock} className={cn("shrink-0", bare && className)}>
|
|
183
|
+
{track}
|
|
184
|
+
</LockedReason>
|
|
185
|
+
) : (
|
|
186
|
+
track
|
|
187
|
+
);
|
|
137
188
|
|
|
138
189
|
if (bare) return control;
|
|
139
190
|
|
|
@@ -148,7 +199,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
|
|
|
148
199
|
htmlFor={inputId}
|
|
149
200
|
className={cn(
|
|
150
201
|
"block text-sm leading-5 text-[var(--text-primary)]",
|
|
151
|
-
|
|
202
|
+
looksDisabled ? "cursor-not-allowed" : "cursor-pointer select-none",
|
|
152
203
|
)}
|
|
153
204
|
>
|
|
154
205
|
{label}
|
|
@@ -168,7 +219,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
|
|
|
168
219
|
);
|
|
169
220
|
|
|
170
221
|
return (
|
|
171
|
-
<div className={cn("flex items-start gap-3",
|
|
222
|
+
<div className={cn("flex items-start gap-3", looksDisabled && "opacity-60", className)}>
|
|
172
223
|
{switchPosition === "end" ? (
|
|
173
224
|
<>
|
|
174
225
|
{words}
|
|
@@ -6,27 +6,67 @@ 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.
|
|
18
54
|
*/
|
|
19
55
|
|
|
56
|
+
/** A tile's size: 28 / 32 / 44px — see {@link TILE_SIZE}. */
|
|
20
57
|
export type TileSize = "sm" | "md" | "lg";
|
|
21
58
|
|
|
22
|
-
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`).
|
|
59
|
+
/** 28 / 32 / 44px. `lg` is the touch target a phone surface wants (`min-h-11`). `glyph`
|
|
60
|
+
* and `swatch` are the content sizes the kit's own pickers draw inside each — use them
|
|
61
|
+
* in `renderTile` so a tile of the app's own matches theirs. */
|
|
23
62
|
export const TILE_SIZE: Record<TileSize, { tile: string; glyph: string; swatch: string }> = {
|
|
24
63
|
sm: { tile: "size-7", glyph: "size-3.5", swatch: "size-4" },
|
|
25
64
|
md: { tile: "size-8", glyph: "size-4", swatch: "size-5" },
|
|
26
65
|
lg: { tile: "size-11", glyph: "size-5", swatch: "size-6" },
|
|
27
66
|
};
|
|
28
67
|
|
|
29
|
-
/** One tile, whatever it shows. `key` is `null` for the "none" entry
|
|
68
|
+
/** One tile, whatever it shows. `key` is `null` for the "none" entry — a tile of its
|
|
69
|
+
* own that leads back to nothing, since clicking a checked tile does not uncheck it. */
|
|
30
70
|
export interface TileItem<T extends string> {
|
|
31
71
|
key: T | null;
|
|
32
72
|
label: string;
|
|
@@ -37,20 +77,28 @@ export interface TileItem<T extends string> {
|
|
|
37
77
|
}
|
|
38
78
|
|
|
39
79
|
export interface TileRadioGroupProps<T extends string> {
|
|
80
|
+
/** The tiles, in reading order. Render them inside a `role="radiogroup"` element
|
|
81
|
+
* with a name — see the module note. */
|
|
40
82
|
items: TileItem<T>[];
|
|
41
83
|
/** The checked key, or `undefined` for "nothing is checked" (the mixed state, or
|
|
42
84
|
* a value that is not among the options). */
|
|
43
85
|
checked: T | null | undefined;
|
|
44
86
|
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
|
-
/**
|
|
87
|
+
/** "automatic" (default): an arrow key moves AND checks, the APG default. "manual":
|
|
88
|
+
* arrows move focus only, Space or Enter checks — for a picker whose change is
|
|
89
|
+
* expensive (a bulk edit that saves on every change). */
|
|
90
|
+
activation?: "automatic" | "manual";
|
|
91
|
+
/** Every tile disabled. Mark the container `aria-disabled` too. Default `false`. */
|
|
92
|
+
disabled?: boolean;
|
|
93
|
+
/** 28 / 32 / 44px tiles ({@link TILE_SIZE}). Default "md"; "lg" is the touch-target
|
|
94
|
+
* size a phone surface wants. */
|
|
95
|
+
size?: TileSize;
|
|
96
|
+
/** What goes inside a tile — `aria-hidden` decoration, since the tile is named by
|
|
97
|
+
* `label`. Not called for the `null` ("none") item, which shows the kit's "no"
|
|
98
|
+
* glyph. The frame, the tick and the note's dot are drawn around it. */
|
|
52
99
|
renderTile: (item: TileItem<T>, selected: boolean) => ReactNode;
|
|
53
|
-
/** Extra classes for a tile, per item
|
|
100
|
+
/** Extra classes for a tile, per item, merged after the kit's — so a `size-*` here
|
|
101
|
+
* beats `size`'s (a tile that grows in a narrow card). */
|
|
54
102
|
tileClassName?: (item: TileItem<T>, selected: boolean) => string | undefined;
|
|
55
103
|
}
|
|
56
104
|
|
|
@@ -72,13 +120,20 @@ const TILE_IDLE =
|
|
|
72
120
|
const TILE_SELECTED =
|
|
73
121
|
"border-[var(--text-primary)] bg-[var(--bg-surface)] ring-1 ring-[var(--text-primary)]";
|
|
74
122
|
|
|
123
|
+
/**
|
|
124
|
+
* The tiles of one radio group, without the group element — render them inside a
|
|
125
|
+
* `role="radiogroup"` with a name (the module note above has the example). One tab stop
|
|
126
|
+
* (the checked tile, else the first that can be chosen), arrows to move — left and
|
|
127
|
+
* right follow the page's direction — Home and End to the ends, and by default the
|
|
128
|
+
* arrows choose as they go.
|
|
129
|
+
*/
|
|
75
130
|
export function TileRadioGroup<T extends string>({
|
|
76
131
|
items,
|
|
77
132
|
checked,
|
|
78
133
|
onSelect,
|
|
79
|
-
activation,
|
|
80
|
-
disabled,
|
|
81
|
-
size,
|
|
134
|
+
activation = "automatic",
|
|
135
|
+
disabled = false,
|
|
136
|
+
size = "md",
|
|
82
137
|
renderTile,
|
|
83
138
|
tileClassName,
|
|
84
139
|
}: TileRadioGroupProps<T>) {
|
|
@@ -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
|