@eifi1/ui-kit 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -25
- package/dist/chart.d.ts +19 -14
- package/dist/components/account-chips.d.ts +10 -3
- package/dist/components/account-chips.js +5 -2
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings.d.ts +33 -2
- package/dist/components/account-settings.js +43 -21
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +53 -14
- package/dist/components/amount-input.js +17 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +29 -0
- package/dist/components/autocomplete.js +134 -104
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +19 -14
- package/dist/components/calculator.d.ts +19 -14
- package/dist/components/checkbox-group.d.ts +104 -0
- package/dist/components/checkbox-group.js +111 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.ts +19 -0
- package/dist/components/checkbox.js +30 -7
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +18 -0
- package/dist/components/chip.js +47 -10
- package/dist/components/chip.js.map +1 -1
- package/dist/components/column-mapper.d.ts +182 -0
- package/dist/components/column-mapper.js +375 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +91 -2
- package/dist/components/combobox.js +454 -308
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +69 -161
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +16 -11
- package/dist/components/country-select.d.ts +167 -0
- package/dist/components/country-select.js +293 -0
- package/dist/components/country-select.js.map +1 -0
- package/dist/components/currency-select.d.ts +17 -1
- package/dist/components/currency-select.js +56 -23
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +69 -166
- package/dist/components/danger-confirm.js +144 -50
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -2
- package/dist/components/data-table-filter-popover.d.ts +1 -2
- package/dist/components/data-table-filters.d.ts +1 -2
- package/dist/components/data-table-labels.d.ts +4 -97
- package/dist/components/data-table-labels.js +9 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table.d.ts +1 -2
- package/dist/components/data-table.js +98 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +66 -5
- package/dist/components/date-picker.js +378 -66
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +42 -1
- package/dist/components/entity-combobox.js +154 -113
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +19 -14
- package/dist/components/field-parts.d.ts +224 -0
- package/dist/components/field-parts.js +156 -0
- package/dist/components/field-parts.js.map +1 -0
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +19 -14
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +41 -15
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +18 -13
- package/dist/components/form-actions.js +69 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +69 -0
- package/dist/components/iban-input.js +125 -0
- package/dist/components/iban-input.js.map +1 -0
- package/dist/components/icon-picker.d.ts +21 -2
- package/dist/components/icon-picker.js +19 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-edit-field.d.ts +167 -0
- package/dist/components/inline-edit-field.js +293 -0
- package/dist/components/inline-edit-field.js.map +1 -0
- package/dist/components/language-select.d.ts +130 -0
- package/dist/components/language-select.js +18 -0
- package/dist/components/language-select.js.map +1 -0
- package/dist/components/line-items.d.ts +25 -3
- package/dist/components/line-items.js +55 -46
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +25 -0
- package/dist/components/list.js +16 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +32 -14
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +291 -86
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +26 -1
- package/dist/components/multi-entity-combobox.js +143 -102
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +8 -1
- package/dist/components/multi-select.js +9 -4
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +34 -19
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +79 -19
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +19 -14
- package/dist/components/one-time-code-input.d.ts +100 -0
- package/dist/components/one-time-code-input.js +137 -0
- package/dist/components/one-time-code-input.js.map +1 -0
- package/dist/components/phone-input.d.ts +69 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +19 -14
- package/dist/components/settings-fields.d.ts +19 -14
- package/dist/components/share-card.d.ts +16 -11
- package/dist/components/sign-chip.d.ts +66 -0
- package/dist/components/sign-chip.js +39 -0
- package/dist/components/sign-chip.js.map +1 -0
- package/dist/components/swatch-picker.d.ts +43 -2
- package/dist/components/swatch-picker.js +80 -32
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/switch.d.ts +19 -0
- package/dist/components/switch.js +24 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/text-link.d.ts +18 -13
- package/dist/components/tile-radio.d.ts +88 -13
- package/dist/components/tile-radio.js +28 -11
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +24 -15
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toggle-group.d.ts +47 -1
- package/dist/components/toggle-group.js +110 -69
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +16 -11
- package/dist/components/ui.js +284 -88
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -2
- package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +348 -41
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +3 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +68 -253
- package/dist/feedback/feedback-thread.js +46 -10
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
- package/dist/feedback.d.ts +66 -2
- package/dist/hooks/use-file-drop.d.ts +19 -14
- package/dist/i18n/defaults.d.ts +19 -14
- package/dist/i18n/defaults.js +19 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +19 -14
- package/dist/i18n/german.js +84 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -10
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +19 -14
- package/dist/i18n/locales/de-CH.d.ts +19 -14
- package/dist/i18n/locales/en.d.ts +19 -14
- package/dist/i18n/locales/en.js +16 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +19 -14
- package/dist/i18n/locales/es.js +83 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +19 -14
- package/dist/i18n/locales/fr.js +82 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +19 -14
- package/dist/i18n/locales/hu.js +85 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +19 -14
- package/dist/i18n/locales/it.js +84 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +19 -14
- package/dist/i18n/locales/zh.js +80 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +19 -14
- package/dist/i18n/review.js +27 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +73 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- package/dist/lib/countries.d.ts +43 -0
- package/dist/lib/countries.js +39 -0
- package/dist/lib/countries.js.map +1 -0
- package/dist/lib/decimal-marks.d.ts +46 -0
- package/dist/lib/decimal-marks.js +40 -0
- package/dist/lib/decimal-marks.js.map +1 -0
- package/dist/lib/iban.d.ts +98 -0
- package/dist/lib/iban.js +154 -0
- package/dist/lib/iban.js.map +1 -0
- package/dist/lib/isin.d.ts +30 -0
- package/dist/lib/isin.js +36 -0
- package/dist/lib/isin.js.map +1 -0
- package/dist/lib/phone.d.ts +69 -0
- package/dist/lib/phone.js +121 -0
- package/dist/lib/phone.js.map +1 -0
- package/dist/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +233 -18
- package/dist/rhf/fields.js +456 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +19 -14
- package/dist/rhf.d.ts +22 -15
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +18 -13
- package/dist/shell/top-bar-brand.d.ts +19 -14
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +18 -13
- package/dist/table-text.d.ts +1 -1
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -16
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +19 -14
- package/package.json +1 -1
- package/src/components/account-chips.tsx +17 -3
- package/src/components/account-settings.tsx +92 -26
- package/src/components/amount-input.tsx +68 -56
- package/src/components/autocomplete.tsx +198 -114
- package/src/components/checkbox-group.tsx +229 -0
- package/src/components/checkbox.tsx +72 -8
- package/src/components/chip.tsx +80 -10
- package/src/components/column-mapper.tsx +666 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +661 -340
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +556 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +429 -82
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +595 -71
- package/src/components/entity-combobox.tsx +235 -124
- package/src/components/field-parts.tsx +403 -0
- package/src/components/field-strip.tsx +149 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +147 -6
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +47 -6
- package/src/components/inline-edit-field.tsx +533 -0
- package/src/components/language-select.tsx +78 -0
- package/src/components/line-items.tsx +35 -2
- package/src/components/list.tsx +55 -6
- package/src/components/money-field.tsx +13 -0
- package/src/components/month-picker.tsx +400 -63
- package/src/components/multi-entity-combobox.tsx +207 -120
- package/src/components/multi-select.tsx +80 -58
- package/src/components/number-field.tsx +28 -46
- package/src/components/number-input.tsx +210 -43
- package/src/components/one-time-code-input.tsx +288 -0
- package/src/components/phone-input.tsx +299 -0
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +143 -31
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +145 -29
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +479 -138
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +597 -46
- package/src/feedback/feedback-dialog.tsx +9 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +113 -9
- package/src/i18n/defaults.ts +18 -1
- package/src/i18n/german.ts +89 -0
- package/src/i18n/kit-labels.tsx +82 -2
- package/src/i18n/locales/en.ts +36 -4
- package/src/i18n/locales/es.ts +87 -0
- package/src/i18n/locales/fr.ts +87 -0
- package/src/i18n/locales/hu.ts +83 -0
- package/src/i18n/locales/it.ts +88 -0
- package/src/i18n/locales/zh.ts +76 -0
- package/src/i18n/review.ts +26 -0
- package/src/index.ts +71 -3
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/countries.ts +91 -0
- package/src/lib/decimal-marks.ts +99 -0
- package/src/lib/iban.ts +171 -0
- package/src/lib/isin.ts +62 -0
- package/src/lib/phone.ts +211 -0
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +860 -9
- package/src/rhf.ts +3 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/tour/tour.tsx +66 -28
- package/src/wizard/stepper-nav.tsx +35 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The pieces of a field's anatomy the kit's fields share — the static label, the hint
|
|
6
|
+
* (caption or "?"), the caption and the error under a field, the label strip over a
|
|
7
|
+
* bare group, the write lock on a control that saves itself. Private to the package —
|
|
8
|
+
* not re-exported from the barrel, except `FieldLabel` and `FLOATING_LABEL_STATIC`,
|
|
9
|
+
* which ui.tsx re-exports as it always has.
|
|
10
|
+
*
|
|
11
|
+
* One file since 0.23: until then the pickers' half (`field-anatomy.tsx`: the date and
|
|
12
|
+
* month pickers, whose trigger is a button in a wrapper of their own, and the groups
|
|
13
|
+
* with no field box) sat beside this one with its own copies of `isTextHint` and the
|
|
14
|
+
* caption class (keksdose K4: "uniform field anatomy … today it is a patchwork").
|
|
15
|
+
*
|
|
16
|
+
* Nothing here imports `ui.tsx`: `ui.tsx` imports this, and so do the combobox family,
|
|
17
|
+
* the pickers and the native choice controls — the lint's no-cycle rule holds it.
|
|
18
|
+
*/
|
|
19
|
+
declare const STATIC_LABEL_TYPE = "text-[11px] leading-tight text-[var(--text-muted)] peer-disabled:opacity-50";
|
|
20
|
+
declare const FLOATING_LABEL_STATIC: string;
|
|
21
|
+
/**
|
|
22
|
+
* The floating label for a custom-dropdown trigger (a `<span>`, since the trigger is a
|
|
23
|
+
* button not a labelable input). Same placement as {@link FloatingField}'s static label,
|
|
24
|
+
* so every labelled field lines up. Render inside a `relative` wrapper, before the trigger.
|
|
25
|
+
*/
|
|
26
|
+
interface FieldLabelProps extends ComponentPropsWithoutRef<"span"> {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
declare function FieldLabel({ children, className, ...rest }: FieldLabelProps): react.JSX.Element;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a `hint` is a CAPTION (plain text: a non-empty string or a number) or
|
|
32
|
+
* something for the label line (a {@link FieldHint} "?", or any other node).
|
|
33
|
+
*
|
|
34
|
+
* The rule {@link Select} and `NumberInput` already keep: the label line is 11px of
|
|
35
|
+
* strip shared with the label, and a sentence placed there ran over the label and into
|
|
36
|
+
* the value; a "?" under the field would be a button floating free of the label it
|
|
37
|
+
* explains.
|
|
38
|
+
*/
|
|
39
|
+
declare function isTextHint(hint: ReactNode): hint is string | number;
|
|
40
|
+
/** The caption under a field — Select's and NumberInput's type, so every field's
|
|
41
|
+
* standing advice reads alike. */
|
|
42
|
+
declare const FIELD_CAPTION_CLASS = "mt-1 text-[11px] leading-tight text-[var(--text-muted)]";
|
|
43
|
+
interface FieldHintParts {
|
|
44
|
+
/** The caller's `aria-describedby` with the caption's id appended — standing
|
|
45
|
+
* advice first; the error (the news) is appended after this by the field. */
|
|
46
|
+
describedBy: string | undefined;
|
|
47
|
+
/** The caption's id while there is one. */
|
|
48
|
+
captionId: string | undefined;
|
|
49
|
+
/** The caption itself, or `undefined` for a hint that is not text. */
|
|
50
|
+
captionText: string | number | undefined;
|
|
51
|
+
/** A non-text hint (a FieldHint) for the label line — or, on a field with no label
|
|
52
|
+
* line, the end edge outside the box. `undefined` when there is none. */
|
|
53
|
+
labelHint: ReactNode | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Split a field's `hint` into the caption under it (attached with `aria-describedby`)
|
|
57
|
+
* or the "?" on its label line. See {@link isTextHint}.
|
|
58
|
+
*/
|
|
59
|
+
declare function useFieldHint(hint: ReactNode, describedBy: string | undefined): FieldHintParts;
|
|
60
|
+
/** The caption under a field, as {@link Select} draws its own. */
|
|
61
|
+
declare function FieldCaption({ parts, className }: {
|
|
62
|
+
parts: FieldHintParts;
|
|
63
|
+
className?: string;
|
|
64
|
+
}): react.JSX.Element | null;
|
|
65
|
+
/**
|
|
66
|
+
* Classes that take a static floating label (`FLOATING_LABEL_STATIC`: `FieldLabel`,
|
|
67
|
+
* `ComboboxFieldLabel`) out of its own absolute slot and into a {@link StaticLabelRow}.
|
|
68
|
+
* `static` wins over `absolute` through `cn`'s merge; `min-w-0` lets it truncate in
|
|
69
|
+
* the row.
|
|
70
|
+
*/
|
|
71
|
+
declare const LABEL_IN_ROW = "static min-w-0";
|
|
72
|
+
/**
|
|
73
|
+
* A static label and its "?" on one line, in the top strip of a field that lays out its
|
|
74
|
+
* own label — the combobox family, which cannot use {@link FloatingField} because the
|
|
75
|
+
* trigger, the anchored panel and the label hang off a box of their own.
|
|
76
|
+
*
|
|
77
|
+
* FloatingField's static row, with one difference: it stops at `end-9`, the column the
|
|
78
|
+
* chevron (or the clear "×") takes, so a long label truncates before it rather than
|
|
79
|
+
* putting the "?" on top of the chevron. The hint keeps its width; the label gives way.
|
|
80
|
+
*/
|
|
81
|
+
declare function StaticLabelRow({ hint, children }: {
|
|
82
|
+
hint: ReactNode;
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
}): react.JSX.Element;
|
|
85
|
+
/**
|
|
86
|
+
* A field with no label line and a FieldHint "?": the hint at the end edge, OUTSIDE the
|
|
87
|
+
* box, as an unlabelled {@link Select} has placed it since 0.15.5. Without a hint the
|
|
88
|
+
* field is returned exactly as it was, so the wrapper costs nothing where it does not
|
|
89
|
+
* apply.
|
|
90
|
+
*/
|
|
91
|
+
declare function EndHintRow({ hint, className, children, }: {
|
|
92
|
+
hint: ReactNode | undefined;
|
|
93
|
+
className?: string;
|
|
94
|
+
children: ReactNode;
|
|
95
|
+
}): react.JSX.Element;
|
|
96
|
+
interface LockReason {
|
|
97
|
+
/** The reason in force — the lock's (with `commit` under a locked
|
|
98
|
+
* `WriteLockProvider`), else the control's own `disabledReason`. */
|
|
99
|
+
reason: ReactNode;
|
|
100
|
+
/** There is a reason: the control is locked the focusable way. */
|
|
101
|
+
locked: boolean;
|
|
102
|
+
/** The id of the reason's hidden copy, for the control's `aria-describedby`. */
|
|
103
|
+
reasonId: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* {@link Button}'s `commit` + `disabledReason`, for a control that IS its own commit — a
|
|
107
|
+
* switch, a checkbox or a select that saves on change, a list row whose click writes.
|
|
108
|
+
* The lock's reason wins over the control's own (`useCommitReason`).
|
|
109
|
+
*/
|
|
110
|
+
declare function useLockReason(commit: boolean | undefined, disabledReason: ReactNode): LockReason;
|
|
111
|
+
/**
|
|
112
|
+
* The kit {@link Tooltip} around a locked control, with a `hidden` copy of the reason for
|
|
113
|
+
* the control's `aria-describedby` — Button's anatomy. The control goes in a FRAGMENT so
|
|
114
|
+
* the Tooltip does not also clone its bubble's id into the description: the hidden copy
|
|
115
|
+
* already describes it, and the same sentence twice would be read on every focus.
|
|
116
|
+
*
|
|
117
|
+
* `className` is the wrapper's, which becomes the outermost element while locked — a
|
|
118
|
+
* control moves its own layout classes here so a lock does not reflow the form.
|
|
119
|
+
*/
|
|
120
|
+
declare function LockedReason({ lock, className, children, }: {
|
|
121
|
+
lock: LockReason;
|
|
122
|
+
className?: string;
|
|
123
|
+
children: ReactNode;
|
|
124
|
+
}): react.JSX.Element;
|
|
125
|
+
/**
|
|
126
|
+
* Set by {@link CheckboxGroup}: its legend carries the one required star, so a box
|
|
127
|
+
* inside it keeps the native `required` (a form still refuses an empty set) but draws no
|
|
128
|
+
* star of its own — four stars on four languages would read as four separate demands.
|
|
129
|
+
* ChoiceCardGroup's `InGroupContext`, for the plain checkbox.
|
|
130
|
+
*/
|
|
131
|
+
declare const RequiredStarOnLegend: react.Context<boolean>;
|
|
132
|
+
/** `undefined`, `null`, `false` and `""` are what a caller's `cond && "…"` evaluates to
|
|
133
|
+
* when there is nothing to say; none of them is content. A numeric `0` is. */
|
|
134
|
+
declare function hasContent(node: ReactNode): boolean;
|
|
135
|
+
interface FieldMessages {
|
|
136
|
+
/** `invalid`, a message, or an `aria-invalid` from outside (a form library). */
|
|
137
|
+
isInvalid: boolean;
|
|
138
|
+
/** The caller's description, then the caption, then the error — the standing
|
|
139
|
+
* advice before the news, as `useFieldError` in ui.tsx orders them. */
|
|
140
|
+
describedBy: string | undefined;
|
|
141
|
+
/** The caption and the error, for under the field; `null` when there is neither. */
|
|
142
|
+
below: ReactNode;
|
|
143
|
+
/** The hint for the LABEL line (a `FieldHint`), or `undefined` when the hint is a
|
|
144
|
+
* caption or there is none. */
|
|
145
|
+
labelHint: ReactNode;
|
|
146
|
+
}
|
|
147
|
+
/** `hint` / `error` / `invalid` resolved the way `Select` resolves them. */
|
|
148
|
+
declare function useFieldMessages({ hint, error, invalid, describedBy, ariaInvalid, }: {
|
|
149
|
+
hint?: ReactNode;
|
|
150
|
+
error?: ReactNode;
|
|
151
|
+
invalid?: boolean;
|
|
152
|
+
describedBy?: string;
|
|
153
|
+
ariaInvalid?: unknown;
|
|
154
|
+
}): FieldMessages;
|
|
155
|
+
/**
|
|
156
|
+
* The field and what is under it, as one box — `FieldGroup` in ui.tsx, for the same
|
|
157
|
+
* reasons written there. Nothing of its own while there is nothing under the field and
|
|
158
|
+
* the caller passed no `error` key (the DOM is exactly what it was before these props);
|
|
159
|
+
* once `error` is passed the box is kept even with no message, so the trigger is never
|
|
160
|
+
* reparented — and remounted, losing focus — as the message comes and goes.
|
|
161
|
+
*/
|
|
162
|
+
declare function FieldBox({ below, reserve, children, }: {
|
|
163
|
+
below: ReactNode;
|
|
164
|
+
reserve: boolean;
|
|
165
|
+
children: ReactNode;
|
|
166
|
+
}): react.JSX.Element;
|
|
167
|
+
/**
|
|
168
|
+
* The static label in a trigger field's top strip — a {@link FieldLabel}, or, with a
|
|
169
|
+
* `FieldHint`, the label and its "?" laid out on ONE flex row (dev#468: two absolute
|
|
170
|
+
* offsets guessing at the same baseline put the "?" three pixels above its word).
|
|
171
|
+
* `FloatingField`'s static-label row, except that it stops at `end-9`: the date and
|
|
172
|
+
* month triggers keep that column (`pe-9`) for the calendar glyph or the clear ×, and a
|
|
173
|
+
* long label truncates before it rather than putting the "?" on top of it.
|
|
174
|
+
*/
|
|
175
|
+
declare function FieldLabelLine({ label, hint }: {
|
|
176
|
+
label: ReactNode;
|
|
177
|
+
hint?: ReactNode;
|
|
178
|
+
}): react.JSX.Element;
|
|
179
|
+
/**
|
|
180
|
+
* How tall the strip over a bare group is — the values {@link FieldStrip}'s `pad` takes,
|
|
181
|
+
* public since 0.23 (keksdose G8), so the names say what the CONTENT needs rather than
|
|
182
|
+
* which kit group first needed it (until 0.23 they were `toggle` and `tiles`).
|
|
183
|
+
*
|
|
184
|
+
* - `"field"` — 16px. keksdose's `ClearedStatusPicker` (live #288 rework, #431,
|
|
185
|
+
* measured): *"16px of label strip plus a 26px group is exactly a labelled Input"*,
|
|
186
|
+
* so a status group in a row of 42px fields ends where they end. The label's line
|
|
187
|
+
* box runs into the group's top hairline, but the ACTIVE fill — the thing feedback
|
|
188
|
+
* #117 was about — starts at 19px, below the label's 17.75px. For content with a
|
|
189
|
+
* border and an inset of its own; ToggleGroup's `labelPlacement="strip"`.
|
|
190
|
+
* - `"clear"` — 20px, keksdose's `FlagPicker` and location field (`pt-5`). A tile has
|
|
191
|
+
* no inset fill: its selection IS its frame, a 2px dark border-and-ring from the
|
|
192
|
+
* tile's top edge, so the tile has to start below the whole label line — and so does
|
|
193
|
+
* anything else with no inset: a map, a row of buttons, a line of text. SwatchPicker,
|
|
194
|
+
* IconPicker, and FieldStrip's default.
|
|
195
|
+
*/
|
|
196
|
+
declare const LABEL_STRIP_PAD: {
|
|
197
|
+
readonly field: "pt-4";
|
|
198
|
+
readonly clear: "pt-5";
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* The 11px label (and its `FieldHint`) over a group that is NOT a field — keksdose K2:
|
|
202
|
+
* a segmented control or a row of swatches standing in a form row beside labelled
|
|
203
|
+
* inputs, whose label must sit on the same line as theirs (`top-1`, `inset-x-3`, the
|
|
204
|
+
* type of a field's static label) without the group growing a field's border round
|
|
205
|
+
* it. `id` goes on the label so the group can be `aria-labelledby` it.
|
|
206
|
+
*
|
|
207
|
+
* `relative` is load-bearing twice over: the row is positioned against it, and a
|
|
208
|
+
* caller's `sr-only` label cannot escape it (sr-only-containment).
|
|
209
|
+
*
|
|
210
|
+
* Any other attribute (`rest`) lands on the wrapper — {@link FieldStrip} makes it the
|
|
211
|
+
* `role="group"` its label names. The kit's own groups pass none, so their wrapper is
|
|
212
|
+
* the bare box it was in 0.22.
|
|
213
|
+
*/
|
|
214
|
+
declare function LabelStrip({ labelId, label, hint, disabled, pad, className, children, ...rest }: Omit<ComponentPropsWithoutRef<"div">, "children"> & {
|
|
215
|
+
labelId: string;
|
|
216
|
+
label: ReactNode;
|
|
217
|
+
hint?: ReactNode;
|
|
218
|
+
/** Dim the label with its group, as `peer-disabled:` dims a field's own. */
|
|
219
|
+
disabled?: boolean;
|
|
220
|
+
pad: keyof typeof LABEL_STRIP_PAD;
|
|
221
|
+
children: ReactNode;
|
|
222
|
+
}): react.JSX.Element;
|
|
223
|
+
|
|
224
|
+
export { EndHintRow, FIELD_CAPTION_CLASS, FLOATING_LABEL_STATIC, FieldBox, FieldCaption, type FieldHintParts, FieldLabel, FieldLabelLine, type FieldLabelProps, type FieldMessages, LABEL_IN_ROW, LABEL_STRIP_PAD, LabelStrip, type LockReason, LockedReason, RequiredStarOnLegend, STATIC_LABEL_TYPE, StaticLabelRow, hasContent, isTextHint, useFieldHint, useFieldMessages, useLockReason };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useId } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Tooltip } from "./tooltip.js";
|
|
6
|
+
import { useCommitReason } from "./write-lock.js";
|
|
7
|
+
import { hasMessage, mergeDescribedBy } from "./choice-parts.js";
|
|
8
|
+
const STATIC_LABEL_TYPE = "text-[11px] leading-tight text-[var(--text-muted)] peer-disabled:opacity-50";
|
|
9
|
+
const FLOATING_LABEL_STATIC = cn(
|
|
10
|
+
"pointer-events-none absolute start-3 top-1",
|
|
11
|
+
STATIC_LABEL_TYPE,
|
|
12
|
+
"max-w-[calc(100%-1.5rem)] truncate"
|
|
13
|
+
);
|
|
14
|
+
function FieldLabel({ children, className, ...rest }) {
|
|
15
|
+
return /* @__PURE__ */ jsx("span", { ...rest, className: cn(FLOATING_LABEL_STATIC, "z-10", className), children });
|
|
16
|
+
}
|
|
17
|
+
function isTextHint(hint) {
|
|
18
|
+
return typeof hint === "string" && hint !== "" || typeof hint === "number";
|
|
19
|
+
}
|
|
20
|
+
const FIELD_CAPTION_CLASS = "mt-1 text-[11px] leading-tight text-[var(--text-muted)]";
|
|
21
|
+
function useFieldHint(hint, describedBy) {
|
|
22
|
+
const captionId = useId();
|
|
23
|
+
const text = isTextHint(hint);
|
|
24
|
+
return {
|
|
25
|
+
describedBy: text ? mergeDescribedBy(describedBy, captionId) : describedBy,
|
|
26
|
+
captionId: text ? captionId : void 0,
|
|
27
|
+
captionText: text ? hint : void 0,
|
|
28
|
+
labelHint: !text && hasMessage(hint) ? hint : void 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function FieldCaption({ parts, className }) {
|
|
32
|
+
if (parts.captionText === void 0) return null;
|
|
33
|
+
return /* @__PURE__ */ jsx("p", { id: parts.captionId, className: cn(FIELD_CAPTION_CLASS, className), children: parts.captionText });
|
|
34
|
+
}
|
|
35
|
+
const LABEL_IN_ROW = "static min-w-0";
|
|
36
|
+
function StaticLabelRow({ hint, children }) {
|
|
37
|
+
return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1", children: [
|
|
38
|
+
children,
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: "pointer-events-auto flex shrink-0 items-center", children: hint })
|
|
40
|
+
] });
|
|
41
|
+
}
|
|
42
|
+
function EndHintRow({
|
|
43
|
+
hint,
|
|
44
|
+
className,
|
|
45
|
+
children
|
|
46
|
+
}) {
|
|
47
|
+
if (hint === void 0) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
48
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5", className), children: [
|
|
49
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: hint })
|
|
51
|
+
] });
|
|
52
|
+
}
|
|
53
|
+
function useLockReason(commit, disabledReason) {
|
|
54
|
+
const reason = useCommitReason(commit, disabledReason);
|
|
55
|
+
const reasonId = useId();
|
|
56
|
+
return { reason, locked: hasMessage(reason), reasonId };
|
|
57
|
+
}
|
|
58
|
+
function LockedReason({
|
|
59
|
+
lock,
|
|
60
|
+
className,
|
|
61
|
+
children
|
|
62
|
+
}) {
|
|
63
|
+
return /* @__PURE__ */ jsx(Tooltip, { label: lock.reason, className, children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
64
|
+
children,
|
|
65
|
+
/* @__PURE__ */ jsx("span", { id: lock.reasonId, hidden: true, children: lock.reason })
|
|
66
|
+
] }) });
|
|
67
|
+
}
|
|
68
|
+
const RequiredStarOnLegend = createContext(false);
|
|
69
|
+
function hasContent(node) {
|
|
70
|
+
return node !== void 0 && node !== null && node !== false && node !== "";
|
|
71
|
+
}
|
|
72
|
+
const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";
|
|
73
|
+
function useFieldMessages({
|
|
74
|
+
hint,
|
|
75
|
+
error,
|
|
76
|
+
invalid,
|
|
77
|
+
describedBy,
|
|
78
|
+
ariaInvalid
|
|
79
|
+
}) {
|
|
80
|
+
const hintId = useId();
|
|
81
|
+
const errorId = useId();
|
|
82
|
+
const textHint = isTextHint(hint);
|
|
83
|
+
const hasError = hasContent(error);
|
|
84
|
+
return {
|
|
85
|
+
isInvalid: Boolean(invalid) || hasError || ariaInvalid === true || ariaInvalid === "true",
|
|
86
|
+
describedBy: [describedBy, textHint && hintId, hasError && errorId].filter(Boolean).join(" ") || void 0,
|
|
87
|
+
below: textHint || hasError ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
88
|
+
textHint && /* @__PURE__ */ jsx("p", { id: hintId, className: FIELD_CAPTION_CLASS, children: hint }),
|
|
89
|
+
hasError && /* @__PURE__ */ jsx("p", { id: errorId, className: FIELD_ERROR_CLASS, children: error })
|
|
90
|
+
] }) : null,
|
|
91
|
+
labelHint: !textHint && hasContent(hint) ? hint : void 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function FieldBox({
|
|
95
|
+
below,
|
|
96
|
+
reserve,
|
|
97
|
+
children
|
|
98
|
+
}) {
|
|
99
|
+
if (below === null && !reserve) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
100
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
101
|
+
children,
|
|
102
|
+
below
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
function FieldLabelLine({ label, hint }) {
|
|
106
|
+
if (!hasContent(hint)) return /* @__PURE__ */ jsx(FieldLabel, { children: label });
|
|
107
|
+
return (
|
|
108
|
+
// `z-10` as FieldLabel has it: the trigger after it is `relative` and would paint
|
|
109
|
+
// over the row (and take the "?"'s hover) otherwise.
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1", children: [
|
|
111
|
+
/* @__PURE__ */ jsx(FieldLabel, { className: "static min-w-0 max-w-full", children: label }),
|
|
112
|
+
/* @__PURE__ */ jsx("span", { className: "pointer-events-auto flex shrink-0 items-center", children: hint })
|
|
113
|
+
] })
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const LABEL_STRIP_PAD = { field: "pt-4", clear: "pt-5" };
|
|
117
|
+
function LabelStrip({
|
|
118
|
+
labelId,
|
|
119
|
+
label,
|
|
120
|
+
hint,
|
|
121
|
+
disabled,
|
|
122
|
+
pad,
|
|
123
|
+
className,
|
|
124
|
+
children,
|
|
125
|
+
...rest
|
|
126
|
+
}) {
|
|
127
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, className: cn("relative min-w-0", LABEL_STRIP_PAD[pad], className), children: [
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-x-3 top-1 flex items-center gap-1", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(FieldLabel, { id: labelId, className: cn("static min-w-0 max-w-full", disabled && "opacity-50"), children: label }),
|
|
130
|
+
hasContent(hint) && /* @__PURE__ */ jsx("span", { className: "pointer-events-auto flex shrink-0 items-center", children: hint })
|
|
131
|
+
] }),
|
|
132
|
+
children
|
|
133
|
+
] });
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
EndHintRow,
|
|
137
|
+
FIELD_CAPTION_CLASS,
|
|
138
|
+
FLOATING_LABEL_STATIC,
|
|
139
|
+
FieldBox,
|
|
140
|
+
FieldCaption,
|
|
141
|
+
FieldLabel,
|
|
142
|
+
FieldLabelLine,
|
|
143
|
+
LABEL_IN_ROW,
|
|
144
|
+
LABEL_STRIP_PAD,
|
|
145
|
+
LabelStrip,
|
|
146
|
+
LockedReason,
|
|
147
|
+
RequiredStarOnLegend,
|
|
148
|
+
STATIC_LABEL_TYPE,
|
|
149
|
+
StaticLabelRow,
|
|
150
|
+
hasContent,
|
|
151
|
+
isTextHint,
|
|
152
|
+
useFieldHint,
|
|
153
|
+
useFieldMessages,
|
|
154
|
+
useLockReason
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=field-parts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/field-parts.tsx"],"sourcesContent":["import { createContext, useId } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason } from \"./write-lock\";\nimport { hasMessage, mergeDescribedBy } from \"./choice-parts\";\n\n/**\n * The pieces of a field's anatomy the kit's fields share — the static label, the hint\n * (caption or \"?\"), the caption and the error under a field, the label strip over a\n * bare group, the write lock on a control that saves itself. Private to the package —\n * not re-exported from the barrel, except `FieldLabel` and `FLOATING_LABEL_STATIC`,\n * which ui.tsx re-exports as it always has.\n *\n * One file since 0.23: until then the pickers' half (`field-anatomy.tsx`: the date and\n * month pickers, whose trigger is a button in a wrapper of their own, and the groups\n * with no field box) sat beside this one with its own copies of `isTextHint` and the\n * caption class (keksdose K4: \"uniform field anatomy … today it is a patchwork\").\n *\n * Nothing here imports `ui.tsx`: `ui.tsx` imports this, and so do the combobox family,\n * the pickers and the native choice controls — the lint's no-cycle rule holds it.\n */\n\n/* ── the static label ─────────────────────────────────────────────────────── */\n\n// The TYPE of the small static label, without any placement. Split out so the\n// label and anything sharing its line (see `hint` on {@link FloatingField}) are\n// laid out by one flex row instead of by two absolute offsets guessing at the\n// same baseline — which is what put dev#468's \"?\" three pixels above the word it\n// belongs to.\nexport const STATIC_LABEL_TYPE =\n \"text-[11px] leading-tight text-[var(--text-muted)] peer-disabled:opacity-50\";\n\n// A field that always has a value (select / dropdown trigger) keeps the label\n// permanently in the floated position — small, in the top strip, value below.\nexport const FLOATING_LABEL_STATIC = cn(\n \"pointer-events-none absolute start-3 top-1\",\n STATIC_LABEL_TYPE,\n \"max-w-[calc(100%-1.5rem)] truncate\",\n);\n\n/**\n * The floating label for a custom-dropdown trigger (a `<span>`, since the trigger is a\n * button not a labelable input). Same placement as {@link FloatingField}'s static label,\n * so every labelled field lines up. Render inside a `relative` wrapper, before the trigger.\n */\nexport interface FieldLabelProps extends ComponentPropsWithoutRef<\"span\"> {\n children: ReactNode;\n}\n\nexport function FieldLabel({ children, className, ...rest }: FieldLabelProps) {\n return (\n <span {...rest} className={cn(FLOATING_LABEL_STATIC, \"z-10\", className)}>\n {children}\n </span>\n );\n}\n\n/* ── hint ─────────────────────────────────────────────────────────────────── */\n\n/**\n * Whether a `hint` is a CAPTION (plain text: a non-empty string or a number) or\n * something for the label line (a {@link FieldHint} \"?\", or any other node).\n *\n * The rule {@link Select} and `NumberInput` already keep: the label line is 11px of\n * strip shared with the label, and a sentence placed there ran over the label and into\n * the value; a \"?\" under the field would be a button floating free of the label it\n * explains.\n */\nexport function isTextHint(hint: ReactNode): hint is string | number {\n return (typeof hint === \"string\" && hint !== \"\") || typeof hint === \"number\";\n}\n\n/** The caption under a field — Select's and NumberInput's type, so every field's\n * standing advice reads alike. */\nexport const FIELD_CAPTION_CLASS = \"mt-1 text-[11px] leading-tight text-[var(--text-muted)]\";\n\nexport interface FieldHintParts {\n /** The caller's `aria-describedby` with the caption's id appended — standing\n * advice first; the error (the news) is appended after this by the field. */\n describedBy: string | undefined;\n /** The caption's id while there is one. */\n captionId: string | undefined;\n /** The caption itself, or `undefined` for a hint that is not text. */\n captionText: string | number | undefined;\n /** A non-text hint (a FieldHint) for the label line — or, on a field with no label\n * line, the end edge outside the box. `undefined` when there is none. */\n labelHint: ReactNode | undefined;\n}\n\n/**\n * Split a field's `hint` into the caption under it (attached with `aria-describedby`)\n * or the \"?\" on its label line. See {@link isTextHint}.\n */\nexport function useFieldHint(hint: ReactNode, describedBy: string | undefined): FieldHintParts {\n const captionId = useId();\n const text = isTextHint(hint);\n return {\n describedBy: text ? mergeDescribedBy(describedBy, captionId) : describedBy,\n captionId: text ? captionId : undefined,\n captionText: text ? hint : undefined,\n labelHint: !text && hasMessage(hint) ? hint : undefined,\n };\n}\n\n/** The caption under a field, as {@link Select} draws its own. */\nexport function FieldCaption({ parts, className }: { parts: FieldHintParts; className?: string }) {\n if (parts.captionText === undefined) return null;\n return (\n <p id={parts.captionId} className={cn(FIELD_CAPTION_CLASS, className)}>\n {parts.captionText}\n </p>\n );\n}\n\n/**\n * Classes that take a static floating label (`FLOATING_LABEL_STATIC`: `FieldLabel`,\n * `ComboboxFieldLabel`) out of its own absolute slot and into a {@link StaticLabelRow}.\n * `static` wins over `absolute` through `cn`'s merge; `min-w-0` lets it truncate in\n * the row.\n */\nexport const LABEL_IN_ROW = \"static min-w-0\";\n\n/**\n * A static label and its \"?\" on one line, in the top strip of a field that lays out its\n * own label — the combobox family, which cannot use {@link FloatingField} because the\n * trigger, the anchored panel and the label hang off a box of their own.\n *\n * FloatingField's static row, with one difference: it stops at `end-9`, the column the\n * chevron (or the clear \"×\") takes, so a long label truncates before it rather than\n * putting the \"?\" on top of the chevron. The hint keeps its width; the label gives way.\n */\nexport function StaticLabelRow({ hint, children }: { hint: ReactNode; children: ReactNode }) {\n return (\n <div className=\"pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1\">\n {children}\n <span className=\"pointer-events-auto flex shrink-0 items-center\">{hint}</span>\n </div>\n );\n}\n\n/**\n * A field with no label line and a FieldHint \"?\": the hint at the end edge, OUTSIDE the\n * box, as an unlabelled {@link Select} has placed it since 0.15.5. Without a hint the\n * field is returned exactly as it was, so the wrapper costs nothing where it does not\n * apply.\n */\nexport function EndHintRow({\n hint,\n className,\n children,\n}: {\n hint: ReactNode | undefined;\n className?: string;\n children: ReactNode;\n}) {\n if (hint === undefined) return <>{children}</>;\n return (\n <div className={cn(\"flex items-center gap-1.5\", className)}>\n <div className=\"min-w-0 flex-1\">{children}</div>\n <span className=\"flex shrink-0 items-center\">{hint}</span>\n </div>\n );\n}\n\n/* ── the write lock on a control that saves itself (keksdose K3) ───────────── */\n\nexport interface LockReason {\n /** The reason in force — the lock's (with `commit` under a locked\n * `WriteLockProvider`), else the control's own `disabledReason`. */\n reason: ReactNode;\n /** There is a reason: the control is locked the focusable way. */\n locked: boolean;\n /** The id of the reason's hidden copy, for the control's `aria-describedby`. */\n reasonId: string;\n}\n\n/**\n * {@link Button}'s `commit` + `disabledReason`, for a control that IS its own commit — a\n * switch, a checkbox or a select that saves on change, a list row whose click writes.\n * The lock's reason wins over the control's own (`useCommitReason`).\n */\nexport function useLockReason(commit: boolean | undefined, disabledReason: ReactNode): LockReason {\n const reason = useCommitReason(commit, disabledReason);\n const reasonId = useId();\n return { reason, locked: hasMessage(reason), reasonId };\n}\n\n/**\n * The kit {@link Tooltip} around a locked control, with a `hidden` copy of the reason for\n * the control's `aria-describedby` — Button's anatomy. The control goes in a FRAGMENT so\n * the Tooltip does not also clone its bubble's id into the description: the hidden copy\n * already describes it, and the same sentence twice would be read on every focus.\n *\n * `className` is the wrapper's, which becomes the outermost element while locked — a\n * control moves its own layout classes here so a lock does not reflow the form.\n */\nexport function LockedReason({\n lock,\n className,\n children,\n}: {\n lock: LockReason;\n className?: string;\n children: ReactNode;\n}) {\n return (\n <Tooltip label={lock.reason} className={className}>\n <>\n {children}\n <span id={lock.reasonId} hidden>\n {lock.reason}\n </span>\n </>\n </Tooltip>\n );\n}\n\n/* ── groups ───────────────────────────────────────────────────────────────── */\n\n/**\n * Set by {@link CheckboxGroup}: its legend carries the one required star, so a box\n * inside it keeps the native `required` (a form still refuses an empty set) but draws no\n * star of its own — four stars on four languages would read as four separate demands.\n * ChoiceCardGroup's `InGroupContext`, for the plain checkbox.\n */\nexport const RequiredStarOnLegend = createContext(false);\n\n/* ── fields not built on FloatingField: pickers and bare groups ──────────── */\n\n/** `undefined`, `null`, `false` and `\"\"` are what a caller's `cond && \"…\"` evaluates to\n * when there is nothing to say; none of them is content. A numeric `0` is. */\nexport function hasContent(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\nconst FIELD_ERROR_CLASS = \"mt-1 text-[11px] leading-tight text-[var(--danger)]\";\n\nexport interface FieldMessages {\n /** `invalid`, a message, or an `aria-invalid` from outside (a form library). */\n isInvalid: boolean;\n /** The caller's description, then the caption, then the error — the standing\n * advice before the news, as `useFieldError` in ui.tsx orders them. */\n describedBy: string | undefined;\n /** The caption and the error, for under the field; `null` when there is neither. */\n below: ReactNode;\n /** The hint for the LABEL line (a `FieldHint`), or `undefined` when the hint is a\n * caption or there is none. */\n labelHint: ReactNode;\n}\n\n/** `hint` / `error` / `invalid` resolved the way `Select` resolves them. */\nexport function useFieldMessages({\n hint,\n error,\n invalid,\n describedBy,\n ariaInvalid,\n}: {\n hint?: ReactNode;\n error?: ReactNode;\n invalid?: boolean;\n describedBy?: string;\n ariaInvalid?: unknown;\n}): FieldMessages {\n const hintId = useId();\n const errorId = useId();\n const textHint = isTextHint(hint);\n const hasError = hasContent(error);\n return {\n isInvalid: Boolean(invalid) || hasError || ariaInvalid === true || ariaInvalid === \"true\",\n describedBy:\n [describedBy, textHint && hintId, hasError && errorId].filter(Boolean).join(\" \") || undefined,\n below:\n textHint || hasError ? (\n <>\n {textHint && (\n <p id={hintId} className={FIELD_CAPTION_CLASS}>\n {hint}\n </p>\n )}\n {hasError && (\n <p id={errorId} className={FIELD_ERROR_CLASS}>\n {error}\n </p>\n )}\n </>\n ) : null,\n labelHint: !textHint && hasContent(hint) ? hint : undefined,\n };\n}\n\n/**\n * The field and what is under it, as one box — `FieldGroup` in ui.tsx, for the same\n * reasons written there. Nothing of its own while there is nothing under the field and\n * the caller passed no `error` key (the DOM is exactly what it was before these props);\n * once `error` is passed the box is kept even with no message, so the trigger is never\n * reparented — and remounted, losing focus — as the message comes and goes.\n */\nexport function FieldBox({\n below,\n reserve,\n children,\n}: {\n below: ReactNode;\n reserve: boolean;\n children: ReactNode;\n}) {\n if (below === null && !reserve) return <>{children}</>;\n return (\n <div>\n {children}\n {below}\n </div>\n );\n}\n\n/**\n * The static label in a trigger field's top strip — a {@link FieldLabel}, or, with a\n * `FieldHint`, the label and its \"?\" laid out on ONE flex row (dev#468: two absolute\n * offsets guessing at the same baseline put the \"?\" three pixels above its word).\n * `FloatingField`'s static-label row, except that it stops at `end-9`: the date and\n * month triggers keep that column (`pe-9`) for the calendar glyph or the clear ×, and a\n * long label truncates before it rather than putting the \"?\" on top of it.\n */\nexport function FieldLabelLine({ label, hint }: { label: ReactNode; hint?: ReactNode }) {\n if (!hasContent(hint)) return <FieldLabel>{label}</FieldLabel>;\n return (\n // `z-10` as FieldLabel has it: the trigger after it is `relative` and would paint\n // over the row (and take the \"?\"'s hover) otherwise.\n <div className=\"pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1\">\n <FieldLabel className=\"static min-w-0 max-w-full\">{label}</FieldLabel>\n <span className=\"pointer-events-auto flex shrink-0 items-center\">{hint}</span>\n </div>\n );\n}\n\n/**\n * How tall the strip over a bare group is — the values {@link FieldStrip}'s `pad` takes,\n * public since 0.23 (keksdose G8), so the names say what the CONTENT needs rather than\n * which kit group first needed it (until 0.23 they were `toggle` and `tiles`).\n *\n * - `\"field\"` — 16px. keksdose's `ClearedStatusPicker` (live #288 rework, #431,\n * measured): *\"16px of label strip plus a 26px group is exactly a labelled Input\"*,\n * so a status group in a row of 42px fields ends where they end. The label's line\n * box runs into the group's top hairline, but the ACTIVE fill — the thing feedback\n * #117 was about — starts at 19px, below the label's 17.75px. For content with a\n * border and an inset of its own; ToggleGroup's `labelPlacement=\"strip\"`.\n * - `\"clear\"` — 20px, keksdose's `FlagPicker` and location field (`pt-5`). A tile has\n * no inset fill: its selection IS its frame, a 2px dark border-and-ring from the\n * tile's top edge, so the tile has to start below the whole label line — and so does\n * anything else with no inset: a map, a row of buttons, a line of text. SwatchPicker,\n * IconPicker, and FieldStrip's default.\n */\nexport const LABEL_STRIP_PAD = { field: \"pt-4\", clear: \"pt-5\" } as const;\n\n/**\n * The 11px label (and its `FieldHint`) over a group that is NOT a field — keksdose K2:\n * a segmented control or a row of swatches standing in a form row beside labelled\n * inputs, whose label must sit on the same line as theirs (`top-1`, `inset-x-3`, the\n * type of a field's static label) without the group growing a field's border round\n * it. `id` goes on the label so the group can be `aria-labelledby` it.\n *\n * `relative` is load-bearing twice over: the row is positioned against it, and a\n * caller's `sr-only` label cannot escape it (sr-only-containment).\n *\n * Any other attribute (`rest`) lands on the wrapper — {@link FieldStrip} makes it the\n * `role=\"group\"` its label names. The kit's own groups pass none, so their wrapper is\n * the bare box it was in 0.22.\n */\nexport function LabelStrip({\n labelId,\n label,\n hint,\n disabled,\n pad,\n className,\n children,\n ...rest\n}: Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> & {\n labelId: string;\n label: ReactNode;\n hint?: ReactNode;\n /** Dim the label with its group, as `peer-disabled:` dims a field's own. */\n disabled?: boolean;\n pad: keyof typeof LABEL_STRIP_PAD;\n children: ReactNode;\n}) {\n return (\n <div {...rest} className={cn(\"relative min-w-0\", LABEL_STRIP_PAD[pad], className)}>\n <div className=\"pointer-events-none absolute inset-x-3 top-1 flex items-center gap-1\">\n {/* `static`: FieldLabel positions itself absolutely; here the ROW is\n positioned, so the label and its \"?\" are centred on one line by the flex\n layout (dev#468). */}\n <FieldLabel id={labelId} className={cn(\"static min-w-0 max-w-full\", disabled && \"opacity-50\")}>\n {label}\n </FieldLabel>\n {hasContent(hint) && <span className=\"pointer-events-auto flex shrink-0 items-center\">{hint}</span>}\n </div>\n {children}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How far down the content starts, under the 11px label — see {@link FieldStripProps.pad}.
|
|
6
|
+
*
|
|
7
|
+
* - `"clear"` (default) — 20px: the content starts below the label's whole line box.
|
|
8
|
+
* - `"field"` — 16px: the strip plus a 26px bordered control is exactly a labelled
|
|
9
|
+
* {@link Input}'s 42px.
|
|
10
|
+
*/
|
|
11
|
+
type FieldStripPad = "clear" | "field";
|
|
12
|
+
/** What a {@link FieldStrip} hands render-prop children. */
|
|
13
|
+
interface FieldStripIds {
|
|
14
|
+
/** The label's id — for content that names ITSELF by the label (`aria-labelledby`)
|
|
15
|
+
* under `group={false}`, or anything else that has to point at it. */
|
|
16
|
+
labelId: string;
|
|
17
|
+
/** The ids of the caption and the error while they are on screen, else `undefined`.
|
|
18
|
+
* The group is described by them already; spread this on the content under
|
|
19
|
+
* `group={false}`, where there is no group to describe. */
|
|
20
|
+
describedBy: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
interface FieldStripProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
|
|
23
|
+
/** The 11px static label, on the line a labelled field's label sits on. */
|
|
24
|
+
label: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Field anatomy, as on {@link Input}: plain text is a caption under the content (and
|
|
27
|
+
* describes the group); a {@link FieldHint} "?" rides the label line.
|
|
28
|
+
*/
|
|
29
|
+
hint?: ReactNode;
|
|
30
|
+
/** A message under the content, in `--danger`, describing the group. `null`,
|
|
31
|
+
* `false` and `""` are no error. The content's own controls are the caller's to
|
|
32
|
+
* mark `aria-invalid`; a `group` cannot be. */
|
|
33
|
+
error?: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Dims the label with the content, as a disabled field's label dims. The content is
|
|
36
|
+
* the caller's: disable (or hide) its controls yourself — keksdose's location cell
|
|
37
|
+
* turns its map into a picture and drops its buttons, which no flag here could guess.
|
|
38
|
+
*/
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* How tall the strip is — what the content needs, measured in keksdose (`pt-5` /
|
|
42
|
+
* `pt-4`, the 0.22 `LABEL_STRIP_PAD`):
|
|
43
|
+
*
|
|
44
|
+
* - `"clear"` (default) — 20px. The content starts below the label's whole line box
|
|
45
|
+
* (4px down, 13.75px tall). For content with no inset of its own: a map, a row of
|
|
46
|
+
* buttons, a pill, a line of text, a row of tiles whose selection is their frame
|
|
47
|
+
* (SwatchPicker, IconPicker).
|
|
48
|
+
* - `"field"` — 16px. The strip plus a 26px bordered control (1 + 2 + 20 + 2 + 1) is
|
|
49
|
+
* exactly a labelled Input's 42px, so the row's bottom edges line up. The label's
|
|
50
|
+
* line box runs 1.75px into the control's top hairline, so only content with an
|
|
51
|
+
* inset of its own keeps clear of it — the segmented group's 2px moat
|
|
52
|
+
* (ToggleGroup's `labelPlacement="strip"`).
|
|
53
|
+
*
|
|
54
|
+
* A responsive strip is a `className`: it merges after the pad, so keksdose's status
|
|
55
|
+
* cell writes `className="pt-5 @3xl:pt-4"` (20px on a phone, 16px in the desktop row).
|
|
56
|
+
*/
|
|
57
|
+
pad?: FieldStripPad;
|
|
58
|
+
/**
|
|
59
|
+
* The wrapper is a `role="group"` named by the label (default), so a screen reader
|
|
60
|
+
* entering the map or the first button hears "Location, group" — what the visible
|
|
61
|
+
* label tells a sighted reader the content is about.
|
|
62
|
+
*
|
|
63
|
+
* `false` for content that is a labelled group ITSELF (a `role="radiogroup"` of the
|
|
64
|
+
* app's own, a TileRadioGroup's container, a fieldset): a group inside a group, both
|
|
65
|
+
* named "Pattern", is read twice. The wrapper is then a plain box, and the content
|
|
66
|
+
* takes the name from the render-prop's `labelId` (and the caption and error from
|
|
67
|
+
* its `describedBy`).
|
|
68
|
+
*/
|
|
69
|
+
group?: boolean;
|
|
70
|
+
/** The content. A function receives {@link FieldStripIds}. */
|
|
71
|
+
children: ReactNode | ((ids: FieldStripIds) => ReactNode);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The kit's label strip for content of your own — keksdose G8.
|
|
75
|
+
*
|
|
76
|
+
* 0.22 gave three groups a strip label (keksdose K2): ToggleGroup's
|
|
77
|
+
* `labelPlacement="strip"`, SwatchPicker's and IconPicker's `label`. Each draws the
|
|
78
|
+
* 11px label (and its `FieldHint`) on the same line as the labelled fields beside it,
|
|
79
|
+
* over content WITHOUT a field's border round it. keksdose's transaction row has one
|
|
80
|
+
* cell left that is not one of those groups: the location — a map, a "Locate me" and a
|
|
81
|
+
* "Search" button — still built by hand as a `relative` cell, a floated `FieldLabel`
|
|
82
|
+
* and a `pt-5` box (transaction-location-field.tsx), and so is the reconciled-status
|
|
83
|
+
* stand-in (cleared-status-toggle.tsx, `ClearedStatusLocked`). This is the strip on its
|
|
84
|
+
* own, the same component the three groups draw theirs with:
|
|
85
|
+
*
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <FieldStrip label={t("transactions.location")} className={className}>
|
|
88
|
+
* <div className="space-y-2">
|
|
89
|
+
* <PinMap … />
|
|
90
|
+
* <div className="flex flex-wrap gap-2"><Button …>Locate me</Button>…</div>
|
|
91
|
+
* </div>
|
|
92
|
+
* </FieldStrip>
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* The label sits where a field's static label sits (`top-1`, `inset-x-3`, 11px in a
|
|
96
|
+
* `leading-tight` line); the content starts `pad` down, full width. `className` styles
|
|
97
|
+
* the wrapper — the grid or flex item in the caller's row — and merges after the pad.
|
|
98
|
+
*
|
|
99
|
+
* Not a {@link Field}: that one sets a 14px label ABOVE its control, in flow, for a form
|
|
100
|
+
* that labels its fields that way. This is for a row whose fields float their labels
|
|
101
|
+
* inside them, where a label above would be the only one of its kind.
|
|
102
|
+
*/
|
|
103
|
+
declare function FieldStrip({ label, hint, error, disabled, pad, group, className, children, ...rest }: FieldStripProps): react.JSX.Element;
|
|
104
|
+
|
|
105
|
+
export { FieldStrip, type FieldStripIds, type FieldStripPad, type FieldStripProps };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { LabelStrip, useFieldMessages } from "./field-parts.js";
|
|
5
|
+
function FieldStrip({
|
|
6
|
+
label,
|
|
7
|
+
hint,
|
|
8
|
+
error,
|
|
9
|
+
disabled = false,
|
|
10
|
+
pad = "clear",
|
|
11
|
+
group = true,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
...rest
|
|
15
|
+
}) {
|
|
16
|
+
const labelId = useId();
|
|
17
|
+
const messages = useFieldMessages({ hint, error });
|
|
18
|
+
const content = typeof children === "function" ? children({ labelId, describedBy: messages.describedBy }) : children;
|
|
19
|
+
const groupDescribedBy = [rest["aria-describedby"], messages.describedBy].filter(Boolean).join(" ") || void 0;
|
|
20
|
+
return /* @__PURE__ */ jsxs(
|
|
21
|
+
LabelStrip,
|
|
22
|
+
{
|
|
23
|
+
...rest,
|
|
24
|
+
...group ? {
|
|
25
|
+
role: "group",
|
|
26
|
+
// An explicit name wins, as on the kit's labelled groups.
|
|
27
|
+
"aria-labelledby": rest["aria-label"] === void 0 && rest["aria-labelledby"] === void 0 ? labelId : rest["aria-labelledby"],
|
|
28
|
+
"aria-describedby": groupDescribedBy
|
|
29
|
+
} : null,
|
|
30
|
+
labelId,
|
|
31
|
+
label,
|
|
32
|
+
hint: messages.labelHint,
|
|
33
|
+
disabled,
|
|
34
|
+
pad,
|
|
35
|
+
className,
|
|
36
|
+
children: [
|
|
37
|
+
content,
|
|
38
|
+
messages.below
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
FieldStrip
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=field-strip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/field-strip.tsx"],"sourcesContent":["import { useId } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { LabelStrip, useFieldMessages } from \"./field-parts\";\n\n/**\n * How far down the content starts, under the 11px label — see {@link FieldStripProps.pad}.\n *\n * - `\"clear\"` (default) — 20px: the content starts below the label's whole line box.\n * - `\"field\"` — 16px: the strip plus a 26px bordered control is exactly a labelled\n * {@link Input}'s 42px.\n */\nexport type FieldStripPad = \"clear\" | \"field\";\n\n/** What a {@link FieldStrip} hands render-prop children. */\nexport interface FieldStripIds {\n /** The label's id — for content that names ITSELF by the label (`aria-labelledby`)\n * under `group={false}`, or anything else that has to point at it. */\n labelId: string;\n /** The ids of the caption and the error while they are on screen, else `undefined`.\n * The group is described by them already; spread this on the content under\n * `group={false}`, where there is no group to describe. */\n describedBy: string | undefined;\n}\n\nexport interface FieldStripProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /** The 11px static label, on the line a labelled field's label sits on. */\n label: ReactNode;\n /**\n * Field anatomy, as on {@link Input}: plain text is a caption under the content (and\n * describes the group); a {@link FieldHint} \"?\" rides the label line.\n */\n hint?: ReactNode;\n /** A message under the content, in `--danger`, describing the group. `null`,\n * `false` and `\"\"` are no error. The content's own controls are the caller's to\n * mark `aria-invalid`; a `group` cannot be. */\n error?: ReactNode;\n /**\n * Dims the label with the content, as a disabled field's label dims. The content is\n * the caller's: disable (or hide) its controls yourself — keksdose's location cell\n * turns its map into a picture and drops its buttons, which no flag here could guess.\n */\n disabled?: boolean;\n /**\n * How tall the strip is — what the content needs, measured in keksdose (`pt-5` /\n * `pt-4`, the 0.22 `LABEL_STRIP_PAD`):\n *\n * - `\"clear\"` (default) — 20px. The content starts below the label's whole line box\n * (4px down, 13.75px tall). For content with no inset of its own: a map, a row of\n * buttons, a pill, a line of text, a row of tiles whose selection is their frame\n * (SwatchPicker, IconPicker).\n * - `\"field\"` — 16px. The strip plus a 26px bordered control (1 + 2 + 20 + 2 + 1) is\n * exactly a labelled Input's 42px, so the row's bottom edges line up. The label's\n * line box runs 1.75px into the control's top hairline, so only content with an\n * inset of its own keeps clear of it — the segmented group's 2px moat\n * (ToggleGroup's `labelPlacement=\"strip\"`).\n *\n * A responsive strip is a `className`: it merges after the pad, so keksdose's status\n * cell writes `className=\"pt-5 @3xl:pt-4\"` (20px on a phone, 16px in the desktop row).\n */\n pad?: FieldStripPad;\n /**\n * The wrapper is a `role=\"group\"` named by the label (default), so a screen reader\n * entering the map or the first button hears \"Location, group\" — what the visible\n * label tells a sighted reader the content is about.\n *\n * `false` for content that is a labelled group ITSELF (a `role=\"radiogroup\"` of the\n * app's own, a TileRadioGroup's container, a fieldset): a group inside a group, both\n * named \"Pattern\", is read twice. The wrapper is then a plain box, and the content\n * takes the name from the render-prop's `labelId` (and the caption and error from\n * its `describedBy`).\n */\n group?: boolean;\n /** The content. A function receives {@link FieldStripIds}. */\n children: ReactNode | ((ids: FieldStripIds) => ReactNode);\n}\n\n/**\n * The kit's label strip for content of your own — keksdose G8.\n *\n * 0.22 gave three groups a strip label (keksdose K2): ToggleGroup's\n * `labelPlacement=\"strip\"`, SwatchPicker's and IconPicker's `label`. Each draws the\n * 11px label (and its `FieldHint`) on the same line as the labelled fields beside it,\n * over content WITHOUT a field's border round it. keksdose's transaction row has one\n * cell left that is not one of those groups: the location — a map, a \"Locate me\" and a\n * \"Search\" button — still built by hand as a `relative` cell, a floated `FieldLabel`\n * and a `pt-5` box (transaction-location-field.tsx), and so is the reconciled-status\n * stand-in (cleared-status-toggle.tsx, `ClearedStatusLocked`). This is the strip on its\n * own, the same component the three groups draw theirs with:\n *\n * ```tsx\n * <FieldStrip label={t(\"transactions.location\")} className={className}>\n * <div className=\"space-y-2\">\n * <PinMap … />\n * <div className=\"flex flex-wrap gap-2\"><Button …>Locate me</Button>…</div>\n * </div>\n * </FieldStrip>\n * ```\n *\n * The label sits where a field's static label sits (`top-1`, `inset-x-3`, 11px in a\n * `leading-tight` line); the content starts `pad` down, full width. `className` styles\n * the wrapper — the grid or flex item in the caller's row — and merges after the pad.\n *\n * Not a {@link Field}: that one sets a 14px label ABOVE its control, in flow, for a form\n * that labels its fields that way. This is for a row whose fields float their labels\n * inside them, where a label above would be the only one of its kind.\n */\nexport function FieldStrip({\n label,\n hint,\n error,\n disabled = false,\n pad = \"clear\",\n group = true,\n className,\n children,\n ...rest\n}: FieldStripProps) {\n const labelId = useId();\n const messages = useFieldMessages({ hint, error });\n const content = typeof children === \"function\" ? children({ labelId, describedBy: messages.describedBy }) : children;\n // The caller's own description first, then the caption, then the error — the order\n // every kit field reads them in.\n const groupDescribedBy = [rest[\"aria-describedby\"], messages.describedBy].filter(Boolean).join(\" \") || undefined;\n return (\n <LabelStrip\n {...rest}\n {...(group\n ? {\n role: \"group\",\n // An explicit name wins, as on the kit's labelled groups.\n \"aria-labelledby\":\n rest[\"aria-label\"] === undefined && rest[\"aria-labelledby\"] === undefined\n ? labelId\n : rest[\"aria-labelledby\"],\n \"aria-describedby\": groupDescribedBy,\n }\n : null)}\n labelId={labelId}\n label={label}\n hint={messages.labelHint}\n disabled={disabled}\n pad={pad}\n className={className}\n >\n {content}\n {messages.below}\n </LabelStrip>\n );\n}\n"],"mappings":";AA4HI;AA5HJ,SAAS,aAAa;AAEtB,SAAS,YAAY,wBAAwB;AAwGtC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,UAAU,MAAM;AACtB,QAAM,WAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACjD,QAAM,UAAU,OAAO,aAAa,aAAa,SAAS,EAAE,SAAS,aAAa,SAAS,YAAY,CAAC,IAAI;AAG5G,QAAM,mBAAmB,CAAC,KAAK,kBAAkB,GAAG,SAAS,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAI,QACD;AAAA,QACE,MAAM;AAAA;AAAA,QAEN,mBACE,KAAK,YAAY,MAAM,UAAa,KAAK,iBAAiB,MAAM,SAC5D,UACA,KAAK,iBAAiB;AAAA,QAC5B,oBAAoB;AAAA,MACtB,IACA;AAAA,MACJ;AAAA,MACA;AAAA,MACA,MAAM,SAAS;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,QACA,SAAS;AAAA;AAAA;AAAA,EACZ;AAEJ;","names":[]}
|