@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 { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { bg as SelectProps } from '../kit-labels-R8mIAc9N.js';
|
|
4
4
|
import { ThemePreference } from '../theme/theme-store.js';
|
|
5
|
-
import '
|
|
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';
|
|
@@ -25,7 +27,6 @@ import './sparkline.js';
|
|
|
25
27
|
import './stat-tile.js';
|
|
26
28
|
import './signature-pad.js';
|
|
27
29
|
import './password-strength.js';
|
|
28
|
-
import './danger-confirm.js';
|
|
29
30
|
import './swatch-picker.js';
|
|
30
31
|
import './tile-radio.js';
|
|
31
32
|
import './icon-picker.js';
|
|
@@ -33,11 +34,10 @@ import './dialog-frame.js';
|
|
|
33
34
|
import './modal.js';
|
|
34
35
|
import '../hooks/use-search-param-state.js';
|
|
35
36
|
import './measured-grid.js';
|
|
36
|
-
import '../feedback-attachment-
|
|
37
|
+
import '../feedback-attachment-NhUm7Zga.js';
|
|
37
38
|
import '../feedback/feedback-thread.js';
|
|
38
39
|
import '../feedback/feedback-inbox.js';
|
|
39
40
|
import './account-settings-labels.js';
|
|
40
|
-
import './confirm-dialog.js';
|
|
41
41
|
import './floating-panel.js';
|
|
42
42
|
import './bulk-action-bar.js';
|
|
43
43
|
import './list.js';
|
|
@@ -56,14 +56,16 @@ import './write-lock.js';
|
|
|
56
56
|
import './legal.js';
|
|
57
57
|
import './account-chips.js';
|
|
58
58
|
import '../lib/format.js';
|
|
59
|
-
import '../data-table-3mi2R3so.js';
|
|
60
|
-
import './data-table-sort.js';
|
|
61
|
-
import './swipeable-row.js';
|
|
62
59
|
import './reauth-dialog.js';
|
|
63
60
|
import './server-wake.js';
|
|
64
61
|
import '../lib/server-wake.js';
|
|
65
62
|
import './translation-review-labels.js';
|
|
66
63
|
import '../lib/translation-review.js';
|
|
64
|
+
import './country-select.js';
|
|
65
|
+
import './inline-edit-field.js';
|
|
66
|
+
import '../lib/iban.js';
|
|
67
|
+
import '../lib/phone.js';
|
|
68
|
+
import './sign-chip.js';
|
|
67
69
|
import 'zustand/middleware';
|
|
68
70
|
import 'zustand';
|
|
69
71
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import 'react';
|
|
2
2
|
import './account-chips.js';
|
|
3
3
|
import './dialog-frame.js';
|
|
4
|
-
export {
|
|
4
|
+
export { a7 as DEFAULT_SHARE_CARD_LABELS, bh as ShareAddRequest, bi as ShareCandidate, bj as ShareCard, bk as ShareCardLabels, bl as ShareCardProps, bm as ShareDialog, bn as ShareDialogProps, bo as ShareGrantee, bp as SharePanel, bq as SharePanelProps, br as SharePendingGrant, bs as ShareRole } from '../kit-labels-R8mIAc9N.js';
|
|
5
5
|
import 'lucide-react';
|
|
6
6
|
import '../lib/format.js';
|
|
7
7
|
import './chip.js';
|
|
8
|
-
import '../data-table-
|
|
8
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
9
9
|
import './data-table-sort.js';
|
|
10
10
|
import './swipeable-row.js';
|
|
11
|
-
import './data-table-labels.js';
|
|
12
11
|
import './modal.js';
|
|
13
12
|
import '../hooks/use-search-param-state.js';
|
|
14
13
|
import './mini-calendar.js';
|
|
@@ -31,16 +30,14 @@ import './sparkline.js';
|
|
|
31
30
|
import './stat-tile.js';
|
|
32
31
|
import './signature-pad.js';
|
|
33
32
|
import './password-strength.js';
|
|
34
|
-
import './danger-confirm.js';
|
|
35
33
|
import './swatch-picker.js';
|
|
36
34
|
import './tile-radio.js';
|
|
37
35
|
import './icon-picker.js';
|
|
38
36
|
import './measured-grid.js';
|
|
39
|
-
import '../feedback-attachment-
|
|
37
|
+
import '../feedback-attachment-NhUm7Zga.js';
|
|
40
38
|
import '../feedback/feedback-thread.js';
|
|
41
39
|
import '../feedback/feedback-inbox.js';
|
|
42
40
|
import './account-settings-labels.js';
|
|
43
|
-
import './confirm-dialog.js';
|
|
44
41
|
import './floating-panel.js';
|
|
45
42
|
import './bulk-action-bar.js';
|
|
46
43
|
import './list.js';
|
|
@@ -62,3 +59,8 @@ 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';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/** The words a {@link SignChip} says — the `signChip` namespace of
|
|
4
|
+
* `<UiKitProvider labels>`, and per chip through its `labels` prop. */
|
|
5
|
+
interface SignChipLabels {
|
|
6
|
+
/** The chip's text while the figure is negative — money going out. */
|
|
7
|
+
outflow: string;
|
|
8
|
+
/** The chip's text while the figure is positive — money coming in. */
|
|
9
|
+
inflow: string;
|
|
10
|
+
/** The chip's accessible name and its tooltip: the state it is in, and what a press
|
|
11
|
+
* turns it into — `current` and `next` are the two words above. The other option is
|
|
12
|
+
* nowhere on screen to be read, so the name has to say both. */
|
|
13
|
+
direction: (current: string, next: string) => string;
|
|
14
|
+
}
|
|
15
|
+
declare const DEFAULT_SIGN_CHIP_LABELS: SignChipLabels;
|
|
16
|
+
interface SignChipProps {
|
|
17
|
+
/** The figure is negative (an outflow) — the same flag {@link AmountInput}'s
|
|
18
|
+
* `negative` is, so one state feeds both: `<AmountInput negative={neg}
|
|
19
|
+
* onNegativeChange={setNeg} />` and `<SignChip negative={neg}
|
|
20
|
+
* onNegativeChange={setNeg} />`. */
|
|
21
|
+
negative: boolean;
|
|
22
|
+
/** A press: the other direction. */
|
|
23
|
+
onNegativeChange: (negative: boolean) => void;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/** Per-chip overrides of the `signChip` namespace. */
|
|
26
|
+
labels?: Partial<SignChipLabels>;
|
|
27
|
+
/** On the chip. Place it in its row from here — keksdose's form passes `self-end`
|
|
28
|
+
* to sit it on the display figure's baseline strip. */
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The SIGN of an amount, as one chip that shows only the state it is in — keksdose
|
|
33
|
+
* K15, its `DirectionToggle` (feedback #417, #430, #431, dev#434) made a kit piece,
|
|
34
|
+
* so the figure and its direction come from one package and wear one palette.
|
|
35
|
+
*
|
|
36
|
+
* WHY A CHIP, NOT A SEGMENTED CONTROL. A two-option toggle spends half its width
|
|
37
|
+
* telling you what the figure is NOT, and at a phone's width it was the widest thing on
|
|
38
|
+
* the form after the figure itself (#417). This sits beside the amount and reads as one
|
|
39
|
+
* phrase with it — "−42,50 Outflow" — and a press flips it. Hiding the alternative costs
|
|
40
|
+
* discoverability, so two things pay it back: the ± glyph and the swap arrows say "this
|
|
41
|
+
* is a control", not a badge; and the name and tooltip spell out both the current state
|
|
42
|
+
* and what a press will do.
|
|
43
|
+
*
|
|
44
|
+
* NOT A PRESSED/UNPRESSED TOGGLE. A {@link Chip} toggle has a FIXED label naming its
|
|
45
|
+
* on-state, with `aria-pressed` carrying the state. Here the word IS the state, so it
|
|
46
|
+
* is an action button with no `aria-pressed`: "Direction: Inflow, not pressed" would
|
|
47
|
+
* name no on-state for "not pressed" to be the absence of. The name changing under
|
|
48
|
+
* focus is the echo.
|
|
49
|
+
*
|
|
50
|
+
* THE COLOUR is the money pairing — the chip's `expense` / `income` tones, the same
|
|
51
|
+
* `--money-expense` / `--money-income` that {@link AmountInput}'s `tone` paints the
|
|
52
|
+
* figure with (dev#434): only the text and the border carry the tint, on a neutral
|
|
53
|
+
* surface, because a filled pill next to a tinted figure reads as one smear.
|
|
54
|
+
*
|
|
55
|
+
* SIZE. `lg` on a phone — the 44px touch target beside a display figure that has no
|
|
56
|
+
* field height to match — and from `md` up exactly a labelled field's 42px, so it never
|
|
57
|
+
* stands taller than the fields in its grid row (#431).
|
|
58
|
+
*
|
|
59
|
+
* An exported chip rather than an `AmountInput signControl` slot: the slot would have
|
|
60
|
+
* to own the row the two share, and every form lays that row out differently (beside
|
|
61
|
+
* the figure on a phone, in a grid column on a desktop). The chip is the part that was
|
|
62
|
+
* being re-written; the row stays the form's.
|
|
63
|
+
*/
|
|
64
|
+
declare function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }: SignChipProps): react.JSX.Element;
|
|
65
|
+
|
|
66
|
+
export { DEFAULT_SIGN_CHIP_LABELS, SignChip, type SignChipLabels, type SignChipProps };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ArrowUpDown, Minus, Plus } from "lucide-react";
|
|
4
|
+
import { Chip } from "./chip.js";
|
|
5
|
+
import { Tooltip } from "./tooltip.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
8
|
+
const DEFAULT_SIGN_CHIP_LABELS = {
|
|
9
|
+
outflow: "Outflow",
|
|
10
|
+
inflow: "Inflow",
|
|
11
|
+
direction: (current, next) => `Direction: ${current} \u2014 switch to ${next}`
|
|
12
|
+
};
|
|
13
|
+
function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }) {
|
|
14
|
+
const labels = useKitLabels("signChip", DEFAULT_SIGN_CHIP_LABELS, labelsProp);
|
|
15
|
+
const current = negative ? labels.outflow : labels.inflow;
|
|
16
|
+
const next = negative ? labels.inflow : labels.outflow;
|
|
17
|
+
const name = labels.direction(current, next);
|
|
18
|
+
return /* @__PURE__ */ jsx(Tooltip, { label: name, lazy: true, children: /* @__PURE__ */ jsxs(
|
|
19
|
+
Chip,
|
|
20
|
+
{
|
|
21
|
+
size: "lg",
|
|
22
|
+
tone: negative ? "expense" : "income",
|
|
23
|
+
icon: negative ? Minus : Plus,
|
|
24
|
+
onClick: () => onNegativeChange(!negative),
|
|
25
|
+
disabled,
|
|
26
|
+
"aria-label": name,
|
|
27
|
+
className: cn("shrink-0 gap-1.5 whitespace-nowrap px-3 font-medium md:min-h-0 md:py-2.5", className),
|
|
28
|
+
children: [
|
|
29
|
+
current,
|
|
30
|
+
/* @__PURE__ */ jsx(ArrowUpDown, { className: "ms-1.5 inline size-3.5 align-[-0.2em] opacity-60", "aria-hidden": true })
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
) });
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
DEFAULT_SIGN_CHIP_LABELS,
|
|
37
|
+
SignChip
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=sign-chip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/sign-chip.tsx"],"sourcesContent":["import { ArrowUpDown, Minus, Plus } from \"lucide-react\";\nimport { Chip } from \"./chip\";\nimport { Tooltip } from \"./tooltip\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\n/** The words a {@link SignChip} says — the `signChip` namespace of\n * `<UiKitProvider labels>`, and per chip through its `labels` prop. */\nexport interface SignChipLabels {\n /** The chip's text while the figure is negative — money going out. */\n outflow: string;\n /** The chip's text while the figure is positive — money coming in. */\n inflow: string;\n /** The chip's accessible name and its tooltip: the state it is in, and what a press\n * turns it into — `current` and `next` are the two words above. The other option is\n * nowhere on screen to be read, so the name has to say both. */\n direction: (current: string, next: string) => string;\n}\n\nexport const DEFAULT_SIGN_CHIP_LABELS: SignChipLabels = {\n outflow: \"Outflow\",\n inflow: \"Inflow\",\n direction: (current, next) => `Direction: ${current} — switch to ${next}`,\n};\n\nexport interface SignChipProps {\n /** The figure is negative (an outflow) — the same flag {@link AmountInput}'s\n * `negative` is, so one state feeds both: `<AmountInput negative={neg}\n * onNegativeChange={setNeg} />` and `<SignChip negative={neg}\n * onNegativeChange={setNeg} />`. */\n negative: boolean;\n /** A press: the other direction. */\n onNegativeChange: (negative: boolean) => void;\n disabled?: boolean;\n /** Per-chip overrides of the `signChip` namespace. */\n labels?: Partial<SignChipLabels>;\n /** On the chip. Place it in its row from here — keksdose's form passes `self-end`\n * to sit it on the display figure's baseline strip. */\n className?: string;\n}\n\n/**\n * The SIGN of an amount, as one chip that shows only the state it is in — keksdose\n * K15, its `DirectionToggle` (feedback #417, #430, #431, dev#434) made a kit piece,\n * so the figure and its direction come from one package and wear one palette.\n *\n * WHY A CHIP, NOT A SEGMENTED CONTROL. A two-option toggle spends half its width\n * telling you what the figure is NOT, and at a phone's width it was the widest thing on\n * the form after the figure itself (#417). This sits beside the amount and reads as one\n * phrase with it — \"−42,50 Outflow\" — and a press flips it. Hiding the alternative costs\n * discoverability, so two things pay it back: the ± glyph and the swap arrows say \"this\n * is a control\", not a badge; and the name and tooltip spell out both the current state\n * and what a press will do.\n *\n * NOT A PRESSED/UNPRESSED TOGGLE. A {@link Chip} toggle has a FIXED label naming its\n * on-state, with `aria-pressed` carrying the state. Here the word IS the state, so it\n * is an action button with no `aria-pressed`: \"Direction: Inflow, not pressed\" would\n * name no on-state for \"not pressed\" to be the absence of. The name changing under\n * focus is the echo.\n *\n * THE COLOUR is the money pairing — the chip's `expense` / `income` tones, the same\n * `--money-expense` / `--money-income` that {@link AmountInput}'s `tone` paints the\n * figure with (dev#434): only the text and the border carry the tint, on a neutral\n * surface, because a filled pill next to a tinted figure reads as one smear.\n *\n * SIZE. `lg` on a phone — the 44px touch target beside a display figure that has no\n * field height to match — and from `md` up exactly a labelled field's 42px, so it never\n * stands taller than the fields in its grid row (#431).\n *\n * An exported chip rather than an `AmountInput signControl` slot: the slot would have\n * to own the row the two share, and every form lays that row out differently (beside\n * the figure on a phone, in a grid column on a desktop). The chip is the part that was\n * being re-written; the row stays the form's.\n */\nexport function SignChip({ negative, onNegativeChange, disabled, labels: labelsProp, className }: SignChipProps) {\n const labels = useKitLabels(\"signChip\", DEFAULT_SIGN_CHIP_LABELS, labelsProp);\n const current = negative ? labels.outflow : labels.inflow;\n const next = negative ? labels.inflow : labels.outflow;\n const name = labels.direction(current, next);\n return (\n <Tooltip label={name} lazy>\n <Chip\n size=\"lg\"\n tone={negative ? \"expense\" : \"income\"}\n icon={negative ? Minus : Plus}\n onClick={() => onNegativeChange(!negative)}\n disabled={disabled}\n aria-label={name}\n // `gap-1.5 px-3`: the chip's narrower body, because on a 375px screen every\n // pixel of it is taken from the figure beside it (#430). `md:py-2.5` with the\n // hairline border is the 42px a labelled field measures (#431).\n className={cn(\"shrink-0 gap-1.5 whitespace-nowrap px-3 font-medium md:min-h-0 md:py-2.5\", className)}\n >\n {current}\n {/* The swap arrows after the word, in the label's own row: decoration (the\n name already says what a press does), set a little apart from the word. */}\n <ArrowUpDown className=\"ms-1.5 inline size-3.5 align-[-0.2em] opacity-60\" aria-hidden />\n </Chip>\n </Tooltip>\n );\n}\n"],"mappings":";AAmFM,SAeE,KAfF;AAnFN,SAAS,aAAa,OAAO,YAAY;AACzC,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAiBtB,MAAM,2BAA2C;AAAA,EACtD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW,CAAC,SAAS,SAAS,cAAc,OAAO,qBAAgB,IAAI;AACzE;AAmDO,SAAS,SAAS,EAAE,UAAU,kBAAkB,UAAU,QAAQ,YAAY,UAAU,GAAkB;AAC/G,QAAM,SAAS,aAAa,YAAY,0BAA0B,UAAU;AAC5E,QAAM,UAAU,WAAW,OAAO,UAAU,OAAO;AACnD,QAAM,OAAO,WAAW,OAAO,SAAS,OAAO;AAC/C,QAAM,OAAO,OAAO,UAAU,SAAS,IAAI;AAC3C,SACE,oBAAC,WAAQ,OAAO,MAAM,MAAI,MACxB;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAM,WAAW,YAAY;AAAA,MAC7B,MAAM,WAAW,QAAQ;AAAA,MACzB,SAAS,MAAM,iBAAiB,CAAC,QAAQ;AAAA,MACzC;AAAA,MACA,cAAY;AAAA,MAIZ,WAAW,GAAG,4EAA4E,SAAS;AAAA,MAElG;AAAA;AAAA,QAGD,oBAAC,eAAY,WAAU,oDAAmD,eAAW,MAAC;AAAA;AAAA;AAAA,EACxF,GACF;AAEJ;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
3
|
import { TileSize } from './tile-radio.js';
|
|
4
4
|
|
|
5
5
|
interface SwatchPickerLabels {
|
|
@@ -62,6 +62,31 @@ interface SwatchPickerProps<T extends string> extends Omit<ComponentPropsWithout
|
|
|
62
62
|
tileClassName?: string;
|
|
63
63
|
disabled?: boolean;
|
|
64
64
|
labels?: Partial<SwatchPickerLabels>;
|
|
65
|
+
/**
|
|
66
|
+
* An 11px static label in a strip over the swatches, named by it (keksdose K2): the
|
|
67
|
+
* transaction row's flag picker stands between labelled fields and hand-builds this
|
|
68
|
+
* (`FieldLabel` + `pt-5`, transaction-flags.tsx:155) because the picker had no label.
|
|
69
|
+
* The label sits on the same line as a field's own (`top-1`, `inset-x-3`); the
|
|
70
|
+
* swatches start 20px down, below its whole line box — see `LABEL_STRIP_PAD`. With a
|
|
71
|
+
* label, `className` styles the wrapper, as on a labelled {@link ToggleGroup}; an
|
|
72
|
+
* explicit `aria-label` / `aria-labelledby` still names the group instead.
|
|
73
|
+
*/
|
|
74
|
+
label?: ReactNode;
|
|
75
|
+
/** A {@link FieldHint} beside the label. Only with `label`. */
|
|
76
|
+
hint?: ReactNode;
|
|
77
|
+
/**
|
|
78
|
+
* Why the colour cannot be changed right now — {@link Button}'s `disabledReason`, for
|
|
79
|
+
* a picker that saves itself on change (keksdose K3, the flag picker in a row a
|
|
80
|
+
* read-only viewer may see). The swatches stay reachable and the arrow keys still
|
|
81
|
+
* walk them, but nothing reaches `onChange`; the reason is shown in the kit
|
|
82
|
+
* {@link Tooltip} under the row (each swatch's own name bubble sits above it) and
|
|
83
|
+
* describes the group. Wins over `disabled`.
|
|
84
|
+
*/
|
|
85
|
+
disabledReason?: ReactNode;
|
|
86
|
+
/** This picker COMMITS — choosing a colour saves it. Under a locked
|
|
87
|
+
* {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
|
|
88
|
+
* reason, as {@link Button}'s `commit` does. */
|
|
89
|
+
commit?: boolean;
|
|
65
90
|
}
|
|
66
91
|
/**
|
|
67
92
|
* A row of colour swatches that is one radio group.
|
|
@@ -75,6 +100,6 @@ interface SwatchPickerProps<T extends string> extends Omit<ComponentPropsWithout
|
|
|
75
100
|
* choose as they go; Home and End jump to the ends. Left and right follow the page's
|
|
76
101
|
* direction.
|
|
77
102
|
*/
|
|
78
|
-
declare function SwatchPicker<T extends string>({ options, value, onChange, allowNone, mixed, activation, size, tileClassName, disabled, labels, className, ...rest }: SwatchPickerProps<T>): react.JSX.Element;
|
|
103
|
+
declare function SwatchPicker<T extends string>({ options, value, onChange, allowNone, mixed, activation, size, tileClassName, disabled, labels, label, hint, disabledReason: ownDisabledReason, commit, className, ...rest }: SwatchPickerProps<T>): react.JSX.Element;
|
|
79
104
|
|
|
80
105
|
export { DEFAULT_SWATCH_PICKER_LABELS, type SwatchOption, SwatchPicker, type SwatchPickerLabels, type SwatchPickerProps };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId, useMemo } from "react";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
5
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
6
6
|
import { TILE_SIZE, TileRadioGroup } from "./tile-radio.js";
|
|
7
|
+
import { Tooltip } from "./tooltip.js";
|
|
8
|
+
import { useCommitReason } from "./write-lock.js";
|
|
9
|
+
import { hasContent, LabelStrip } from "./field-anatomy.js";
|
|
7
10
|
const DEFAULT_SWATCH_PICKER_LABELS = {
|
|
8
11
|
none: "No colour",
|
|
9
12
|
mixed: "Mixed: the selected items have different colours"
|
|
@@ -19,56 +22,95 @@ function SwatchPicker({
|
|
|
19
22
|
tileClassName,
|
|
20
23
|
disabled = false,
|
|
21
24
|
labels,
|
|
25
|
+
label,
|
|
26
|
+
hint,
|
|
27
|
+
disabledReason: ownDisabledReason,
|
|
28
|
+
commit,
|
|
22
29
|
className,
|
|
23
30
|
...rest
|
|
24
31
|
}) {
|
|
25
32
|
const text = useKitLabels("swatchPicker", DEFAULT_SWATCH_PICKER_LABELS, labels);
|
|
26
33
|
const mixedId = useId();
|
|
34
|
+
const labelId = useId();
|
|
35
|
+
const reasonId = useId();
|
|
36
|
+
const disabledReason = useCommitReason(commit, ownDisabledReason);
|
|
37
|
+
const locked = hasContent(disabledReason);
|
|
38
|
+
const labelled = hasContent(label);
|
|
27
39
|
const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);
|
|
28
40
|
const items = [
|
|
29
41
|
...allowNone ? [{ key: null, label: text.none }] : [],
|
|
30
42
|
...options.map((o) => ({ key: o.value, label: o.label, note: o.note, disabled: o.disabled }))
|
|
31
43
|
];
|
|
32
|
-
|
|
44
|
+
const tiles = /* @__PURE__ */ jsx(
|
|
45
|
+
TileRadioGroup,
|
|
46
|
+
{
|
|
47
|
+
items,
|
|
48
|
+
checked: mixed ? void 0 : value,
|
|
49
|
+
onSelect: (next) => {
|
|
50
|
+
if (!locked) onChange(next);
|
|
51
|
+
},
|
|
52
|
+
activation,
|
|
53
|
+
disabled: disabled && !locked,
|
|
54
|
+
size,
|
|
55
|
+
tileClassName: tileClassName || locked ? (_item, selected) => cn(
|
|
56
|
+
// The disabled tile's look, without its `disabled`: dimmed, a
|
|
57
|
+
// not-allowed cursor, and no hover offer on a tile that cannot be
|
|
58
|
+
// chosen. Not on the selected tile, whose frame IS its state.
|
|
59
|
+
locked && "cursor-not-allowed opacity-50",
|
|
60
|
+
locked && !selected && "hover:border-[var(--border)] hover:bg-[var(--bg-surface)]",
|
|
61
|
+
tileClassName
|
|
62
|
+
) : void 0,
|
|
63
|
+
renderTile: (item) => {
|
|
64
|
+
const opt = item.key === null ? void 0 : byValue.get(item.key);
|
|
65
|
+
return /* @__PURE__ */ jsx(
|
|
66
|
+
"span",
|
|
67
|
+
{
|
|
68
|
+
"aria-hidden": true,
|
|
69
|
+
className: cn(
|
|
70
|
+
"rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15",
|
|
71
|
+
TILE_SIZE[size].swatch,
|
|
72
|
+
opt?.swatchClassName
|
|
73
|
+
),
|
|
74
|
+
style: { background: opt?.color }
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
const group = /* @__PURE__ */ jsxs(
|
|
33
81
|
"div",
|
|
34
82
|
{
|
|
35
83
|
...rest,
|
|
36
84
|
role: "radiogroup",
|
|
37
|
-
"aria-
|
|
38
|
-
"aria-
|
|
39
|
-
|
|
85
|
+
"aria-labelledby": labelled && rest["aria-label"] === void 0 && rest["aria-labelledby"] === void 0 ? labelId : rest["aria-labelledby"],
|
|
86
|
+
"aria-disabled": disabled || locked || void 0,
|
|
87
|
+
"aria-describedby": [rest["aria-describedby"], mixed && mixedId, locked && reasonId].filter(Boolean).join(" ") || void 0,
|
|
88
|
+
className: cn("flex flex-wrap items-center gap-1.5", !labelled && className),
|
|
40
89
|
children: [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"span",
|
|
55
|
-
|
|
56
|
-
"aria-hidden": true,
|
|
57
|
-
className: cn(
|
|
58
|
-
"rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15",
|
|
59
|
-
TILE_SIZE[size].swatch,
|
|
60
|
-
opt?.swatchClassName
|
|
61
|
-
),
|
|
62
|
-
style: { background: opt?.color }
|
|
63
|
-
}
|
|
64
|
-
);
|
|
90
|
+
locked ? (
|
|
91
|
+
// Inside the group, round the tiles — not round the group, whose box is the
|
|
92
|
+
// caller's row item. This row inherits the group's gap, wrap and alignment, so
|
|
93
|
+
// the tiles sit exactly where they sit unlocked. Below the row: each tile's own
|
|
94
|
+
// bubble (its colour's name) is above it.
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
Tooltip,
|
|
97
|
+
{
|
|
98
|
+
label: disabledReason,
|
|
99
|
+
side: "bottom",
|
|
100
|
+
className: "flex min-w-0 flex-1 gap-[inherit] [align-items:inherit] [flex-wrap:inherit] [justify-content:inherit]",
|
|
101
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
+
tiles,
|
|
103
|
+
/* @__PURE__ */ jsx("span", { id: reasonId, hidden: true, children: disabledReason })
|
|
104
|
+
] })
|
|
65
105
|
}
|
|
66
|
-
|
|
67
|
-
),
|
|
106
|
+
)
|
|
107
|
+
) : tiles,
|
|
68
108
|
mixed && /* @__PURE__ */ jsx("span", { id: mixedId, hidden: true, children: text.mixed })
|
|
69
109
|
]
|
|
70
110
|
}
|
|
71
111
|
);
|
|
112
|
+
if (!labelled) return group;
|
|
113
|
+
return /* @__PURE__ */ jsx(LabelStrip, { labelId, label, hint, disabled: disabled || locked, pad: "tiles", className, children: group });
|
|
72
114
|
}
|
|
73
115
|
export {
|
|
74
116
|
DEFAULT_SWATCH_PICKER_LABELS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/swatch-picker.tsx"],"sourcesContent":["import { useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { TILE_SIZE, TileRadioGroup } from \"./tile-radio\";\nimport type { TileItem, TileSize } from \"./tile-radio\";\n\nexport interface SwatchPickerLabels {\n /** The \"no colour\" tile's name, when `allowNone` is set. */\n none: string;\n /** Describes the group while `mixed` is set — a bulk edit over rows that disagree. */\n mixed: string;\n}\n\nexport const DEFAULT_SWATCH_PICKER_LABELS: SwatchPickerLabels = {\n none: \"No colour\",\n mixed: \"Mixed: the selected items have different colours\",\n};\n\nexport interface SwatchOption<T extends string> {\n value: T;\n /** Any CSS colour — a token (`var(--chart-3)`), a hex, an `oklch()`. Painted as an\n * inline background, so it does not have to be a class the kit's CSS knows.\n * Leave it out when `swatchClassName` paints the dot instead. For a colour that\n * must match a `StatusDot` / `Chip` hue elsewhere, `statusDotColor(\"teal\")` rather\n * than `\"var(--hue-teal)\"` written out — one table, so the two cannot drift. */\n color?: string;\n /** The colour's name, in the user's language. Shown in the bubble and read out;\n * required, because a swatch with no name is a colour only some people can read. */\n label: string;\n /** Classes for the swatch dot, for a palette that lives in classes rather than\n * values (a light/dark pair). An inline `color` would win over a class, so pass\n * one or the other. */\n swatchClassName?: string;\n /** A remark read after the name and shown under it in the bubble (\"used by\n * Groceries\"). The tile wears a dot while it has one. */\n note?: string;\n disabled?: boolean;\n}\n\n/**\n * `onChange` is the picker's own — the chosen VALUE, not a DOM event — so the div's\n * is omitted rather than shadowed, as on {@link ToggleGroup}.\n */\nexport interface SwatchPickerProps<T extends string>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\" | \"children\" | \"defaultValue\"> {\n options: SwatchOption<T>[];\n /** The chosen colour, or `null` for none. */\n value: T | null;\n onChange: (value: T | null) => void;\n /** Lead with a \"no colour\" tile whose value is `null`. Clearing a colour has to be\n * as reachable as setting one, and a radio cannot be unchecked by clicking it. */\n allowNone?: boolean;\n /**\n * \"Some of each\" — a bulk edit over rows whose colours differ. Nothing is checked\n * (not even \"none\", whatever `value` says), and the group is described as mixed.\n * Distinct from `value={null}`, which is an answer: \"no colour\".\n */\n mixed?: boolean;\n /** See {@link IconPickerProps.activation}. Default \"automatic\". */\n activation?: \"automatic\" | \"manual\";\n /** 28 / 32 / 44px tiles. Default \"md\"; \"lg\" is the touch-target size. */\n size?: TileSize;\n /**\n * Classes for every tile's frame, merged after the kit's own — so a caller's\n * `size-*` beats `size`'s. For a tile that has to follow a container query or a\n * breakpoint (keksdose grows its tiles to 40px in a narrow card:\n * `tileClassName=\"@max-md:size-10\"`) without reaching into the kit's markup with an\n * `[&_[role=radio]]` selector. The swatch dot / glyph inside keeps `size`'s\n * dimensions; the tile grows round it.\n */\n tileClassName?: string;\n disabled?: boolean;\n labels?: Partial<SwatchPickerLabels>;\n}\n\n/**\n * A row of colour swatches that is one radio group.\n *\n * Name the group with `aria-label` or `aria-labelledby` — usually the visible heading\n * above it. Each swatch is named by its `label` and shows it in a bubble on hover and\n * focus, so the name is not the screen reader's alone.\n *\n * Keyboard (the APG radio group): Tab reaches the checked swatch, or the first one\n * while nothing is checked; the arrow keys move between swatches and, by default,\n * choose as they go; Home and End jump to the ends. Left and right follow the page's\n * direction.\n */\nexport function SwatchPicker<T extends string>({\n options,\n value,\n onChange,\n allowNone = false,\n mixed = false,\n activation = \"automatic\",\n size = \"md\",\n tileClassName,\n disabled = false,\n labels,\n className,\n ...rest\n}: SwatchPickerProps<T>) {\n const text = useKitLabels(\"swatchPicker\", DEFAULT_SWATCH_PICKER_LABELS, labels);\n const mixedId = useId();\n const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);\n const items: TileItem<T>[] = [\n ...(allowNone ? [{ key: null, label: text.none }] : []),\n ...options.map((o) => ({ key: o.value, label: o.label, note: o.note, disabled: o.disabled })),\n ];\n return (\n <div\n {...rest}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n aria-describedby={\n [rest[\"aria-describedby\"], mixed && mixedId].filter(Boolean).join(\" \") || undefined\n }\n className={cn(\"flex flex-wrap items-center gap-1.5\", className)}\n >\n <TileRadioGroup\n items={items}\n checked={mixed ? undefined : value}\n onSelect={onChange}\n activation={activation}\n disabled={disabled}\n size={size}\n tileClassName={tileClassName ? () => tileClassName : undefined}\n renderTile={(item) => {\n const opt = item.key === null ? undefined : byValue.get(item.key);\n return (\n <span\n aria-hidden\n // A hairline round the dot, drawn over whatever colour it is: a pale\n // yellow swatch on a white surface is otherwise a hole in the row.\n className={cn(\n \"rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15\",\n TILE_SIZE[size].swatch,\n opt?.swatchClassName,\n )}\n style={{ background: opt?.color }}\n />\n );\n }}\n />\n {/* `hidden`, not `sr-only`: a description is read from hidden text, and this\n way it takes no room in the row. */}\n {mixed && (\n <span id={mixedId} hidden>\n {text.mixed}\n </span>\n )}\n </div>\n );\n}\n"],"mappings":";AA8GI,SAoBQ,KApBR;AA9GJ,SAAS,OAAO,eAAe;AAE/B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,WAAW,sBAAsB;AAUnC,MAAM,+BAAmD;AAAA,EAC9D,MAAM;AAAA,EACN,OAAO;AACT;AAuEO,SAAS,aAA+B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,OAAO,aAAa,gBAAgB,8BAA8B,MAAM;AAC9E,QAAM,UAAU,MAAM;AACtB,QAAM,UAAU,QAAQ,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;AAClF,QAAM,QAAuB;AAAA,IAC3B,GAAI,YAAY,CAAC,EAAE,KAAK,MAAM,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC;AAAA,IACrD,GAAG,QAAQ,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EAC9F;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,iBAAe,YAAY;AAAA,MAC3B,oBACE,CAAC,KAAK,kBAAkB,GAAG,SAAS,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK;AAAA,MAE5E,WAAW,GAAG,uCAAuC,SAAS;AAAA,MAE9D;AAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,SAAS,QAAQ,SAAY;AAAA,YAC7B,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA,YACA,eAAe,gBAAgB,MAAM,gBAAgB;AAAA,YACrD,YAAY,CAAC,SAAS;AACpB,oBAAM,MAAM,KAAK,QAAQ,OAAO,SAAY,QAAQ,IAAI,KAAK,GAAG;AAChE,qBACE;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAW;AAAA,kBAGX,WAAW;AAAA,oBACT;AAAA,oBACA,UAAU,IAAI,EAAE;AAAA,oBAChB,KAAK;AAAA,kBACP;AAAA,kBACA,OAAO,EAAE,YAAY,KAAK,MAAM;AAAA;AAAA,cAClC;AAAA,YAEJ;AAAA;AAAA,QACF;AAAA,QAGC,SACC,oBAAC,UAAK,IAAI,SAAS,QAAM,MACtB,eAAK,OACR;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/swatch-picker.tsx"],"sourcesContent":["import { useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { TILE_SIZE, TileRadioGroup } from \"./tile-radio\";\nimport type { TileItem, TileSize } from \"./tile-radio\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason } from \"./write-lock\";\nimport { hasContent, LabelStrip } from \"./field-anatomy\";\n\nexport interface SwatchPickerLabels {\n /** The \"no colour\" tile's name, when `allowNone` is set. */\n none: string;\n /** Describes the group while `mixed` is set — a bulk edit over rows that disagree. */\n mixed: string;\n}\n\nexport const DEFAULT_SWATCH_PICKER_LABELS: SwatchPickerLabels = {\n none: \"No colour\",\n mixed: \"Mixed: the selected items have different colours\",\n};\n\nexport interface SwatchOption<T extends string> {\n value: T;\n /** Any CSS colour — a token (`var(--chart-3)`), a hex, an `oklch()`. Painted as an\n * inline background, so it does not have to be a class the kit's CSS knows.\n * Leave it out when `swatchClassName` paints the dot instead. For a colour that\n * must match a `StatusDot` / `Chip` hue elsewhere, `statusDotColor(\"teal\")` rather\n * than `\"var(--hue-teal)\"` written out — one table, so the two cannot drift. */\n color?: string;\n /** The colour's name, in the user's language. Shown in the bubble and read out;\n * required, because a swatch with no name is a colour only some people can read. */\n label: string;\n /** Classes for the swatch dot, for a palette that lives in classes rather than\n * values (a light/dark pair). An inline `color` would win over a class, so pass\n * one or the other. */\n swatchClassName?: string;\n /** A remark read after the name and shown under it in the bubble (\"used by\n * Groceries\"). The tile wears a dot while it has one. */\n note?: string;\n disabled?: boolean;\n}\n\n/**\n * `onChange` is the picker's own — the chosen VALUE, not a DOM event — so the div's\n * is omitted rather than shadowed, as on {@link ToggleGroup}.\n */\nexport interface SwatchPickerProps<T extends string>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\" | \"children\" | \"defaultValue\"> {\n options: SwatchOption<T>[];\n /** The chosen colour, or `null` for none. */\n value: T | null;\n onChange: (value: T | null) => void;\n /** Lead with a \"no colour\" tile whose value is `null`. Clearing a colour has to be\n * as reachable as setting one, and a radio cannot be unchecked by clicking it. */\n allowNone?: boolean;\n /**\n * \"Some of each\" — a bulk edit over rows whose colours differ. Nothing is checked\n * (not even \"none\", whatever `value` says), and the group is described as mixed.\n * Distinct from `value={null}`, which is an answer: \"no colour\".\n */\n mixed?: boolean;\n /** See {@link IconPickerProps.activation}. Default \"automatic\". */\n activation?: \"automatic\" | \"manual\";\n /** 28 / 32 / 44px tiles. Default \"md\"; \"lg\" is the touch-target size. */\n size?: TileSize;\n /**\n * Classes for every tile's frame, merged after the kit's own — so a caller's\n * `size-*` beats `size`'s. For a tile that has to follow a container query or a\n * breakpoint (keksdose grows its tiles to 40px in a narrow card:\n * `tileClassName=\"@max-md:size-10\"`) without reaching into the kit's markup with an\n * `[&_[role=radio]]` selector. The swatch dot / glyph inside keeps `size`'s\n * dimensions; the tile grows round it.\n */\n tileClassName?: string;\n disabled?: boolean;\n labels?: Partial<SwatchPickerLabels>;\n /**\n * An 11px static label in a strip over the swatches, named by it (keksdose K2): the\n * transaction row's flag picker stands between labelled fields and hand-builds this\n * (`FieldLabel` + `pt-5`, transaction-flags.tsx:155) because the picker had no label.\n * The label sits on the same line as a field's own (`top-1`, `inset-x-3`); the\n * swatches start 20px down, below its whole line box — see `LABEL_STRIP_PAD`. With a\n * label, `className` styles the wrapper, as on a labelled {@link ToggleGroup}; an\n * explicit `aria-label` / `aria-labelledby` still names the group instead.\n */\n label?: ReactNode;\n /** A {@link FieldHint} beside the label. Only with `label`. */\n hint?: ReactNode;\n /**\n * Why the colour cannot be changed right now — {@link Button}'s `disabledReason`, for\n * a picker that saves itself on change (keksdose K3, the flag picker in a row a\n * read-only viewer may see). The swatches stay reachable and the arrow keys still\n * walk them, but nothing reaches `onChange`; the reason is shown in the kit\n * {@link Tooltip} under the row (each swatch's own name bubble sits above it) and\n * describes the group. Wins over `disabled`.\n */\n disabledReason?: ReactNode;\n /** This picker COMMITS — choosing a colour saves it. Under a locked\n * {@link WriteLockProvider} it takes the `disabledReason` path with the lock's\n * reason, as {@link Button}'s `commit` does. */\n commit?: boolean;\n}\n\n/**\n * A row of colour swatches that is one radio group.\n *\n * Name the group with `aria-label` or `aria-labelledby` — usually the visible heading\n * above it. Each swatch is named by its `label` and shows it in a bubble on hover and\n * focus, so the name is not the screen reader's alone.\n *\n * Keyboard (the APG radio group): Tab reaches the checked swatch, or the first one\n * while nothing is checked; the arrow keys move between swatches and, by default,\n * choose as they go; Home and End jump to the ends. Left and right follow the page's\n * direction.\n */\nexport function SwatchPicker<T extends string>({\n options,\n value,\n onChange,\n allowNone = false,\n mixed = false,\n activation = \"automatic\",\n size = \"md\",\n tileClassName,\n disabled = false,\n labels,\n label,\n hint,\n disabledReason: ownDisabledReason,\n commit,\n className,\n ...rest\n}: SwatchPickerProps<T>) {\n const text = useKitLabels(\"swatchPicker\", DEFAULT_SWATCH_PICKER_LABELS, labels);\n const mixedId = useId();\n const labelId = useId();\n const reasonId = useId();\n const disabledReason = useCommitReason(commit, ownDisabledReason);\n const locked = hasContent(disabledReason);\n const labelled = hasContent(label);\n const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);\n const items: TileItem<T>[] = [\n ...(allowNone ? [{ key: null, label: text.none }] : []),\n ...options.map((o) => ({ key: o.value, label: o.label, note: o.note, disabled: o.disabled })),\n ];\n const tiles = (\n <TileRadioGroup\n items={items}\n checked={mixed ? undefined : value}\n // A locked picker saves on change, so the change is swallowed — the arrow keys\n // still move the focus (and, automatic, would choose), and nothing is chosen.\n onSelect={(next) => {\n if (!locked) onChange(next);\n }}\n activation={activation}\n // A reason wins over `disabled`: the tiles stay in the tab order.\n disabled={disabled && !locked}\n size={size}\n tileClassName={\n tileClassName || locked\n ? (_item, selected) =>\n cn(\n // The disabled tile's look, without its `disabled`: dimmed, a\n // not-allowed cursor, and no hover offer on a tile that cannot be\n // chosen. Not on the selected tile, whose frame IS its state.\n locked && \"cursor-not-allowed opacity-50\",\n locked && !selected && \"hover:border-[var(--border)] hover:bg-[var(--bg-surface)]\",\n tileClassName,\n )\n : undefined\n }\n renderTile={(item) => {\n const opt = item.key === null ? undefined : byValue.get(item.key);\n return (\n <span\n aria-hidden\n // A hairline round the dot, drawn over whatever colour it is: a pale\n // yellow swatch on a white surface is otherwise a hole in the row.\n className={cn(\n \"rounded-full ring-1 ring-inset ring-black/10 dark:ring-white/15\",\n TILE_SIZE[size].swatch,\n opt?.swatchClassName,\n )}\n style={{ background: opt?.color }}\n />\n );\n }}\n />\n );\n const group = (\n <div\n {...rest}\n role=\"radiogroup\"\n aria-labelledby={\n labelled && rest[\"aria-label\"] === undefined && rest[\"aria-labelledby\"] === undefined\n ? labelId\n : rest[\"aria-labelledby\"]\n }\n aria-disabled={disabled || locked || undefined}\n aria-describedby={\n [rest[\"aria-describedby\"], mixed && mixedId, locked && reasonId].filter(Boolean).join(\" \") ||\n undefined\n }\n className={cn(\"flex flex-wrap items-center gap-1.5\", !labelled && className)}\n >\n {locked ? (\n // Inside the group, round the tiles — not round the group, whose box is the\n // caller's row item. This row inherits the group's gap, wrap and alignment, so\n // the tiles sit exactly where they sit unlocked. Below the row: each tile's own\n // bubble (its colour's name) is above it.\n <Tooltip\n label={disabledReason}\n side=\"bottom\"\n className=\"flex min-w-0 flex-1 gap-[inherit] [align-items:inherit] [flex-wrap:inherit] [justify-content:inherit]\"\n >\n <>\n {tiles}\n <span id={reasonId} hidden>\n {disabledReason}\n </span>\n </>\n </Tooltip>\n ) : (\n tiles\n )}\n {/* `hidden`, not `sr-only`: a description is read from hidden text, and this\n way it takes no room in the row. */}\n {mixed && (\n <span id={mixedId} hidden>\n {text.mixed}\n </span>\n )}\n </div>\n );\n if (!labelled) return group;\n return (\n <LabelStrip labelId={labelId} label={label} hint={hint} disabled={disabled || locked} pad=\"tiles\" className={className}>\n {group}\n </LabelStrip>\n );\n}\n"],"mappings":";AA+KU,SAyCA,UAzCA,KAyCA,YAzCA;AA/KV,SAAS,OAAO,eAAe;AAE/B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,WAAW,sBAAsB;AAE1C,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,YAAY,kBAAkB;AAShC,MAAM,+BAAmD;AAAA,EAC9D,MAAM;AAAA,EACN,OAAO;AACT;AAgGO,SAAS,aAA+B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,OAAO,aAAa,gBAAgB,8BAA8B,MAAM;AAC9E,QAAM,UAAU,MAAM;AACtB,QAAM,UAAU,MAAM;AACtB,QAAM,WAAW,MAAM;AACvB,QAAM,iBAAiB,gBAAgB,QAAQ,iBAAiB;AAChE,QAAM,SAAS,WAAW,cAAc;AACxC,QAAM,WAAW,WAAW,KAAK;AACjC,QAAM,UAAU,QAAQ,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;AAClF,QAAM,QAAuB;AAAA,IAC3B,GAAI,YAAY,CAAC,EAAE,KAAK,MAAM,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC;AAAA,IACrD,GAAG,QAAQ,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EAC9F;AACA,QAAM,QACJ;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,SAAS,QAAQ,SAAY;AAAA,MAG7B,UAAU,CAAC,SAAS;AAClB,YAAI,CAAC,OAAQ,UAAS,IAAI;AAAA,MAC5B;AAAA,MACA;AAAA,MAEA,UAAU,YAAY,CAAC;AAAA,MACvB;AAAA,MACA,eACE,iBAAiB,SACb,CAAC,OAAO,aACN;AAAA;AAAA;AAAA;AAAA,QAIE,UAAU;AAAA,QACV,UAAU,CAAC,YAAY;AAAA,QACvB;AAAA,MACF,IACF;AAAA,MAEN,YAAY,CAAC,SAAS;AACpB,cAAM,MAAM,KAAK,QAAQ,OAAO,SAAY,QAAQ,IAAI,KAAK,GAAG;AAChE,eACE;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YAGX,WAAW;AAAA,cACT;AAAA,cACA,UAAU,IAAI,EAAE;AAAA,cAChB,KAAK;AAAA,YACP;AAAA,YACA,OAAO,EAAE,YAAY,KAAK,MAAM;AAAA;AAAA,QAClC;AAAA,MAEJ;AAAA;AAAA,EACF;AAEF,QAAM,QACJ;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,mBACE,YAAY,KAAK,YAAY,MAAM,UAAa,KAAK,iBAAiB,MAAM,SACxE,UACA,KAAK,iBAAiB;AAAA,MAE5B,iBAAe,YAAY,UAAU;AAAA,MACrC,oBACE,CAAC,KAAK,kBAAkB,GAAG,SAAS,SAAS,UAAU,QAAQ,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KACzF;AAAA,MAEF,WAAW,GAAG,uCAAuC,CAAC,YAAY,SAAS;AAAA,MAE1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAKC;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,MAAK;AAAA,cACL,WAAU;AAAA,cAEV,2CACG;AAAA;AAAA,gBACD,oBAAC,UAAK,IAAI,UAAU,QAAM,MACvB,0BACH;AAAA,iBACF;AAAA;AAAA,UACF;AAAA,YAEA;AAAA,QAID,SACC,oBAAC,UAAK,IAAI,SAAS,QAAM,MACtB,eAAK,OACR;AAAA;AAAA;AAAA,EAEJ;AAEF,MAAI,CAAC,SAAU,QAAO;AACtB,SACE,oBAAC,cAAW,SAAkB,OAAc,MAAY,UAAU,YAAY,QAAQ,KAAI,SAAQ,WAC/F,iBACH;AAEJ;","names":[]}
|
|
@@ -56,6 +56,25 @@ interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"
|
|
|
56
56
|
* label WITHOUT a literal "*". No mark without a label.
|
|
57
57
|
*/
|
|
58
58
|
required?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Why the switch cannot be flipped — {@link Button}'s `disabledReason`. A switch is
|
|
61
|
+
* the control that "takes effect the moment it is flipped", so it is the first one
|
|
62
|
+
* keksdose K3 names: its settings rows sat behind a `SaveGuard` that forced a native
|
|
63
|
+
* `disabled`, out of the tab order, so the reason never reached a keyboard.
|
|
64
|
+
*
|
|
65
|
+
* With a reason the switch is `aria-disabled` instead: still focusable, a click or
|
|
66
|
+
* Space flips nothing and fires no `onChange` / `onCheckedChange`, the reason is in
|
|
67
|
+
* the kit {@link Tooltip} on the track and on its `aria-describedby`, and the row
|
|
68
|
+
* fades as a disabled one does. It wins over `disabled`. Changing the lock mounts the
|
|
69
|
+
* switch anew (it moves in or out of the Tooltip), as a locked Button is.
|
|
70
|
+
*/
|
|
71
|
+
disabledReason?: ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* This switch COMMITS — flipping it saves. Under a locked {@link WriteLockProvider} it
|
|
74
|
+
* is locked the `disabledReason` way with the lock's reason (which wins over its
|
|
75
|
+
* own). No provider, or an unlocked one: no effect. Button's `commit`, for K3.
|
|
76
|
+
*/
|
|
77
|
+
commit?: boolean;
|
|
59
78
|
}
|
|
60
79
|
/**
|
|
61
80
|
* A native checkbox drawn as a track and thumb, announced as a switch.
|
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { forwardRef, useId } from "react";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
5
|
import { hasMessage, mergeDescribedBy } from "./choice-parts.js";
|
|
6
|
+
import { LockedReason, useLockReason } from "./field-parts.js";
|
|
6
7
|
const SIZES = {
|
|
7
8
|
// 28×16 track, 12px thumb: the dense setting rows in Kastlan's tables.
|
|
8
9
|
sm: { track: "h-4 w-7", thumb: "size-3 peer-checked:start-[14px]" },
|
|
@@ -23,35 +24,52 @@ const Switch = forwardRef(function Switch2({
|
|
|
23
24
|
id,
|
|
24
25
|
disabled,
|
|
25
26
|
required,
|
|
27
|
+
disabledReason,
|
|
28
|
+
commit,
|
|
29
|
+
onClick,
|
|
26
30
|
...rest
|
|
27
31
|
}, ref) {
|
|
28
32
|
const generated = useId();
|
|
29
33
|
const inputId = id ?? generated;
|
|
34
|
+
const lock = useLockReason(commit, disabledReason);
|
|
35
|
+
const locked = lock.locked;
|
|
36
|
+
const looksDisabled = Boolean(disabled) || locked;
|
|
30
37
|
const descriptionId = `${inputId}-description`;
|
|
31
38
|
const showDescription = hasMessage(description);
|
|
32
39
|
const bare = label === void 0 && !showDescription;
|
|
33
40
|
const geometry = SIZES[size];
|
|
34
|
-
const
|
|
41
|
+
const track = /* @__PURE__ */ jsxs("span", { className: cn("relative inline-flex shrink-0", bare && !locked && className), children: [
|
|
35
42
|
/* @__PURE__ */ jsx(
|
|
36
43
|
"input",
|
|
37
44
|
{
|
|
38
45
|
ref,
|
|
39
46
|
id: bare ? id : inputId,
|
|
40
|
-
disabled,
|
|
47
|
+
disabled: locked ? void 0 : disabled,
|
|
41
48
|
required,
|
|
42
49
|
...rest,
|
|
43
50
|
type: "checkbox",
|
|
44
51
|
role: "switch",
|
|
45
|
-
"aria-
|
|
52
|
+
"aria-disabled": locked || rest["aria-disabled"] || void 0,
|
|
53
|
+
"aria-describedby": mergeDescribedBy(
|
|
54
|
+
rest["aria-describedby"],
|
|
55
|
+
showDescription && descriptionId,
|
|
56
|
+
locked && lock.reasonId
|
|
57
|
+
),
|
|
58
|
+
onClick: locked ? void 0 : onClick,
|
|
46
59
|
onChange: (e) => {
|
|
60
|
+
if (locked) {
|
|
61
|
+
e.currentTarget.checked = !e.currentTarget.checked;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
47
64
|
onChange?.(e);
|
|
48
65
|
onCheckedChange?.(e.target.checked);
|
|
49
66
|
},
|
|
50
|
-
className: cn(TRACK, geometry.track, inputClassName)
|
|
67
|
+
className: cn(TRACK, geometry.track, locked && "cursor-not-allowed", inputClassName)
|
|
51
68
|
}
|
|
52
69
|
),
|
|
53
70
|
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(THUMB, geometry.thumb) })
|
|
54
71
|
] });
|
|
72
|
+
const control = locked ? /* @__PURE__ */ jsx(LockedReason, { lock, className: cn("shrink-0", bare && className), children: track }) : track;
|
|
55
73
|
if (bare) return control;
|
|
56
74
|
const cell = /* @__PURE__ */ jsx("span", { className: "flex h-5 shrink-0 items-center", children: control });
|
|
57
75
|
const words = /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
@@ -61,7 +79,7 @@ const Switch = forwardRef(function Switch2({
|
|
|
61
79
|
htmlFor: inputId,
|
|
62
80
|
className: cn(
|
|
63
81
|
"block text-sm leading-5 text-[var(--text-primary)]",
|
|
64
|
-
|
|
82
|
+
looksDisabled ? "cursor-not-allowed" : "cursor-pointer select-none"
|
|
65
83
|
),
|
|
66
84
|
children: [
|
|
67
85
|
label,
|
|
@@ -71,7 +89,7 @@ const Switch = forwardRef(function Switch2({
|
|
|
71
89
|
),
|
|
72
90
|
showDescription && /* @__PURE__ */ jsx("span", { id: descriptionId, className: "mt-0.5 block text-xs text-[var(--text-muted)]", children: description })
|
|
73
91
|
] });
|
|
74
|
-
return /* @__PURE__ */ jsx("div", { className: cn("flex items-start gap-3",
|
|
92
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-start gap-3", looksDisabled && "opacity-60", className), children: switchPosition === "end" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
93
|
words,
|
|
76
94
|
cell
|
|
77
95
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/switch.tsx"],"sourcesContent":["import { forwardRef, useId } from \"react\";\nimport type { ChangeEvent, InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { hasMessage, mergeDescribedBy } from \"./choice-parts\";\n\n/**\n * An on/off switch that is still `<input type=\"checkbox\">`, with `role=\"switch\"`.\n *\n * Kastlan's was a Radix `<button>`; this keeps the native element for the reasons\n * `Checkbox` gives (form submission, `required`, reset, `<label for>`), and adds the\n * one thing a checkbox is not: `role=\"switch\"`, which screen readers announce as\n * \"on/off\" rather than \"checked/not checked\". Use it for a setting that takes effect\n * the moment it is flipped; a choice that waits for a Save button is a checkbox.\n */\n\n/** Geometry per size. The thumb sits `2px` in from each end of the track, so its\n * checked offset is `track width − thumb − 2px`. `start-*`, never `left-*` or a\n * `translate-x`: an inline-start offset follows the writing direction, so in a\n * right-to-left form the thumb travels right-to-left with no `rtl:` override to\n * keep in step. */\nconst SIZES = {\n // 28×16 track, 12px thumb: the dense setting rows in Kastlan's tables.\n sm: { track: \"h-4 w-7\", thumb: \"size-3 peer-checked:start-[14px]\" },\n // 36×20 track, 16px thumb.\n md: { track: \"h-5 w-9\", thumb: \"size-4 peer-checked:start-[18px]\" },\n} as const;\n\nexport type SwitchSize = keyof typeof SIZES;\n\n// The track IS the input — `appearance-none` and a pill — so its box is the pointer\n// target and the focus ring lands around the whole control, not the thumb alone.\n//\n// Unchecked it takes `--border-strong`, not `--border`: a hairline-coloured track on\n// a surface-coloured card is a switch you have to look for. Checked it takes the\n// brand fill, which is the only difference in COLOUR; the thumb's position is the\n// difference in SHAPE, so the state never rests on colour alone.\nconst TRACK =\n \"peer block shrink-0 cursor-pointer appearance-none rounded-full bg-[var(--border-strong)] transition-colors \" +\n \"checked:bg-[var(--brand)] \" +\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\";\n\n// The thumb, laid over the input and ignoring the pointer so every click still reaches\n// the input. It swaps to `--brand-contrast` on the brand fill: the surface colour would\n// match the fill in some presets, and the contrast token is the one guaranteed to read\n// against --brand.\nconst THUMB =\n \"pointer-events-none absolute start-0.5 top-1/2 -translate-y-1/2 rounded-full bg-[var(--bg-surface)] shadow-sm \" +\n \"transition-[inset-inline-start,background-color] motion-reduce:transition-none \" +\n \"peer-checked:bg-[var(--brand-contrast)]\";\n\nexport interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"role\"> {\n /** `\"md\"` (36×20, default) or `\"sm\"` (28×16) — Kastlan's `default` and `sm`. */\n size?: SwitchSize;\n /** The words beside the switch, as a `<label for>` — see {@link Checkbox}'s\n * `label` for why it does not wrap the input. Without it, give the switch an\n * `aria-label`. */\n label?: ReactNode;\n /** Secondary text under the label, attached with `aria-describedby` (merged with\n * any the caller passed). */\n description?: ReactNode;\n /** Which end of the row the switch sits at when there is a label. `\"end\"` (the\n * default) is the settings-list shape Kastlan's profile page uses — the words at\n * the start, the control at the far end; `\"start\"` puts it before the words, like\n * a checkbox. Logical, so it follows the writing direction. */\n switchPosition?: \"start\" | \"end\";\n /** The new state as a boolean — Radix's `onCheckedChange`, so Kastlan's call sites\n * move without unwrapping the event. Fires alongside `onChange`. */\n onCheckedChange?: (checked: boolean) => void;\n /** Classes for the `<input>` (the track). `className` styles the outermost element:\n * the row when there is a label, the switch's own wrapper when there is not. */\n inputClassName?: string;\n /**\n * Must be ticked to submit — a consent, an acceptance before paying. Reaches the\n * `<input>` as the native `required` (so a `<form>` refuses to submit without it and a\n * screen reader announces \"required\"), and draws the kit's required mark after the\n * label, the same `aria-hidden` star as {@link Label}'s: the word is announced from\n * the control, so a star inside the name would only be read out as noise. Write the\n * label WITHOUT a literal \"*\". No mark without a label.\n */\n required?: boolean;\n}\n\n/**\n * A native checkbox drawn as a track and thumb, announced as a switch.\n *\n * Every other prop reaches the `<input>`, so it works controlled (`checked` +\n * `onChange`/`onCheckedChange`), uncontrolled (`defaultChecked`) and in a plain\n * `<form>` (`name`, `value`).\n */\nexport const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(\n {\n size = \"md\",\n label,\n description,\n switchPosition = \"end\",\n onCheckedChange,\n onChange,\n className,\n inputClassName,\n id,\n disabled,\n required,\n ...rest\n },\n ref,\n) {\n const generated = useId();\n const inputId = id ?? generated;\n const descriptionId = `${inputId}-description`;\n const showDescription = hasMessage(description);\n const bare = label === undefined && !showDescription;\n const geometry = SIZES[size];\n\n const control = (\n <span className={cn(\"relative inline-flex shrink-0\", bare && className)}>\n <input\n ref={ref}\n id={bare ? id : inputId}\n disabled={disabled}\n required={required}\n {...rest}\n // After the spread: a caller's props object must not be able to turn this\n // back into a plain checkbox, or into a text field.\n type=\"checkbox\"\n role=\"switch\"\n aria-describedby={mergeDescribedBy(rest[\"aria-describedby\"], showDescription && descriptionId)}\n onChange={(e: ChangeEvent<HTMLInputElement>) => {\n onChange?.(e);\n onCheckedChange?.(e.target.checked);\n }}\n className={cn(TRACK, geometry.track, inputClassName)}\n />\n <span aria-hidden className={cn(THUMB, geometry.thumb)} />\n </span>\n );\n\n if (bare) return control;\n\n // The switch's cell is one label line tall (text-sm, 20px) so it centres on the\n // FIRST line of a label that wraps, and a description underneath does not drag it\n // down to the middle of the paragraph.\n const cell = <span className=\"flex h-5 shrink-0 items-center\">{control}</span>;\n const words = (\n <span className=\"min-w-0 flex-1\">\n {label !== undefined && (\n <label\n htmlFor={inputId}\n className={cn(\n \"block text-sm leading-5 text-[var(--text-primary)]\",\n disabled ? \"cursor-not-allowed\" : \"cursor-pointer select-none\",\n )}\n >\n {label}\n {required && (\n <span aria-hidden className=\"ms-0.5 text-[var(--danger)]\">\n *\n </span>\n )}\n </label>\n )}\n {showDescription && (\n <span id={descriptionId} className=\"mt-0.5 block text-xs text-[var(--text-muted)]\">\n {description}\n </span>\n )}\n </span>\n );\n\n return (\n <div className={cn(\"flex items-start gap-3\", disabled && \"opacity-60\", className)}>\n {switchPosition === \"end\" ? (\n <>\n {words}\n {cell}\n </>\n ) : (\n <>\n {cell}\n {words}\n </>\n )}\n </div>\n );\n});\nSwitch.displayName = \"Switch\";\n"],"mappings":";AAmHI,SAyDI,UAxDF,KADF;AAnHJ,SAAS,YAAY,aAAa;AAElC,SAAS,UAAU;AACnB,SAAS,YAAY,wBAAwB;AAiB7C,MAAM,QAAQ;AAAA;AAAA,EAEZ,IAAI,EAAE,OAAO,WAAW,OAAO,mCAAmC;AAAA;AAAA,EAElE,IAAI,EAAE,OAAO,WAAW,OAAO,mCAAmC;AACpE;AAWA,MAAM,QACJ;AASF,MAAM,QACJ;AA2CK,MAAM,SAAS,WAA0C,SAASA,QACvE;AAAA,EACE,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,YAAY,MAAM;AACxB,QAAM,UAAU,MAAM;AACtB,QAAM,gBAAgB,GAAG,OAAO;AAChC,QAAM,kBAAkB,WAAW,WAAW;AAC9C,QAAM,OAAO,UAAU,UAAa,CAAC;AACrC,QAAM,WAAW,MAAM,IAAI;AAE3B,QAAM,UACJ,qBAAC,UAAK,WAAW,GAAG,iCAAiC,QAAQ,SAAS,GACpE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,IAAI,OAAO,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,QACC,GAAG;AAAA,QAGJ,MAAK;AAAA,QACL,MAAK;AAAA,QACL,oBAAkB,iBAAiB,KAAK,kBAAkB,GAAG,mBAAmB,aAAa;AAAA,QAC7F,UAAU,CAAC,MAAqC;AAC9C,qBAAW,CAAC;AACZ,4BAAkB,EAAE,OAAO,OAAO;AAAA,QACpC;AAAA,QACA,WAAW,GAAG,OAAO,SAAS,OAAO,cAAc;AAAA;AAAA,IACrD;AAAA,IACA,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,OAAO,SAAS,KAAK,GAAG;AAAA,KAC1D;AAGF,MAAI,KAAM,QAAO;AAKjB,QAAM,OAAO,oBAAC,UAAK,WAAU,kCAAkC,mBAAQ;AACvE,QAAM,QACJ,qBAAC,UAAK,WAAU,kBACb;AAAA,cAAU,UACT;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW;AAAA,UACT;AAAA,UACA,WAAW,uBAAuB;AAAA,QACpC;AAAA,QAEC;AAAA;AAAA,UACA,YACC,oBAAC,UAAK,eAAW,MAAC,WAAU,+BAA8B,eAE1D;AAAA;AAAA;AAAA,IAEJ;AAAA,IAED,mBACC,oBAAC,UAAK,IAAI,eAAe,WAAU,iDAChC,uBACH;AAAA,KAEJ;AAGF,SACE,oBAAC,SAAI,WAAW,GAAG,0BAA0B,YAAY,cAAc,SAAS,GAC7E,6BAAmB,QAClB,iCACG;AAAA;AAAA,IACA;AAAA,KACH,IAEA,iCACG;AAAA;AAAA,IACA;AAAA,KACH,GAEJ;AAEJ,CAAC;AACD,OAAO,cAAc;","names":["Switch"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/switch.tsx"],"sourcesContent":["import { forwardRef, useId } from \"react\";\nimport type { ChangeEvent, InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { hasMessage, mergeDescribedBy } from \"./choice-parts\";\nimport { LockedReason, useLockReason } from \"./field-parts\";\n\n/**\n * An on/off switch that is still `<input type=\"checkbox\">`, with `role=\"switch\"`.\n *\n * Kastlan's was a Radix `<button>`; this keeps the native element for the reasons\n * `Checkbox` gives (form submission, `required`, reset, `<label for>`), and adds the\n * one thing a checkbox is not: `role=\"switch\"`, which screen readers announce as\n * \"on/off\" rather than \"checked/not checked\". Use it for a setting that takes effect\n * the moment it is flipped; a choice that waits for a Save button is a checkbox.\n */\n\n/** Geometry per size. The thumb sits `2px` in from each end of the track, so its\n * checked offset is `track width − thumb − 2px`. `start-*`, never `left-*` or a\n * `translate-x`: an inline-start offset follows the writing direction, so in a\n * right-to-left form the thumb travels right-to-left with no `rtl:` override to\n * keep in step. */\nconst SIZES = {\n // 28×16 track, 12px thumb: the dense setting rows in Kastlan's tables.\n sm: { track: \"h-4 w-7\", thumb: \"size-3 peer-checked:start-[14px]\" },\n // 36×20 track, 16px thumb.\n md: { track: \"h-5 w-9\", thumb: \"size-4 peer-checked:start-[18px]\" },\n} as const;\n\nexport type SwitchSize = keyof typeof SIZES;\n\n// The track IS the input — `appearance-none` and a pill — so its box is the pointer\n// target and the focus ring lands around the whole control, not the thumb alone.\n//\n// Unchecked it takes `--border-strong`, not `--border`: a hairline-coloured track on\n// a surface-coloured card is a switch you have to look for. Checked it takes the\n// brand fill, which is the only difference in COLOUR; the thumb's position is the\n// difference in SHAPE, so the state never rests on colour alone.\nconst TRACK =\n \"peer block shrink-0 cursor-pointer appearance-none rounded-full bg-[var(--border-strong)] transition-colors \" +\n \"checked:bg-[var(--brand)] \" +\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\";\n\n// The thumb, laid over the input and ignoring the pointer so every click still reaches\n// the input. It swaps to `--brand-contrast` on the brand fill: the surface colour would\n// match the fill in some presets, and the contrast token is the one guaranteed to read\n// against --brand.\nconst THUMB =\n \"pointer-events-none absolute start-0.5 top-1/2 -translate-y-1/2 rounded-full bg-[var(--bg-surface)] shadow-sm \" +\n \"transition-[inset-inline-start,background-color] motion-reduce:transition-none \" +\n \"peer-checked:bg-[var(--brand-contrast)]\";\n\nexport interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"role\"> {\n /** `\"md\"` (36×20, default) or `\"sm\"` (28×16) — Kastlan's `default` and `sm`. */\n size?: SwitchSize;\n /** The words beside the switch, as a `<label for>` — see {@link Checkbox}'s\n * `label` for why it does not wrap the input. Without it, give the switch an\n * `aria-label`. */\n label?: ReactNode;\n /** Secondary text under the label, attached with `aria-describedby` (merged with\n * any the caller passed). */\n description?: ReactNode;\n /** Which end of the row the switch sits at when there is a label. `\"end\"` (the\n * default) is the settings-list shape Kastlan's profile page uses — the words at\n * the start, the control at the far end; `\"start\"` puts it before the words, like\n * a checkbox. Logical, so it follows the writing direction. */\n switchPosition?: \"start\" | \"end\";\n /** The new state as a boolean — Radix's `onCheckedChange`, so Kastlan's call sites\n * move without unwrapping the event. Fires alongside `onChange`. */\n onCheckedChange?: (checked: boolean) => void;\n /** Classes for the `<input>` (the track). `className` styles the outermost element:\n * the row when there is a label, the switch's own wrapper when there is not. */\n inputClassName?: string;\n /**\n * Must be ticked to submit — a consent, an acceptance before paying. Reaches the\n * `<input>` as the native `required` (so a `<form>` refuses to submit without it and a\n * screen reader announces \"required\"), and draws the kit's required mark after the\n * label, the same `aria-hidden` star as {@link Label}'s: the word is announced from\n * the control, so a star inside the name would only be read out as noise. Write the\n * label WITHOUT a literal \"*\". No mark without a label.\n */\n required?: boolean;\n /**\n * Why the switch cannot be flipped — {@link Button}'s `disabledReason`. A switch is\n * the control that \"takes effect the moment it is flipped\", so it is the first one\n * keksdose K3 names: its settings rows sat behind a `SaveGuard` that forced a native\n * `disabled`, out of the tab order, so the reason never reached a keyboard.\n *\n * With a reason the switch is `aria-disabled` instead: still focusable, a click or\n * Space flips nothing and fires no `onChange` / `onCheckedChange`, the reason is in\n * the kit {@link Tooltip} on the track and on its `aria-describedby`, and the row\n * fades as a disabled one does. It wins over `disabled`. Changing the lock mounts the\n * switch anew (it moves in or out of the Tooltip), as a locked Button is.\n */\n disabledReason?: ReactNode;\n /**\n * This switch COMMITS — flipping it saves. Under a locked {@link WriteLockProvider} it\n * is locked the `disabledReason` way with the lock's reason (which wins over its\n * own). No provider, or an unlocked one: no effect. Button's `commit`, for K3.\n */\n commit?: boolean;\n}\n\n/**\n * A native checkbox drawn as a track and thumb, announced as a switch.\n *\n * Every other prop reaches the `<input>`, so it works controlled (`checked` +\n * `onChange`/`onCheckedChange`), uncontrolled (`defaultChecked`) and in a plain\n * `<form>` (`name`, `value`).\n */\nexport const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(\n {\n size = \"md\",\n label,\n description,\n switchPosition = \"end\",\n onCheckedChange,\n onChange,\n className,\n inputClassName,\n id,\n disabled,\n required,\n disabledReason,\n commit,\n onClick,\n ...rest\n },\n ref,\n) {\n const generated = useId();\n const inputId = id ?? generated;\n const lock = useLockReason(commit, disabledReason);\n const locked = lock.locked;\n // Locked is disabled the focusable way — the row fades and the words stop inviting a\n // click, as for `disabled`.\n const looksDisabled = Boolean(disabled) || locked;\n const descriptionId = `${inputId}-description`;\n const showDescription = hasMessage(description);\n const bare = label === undefined && !showDescription;\n const geometry = SIZES[size];\n\n const track = (\n <span className={cn(\"relative inline-flex shrink-0\", bare && !locked && className)}>\n <input\n ref={ref}\n id={bare ? id : inputId}\n // A reason wins over `disabled`, as on Button: the switch stays reachable.\n disabled={locked ? undefined : disabled}\n required={required}\n {...rest}\n // After the spread: a caller's props object must not be able to turn this\n // back into a plain checkbox, or into a text field.\n type=\"checkbox\"\n role=\"switch\"\n aria-disabled={locked || rest[\"aria-disabled\"] || undefined}\n aria-describedby={mergeDescribedBy(\n rest[\"aria-describedby\"],\n showDescription && descriptionId,\n locked && lock.reasonId,\n )}\n // A locked switch's click is swallowed, as a locked Button's is.\n onClick={locked ? undefined : onClick}\n onChange={(e: ChangeEvent<HTMLInputElement>) => {\n if (locked) {\n // Put the thumb back and report nothing — see Checkbox for why here and not\n // by cancelling the click. Label clicks and Space arrive as clicks too.\n e.currentTarget.checked = !e.currentTarget.checked;\n return;\n }\n onChange?.(e);\n onCheckedChange?.(e.target.checked);\n }}\n className={cn(TRACK, geometry.track, locked && \"cursor-not-allowed\", inputClassName)}\n />\n <span aria-hidden className={cn(THUMB, geometry.thumb)} />\n </span>\n );\n // The Tooltip goes round the track, not the row — the row is the caller's layout. A\n // bare switch hands its `className` to the wrapper, then the outermost element.\n const control = locked ? (\n <LockedReason lock={lock} className={cn(\"shrink-0\", bare && className)}>\n {track}\n </LockedReason>\n ) : (\n track\n );\n\n if (bare) return control;\n\n // The switch's cell is one label line tall (text-sm, 20px) so it centres on the\n // FIRST line of a label that wraps, and a description underneath does not drag it\n // down to the middle of the paragraph.\n const cell = <span className=\"flex h-5 shrink-0 items-center\">{control}</span>;\n const words = (\n <span className=\"min-w-0 flex-1\">\n {label !== undefined && (\n <label\n htmlFor={inputId}\n className={cn(\n \"block text-sm leading-5 text-[var(--text-primary)]\",\n looksDisabled ? \"cursor-not-allowed\" : \"cursor-pointer select-none\",\n )}\n >\n {label}\n {required && (\n <span aria-hidden className=\"ms-0.5 text-[var(--danger)]\">\n *\n </span>\n )}\n </label>\n )}\n {showDescription && (\n <span id={descriptionId} className=\"mt-0.5 block text-xs text-[var(--text-muted)]\">\n {description}\n </span>\n )}\n </span>\n );\n\n return (\n <div className={cn(\"flex items-start gap-3\", looksDisabled && \"opacity-60\", className)}>\n {switchPosition === \"end\" ? (\n <>\n {words}\n {cell}\n </>\n ) : (\n <>\n {cell}\n {words}\n </>\n )}\n </div>\n );\n});\nSwitch.displayName = \"Switch\";\n"],"mappings":";AA+II,SAgFI,UA/EF,KADF;AA/IJ,SAAS,YAAY,aAAa;AAElC,SAAS,UAAU;AACnB,SAAS,YAAY,wBAAwB;AAC7C,SAAS,cAAc,qBAAqB;AAiB5C,MAAM,QAAQ;AAAA;AAAA,EAEZ,IAAI,EAAE,OAAO,WAAW,OAAO,mCAAmC;AAAA;AAAA,EAElE,IAAI,EAAE,OAAO,WAAW,OAAO,mCAAmC;AACpE;AAWA,MAAM,QACJ;AASF,MAAM,QACJ;AA8DK,MAAM,SAAS,WAA0C,SAASA,QACvE;AAAA,EACE,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,YAAY,MAAM;AACxB,QAAM,UAAU,MAAM;AACtB,QAAM,OAAO,cAAc,QAAQ,cAAc;AACjD,QAAM,SAAS,KAAK;AAGpB,QAAM,gBAAgB,QAAQ,QAAQ,KAAK;AAC3C,QAAM,gBAAgB,GAAG,OAAO;AAChC,QAAM,kBAAkB,WAAW,WAAW;AAC9C,QAAM,OAAO,UAAU,UAAa,CAAC;AACrC,QAAM,WAAW,MAAM,IAAI;AAE3B,QAAM,QACJ,qBAAC,UAAK,WAAW,GAAG,iCAAiC,QAAQ,CAAC,UAAU,SAAS,GAC/E;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,IAAI,OAAO,KAAK;AAAA,QAEhB,UAAU,SAAS,SAAY;AAAA,QAC/B;AAAA,QACC,GAAG;AAAA,QAGJ,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe,UAAU,KAAK,eAAe,KAAK;AAAA,QAClD,oBAAkB;AAAA,UAChB,KAAK,kBAAkB;AAAA,UACvB,mBAAmB;AAAA,UACnB,UAAU,KAAK;AAAA,QACjB;AAAA,QAEA,SAAS,SAAS,SAAY;AAAA,QAC9B,UAAU,CAAC,MAAqC;AAC9C,cAAI,QAAQ;AAGV,cAAE,cAAc,UAAU,CAAC,EAAE,cAAc;AAC3C;AAAA,UACF;AACA,qBAAW,CAAC;AACZ,4BAAkB,EAAE,OAAO,OAAO;AAAA,QACpC;AAAA,QACA,WAAW,GAAG,OAAO,SAAS,OAAO,UAAU,sBAAsB,cAAc;AAAA;AAAA,IACrF;AAAA,IACA,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,OAAO,SAAS,KAAK,GAAG;AAAA,KAC1D;AAIF,QAAM,UAAU,SACd,oBAAC,gBAAa,MAAY,WAAW,GAAG,YAAY,QAAQ,SAAS,GAClE,iBACH,IAEA;AAGF,MAAI,KAAM,QAAO;AAKjB,QAAM,OAAO,oBAAC,UAAK,WAAU,kCAAkC,mBAAQ;AACvE,QAAM,QACJ,qBAAC,UAAK,WAAU,kBACb;AAAA,cAAU,UACT;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW;AAAA,UACT;AAAA,UACA,gBAAgB,uBAAuB;AAAA,QACzC;AAAA,QAEC;AAAA;AAAA,UACA,YACC,oBAAC,UAAK,eAAW,MAAC,WAAU,+BAA8B,eAE1D;AAAA;AAAA;AAAA,IAEJ;AAAA,IAED,mBACC,oBAAC,UAAK,IAAI,eAAe,WAAU,iDAChC,uBACH;AAAA,KAEJ;AAGF,SACE,oBAAC,SAAI,WAAW,GAAG,0BAA0B,iBAAiB,cAAc,SAAS,GAClF,6BAAmB,QAClB,iCACG;AAAA;AAAA,IACA;AAAA,KACH,IAEA,iCACG;AAAA;AAAA,IACA;AAAA,KACH,GAEJ;AAEJ,CAAC;AACD,OAAO,cAAc;","names":["Switch"]}
|