atlas-react-kit 1.2.1 → 1.2.3
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/COMPONENTS.md +22 -18
- package/DESIGN_RULES.md +7 -7
- package/dist/alert.js +2 -2
- package/dist/badge.js +1 -1
- package/dist/button.js +1 -1
- package/dist/card.js +2 -1
- package/dist/chart.js +1 -1
- package/dist/combobox.d.ts +4 -3
- package/dist/combobox.js +3 -7
- package/dist/dialog.js +2 -1
- package/dist/dropdown-menu.js +6 -4
- package/dist/field.js +2 -2
- package/dist/form/checkbox-field.d.ts +2 -4
- package/dist/form/checkbox-field.js +9 -12
- package/dist/form/combobox-field.d.ts +2 -2
- package/dist/form/combobox-field.js +5 -6
- package/dist/form/control-field.d.ts +5 -5
- package/dist/form/control-field.js +9 -7
- package/dist/form/date-picker-field.d.ts +1 -1
- package/dist/form/date-picker-field.js +3 -10
- package/dist/form/date-time-picker-field.d.ts +1 -1
- package/dist/form/date-time-picker-field.js +3 -10
- package/dist/form/feedback-field.js +2 -1
- package/dist/form/field-error.d.ts +10 -1
- package/dist/form/field-error.js +2 -1
- package/dist/form/field-shell.d.ts +27 -16
- package/dist/form/field-shell.js +27 -2
- package/dist/form/focus-first-invalid.d.ts +7 -4
- package/dist/form/focus-first-invalid.js +10 -6
- package/dist/form/markdown-field.d.ts +1 -1
- package/dist/form/markdown-field.js +8 -4
- package/dist/form/select-field.d.ts +2 -8
- package/dist/form/select-field.js +5 -7
- package/dist/form/text-field.d.ts +2 -8
- package/dist/form/text-field.js +8 -3
- package/dist/form/toggle-group-field.d.ts +2 -2
- package/dist/form/toggle-group-field.js +5 -6
- package/dist/info-tip.d.ts +3 -1
- package/dist/info-tip.js +4 -1
- package/dist/internal/calendar-size.d.ts +1 -1
- package/dist/internal/calendar-size.js +1 -1
- package/dist/internal/form-control.d.ts +6 -0
- package/dist/internal/form-control.js +6 -0
- package/dist/internal/kanban-layout.js +2 -1
- package/dist/internal/listbox-navigation.d.ts +8 -8
- package/dist/internal/listbox-navigation.js +23 -23
- package/dist/internal/overlay-styles.d.ts +5 -0
- package/dist/internal/overlay-styles.js +5 -0
- package/dist/internal/styles.d.ts +10 -3
- package/dist/internal/styles.js +12 -5
- package/dist/internal/tabs-styles.js +1 -1
- package/dist/kanban.js +3 -3
- package/dist/meter.js +1 -1
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +3 -3
- package/dist/picker-shell.d.ts +3 -1
- package/dist/picker-shell.js +3 -3
- package/dist/popover.js +4 -4
- package/dist/progress.js +1 -1
- package/dist/search-picker.js +4 -8
- package/dist/select.js +4 -4
- package/dist/separator.js +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/slider.js +1 -1
- package/dist/tabs.js +1 -1
- package/dist/toast.js +11 -19
- package/dist/toggle-group.d.ts +1 -1
- package/dist/toggle-group.js +36 -2
- package/package.json +1 -1
- package/skills/atlas-react-kit/SKILL.md +1 -1
- package/theme.css +6 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { errorText } from "../internal/styles.js";
|
|
2
3
|
import { cn } from "../utils.js";
|
|
3
4
|
/** Form-level submit or mutation error (not tied to a single field). */
|
|
4
5
|
export function FormFeedbackField({ message, className, }) {
|
|
5
6
|
if (!message)
|
|
6
7
|
return null;
|
|
7
|
-
return (_jsx("p", { "data-slot": "form-feedback", className: cn("
|
|
8
|
+
return (_jsx("p", { "data-slot": "form-feedback", className: cn("border-t border-destructive pt-3", errorText, className), role: "alert", children: message }));
|
|
8
9
|
}
|
|
@@ -38,13 +38,22 @@ export type FormFieldBaseProps = ErrorTimingProps & {
|
|
|
38
38
|
*/
|
|
39
39
|
fieldClassName?: string;
|
|
40
40
|
};
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* What the `form/*` fields that are not bound to a `field` API take in its
|
|
43
|
+
* place: its meta and its name.
|
|
44
|
+
*/
|
|
42
45
|
export type FormFieldMetaProps = {
|
|
43
46
|
/**
|
|
44
47
|
* The field's meta (TanStack Form's `field.state.meta`); its first error
|
|
45
48
|
* shows under the control. Leave it out for no error text.
|
|
46
49
|
*/
|
|
47
50
|
meta?: FieldMetaState;
|
|
51
|
+
/**
|
|
52
|
+
* The field's name (TanStack Form's `field.name`). Put on the control as
|
|
53
|
+
* `data-field-name`, so `focusFirstInvalid` can find it. Leave it out and
|
|
54
|
+
* the field is not a focus target.
|
|
55
|
+
*/
|
|
56
|
+
fieldName?: string;
|
|
48
57
|
};
|
|
49
58
|
/** The optional hint the `form/*` fields render under the label. */
|
|
50
59
|
export type FormFieldDescriptionProps = {
|
package/dist/form/field-error.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { errorText } from "../internal/styles.js";
|
|
2
3
|
import { cn } from "../utils.js";
|
|
3
4
|
/** First TanStack Form error message for a field, if any. */
|
|
4
5
|
export function getFieldErrorMessage(message) {
|
|
@@ -10,5 +11,5 @@ export function FieldError({ id, message, text, className, }) {
|
|
|
10
11
|
const resolved = text ?? getFieldErrorMessage(message);
|
|
11
12
|
if (!resolved)
|
|
12
13
|
return null;
|
|
13
|
-
return (_jsx("p", { id: id, "data-slot": "form-field-error", className: cn(
|
|
14
|
+
return (_jsx("p", { id: id, "data-slot": "form-field-error", className: cn(errorText, className), role: "alert", children: resolved }));
|
|
14
15
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps, type ComponentType, type ReactNode } from "react";
|
|
2
2
|
import type { DateLike, DatePickerCalendarProps } from "../date-picker.js";
|
|
3
3
|
import type { AsChildProps } from "../internal/as-child.js";
|
|
4
4
|
import { type ErrorTimingProps, type FieldMetaState, type FormFieldBaseProps, type FormFieldMetaProps } from "./field-error.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The error text a `form/*` field shows (`metaErrorText`) and its control
|
|
7
|
+
* id. The id is generated per field, so two forms that share a field name
|
|
8
|
+
* (login and sign-up `email`) never share an id, and each label, hint, and
|
|
9
|
+
* error links to its own control.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export declare function metaErrorText(meta: FieldMetaState | undefined, { submitted, showError }?: ErrorTimingProps): string | undefined;
|
|
11
|
+
export declare function useFormField(meta: FieldMetaState | undefined, timing: ErrorTimingProps): {
|
|
12
|
+
id: string;
|
|
13
|
+
errorText: string | undefined;
|
|
14
|
+
};
|
|
16
15
|
/**
|
|
17
16
|
* ARIA for a field's control, from the ids `FormFieldShell` renders:
|
|
18
17
|
* invalid while there is error text, described by the hint, the hidden
|
|
@@ -37,12 +36,6 @@ export declare function fieldAria(id: string, errorText: string | undefined, { d
|
|
|
37
36
|
* `onValueChange`, and `placeholder`.
|
|
38
37
|
*/
|
|
39
38
|
export type PickerFieldBaseProps = FormFieldBaseProps & FormFieldMetaProps & {
|
|
40
|
-
/**
|
|
41
|
-
* Id of the trigger the label points at. Defaults to a generated id;
|
|
42
|
-
* pass the field name (`id={field.name}`) so a submit handler such as
|
|
43
|
-
* `focusFirstInvalid` can find the control.
|
|
44
|
-
*/
|
|
45
|
-
id?: string;
|
|
46
39
|
/** Disables the trigger so no date can be picked. */
|
|
47
40
|
disabled?: boolean;
|
|
48
41
|
/** Marks the label and adds "Required" to the trigger's description. */
|
|
@@ -58,6 +51,23 @@ export type PickerFieldBaseProps = FormFieldBaseProps & FormFieldMetaProps & {
|
|
|
58
51
|
/** Day Picker options forwarded to the calendar (disabled days, etc.). */
|
|
59
52
|
calendarProps?: DatePickerCalendarProps;
|
|
60
53
|
};
|
|
54
|
+
/** The props a date or date-time field keeps for its shell, not the picker. */
|
|
55
|
+
type PickerFieldOwnKey = "fieldName" | "label" | "meta" | "submitted" | "showError" | "required" | "fieldClassName";
|
|
56
|
+
/**
|
|
57
|
+
* The date and date-time fields: `picker` (the trigger) in a
|
|
58
|
+
* `FormFieldShell`, given every prop of `field` the shell does not take. A
|
|
59
|
+
* button cannot carry `required`, so the trigger is described by the hidden
|
|
60
|
+
* "Required" text and reads "label, value".
|
|
61
|
+
*/
|
|
62
|
+
export declare function FormPickerField<P extends PickerFieldBaseProps>({ field, picker: Picker, }: {
|
|
63
|
+
/** The field's props. */
|
|
64
|
+
field: P;
|
|
65
|
+
/** The picker trigger. */
|
|
66
|
+
picker: ComponentType<Omit<P, PickerFieldOwnKey> & {
|
|
67
|
+
id: string;
|
|
68
|
+
"data-field-name"?: string;
|
|
69
|
+
} & ReturnType<typeof fieldAria>>;
|
|
70
|
+
}): import("react").JSX.Element;
|
|
61
71
|
/**
|
|
62
72
|
* Every `form/*` field's wrapper (`data-slot="form-field"`): children with
|
|
63
73
|
* the field spacing, then the error text. `asChild` makes the one child the
|
|
@@ -96,3 +106,4 @@ export declare function FormFieldShell({ id, label, labelAction, description, er
|
|
|
96
106
|
required?: boolean;
|
|
97
107
|
children: ReactNode;
|
|
98
108
|
}): import("react").JSX.Element;
|
|
109
|
+
export {};
|
package/dist/form/field-shell.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, } from "react";
|
|
2
4
|
import { Slot } from "radix-ui";
|
|
3
|
-
import { pickerValueId } from "../internal/form-control.js";
|
|
5
|
+
import { fieldNameProps, pickerValueId } from "../internal/form-control.js";
|
|
4
6
|
import { Label } from "../label.js";
|
|
5
7
|
import { cn } from "../utils.js";
|
|
6
8
|
import { FieldError, getFieldErrorMessage, } from "./field-error.js";
|
|
@@ -14,13 +16,22 @@ import { FieldError, getFieldErrorMessage, } from "./field-error.js";
|
|
|
14
16
|
* `showError` is false, or the field is untouched (`isTouched: false`)
|
|
15
17
|
* and the form has not been submitted.
|
|
16
18
|
*/
|
|
17
|
-
|
|
19
|
+
function metaErrorText(meta, { submitted, showError = true } = {}) {
|
|
18
20
|
if (!meta || !showError)
|
|
19
21
|
return undefined;
|
|
20
22
|
if (meta.isTouched === false && !submitted)
|
|
21
23
|
return undefined;
|
|
22
24
|
return getFieldErrorMessage(meta.errors[0]);
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The error text a `form/*` field shows (`metaErrorText`) and its control
|
|
28
|
+
* id. The id is generated per field, so two forms that share a field name
|
|
29
|
+
* (login and sign-up `email`) never share an id, and each label, hint, and
|
|
30
|
+
* error links to its own control.
|
|
31
|
+
*/
|
|
32
|
+
export function useFormField(meta, timing) {
|
|
33
|
+
return { id: useId(), errorText: metaErrorText(meta, timing) };
|
|
34
|
+
}
|
|
24
35
|
/** Id of the label `FormFieldShell` renders for control `id`. */
|
|
25
36
|
function labelIdFor(id) {
|
|
26
37
|
return `${id}-label`;
|
|
@@ -65,6 +76,20 @@ export function fieldAria(id, errorText, { description, required, labelledBy, }
|
|
|
65
76
|
}),
|
|
66
77
|
};
|
|
67
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The date and date-time fields: `picker` (the trigger) in a
|
|
81
|
+
* `FormFieldShell`, given every prop of `field` the shell does not take. A
|
|
82
|
+
* button cannot carry `required`, so the trigger is described by the hidden
|
|
83
|
+
* "Required" text and reads "label, value".
|
|
84
|
+
*/
|
|
85
|
+
export function FormPickerField({ field, picker: Picker, }) {
|
|
86
|
+
const { fieldName, label, meta, submitted, showError, required, fieldClassName, ...pickerProps } = field;
|
|
87
|
+
const { id, errorText } = useFormField(meta, { submitted, showError });
|
|
88
|
+
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Picker, { ...pickerProps, id: id, ...fieldNameProps(fieldName), ...fieldAria(id, errorText, {
|
|
89
|
+
required: required && "described",
|
|
90
|
+
labelledBy: "label-value",
|
|
91
|
+
}) }) }));
|
|
92
|
+
}
|
|
68
93
|
/**
|
|
69
94
|
* Every `form/*` field's wrapper (`data-slot="form-field"`): children with
|
|
70
95
|
* the field spacing, then the error text. `asChild` makes the one child the
|
|
@@ -15,14 +15,17 @@ export type InvalidSubmitEvent = {
|
|
|
15
15
|
/**
|
|
16
16
|
* Invalid-submit handler that focuses the first field, in page order, whose
|
|
17
17
|
* meta has errors. It reads the form state, not the rendered `aria-invalid`,
|
|
18
|
-
* so it works before React has re-rendered. Fields are matched by
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* so it works before React has re-rendered. Fields are matched by the
|
|
19
|
+
* `data-field-name` each `form/*` field puts on its control: `FormTextField`
|
|
20
|
+
* and `FormMarkdownField` read `field.name`; the other fields take
|
|
21
|
+
* `fieldName={field.name}`. The lookup stays inside the form with id
|
|
22
|
+
* `formId`, so a second form with the same field names is never touched.
|
|
23
|
+
* The form library needs no focus API of its own.
|
|
21
24
|
*
|
|
22
25
|
* ```tsx
|
|
23
26
|
* const form = useForm({ onSubmitInvalid: focusFirstInvalid("order-form") })
|
|
24
27
|
* <form id="order-form" onSubmit={...}>
|
|
25
|
-
* <FormSelectField
|
|
28
|
+
* <FormSelectField fieldName={field.name} ... />
|
|
26
29
|
* ```
|
|
27
30
|
*/
|
|
28
31
|
export declare function focusFirstInvalid(formId: string): ({ formApi }: InvalidSubmitEvent) => void;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { FIELD_NAME_ATTRIBUTE } from "../internal/form-control.js";
|
|
1
2
|
/**
|
|
2
3
|
* Invalid-submit handler that focuses the first field, in page order, whose
|
|
3
4
|
* meta has errors. It reads the form state, not the rendered `aria-invalid`,
|
|
4
|
-
* so it works before React has re-rendered. Fields are matched by
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* so it works before React has re-rendered. Fields are matched by the
|
|
6
|
+
* `data-field-name` each `form/*` field puts on its control: `FormTextField`
|
|
7
|
+
* and `FormMarkdownField` read `field.name`; the other fields take
|
|
8
|
+
* `fieldName={field.name}`. The lookup stays inside the form with id
|
|
9
|
+
* `formId`, so a second form with the same field names is never touched.
|
|
10
|
+
* The form library needs no focus API of its own.
|
|
7
11
|
*
|
|
8
12
|
* ```tsx
|
|
9
13
|
* const form = useForm({ onSubmitInvalid: focusFirstInvalid("order-form") })
|
|
10
14
|
* <form id="order-form" onSubmit={...}>
|
|
11
|
-
* <FormSelectField
|
|
15
|
+
* <FormSelectField fieldName={field.name} ... />
|
|
12
16
|
* ```
|
|
13
17
|
*/
|
|
14
18
|
export function focusFirstInvalid(formId) {
|
|
@@ -19,8 +23,8 @@ export function focusFirstInvalid(formId) {
|
|
|
19
23
|
const invalid = new Set(Object.entries(formApi.state.fieldMeta)
|
|
20
24
|
.filter(([, meta]) => meta?.errors.length)
|
|
21
25
|
.map(([name]) => name));
|
|
22
|
-
for (const element of form.
|
|
23
|
-
if (
|
|
26
|
+
for (const element of form.querySelectorAll(`[${FIELD_NAME_ATTRIBUTE}]`)) {
|
|
27
|
+
if (invalid.has(element.getAttribute(FIELD_NAME_ATTRIBUTE) ?? "")) {
|
|
24
28
|
element.focus();
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
@@ -3,7 +3,7 @@ import type { TextFieldBaseProps } from "./text-field.js";
|
|
|
3
3
|
* GFM body field: Write is a textarea, Preview is the sanitized Markdown
|
|
4
4
|
* renderer. String-in/string-out — not a WYSIWYG document model.
|
|
5
5
|
*/
|
|
6
|
-
export declare function FormMarkdownField({ field,
|
|
6
|
+
export declare function FormMarkdownField({ field, label, placeholder, rows, showError, submitted, required, className, fieldClassName, previewClassName, }: TextFieldBaseProps & {
|
|
7
7
|
/** Write textarea placeholder. Default "Markdown supported". */
|
|
8
8
|
placeholder?: string;
|
|
9
9
|
/** Write textarea rows. Default 5. */
|
|
@@ -4,9 +4,10 @@ import { lazy, Suspense, useState } from "react";
|
|
|
4
4
|
import { Spinner } from "../spinner.js";
|
|
5
5
|
import { Textarea } from "../textarea.js";
|
|
6
6
|
import { ToggleGroup, ToggleGroupItem } from "../toggle-group.js";
|
|
7
|
+
import { fieldNameProps } from "../internal/form-control.js";
|
|
7
8
|
import { fieldSurface } from "../internal/styles.js";
|
|
8
9
|
import { cn, onInputChange } from "../utils.js";
|
|
9
|
-
import { fieldAria, FormFieldShell,
|
|
10
|
+
import { fieldAria, FormFieldShell, useFormField } from "./field-shell.js";
|
|
10
11
|
// The renderer (react-markdown + remark-gfm) is fetched the first time
|
|
11
12
|
// Preview is shown or hovered, so a form in Write mode stays light.
|
|
12
13
|
const loadMarkdown = () => import("../markdown.js");
|
|
@@ -16,14 +17,17 @@ const Markdown = lazy(() => loadMarkdown().then((module) => ({ default: module.M
|
|
|
16
17
|
* GFM body field: Write is a textarea, Preview is the sanitized Markdown
|
|
17
18
|
* renderer. String-in/string-out — not a WYSIWYG document model.
|
|
18
19
|
*/
|
|
19
|
-
export function FormMarkdownField({ field,
|
|
20
|
+
export function FormMarkdownField({ field, label, placeholder = "Markdown supported", rows = 5, showError, submitted, required, className, fieldClassName, previewClassName, }) {
|
|
21
|
+
const { id, errorText } = useFormField(field.state.meta, {
|
|
22
|
+
submitted,
|
|
23
|
+
showError,
|
|
24
|
+
});
|
|
20
25
|
const [mode, setMode] = useState("write");
|
|
21
26
|
const onChange = onInputChange(field.handleChange);
|
|
22
|
-
const errorText = metaErrorText(field.state.meta, { submitted, showError });
|
|
23
27
|
const trimmed = field.state.value.trim();
|
|
24
28
|
function handleModeChange(value) {
|
|
25
29
|
if (value === "write" || value === "preview")
|
|
26
30
|
setMode(value);
|
|
27
31
|
}
|
|
28
|
-
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, labelFor: mode === "write", required: required, className: fieldClassName, labelAction: _jsxs(ToggleGroup, { type: "single", value: mode, onValueChange: handleModeChange, variant: "outline", size: "sm", spacing: 0, "aria-label": `${label} mode`, children: [_jsx(ToggleGroupItem, { value: "write", children: "Write" }), _jsx(ToggleGroupItem, { value: "preview", onPointerEnter: preloadMarkdown, onFocus: preloadMarkdown, children: "Preview" })] }), children: mode === "write" ? (_jsx(Textarea, { id: id, value: field.state.value, onChange: onChange, onBlur: field.handleBlur, placeholder: placeholder, rows: rows, required: required, className: className, ...fieldAria(id, errorText) })) : (_jsx("div", { "data-slot": "form-markdown-preview", className: cn(fieldSurface, "min-h-16", previewClassName), "aria-live": "polite", children: trimmed ? (_jsx(Suspense, { fallback: _jsx(Spinner, { "aria-label": "Loading preview" }), children: _jsx(Markdown, { content: trimmed }) })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing to preview" })) })) }));
|
|
32
|
+
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, labelFor: mode === "write", required: required, className: fieldClassName, labelAction: _jsxs(ToggleGroup, { type: "single", value: mode, onValueChange: handleModeChange, variant: "outline", size: "sm", spacing: 0, "aria-label": `${label} mode`, children: [_jsx(ToggleGroupItem, { value: "write", children: "Write" }), _jsx(ToggleGroupItem, { value: "preview", onPointerEnter: preloadMarkdown, onFocus: preloadMarkdown, children: "Preview" })] }), children: mode === "write" ? (_jsx(Textarea, { id: id, ...fieldNameProps(field.name), value: field.state.value, onChange: onChange, onBlur: field.handleBlur, placeholder: placeholder, rows: rows, required: required, className: className, ...fieldAria(id, errorText) })) : (_jsx("div", { "data-slot": "form-markdown-preview", className: cn(fieldSurface, "min-h-16", previewClassName), "aria-live": "polite", children: trimmed ? (_jsx(Suspense, { fallback: _jsx(Spinner, { "aria-label": "Loading preview" }), children: _jsx(Markdown, { content: trimmed }) })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing to preview" })) })) }));
|
|
29
33
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
2
|
import type { FormFieldBaseProps, FormFieldMetaProps } from "./field-error.js";
|
|
3
3
|
type Props = FormFieldBaseProps & FormFieldMetaProps & {
|
|
4
4
|
/** The selected item's value, or `undefined` for none. */
|
|
@@ -7,12 +7,6 @@ type Props = FormFieldBaseProps & FormFieldMetaProps & {
|
|
|
7
7
|
onValueChange: (value: string) => void;
|
|
8
8
|
/** Trigger text while no item is selected. */
|
|
9
9
|
placeholder?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Id of the trigger the label points at. Defaults to a generated id;
|
|
12
|
-
* pass the field name (`id={field.name}`) so a submit handler such as
|
|
13
|
-
* `focusFirstInvalid` can find the control.
|
|
14
|
-
*/
|
|
15
|
-
id?: string;
|
|
16
10
|
/** Requires a value (native form validation) and marks the label. */
|
|
17
11
|
required?: boolean;
|
|
18
12
|
/** Forwarded to the select trigger. */
|
|
@@ -23,5 +17,5 @@ type Props = FormFieldBaseProps & FormFieldMetaProps & {
|
|
|
23
17
|
children: ReactNode;
|
|
24
18
|
};
|
|
25
19
|
/** Select control with built-in field error text and invalid styling. */
|
|
26
|
-
export declare function FormSelectField({
|
|
20
|
+
export declare function FormSelectField({ fieldName, value, meta, submitted, showError, label, onValueChange, placeholder, required, className, fieldClassName, contentClassName, children, }: Props): import("react").JSX.Element;
|
|
27
21
|
export {};
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { fieldNameProps } from "../internal/form-control.js";
|
|
4
4
|
import { Select, SelectContent, SelectTrigger, SelectValue } from "../select.js";
|
|
5
|
-
import { fieldAria, FormFieldShell,
|
|
5
|
+
import { fieldAria, FormFieldShell, useFormField } from "./field-shell.js";
|
|
6
6
|
/** Select control with built-in field error text and invalid styling. */
|
|
7
|
-
export function FormSelectField({
|
|
8
|
-
const errorText =
|
|
9
|
-
|
|
10
|
-
const id = idProp ?? generatedId;
|
|
11
|
-
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsxs(Select, { value: value, onValueChange: onValueChange, required: required, children: [_jsx(SelectTrigger, { id: id, className: className, ...fieldAria(id, errorText, { required }), children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { className: contentClassName, children: children })] }) }));
|
|
7
|
+
export function FormSelectField({ fieldName, value, meta, submitted, showError, label, onValueChange, placeholder, required, className, fieldClassName, contentClassName, children, }) {
|
|
8
|
+
const { id, errorText } = useFormField(meta, { submitted, showError });
|
|
9
|
+
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsxs(Select, { value: value, onValueChange: onValueChange, required: required, children: [_jsx(SelectTrigger, { id: id, ...fieldNameProps(fieldName), className: className, ...fieldAria(id, errorText, { required }), children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { className: contentClassName, children: children })] }) }));
|
|
12
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FieldMetaState, FormFieldBaseProps } from "./field-error.js";
|
|
2
2
|
export type TextFieldApi = {
|
|
3
|
-
/** Field name; the
|
|
3
|
+
/** Field name; put on the control as `data-field-name` for `focusFirstInvalid`. */
|
|
4
4
|
name: string;
|
|
5
5
|
state: {
|
|
6
6
|
/** Current text. */
|
|
@@ -20,12 +20,6 @@ export type TextFieldApi = {
|
|
|
20
20
|
export type TextFieldBaseProps = FormFieldBaseProps & {
|
|
21
21
|
/** The TanStack Form field (or the same shape) the control reads and writes. */
|
|
22
22
|
field: TextFieldApi;
|
|
23
|
-
/**
|
|
24
|
-
* Control id the label points at. Defaults to `field.name`; pass a unique
|
|
25
|
-
* id when two forms on one page share a field name (login and sign-up
|
|
26
|
-
* `email`), or the second label focuses the first input.
|
|
27
|
-
*/
|
|
28
|
-
id?: string;
|
|
29
23
|
/** Sets `required` on the text control and marks the label. */
|
|
30
24
|
required?: boolean;
|
|
31
25
|
/** Forwarded to the text control (the input or textarea). */
|
|
@@ -35,7 +29,7 @@ export type TextFieldBaseProps = FormFieldBaseProps & {
|
|
|
35
29
|
* Text input, or a textarea with `multiline`, bound to a TanStack Form
|
|
36
30
|
* `field`, with a label, optional hint, and the `form/*` error text.
|
|
37
31
|
*/
|
|
38
|
-
export declare function FormTextField({ field,
|
|
32
|
+
export declare function FormTextField({ field, label, placeholder, description, autoFocus, multiline, rows, inputType, showError, submitted, autoComplete, required, minLength, className, fieldClassName, }: TextFieldBaseProps & {
|
|
39
33
|
/** Text shown in the empty control. */
|
|
40
34
|
placeholder?: string;
|
|
41
35
|
/** Optional hint under the label (employer vs referrer, etc.). */
|
package/dist/form/text-field.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Input } from "../input.js";
|
|
4
|
+
import { fieldNameProps } from "../internal/form-control.js";
|
|
4
5
|
import { Textarea } from "../textarea.js";
|
|
5
6
|
import { onInputChange } from "../utils.js";
|
|
6
|
-
import { fieldAria, FormFieldShell,
|
|
7
|
+
import { fieldAria, FormFieldShell, useFormField } from "./field-shell.js";
|
|
7
8
|
/**
|
|
8
9
|
* Text input, or a textarea with `multiline`, bound to a TanStack Form
|
|
9
10
|
* `field`, with a label, optional hint, and the `form/*` error text.
|
|
10
11
|
*/
|
|
11
|
-
export function FormTextField({ field,
|
|
12
|
+
export function FormTextField({ field, label, placeholder, description, autoFocus, multiline, rows = 3, inputType = "text", showError, submitted, autoComplete, required, minLength, className, fieldClassName, }) {
|
|
13
|
+
const { id, errorText } = useFormField(field.state.meta, {
|
|
14
|
+
submitted,
|
|
15
|
+
showError,
|
|
16
|
+
});
|
|
12
17
|
const onChange = onInputChange(field.handleChange);
|
|
13
|
-
const errorText = metaErrorText(field.state.meta, { submitted, showError });
|
|
14
18
|
const controlProps = {
|
|
15
19
|
id,
|
|
20
|
+
...fieldNameProps(field.name),
|
|
16
21
|
value: field.state.value,
|
|
17
22
|
onChange,
|
|
18
23
|
onBlur: field.handleBlur,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
2
|
import type { FormFieldBaseProps, FormFieldDescriptionProps, FormFieldMetaProps } from "./field-error.js";
|
|
3
3
|
export type ToggleGroupFieldOption<T extends string> = {
|
|
4
4
|
/** Value passed to `onValueChange` when this item is pressed. */
|
|
@@ -26,5 +26,5 @@ type Props<T extends string> = FormFieldBaseProps & FormFieldMetaProps & FormFie
|
|
|
26
26
|
* One choice from a few as a joined outline toggle group (a segmented
|
|
27
27
|
* control), with a visible label and the `form/*` error wiring.
|
|
28
28
|
*/
|
|
29
|
-
export declare function FormToggleGroupField<T extends string>({ label, description, value, onValueChange, options, meta, submitted, showError, required, disabled, size, className, fieldClassName, }: Props<T>): import("react").JSX.Element;
|
|
29
|
+
export declare function FormToggleGroupField<T extends string>({ fieldName, label, description, value, onValueChange, options, meta, submitted, showError, required, disabled, size, className, fieldClassName, }: Props<T>): import("react").JSX.Element;
|
|
30
30
|
export {};
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { fieldNameProps } from "../internal/form-control.js";
|
|
4
4
|
import { ToggleGroup, ToggleGroupItem } from "../toggle-group.js";
|
|
5
|
-
import { fieldAria, FormFieldShell,
|
|
5
|
+
import { fieldAria, FormFieldShell, useFormField } from "./field-shell.js";
|
|
6
6
|
/**
|
|
7
7
|
* One choice from a few as a joined outline toggle group (a segmented
|
|
8
8
|
* control), with a visible label and the `form/*` error wiring.
|
|
9
9
|
*/
|
|
10
|
-
export function FormToggleGroupField({ label, description, value, onValueChange, options, meta, submitted, showError, required, disabled, size = "sm", className, fieldClassName, }) {
|
|
11
|
-
const id =
|
|
12
|
-
const errorText = metaErrorText(meta, { submitted, showError });
|
|
10
|
+
export function FormToggleGroupField({ fieldName, label, description, value, onValueChange, options, meta, submitted, showError, required, disabled, size = "sm", className, fieldClassName, }) {
|
|
11
|
+
const { id, errorText } = useFormField(meta, { submitted, showError });
|
|
13
12
|
function handleValueChange(next) {
|
|
14
13
|
const option = options.find((o) => o.value === next);
|
|
15
14
|
if (option)
|
|
16
15
|
onValueChange(option.value);
|
|
17
16
|
}
|
|
18
|
-
return (_jsx(FormFieldShell, { id: id, label: label, labelFor: false, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(ToggleGroup, { id: id, type: "single", value: value, onValueChange: handleValueChange, variant: "outline", size: size, spacing: 0, disabled: disabled, ...fieldAria(id, errorText, { description, labelledBy: "label" }), className: className, children: options.map((option) => (_jsx(ToggleGroupItem, { value: option.value, children: option.label }, option.value))) }) }));
|
|
17
|
+
return (_jsx(FormFieldShell, { id: id, label: label, labelFor: false, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(ToggleGroup, { id: id, ...fieldNameProps(fieldName), type: "single", value: value, onValueChange: handleValueChange, variant: "outline", size: size, spacing: 0, disabled: disabled, ...fieldAria(id, errorText, { description, labelledBy: "label" }), className: className, children: options.map((option) => (_jsx(ToggleGroupItem, { value: option.value, children: option.label }, option.value))) }) }));
|
|
19
18
|
}
|
package/dist/info-tip.d.ts
CHANGED
|
@@ -13,7 +13,9 @@ export type InfoTipProps = {
|
|
|
13
13
|
/**
|
|
14
14
|
* `inline` sits in a line of text: the glyph is 1em, a quarter em after
|
|
15
15
|
* the word, on the text's cap height, and keeps the line height; the 44px
|
|
16
|
-
* hit area overflows.
|
|
16
|
+
* hit area overflows. `box` is a 44px button; after a label (any earlier
|
|
17
|
+
* sibling) its glyph sits that same quarter em from the text, so leave the
|
|
18
|
+
* gap off the parent. Default `box`.
|
|
17
19
|
*/
|
|
18
20
|
layout?: InfoTipLayout;
|
|
19
21
|
/** Panel side. Default `top`; Radix still flips on collision. */
|
package/dist/info-tip.js
CHANGED
|
@@ -54,9 +54,12 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
54
54
|
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { ...props, type: "button", "aria-label": label, "data-slot": "info-tip-trigger", "data-layout": layout, className: cn(focusRing, "relative inline-flex shrink-0 items-center justify-center rounded-none text-muted-foreground", quietTextHover,
|
|
55
55
|
// Inline: 1em scales with the sentence; -0.125em centres the
|
|
56
56
|
// glyph on the cap height without growing the line box.
|
|
57
|
+
// Box: the 44px target leaves 12px either side of the 20px
|
|
58
|
+
// glyph. After a label, pull it back over that padding so the
|
|
59
|
+
// glyph sits the same quarter em from the text as it does inline.
|
|
57
60
|
inline
|
|
58
61
|
? cn("ml-[0.25em] size-[1em] align-[-0.125em]", hitArea44)
|
|
59
|
-
: "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, onClick: (event) => {
|
|
62
|
+
: "size-11 not-first:ml-[calc(0.25em-0.75rem)]", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, onClick: (event) => {
|
|
60
63
|
// Hover already opened the panel; this click pins it instead of
|
|
61
64
|
// toggling it closed (Playwright and real mice both hover before
|
|
62
65
|
// they click). Preventing default skips Radix's toggle.
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Calendar cell size and padding, shared with LazyCalendar's placeholder
|
|
3
3
|
* (here, not in calendar.tsx, so the placeholder skips Day Picker).
|
|
4
4
|
*/
|
|
5
|
-
export declare const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(
|
|
5
|
+
export declare const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(2)]";
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Calendar cell size and padding, shared with LazyCalendar's placeholder
|
|
3
3
|
* (here, not in calendar.tsx, so the placeholder skips Day Picker).
|
|
4
4
|
*/
|
|
5
|
-
export const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(
|
|
5
|
+
export const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(2)]";
|
|
@@ -17,6 +17,12 @@ export declare const pickerTriggerClassName = "h-10 w-full border-input bg-backg
|
|
|
17
17
|
export declare const pickerContentClassName = "max-h-(--radix-popover-content-available-height) overflow-y-auto rounded-none p-0";
|
|
18
18
|
/** Gutter used when a calendar popover collides with the viewport. */
|
|
19
19
|
export declare const PICKER_COLLISION_PADDING = 16;
|
|
20
|
+
/** Carries a field's name on its control, for `focusFirstInvalid`. */
|
|
21
|
+
export declare const FIELD_NAME_ATTRIBUTE = "data-field-name";
|
|
22
|
+
/** The props that mark a control with its field's name. */
|
|
23
|
+
export declare function fieldNameProps(name: string | undefined): {
|
|
24
|
+
"data-field-name": string | undefined;
|
|
25
|
+
};
|
|
20
26
|
/**
|
|
21
27
|
* Id of a picker trigger's value text. A form field lists it after the label
|
|
22
28
|
* id in `aria-labelledby` so the trigger reads "label, value".
|
|
@@ -18,6 +18,12 @@ export const pickerTriggerClassName = `${formControlTriggerClassName} h-auto jus
|
|
|
18
18
|
export const pickerContentClassName = "max-h-(--radix-popover-content-available-height) overflow-y-auto rounded-none p-0";
|
|
19
19
|
/** Gutter used when a calendar popover collides with the viewport. */
|
|
20
20
|
export const PICKER_COLLISION_PADDING = 16;
|
|
21
|
+
/** Carries a field's name on its control, for `focusFirstInvalid`. */
|
|
22
|
+
export const FIELD_NAME_ATTRIBUTE = "data-field-name";
|
|
23
|
+
/** The props that mark a control with its field's name. */
|
|
24
|
+
export function fieldNameProps(name) {
|
|
25
|
+
return { [FIELD_NAME_ATTRIBUTE]: name };
|
|
26
|
+
}
|
|
21
27
|
/**
|
|
22
28
|
* Id of a picker trigger's value text. A form field lists it after the label
|
|
23
29
|
* id in `aria-labelledby` so the trigger reads "label, value".
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
1
2
|
/** Drop zone ids for the drag-time action rail. */
|
|
2
3
|
export const KANBAN_COMPLETE_ID = "__kanban_complete__";
|
|
3
4
|
export const KANBAN_DELETE_ID = "__kanban_delete__";
|
|
@@ -45,7 +46,7 @@ export function resolveKanbanDrop(input, groups) {
|
|
|
45
46
|
return null;
|
|
46
47
|
const columnItems = groups[toColumnId] ?? [];
|
|
47
48
|
const remaining = columnItems.filter((id) => id !== itemId);
|
|
48
|
-
const index =
|
|
49
|
+
const index = clamp(rawIndex ?? remaining.length, 0, remaining.length);
|
|
49
50
|
if (initialGroup === toColumnId && index === columnItems.indexOf(itemId)) {
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
@@ -12,19 +12,19 @@ export declare function listboxOptionId(listId: string, index: number): string;
|
|
|
12
12
|
export declare function scrollOptionIntoView(node: HTMLElement | null): void;
|
|
13
13
|
/**
|
|
14
14
|
* Shared keydown for a search input that drives a listbox: navigation keys
|
|
15
|
-
* move the highlight
|
|
16
|
-
* the key is handled (or an IME is composing, when every key
|
|
17
|
-
* IME), so the caller can stop; `false` leaves the key to the
|
|
18
|
-
* example SearchPicker's Escape).
|
|
15
|
+
* move the highlight over `items`, Enter picks the highlighted item. Returns
|
|
16
|
+
* `true` when the key is handled (or an IME is composing, when every key
|
|
17
|
+
* belongs to the IME), so the caller can stop; `false` leaves the key to the
|
|
18
|
+
* caller (for example SearchPicker's Escape).
|
|
19
19
|
*/
|
|
20
|
-
export declare function handleListboxKey(event: {
|
|
20
|
+
export declare function handleListboxKey<T>(event: {
|
|
21
21
|
key: string;
|
|
22
22
|
keyCode: number;
|
|
23
23
|
nativeEvent: KeyboardEvent;
|
|
24
24
|
preventDefault: () => void;
|
|
25
|
-
}, {
|
|
25
|
+
}, { items, highlight, setHighlight, onPick, }: {
|
|
26
|
+
items: readonly T[];
|
|
26
27
|
highlight: number;
|
|
27
|
-
length: number;
|
|
28
28
|
setHighlight: (index: number) => void;
|
|
29
|
-
|
|
29
|
+
onPick: (item: T) => void;
|
|
30
30
|
}): boolean;
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
1
2
|
/** Page keys move ten rows, a little more than one max-h-60 list viewport. */
|
|
2
3
|
const PAGE_SIZE = 10;
|
|
4
|
+
/** Rows each key moves the highlight; Home and End jump to the ends. */
|
|
5
|
+
const keySteps = new Map([
|
|
6
|
+
["ArrowDown", 1],
|
|
7
|
+
["ArrowUp", -1],
|
|
8
|
+
["PageDown", PAGE_SIZE],
|
|
9
|
+
["PageUp", -PAGE_SIZE],
|
|
10
|
+
["Home", -Infinity],
|
|
11
|
+
["End", Infinity],
|
|
12
|
+
]);
|
|
3
13
|
/** Return the next highlighted index for a listbox navigation key. */
|
|
4
14
|
function moveListboxHighlight(key, index, length) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return Math.max(index - 1, 0);
|
|
10
|
-
case "Home":
|
|
11
|
-
return 0;
|
|
12
|
-
case "End":
|
|
13
|
-
return length === 0 ? 0 : length - 1;
|
|
14
|
-
case "PageDown":
|
|
15
|
-
return length === 0 ? 0 : Math.min(index + PAGE_SIZE, length - 1);
|
|
16
|
-
case "PageUp":
|
|
17
|
-
return Math.max(index - PAGE_SIZE, 0);
|
|
18
|
-
default:
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
15
|
+
const step = keySteps.get(key);
|
|
16
|
+
if (step === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
return clamp(index + step, 0, Math.max(length - 1, 0));
|
|
21
19
|
}
|
|
22
20
|
/**
|
|
23
21
|
* Option id for `aria-activedescendant`. Index-based because option values
|
|
@@ -45,15 +43,15 @@ function isComposingKey(event) {
|
|
|
45
43
|
}
|
|
46
44
|
/**
|
|
47
45
|
* Shared keydown for a search input that drives a listbox: navigation keys
|
|
48
|
-
* move the highlight
|
|
49
|
-
* the key is handled (or an IME is composing, when every key
|
|
50
|
-
* IME), so the caller can stop; `false` leaves the key to the
|
|
51
|
-
* example SearchPicker's Escape).
|
|
46
|
+
* move the highlight over `items`, Enter picks the highlighted item. Returns
|
|
47
|
+
* `true` when the key is handled (or an IME is composing, when every key
|
|
48
|
+
* belongs to the IME), so the caller can stop; `false` leaves the key to the
|
|
49
|
+
* caller (for example SearchPicker's Escape).
|
|
52
50
|
*/
|
|
53
|
-
export function handleListboxKey(event, {
|
|
51
|
+
export function handleListboxKey(event, { items, highlight, setHighlight, onPick, }) {
|
|
54
52
|
if (isComposingKey(event))
|
|
55
53
|
return true;
|
|
56
|
-
const next = moveListboxHighlight(event.key, highlight, length);
|
|
54
|
+
const next = moveListboxHighlight(event.key, highlight, items.length);
|
|
57
55
|
if (next !== undefined) {
|
|
58
56
|
event.preventDefault();
|
|
59
57
|
setHighlight(next);
|
|
@@ -61,7 +59,9 @@ export function handleListboxKey(event, { highlight, length, setHighlight, onEnt
|
|
|
61
59
|
}
|
|
62
60
|
if (event.key === "Enter") {
|
|
63
61
|
event.preventDefault();
|
|
64
|
-
|
|
62
|
+
const item = items[highlight];
|
|
63
|
+
if (item !== undefined)
|
|
64
|
+
onPick(item);
|
|
65
65
|
return true;
|
|
66
66
|
}
|
|
67
67
|
return false;
|
|
@@ -17,6 +17,11 @@ export declare const overlaySideSlide = "data-[side=bottom]:slide-in-from-top-2
|
|
|
17
17
|
export declare const overlayFade = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0";
|
|
18
18
|
/** Fade plus a slight zoom, for panels (the backdrop only fades). */
|
|
19
19
|
export declare const overlayFadeZoom = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 data-open:zoom-in-95 data-closed:zoom-out-95";
|
|
20
|
+
/**
|
|
21
|
+
* A panel opened from a trigger (Popover, Select, DropdownMenu): the
|
|
22
|
+
* surface, sliding in from the trigger's side with a fade and zoom.
|
|
23
|
+
*/
|
|
24
|
+
export declare const anchoredPanel = "rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 data-open:zoom-in-95 data-closed:zoom-out-95";
|
|
20
25
|
/**
|
|
21
26
|
* Base of a menu or listbox row (SelectItem, DropdownMenuItem, Combobox and
|
|
22
27
|
* SearchPicker options). Each component adds its own right padding and
|