@eifi1/ui-kit 0.22.0 → 0.24.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 +8 -7
- package/dist/components/amount-input.d.ts +34 -7
- package/dist/components/amount-input.js +14 -10
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +26 -0
- package/dist/components/autocomplete.js +164 -137
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +8 -7
- package/dist/components/calculator.d.ts +8 -7
- package/dist/components/column-mapper.d.ts +235 -0
- package/dist/components/column-mapper.js +411 -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 +113 -3
- package/dist/components/combobox.js +456 -313
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +8 -7
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -4
- package/dist/components/country-select.d.ts +50 -7
- package/dist/components/country-select.js +65 -9
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +8 -7
- package/dist/components/danger-confirm.js +64 -7
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/date-picker.d.ts +41 -0
- package/dist/components/date-picker.js +269 -8
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +48 -2
- package/dist/components/entity-combobox.js +159 -114
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +8 -7
- package/dist/components/field-parts.d.ts +116 -6
- package/dist/components/field-parts.js +84 -0
- package/dist/components/field-parts.js.map +1 -1
- 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 +8 -7
- package/dist/components/file-dropzone.d.ts +8 -7
- package/dist/components/form-actions.d.ts +8 -7
- package/dist/components/form-actions.js +23 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +8 -7
- package/dist/components/icon-picker.d.ts +10 -1
- package/dist/components/icon-picker.js +12 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/language-select.d.ts +8 -7
- package/dist/components/money-field.d.ts +11 -7
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.js +2 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +31 -2
- package/dist/components/multi-entity-combobox.js +152 -103
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +8 -7
- package/dist/components/number-input.d.ts +8 -7
- package/dist/components/numpad-sheet.d.ts +8 -7
- package/dist/components/phone-input.d.ts +8 -7
- 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 +8 -7
- package/dist/components/settings-fields.d.ts +8 -7
- package/dist/components/share-card.d.ts +8 -7
- package/dist/components/swatch-picker.d.ts +17 -1
- package/dist/components/swatch-picker.js +10 -4
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.d.ts +8 -7
- package/dist/components/tile-radio.d.ts +20 -0
- package/dist/components/tile-radio.js +25 -8
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +8 -7
- package/dist/components/toggle-group.js +2 -2
- 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 +5 -4
- package/dist/components/ui.js +60 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +66 -1
- package/dist/feedback/feedback-attachment.js +185 -65
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +66 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +67 -252
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +66 -287
- package/dist/feedback/feedback-thread.js +16 -3
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
- package/dist/feedback.d.ts +65 -3
- package/dist/hooks/use-file-drop.d.ts +8 -7
- package/dist/i18n/defaults.d.ts +8 -7
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +8 -7
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +8 -7
- package/dist/i18n/locales/de-CH.d.ts +8 -7
- package/dist/i18n/locales/en.d.ts +8 -7
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +8 -7
- package/dist/i18n/locales/es.js +37 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -7
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -7
- package/dist/i18n/locales/hu.js +38 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -7
- package/dist/i18n/locales/it.js +37 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -7
- package/dist/i18n/locales/zh.js +34 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +8 -7
- package/dist/i18n/review.js +20 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +16 -0
- 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/strip-fade.d.ts +2 -2
- package/dist/lib/strip-fade.js.map +1 -1
- 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 +142 -13
- package/dist/rhf/fields.js +185 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +8 -7
- package/dist/rhf.d.ts +9 -8
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +8 -7
- package/dist/shell/top-bar-brand.d.ts +8 -7
- 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 +8 -7
- package/dist/table-text.d.ts +1 -1
- package/dist/wizard/stepper-nav.d.ts +38 -9
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +8 -7
- package/package.json +1 -1
- package/src/components/amount-input.tsx +50 -14
- package/src/components/autocomplete.tsx +188 -116
- package/src/components/column-mapper.tsx +768 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +655 -335
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +135 -22
- package/src/components/danger-confirm.tsx +274 -24
- package/src/components/date-picker.tsx +423 -10
- package/src/components/entity-combobox.tsx +243 -129
- package/src/components/field-parts.tsx +224 -5
- package/src/components/field-strip.tsx +149 -0
- package/src/components/form-actions.tsx +52 -5
- package/src/components/icon-picker.tsx +23 -4
- package/src/components/money-field.tsx +3 -0
- package/src/components/month-picker.tsx +2 -1
- package/src/components/multi-entity-combobox.tsx +221 -121
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/swatch-picker.tsx +29 -5
- package/src/components/tile-radio.tsx +74 -13
- package/src/components/toggle-group.tsx +2 -2
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +114 -33
- package/src/feedback/feedback-attachment.tsx +372 -72
- package/src/feedback/feedback-dialog.tsx +7 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +112 -9
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +42 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/en.ts +27 -5
- package/src/i18n/locales/es.ts +41 -0
- package/src/i18n/locales/fr.ts +42 -0
- package/src/i18n/locales/hu.ts +37 -0
- package/src/i18n/locales/it.ts +41 -0
- package/src/i18n/locales/zh.ts +32 -0
- package/src/i18n/review.ts +19 -0
- package/src/index.ts +18 -0
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/strip-fade.ts +2 -2
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +421 -6
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/wizard/stepper-nav.tsx +35 -1
- package/dist/components/field-anatomy.d.ts +0 -95
- package/dist/components/field-anatomy.js +0 -84
- package/dist/components/field-anatomy.js.map +0 -1
- package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
- package/src/components/field-anatomy.tsx +0 -190
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLayoutEffect, useRef } from "react";
|
|
1
|
+
import { useId, useLayoutEffect, useRef } from "react";
|
|
2
2
|
import type { KeyboardEvent as ReactKeyboardEvent, ReactNode } from "react";
|
|
3
3
|
import { Link } from "react-router";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
@@ -11,9 +11,9 @@ import { TOPBAR_TRIGGER_CLASS } from "./topbar-controls";
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* One row of a {@link TopBarActionMenu}. An `action` (default) runs `onSelect`, a
|
|
14
|
-
* `link` navigates via react-router,
|
|
15
|
-
* rows close the menu automatically when chosen.
|
|
16
|
-
* actions, keeping the common case terse.
|
|
14
|
+
* `link` navigates via react-router, a `divider` draws a rule, and a `heading` names
|
|
15
|
+
* the rows after it. Action and link rows close the menu automatically when chosen.
|
|
16
|
+
* `kind` may be omitted for actions, keeping the common case terse.
|
|
17
17
|
*
|
|
18
18
|
* The rows are {@link MenuItem}s, so they take its options: `tone: "danger"` for the
|
|
19
19
|
* log-out row, `checked` (+ `checkable`) for a choice, `current` for the link to the
|
|
@@ -46,7 +46,67 @@ export type TopBarMenuEntry =
|
|
|
46
46
|
current?: boolean;
|
|
47
47
|
disabled?: boolean;
|
|
48
48
|
}
|
|
49
|
-
| { kind: "divider"; key: string }
|
|
49
|
+
| { kind: "divider"; key: string }
|
|
50
|
+
| {
|
|
51
|
+
/**
|
|
52
|
+
* A group heading (0.23, keksdose's guided-tours menu): the rows after it, up to
|
|
53
|
+
* the next heading, the next `divider` or the end of `entries`, are ONE group
|
|
54
|
+
* named by `label`. keksdose files its tours by topic and separated the blocks
|
|
55
|
+
* with bare dividers — "the kit's menu has no heading row to name them with"
|
|
56
|
+
* (tour-menu.tsx) — so a sighted reader had to guess what each block was and a
|
|
57
|
+
* screen reader was told nothing at all.
|
|
58
|
+
*
|
|
59
|
+
* Not a row: it takes no focus, the arrow keys (and Home/End) pass over it, and it
|
|
60
|
+
* never closes the menu. It is announced the way the kit's other grouped lists
|
|
61
|
+
* announce theirs (`CommandPalette`): the group's rows sit in a `role="group"`
|
|
62
|
+
* whose `aria-labelledby` is this heading, so a reader says "On this page, group"
|
|
63
|
+
* when the focus enters the first row, and nothing between rows.
|
|
64
|
+
*
|
|
65
|
+
* A divider ENDS the group, so a closing row such as "All tours…" behind a rule
|
|
66
|
+
* belongs to no topic. A heading with no rows under it is not drawn — an empty
|
|
67
|
+
* group names nothing; build the list from data and it may well be empty.
|
|
68
|
+
*
|
|
69
|
+
* The menu-wide `heading` prop is the panel's title, drawn once above everything;
|
|
70
|
+
* with group headings it is usually redundant (two stacked caps lines read as one),
|
|
71
|
+
* and the trigger's `ariaLabel` already names the menu.
|
|
72
|
+
*/
|
|
73
|
+
kind: "heading";
|
|
74
|
+
key: string;
|
|
75
|
+
/** The group's name, shown above its rows and announced as the group's name. Keep
|
|
76
|
+
* it short: it is set in small caps and wraps on a narrow panel. */
|
|
77
|
+
label: ReactNode;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
type HeadingEntry = Extract<TopBarMenuEntry, { kind: "heading" }>;
|
|
81
|
+
type DividerEntry = Extract<TopBarMenuEntry, { kind: "divider" }>;
|
|
82
|
+
type RowEntry = Exclude<TopBarMenuEntry, HeadingEntry | DividerEntry>;
|
|
83
|
+
|
|
84
|
+
/** What the panel draws from `entries`: loose rows and rules, and headed groups. */
|
|
85
|
+
type Block = { kind: "loose"; entry: RowEntry | DividerEntry } | { kind: "group"; heading: HeadingEntry; rows: RowEntry[] };
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Cut `entries` into blocks: a heading opens a group that takes the rows after it until
|
|
89
|
+
* the next heading or divider; everything else stands alone, as it always has.
|
|
90
|
+
*/
|
|
91
|
+
function toBlocks(entries: readonly TopBarMenuEntry[]): Block[] {
|
|
92
|
+
const blocks: Block[] = [];
|
|
93
|
+
let group: Extract<Block, { kind: "group" }> | null = null;
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
if (entry.kind === "heading") {
|
|
96
|
+
group = { kind: "group", heading: entry, rows: [] };
|
|
97
|
+
blocks.push(group);
|
|
98
|
+
} else if (entry.kind === "divider") {
|
|
99
|
+
group = null;
|
|
100
|
+
blocks.push({ kind: "loose", entry });
|
|
101
|
+
} else if (group) {
|
|
102
|
+
group.rows.push(entry);
|
|
103
|
+
} else {
|
|
104
|
+
blocks.push({ kind: "loose", entry });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// A heading with nothing under it names nothing: not drawn (see the `heading` kind).
|
|
108
|
+
return blocks.filter((block) => block.kind === "loose" || block.rows.length > 0);
|
|
109
|
+
}
|
|
50
110
|
|
|
51
111
|
/**
|
|
52
112
|
* The non-interactive identity block at the top of an account menu — kastlan's name +
|
|
@@ -130,6 +190,11 @@ function keepTabInsidePanel(e: ReactKeyboardEvent<HTMLUListElement>) {
|
|
|
130
190
|
* It is also the ACCOUNT menu kastlan and keksdose each hand-build on `HoverMenu`: a
|
|
131
191
|
* custom `trigger` (the avatar), a `header` (name + role / email), the rows, and a
|
|
132
192
|
* free-content `footer` (the legal links).
|
|
193
|
+
*
|
|
194
|
+
* A long menu is grouped by topic with `{ kind: "heading" }` entries (0.23) — see
|
|
195
|
+
* {@link TopBarMenuEntry}. There is no separate phone layout: the panel is the same
|
|
196
|
+
* hover panel at 390px, kept inside the viewport by `HoverMenu`, and a heading wraps
|
|
197
|
+
* rather than widening it.
|
|
133
198
|
*/
|
|
134
199
|
export function TopBarActionMenu({
|
|
135
200
|
icon,
|
|
@@ -206,6 +271,10 @@ export function TopBarActionMenu({
|
|
|
206
271
|
align?: "start" | "end" | "left" | "right";
|
|
207
272
|
panelClassName?: string;
|
|
208
273
|
}) {
|
|
274
|
+
// Names the groups' headings for `aria-labelledby`. Positional rather than from the
|
|
275
|
+
// entry's `key`, which is the caller's text and may hold a space — and a space in
|
|
276
|
+
// `aria-labelledby` is a list separator.
|
|
277
|
+
const uid = useId();
|
|
209
278
|
return (
|
|
210
279
|
<HoverMenu
|
|
211
280
|
ariaLabel={ariaLabel}
|
|
@@ -258,6 +327,40 @@ export function TopBarActionMenu({
|
|
|
258
327
|
const headerExtra = typeof header?.extra === "function" ? header.extra(close) : header?.extra;
|
|
259
328
|
const footerClass =
|
|
260
329
|
"flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--text-muted)] [&_a:hover]:text-[var(--text-primary)] [&_a]:rounded-sm [&_a:focus-visible]:outline-none [&_a:focus-visible]:ring-2 [&_a:focus-visible]:ring-[var(--brand)]";
|
|
330
|
+
/** One action or link row — loose, or inside a heading's group. */
|
|
331
|
+
const row = (entry: RowEntry) => (
|
|
332
|
+
<li key={entry.key}>
|
|
333
|
+
{entry.kind === "link" ? (
|
|
334
|
+
<MenuItem
|
|
335
|
+
href={entry.to}
|
|
336
|
+
renderLink={({ href, ...p }) => <Link to={href} {...p} />}
|
|
337
|
+
onClick={close}
|
|
338
|
+
leading={entry.icon}
|
|
339
|
+
trailing={entry.trailing}
|
|
340
|
+
tone={entry.tone}
|
|
341
|
+
current={entry.current}
|
|
342
|
+
disabled={entry.disabled}
|
|
343
|
+
>
|
|
344
|
+
{entry.label}
|
|
345
|
+
</MenuItem>
|
|
346
|
+
) : (
|
|
347
|
+
<MenuItem
|
|
348
|
+
onClick={() => {
|
|
349
|
+
entry.onSelect();
|
|
350
|
+
close();
|
|
351
|
+
}}
|
|
352
|
+
leading={entry.icon}
|
|
353
|
+
trailing={entry.trailing}
|
|
354
|
+
tone={entry.tone}
|
|
355
|
+
checked={entry.checked}
|
|
356
|
+
checkable={entry.checkable}
|
|
357
|
+
disabled={entry.disabled}
|
|
358
|
+
>
|
|
359
|
+
{entry.label}
|
|
360
|
+
</MenuItem>
|
|
361
|
+
)}
|
|
362
|
+
</li>
|
|
363
|
+
);
|
|
261
364
|
return (
|
|
262
365
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- a Tab bubbling up from a row or footer link, see keepTabInsidePanel
|
|
263
366
|
<ul className={cn("py-1", panelClassName)} onKeyDown={keepTabInsidePanel}>
|
|
@@ -275,41 +378,34 @@ export function TopBarActionMenu({
|
|
|
275
378
|
{heading}
|
|
276
379
|
</li>
|
|
277
380
|
)}
|
|
278
|
-
{entries.map((
|
|
279
|
-
if (
|
|
280
|
-
|
|
381
|
+
{toBlocks(entries).map((block, i, blocks) => {
|
|
382
|
+
if (block.kind === "loose") {
|
|
383
|
+
const entry = block.entry;
|
|
384
|
+
if (entry.kind === "divider") {
|
|
385
|
+
return <li key={entry.key} className="my-1 border-t border-[var(--border)]" />;
|
|
386
|
+
}
|
|
387
|
+
return row(entry);
|
|
281
388
|
}
|
|
389
|
+
const headingId = `${uid}-group-${i}`;
|
|
390
|
+
const before = blocks[i - 1];
|
|
391
|
+
// Under a rule (or at the top of the rows) the rule's margin is the gap;
|
|
392
|
+
// straight under another group's last row the heading makes its own.
|
|
393
|
+
const tight = !before || (before.kind === "loose" && before.entry.kind === "divider");
|
|
282
394
|
return (
|
|
283
|
-
<li key={
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
) : (
|
|
298
|
-
<MenuItem
|
|
299
|
-
onClick={() => {
|
|
300
|
-
entry.onSelect();
|
|
301
|
-
close();
|
|
302
|
-
}}
|
|
303
|
-
leading={entry.icon}
|
|
304
|
-
trailing={entry.trailing}
|
|
305
|
-
tone={entry.tone}
|
|
306
|
-
checked={entry.checked}
|
|
307
|
-
checkable={entry.checkable}
|
|
308
|
-
disabled={entry.disabled}
|
|
309
|
-
>
|
|
310
|
-
{entry.label}
|
|
311
|
-
</MenuItem>
|
|
312
|
-
)}
|
|
395
|
+
<li key={block.heading.key} role="none">
|
|
396
|
+
<div
|
|
397
|
+
id={headingId}
|
|
398
|
+
role="presentation"
|
|
399
|
+
className={cn(
|
|
400
|
+
"px-3 pb-0.5 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)] [overflow-wrap:anywhere]",
|
|
401
|
+
tight ? "pt-1" : "pt-2.5",
|
|
402
|
+
)}
|
|
403
|
+
>
|
|
404
|
+
{block.heading.label}
|
|
405
|
+
</div>
|
|
406
|
+
<ul role="group" aria-labelledby={headingId}>
|
|
407
|
+
{block.rows.map(row)}
|
|
408
|
+
</ul>
|
|
313
409
|
</li>
|
|
314
410
|
);
|
|
315
411
|
})}
|
|
@@ -31,7 +31,7 @@ import type { UseWizardReturn, WizardLabels } from "./types";
|
|
|
31
31
|
* `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,
|
|
32
32
|
* `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button
|
|
33
33
|
* looks and what wraps it, because that is presentation and usually depends on
|
|
34
|
-
* state the app holds (a "replace everything" toggle, a write lock).
|
|
34
|
+
* state the app holds (a "replace everything" toggle, a write lock — `finishCommit`).
|
|
35
35
|
*/
|
|
36
36
|
export function StepperNav<TData extends Record<string, unknown>>({
|
|
37
37
|
wizard,
|
|
@@ -42,6 +42,8 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
42
42
|
labels,
|
|
43
43
|
finishVariant = "brand",
|
|
44
44
|
finishDisabled = false,
|
|
45
|
+
finishCommit,
|
|
46
|
+
finishDisabledReason,
|
|
45
47
|
doneDisabled = false,
|
|
46
48
|
renderFinish,
|
|
47
49
|
}: {
|
|
@@ -57,6 +59,30 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
57
59
|
/** Disable Finish for a reason of the app's own, on top of the kit's gates
|
|
58
60
|
* (`canFinish`, submitting, validating). */
|
|
59
61
|
finishDisabled?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Finish COMMITS — {@link Button}'s `commit`, handed to the Finish button. Under a
|
|
64
|
+
* locked {@link WriteLockProvider} it is `aria-disabled` but focusable, with the lock's
|
|
65
|
+
* reason in the kit Tooltip, and a click (or Enter) does not run `wizard.finish`.
|
|
66
|
+
* Every other button of the bar stays live: the steps before the commit write nothing,
|
|
67
|
+
* so the whole wizard can still be walked through on a read-only page.
|
|
68
|
+
*
|
|
69
|
+
* keksdose G1: its YNAB import runs the mapping wizard on the read-only demo (dev#497)
|
|
70
|
+
* and locks only the confirm step's Finish — by cloning `commit` into the element
|
|
71
|
+
* `renderFinish` hands it, because StepperNav took no `commit` of its own. That clone
|
|
72
|
+
* still works; this is the same thing without one. Named `finishCommit` beside
|
|
73
|
+
* `finishVariant` / `finishDisabled` rather than a bare `commit`, which on a component
|
|
74
|
+
* with five buttons would not say which one saves — and would read like `useWizard`'s
|
|
75
|
+
* `commits`, which is a step's policy, not a lock.
|
|
76
|
+
*/
|
|
77
|
+
finishCommit?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Why Finish is not available — {@link Button}'s `disabledReason` on the Finish button:
|
|
80
|
+
* `aria-disabled` and focusable, the reason in the kit Tooltip and on the button's
|
|
81
|
+
* `aria-describedby`, `wizard.finish` not run. The explained form of `finishDisabled`
|
|
82
|
+
* (a native `disabled` takes the button out of the tab order, so its reason never
|
|
83
|
+
* reaches a keyboard). Under a lock with `finishCommit`, the lock's reason wins.
|
|
84
|
+
*/
|
|
85
|
+
finishDisabledReason?: ReactNode;
|
|
60
86
|
/**
|
|
61
87
|
* Disable Done — the button of the last step after a commit — while the step still
|
|
62
88
|
* has work of its own running. keksdose's YNAB import ends on a recurring step whose
|
|
@@ -82,6 +108,10 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
82
108
|
* re-derive (the problem this component exists to end); `onBeforeFinish` can veto
|
|
83
109
|
* a click but cannot explain a disabled button. The element is a plain `<Button>`,
|
|
84
110
|
* so a wrapper that clones it with `disabled` works as expected.
|
|
111
|
+
*
|
|
112
|
+
* For the write lock itself there is no need to wrap any more: `finishCommit` (and
|
|
113
|
+
* `finishDisabledReason`) put the kit Button's own Tooltip on Finish. An element
|
|
114
|
+
* cloned with `commit` here keeps working the same way.
|
|
85
115
|
*/
|
|
86
116
|
renderFinish?: (button: ReactElement) => ReactNode;
|
|
87
117
|
}) {
|
|
@@ -134,6 +164,10 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
134
164
|
variant={finishVariant}
|
|
135
165
|
data-tour="wizard-finish"
|
|
136
166
|
onClick={wizard.finish}
|
|
167
|
+
// The kit Button's lock: a reason (its own or, with `commit`, the provider's)
|
|
168
|
+
// wins over `disabled` and keeps the button reachable, saying why.
|
|
169
|
+
commit={finishCommit}
|
|
170
|
+
disabledReason={finishDisabledReason}
|
|
137
171
|
disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}
|
|
138
172
|
aria-busy={wizard.isSubmitting || pending || undefined}
|
|
139
173
|
>
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
/** `undefined`, `null`, `false` and `""` are what a caller's `cond && "…"` evaluates to
|
|
5
|
-
* when there is nothing to say; none of them is content. A numeric `0` is. */
|
|
6
|
-
declare function hasContent(node: ReactNode): boolean;
|
|
7
|
-
/**
|
|
8
|
-
* A `hint` that is plain TEXT is a caption under the field; anything else (a
|
|
9
|
-
* `FieldHint` "?") rides the label line — the rule `Select` set in 0.15: the label
|
|
10
|
-
* line is 11px of strip shared with the label, and a sentence placed there was set on
|
|
11
|
-
* top of both the label and the value.
|
|
12
|
-
*/
|
|
13
|
-
declare function isTextHint(hint: ReactNode): boolean;
|
|
14
|
-
interface FieldMessages {
|
|
15
|
-
/** `invalid`, a message, or an `aria-invalid` from outside (a form library). */
|
|
16
|
-
isInvalid: boolean;
|
|
17
|
-
/** The caller's description, then the caption, then the error — the standing
|
|
18
|
-
* advice before the news, as `useFieldError` in ui.tsx orders them. */
|
|
19
|
-
describedBy: string | undefined;
|
|
20
|
-
/** The caption and the error, for under the field; `null` when there is neither. */
|
|
21
|
-
below: ReactNode;
|
|
22
|
-
/** The hint for the LABEL line (a `FieldHint`), or `undefined` when the hint is a
|
|
23
|
-
* caption or there is none. */
|
|
24
|
-
labelHint: ReactNode;
|
|
25
|
-
}
|
|
26
|
-
/** `hint` / `error` / `invalid` resolved the way `Select` resolves them. */
|
|
27
|
-
declare function useFieldMessages({ hint, error, invalid, describedBy, ariaInvalid, }: {
|
|
28
|
-
hint?: ReactNode;
|
|
29
|
-
error?: ReactNode;
|
|
30
|
-
invalid?: boolean;
|
|
31
|
-
describedBy?: string;
|
|
32
|
-
ariaInvalid?: unknown;
|
|
33
|
-
}): FieldMessages;
|
|
34
|
-
/**
|
|
35
|
-
* The field and what is under it, as one box — `FieldGroup` in ui.tsx, for the same
|
|
36
|
-
* reasons written there. Nothing of its own while there is nothing under the field and
|
|
37
|
-
* the caller passed no `error` key (the DOM is exactly what it was before these props);
|
|
38
|
-
* once `error` is passed the box is kept even with no message, so the trigger is never
|
|
39
|
-
* reparented — and remounted, losing focus — as the message comes and goes.
|
|
40
|
-
*/
|
|
41
|
-
declare function FieldBox({ below, reserve, children, }: {
|
|
42
|
-
below: ReactNode;
|
|
43
|
-
reserve: boolean;
|
|
44
|
-
children: ReactNode;
|
|
45
|
-
}): react.JSX.Element;
|
|
46
|
-
/**
|
|
47
|
-
* The static label in a trigger field's top strip — a {@link FieldLabel}, or, with a
|
|
48
|
-
* `FieldHint`, the label and its "?" laid out on ONE flex row (dev#468: two absolute
|
|
49
|
-
* offsets guessing at the same baseline put the "?" three pixels above its word).
|
|
50
|
-
* `FloatingField`'s static-label row, except that it stops at `end-9`: the date and
|
|
51
|
-
* month triggers keep that column (`pe-9`) for the calendar glyph or the clear ×, and a
|
|
52
|
-
* long label truncates before it rather than putting the "?" on top of it.
|
|
53
|
-
*/
|
|
54
|
-
declare function FieldLabelLine({ label, hint }: {
|
|
55
|
-
label: ReactNode;
|
|
56
|
-
hint?: ReactNode;
|
|
57
|
-
}): react.JSX.Element;
|
|
58
|
-
/**
|
|
59
|
-
* How tall the strip over a bare group is.
|
|
60
|
-
*
|
|
61
|
-
* - `"toggle"` — 16px. keksdose's `ClearedStatusPicker` (live #288 rework, #431,
|
|
62
|
-
* measured): *"16px of label strip plus a 26px group is exactly a labelled Input"*,
|
|
63
|
-
* so a status group in a row of 42px fields ends where they end. The label's line
|
|
64
|
-
* box runs into the group's top hairline, but the ACTIVE fill — the thing feedback
|
|
65
|
-
* #117 was about — starts at 19px, below the label's 17.75px.
|
|
66
|
-
* - `"tiles"` — 20px, keksdose's `FlagPicker` and location field (`pt-5`). A tile has
|
|
67
|
-
* no inset fill: its selection IS its frame, a 2px dark border-and-ring from the
|
|
68
|
-
* tile's top edge, so the tile has to start below the whole label line.
|
|
69
|
-
*/
|
|
70
|
-
declare const LABEL_STRIP_PAD: {
|
|
71
|
-
readonly toggle: "pt-4";
|
|
72
|
-
readonly tiles: "pt-5";
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* The 11px label (and its `FieldHint`) over a group that is NOT a field — keksdose K2:
|
|
76
|
-
* a segmented control or a row of swatches standing in a form row beside labelled
|
|
77
|
-
* inputs, whose label must sit on the same line as theirs (`top-1`, `inset-x-3`, the
|
|
78
|
-
* type of a field's static label) without the group growing a field's border round
|
|
79
|
-
* it. `id` goes on the label so the group can be `aria-labelledby` it.
|
|
80
|
-
*
|
|
81
|
-
* `relative` is load-bearing twice over: the row is positioned against it, and a
|
|
82
|
-
* caller's `sr-only` label cannot escape it (sr-only-containment).
|
|
83
|
-
*/
|
|
84
|
-
declare function LabelStrip({ labelId, label, hint, disabled, pad, className, children, }: {
|
|
85
|
-
labelId: string;
|
|
86
|
-
label: ReactNode;
|
|
87
|
-
hint?: ReactNode;
|
|
88
|
-
/** Dim the label with its group, as `peer-disabled:` dims a field's own. */
|
|
89
|
-
disabled?: boolean;
|
|
90
|
-
pad: keyof typeof LABEL_STRIP_PAD;
|
|
91
|
-
className?: string;
|
|
92
|
-
children: ReactNode;
|
|
93
|
-
}): react.JSX.Element;
|
|
94
|
-
|
|
95
|
-
export { FieldBox, FieldLabelLine, type FieldMessages, LABEL_STRIP_PAD, LabelStrip, hasContent, isTextHint, useFieldMessages };
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId } from "react";
|
|
4
|
-
import { cn } from "../lib/cn.js";
|
|
5
|
-
import { FieldLabel } from "./ui.js";
|
|
6
|
-
function hasContent(node) {
|
|
7
|
-
return node !== void 0 && node !== null && node !== false && node !== "";
|
|
8
|
-
}
|
|
9
|
-
function isTextHint(hint) {
|
|
10
|
-
return typeof hint === "string" && hint !== "" || typeof hint === "number";
|
|
11
|
-
}
|
|
12
|
-
const FIELD_CAPTION_CLASS = "mt-1 text-[11px] leading-tight text-[var(--text-muted)]";
|
|
13
|
-
const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";
|
|
14
|
-
function useFieldMessages({
|
|
15
|
-
hint,
|
|
16
|
-
error,
|
|
17
|
-
invalid,
|
|
18
|
-
describedBy,
|
|
19
|
-
ariaInvalid
|
|
20
|
-
}) {
|
|
21
|
-
const hintId = useId();
|
|
22
|
-
const errorId = useId();
|
|
23
|
-
const textHint = isTextHint(hint);
|
|
24
|
-
const hasError = hasContent(error);
|
|
25
|
-
return {
|
|
26
|
-
isInvalid: Boolean(invalid) || hasError || ariaInvalid === true || ariaInvalid === "true",
|
|
27
|
-
describedBy: [describedBy, textHint && hintId, hasError && errorId].filter(Boolean).join(" ") || void 0,
|
|
28
|
-
below: textHint || hasError ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29
|
-
textHint && /* @__PURE__ */ jsx("p", { id: hintId, className: FIELD_CAPTION_CLASS, children: hint }),
|
|
30
|
-
hasError && /* @__PURE__ */ jsx("p", { id: errorId, className: FIELD_ERROR_CLASS, children: error })
|
|
31
|
-
] }) : null,
|
|
32
|
-
labelHint: !textHint && hasContent(hint) ? hint : void 0
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function FieldBox({
|
|
36
|
-
below,
|
|
37
|
-
reserve,
|
|
38
|
-
children
|
|
39
|
-
}) {
|
|
40
|
-
if (below === null && !reserve) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
41
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
42
|
-
children,
|
|
43
|
-
below
|
|
44
|
-
] });
|
|
45
|
-
}
|
|
46
|
-
function FieldLabelLine({ label, hint }) {
|
|
47
|
-
if (!hasContent(hint)) return /* @__PURE__ */ jsx(FieldLabel, { children: label });
|
|
48
|
-
return (
|
|
49
|
-
// `z-10` as FieldLabel has it: the trigger after it is `relative` and would paint
|
|
50
|
-
// over the row (and take the "?"'s hover) otherwise.
|
|
51
|
-
/* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute start-3 end-9 top-1 z-10 flex items-center gap-1", children: [
|
|
52
|
-
/* @__PURE__ */ jsx(FieldLabel, { className: "static min-w-0 max-w-full", children: label }),
|
|
53
|
-
/* @__PURE__ */ jsx("span", { className: "pointer-events-auto flex shrink-0 items-center", children: hint })
|
|
54
|
-
] })
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
const LABEL_STRIP_PAD = { toggle: "pt-4", tiles: "pt-5" };
|
|
58
|
-
function LabelStrip({
|
|
59
|
-
labelId,
|
|
60
|
-
label,
|
|
61
|
-
hint,
|
|
62
|
-
disabled,
|
|
63
|
-
pad,
|
|
64
|
-
className,
|
|
65
|
-
children
|
|
66
|
-
}) {
|
|
67
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("relative min-w-0", LABEL_STRIP_PAD[pad], className), children: [
|
|
68
|
-
/* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-x-3 top-1 flex items-center gap-1", children: [
|
|
69
|
-
/* @__PURE__ */ jsx(FieldLabel, { id: labelId, className: cn("static min-w-0 max-w-full", disabled && "opacity-50"), children: label }),
|
|
70
|
-
hasContent(hint) && /* @__PURE__ */ jsx("span", { className: "pointer-events-auto flex shrink-0 items-center", children: hint })
|
|
71
|
-
] }),
|
|
72
|
-
children
|
|
73
|
-
] });
|
|
74
|
-
}
|
|
75
|
-
export {
|
|
76
|
-
FieldBox,
|
|
77
|
-
FieldLabelLine,
|
|
78
|
-
LABEL_STRIP_PAD,
|
|
79
|
-
LabelStrip,
|
|
80
|
-
hasContent,
|
|
81
|
-
isTextHint,
|
|
82
|
-
useFieldMessages
|
|
83
|
-
};
|
|
84
|
-
//# sourceMappingURL=field-anatomy.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/field-anatomy.tsx"],"sourcesContent":["// Internal — not re-exported from the barrel. The pieces of a field's anatomy (its\n// label line, the caption and the error under it, the label STRIP over a bare group)\n// for the controls that are not built on `FloatingField`: the date and month pickers,\n// whose trigger is a button in a wrapper of their own, and the groups (ToggleGroup,\n// SwatchPicker, IconPicker), which have no field box at all.\n//\n// `Input` and `Select` keep their own copies in ui.tsx; these follow them class for\n// class, so a DatePicker's caption and a Select's caption are the same 11px line in\n// the same place (keksdose K4: \"uniform field anatomy … today it is a patchwork\").\n\nimport { useId } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { FieldLabel } from \"./ui\";\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\n/**\n * A `hint` that is plain TEXT is a caption under the field; anything else (a\n * `FieldHint` \"?\") rides the label line — the rule `Select` set in 0.15: the label\n * line is 11px of strip shared with the label, and a sentence placed there was set on\n * top of both the label and the value.\n */\nexport function isTextHint(hint: ReactNode): boolean {\n return (typeof hint === \"string\" && hint !== \"\") || typeof hint === \"number\";\n}\n\nconst FIELD_CAPTION_CLASS = \"mt-1 text-[11px] leading-tight text-[var(--text-muted)]\";\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.\n *\n * - `\"toggle\"` — 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.\n * - `\"tiles\"` — 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.\n */\nexport const LABEL_STRIP_PAD = { toggle: \"pt-4\", tiles: \"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 */\nexport function LabelStrip({\n labelId,\n label,\n hint,\n disabled,\n pad,\n className,\n children,\n}: {\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 className?: string;\n children: ReactNode;\n}) {\n return (\n <div 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":";AAuEQ,mBAEI,KAFJ;AA7DR,SAAS,aAAa;AAEtB,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAIpB,SAAS,WAAW,MAA0B;AACnD,SAAO,SAAS,UAAa,SAAS,QAAQ,SAAS,SAAS,SAAS;AAC3E;AAQO,SAAS,WAAW,MAA0B;AACnD,SAAQ,OAAO,SAAS,YAAY,SAAS,MAAO,OAAO,SAAS;AACtE;AAEA,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAgBnB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMkB;AAChB,QAAM,SAAS,MAAM;AACrB,QAAM,UAAU,MAAM;AACtB,QAAM,WAAW,WAAW,IAAI;AAChC,QAAM,WAAW,WAAW,KAAK;AACjC,SAAO;AAAA,IACL,WAAW,QAAQ,OAAO,KAAK,YAAY,gBAAgB,QAAQ,gBAAgB;AAAA,IACnF,aACE,CAAC,aAAa,YAAY,QAAQ,YAAY,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK;AAAA,IACtF,OACE,YAAY,WACV,iCACG;AAAA,kBACC,oBAAC,OAAE,IAAI,QAAQ,WAAW,qBACvB,gBACH;AAAA,MAED,YACC,oBAAC,OAAE,IAAI,SAAS,WAAW,mBACxB,iBACH;AAAA,OAEJ,IACE;AAAA,IACN,WAAW,CAAC,YAAY,WAAW,IAAI,IAAI,OAAO;AAAA,EACpD;AACF;AASO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,UAAU,QAAQ,CAAC,QAAS,QAAO,gCAAG,UAAS;AACnD,SACE,qBAAC,SACE;AAAA;AAAA,IACA;AAAA,KACH;AAEJ;AAUO,SAAS,eAAe,EAAE,OAAO,KAAK,GAA2C;AACtF,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,oBAAC,cAAY,iBAAM;AACjD;AAAA;AAAA;AAAA,IAGE,qBAAC,SAAI,WAAU,iFACb;AAAA,0BAAC,cAAW,WAAU,6BAA6B,iBAAM;AAAA,MACzD,oBAAC,UAAK,WAAU,kDAAkD,gBAAK;AAAA,OACzE;AAAA;AAEJ;AAcO,MAAM,kBAAkB,EAAE,QAAQ,QAAQ,OAAO,OAAO;AAYxD,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,qBAAC,SAAI,WAAW,GAAG,oBAAoB,gBAAgB,GAAG,GAAG,SAAS,GACpE;AAAA,yBAAC,SAAI,WAAU,wEAIb;AAAA,0BAAC,cAAW,IAAI,SAAS,WAAW,GAAG,6BAA6B,YAAY,YAAY,GACzF,iBACH;AAAA,MACC,WAAW,IAAI,KAAK,oBAAC,UAAK,WAAU,kDAAkD,gBAAK;AAAA,OAC9F;AAAA,IACC;AAAA,KACH;AAEJ;","names":[]}
|