atlas-react-kit 1.1.0 → 1.2.1
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 -26
- package/DESIGN_RULES.md +14 -40
- package/README.md +7 -6
- package/dist/accordion.js +2 -2
- package/dist/alert.js +6 -5
- package/dist/badge.js +5 -2
- package/dist/breadcrumb.js +2 -2
- package/dist/button.js +7 -4
- package/dist/calendar.js +5 -4
- package/dist/combobox.js +7 -13
- package/dist/date-time-picker.js +2 -1
- package/dist/dropdown-menu.js +4 -4
- package/dist/field.d.ts +6 -3
- package/dist/field.js +3 -2
- package/dist/form/checkbox-field.d.ts +1 -1
- package/dist/form/checkbox-field.js +8 -11
- package/dist/form/combobox-field.js +2 -2
- package/dist/form/control-field.js +2 -7
- package/dist/form/date-picker-field.js +5 -2
- package/dist/form/date-time-picker-field.js +5 -2
- package/dist/form/field-error.d.ts +3 -3
- package/dist/form/field-shell.d.ts +36 -31
- package/dist/form/field-shell.js +41 -34
- package/dist/form/markdown-field.js +2 -2
- package/dist/form/select-field.js +2 -2
- package/dist/form/text-field.js +2 -4
- package/dist/form/toggle-group-field.js +2 -2
- package/dist/info-tip.d.ts +5 -4
- package/dist/info-tip.js +15 -14
- package/dist/internal/active-link.d.ts +7 -0
- package/dist/internal/active-link.js +9 -0
- package/dist/internal/calendar-size.d.ts +5 -0
- package/dist/internal/calendar-size.js +5 -0
- package/dist/internal/form-control.d.ts +1 -1
- package/dist/internal/form-control.js +2 -1
- package/dist/internal/listbox-navigation.d.ts +0 -5
- package/dist/internal/listbox-navigation.js +0 -7
- package/dist/internal/listbox-option.d.ts +16 -3
- package/dist/internal/listbox-option.js +22 -5
- package/dist/internal/overlay-styles.d.ts +10 -7
- package/dist/internal/overlay-styles.js +10 -7
- package/dist/internal/search.d.ts +9 -1
- package/dist/internal/search.js +14 -5
- package/dist/internal/styles.d.ts +43 -0
- package/dist/internal/styles.js +43 -0
- package/dist/internal/tabs-styles.d.ts +8 -7
- package/dist/internal/tabs-styles.js +15 -13
- package/dist/internal/use-media-query.d.ts +5 -0
- package/dist/internal/use-media-query.js +5 -0
- package/dist/kanban.d.ts +1 -1
- package/dist/kanban.js +6 -4
- package/dist/label.js +2 -1
- package/dist/lazy-calendar.js +3 -2
- package/dist/nav-link.js +2 -1
- package/dist/popover.js +4 -3
- package/dist/search-picker.d.ts +1 -1
- package/dist/search-picker.js +7 -12
- package/dist/select.js +6 -6
- package/dist/skip-link.js +2 -1
- package/dist/tabs.d.ts +3 -3
- package/dist/tabs.js +7 -6
- package/dist/toast.js +11 -10
- package/dist/toggle.d.ts +2 -0
- package/dist/toggle.js +8 -3
- package/dist/tooltip.js +2 -2
- package/package.json +1 -1
- package/skills/atlas-react-kit/SKILL.md +46 -42
package/dist/dropdown-menu.js
CHANGED
|
@@ -3,11 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
5
5
|
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
6
|
-
import { menuLabel,
|
|
7
|
-
import { dataDisabledState, optionState } from "./internal/styles.js";
|
|
6
|
+
import { menuLabel, optionRow, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
|
|
7
|
+
import { dataDisabledState, inlineIcon, optionState, } from "./internal/styles.js";
|
|
8
8
|
import { cn } from "./utils.js";
|
|
9
9
|
/** Menu row: the shared option row with menu padding and the inset slot. */
|
|
10
|
-
const menuItemClassName = cn(optionRow, optionState.focus, dataDisabledState,
|
|
10
|
+
const menuItemClassName = cn(optionRow, optionState.focus, dataDisabledState, inlineIcon.default, "pr-3 data-inset:pl-9");
|
|
11
11
|
const menuContentClassName = cn(overlaySurface, overlaySideSlide, overlayFadeZoom, "z-50 overflow-x-hidden overflow-y-auto p-1");
|
|
12
12
|
/** Action menu for commands (row actions, overflow menus). Use `Select` to pick a value. */
|
|
13
13
|
function DropdownMenu({ ...props }) {
|
|
@@ -19,7 +19,7 @@ function DropdownMenuPortal({ ...props }) {
|
|
|
19
19
|
function DropdownMenuTrigger({ ...props }) {
|
|
20
20
|
return (_jsx(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props }));
|
|
21
21
|
}
|
|
22
|
-
function DropdownMenuContent({ className, align = "start", sideOffset =
|
|
22
|
+
function DropdownMenuContent({ className, align = "start", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
|
|
23
23
|
return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { "data-slot": "dropdown-menu-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(menuContentClassName, "max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)", className), ...props }) }));
|
|
24
24
|
}
|
|
25
25
|
function DropdownMenuGroup({ ...props }) {
|
package/dist/field.d.ts
CHANGED
|
@@ -31,10 +31,13 @@ declare function FieldSeparator({ children, className, ...props }: React.Compone
|
|
|
31
31
|
/** Optional text shown in a gap in the middle of the line, e.g. "Or". */
|
|
32
32
|
children?: React.ReactNode;
|
|
33
33
|
}): import("react").JSX.Element;
|
|
34
|
+
/** One error as form libraries report it; `FieldError` shows its `message`. */
|
|
35
|
+
type FieldErrorItem = {
|
|
36
|
+
message?: string;
|
|
37
|
+
} | undefined;
|
|
34
38
|
declare function FieldError({ className, children, errors, ...props }: React.ComponentProps<"div"> & {
|
|
35
39
|
/** Errors to list, deduplicated by message; `children` replaces them. */
|
|
36
|
-
errors?:
|
|
37
|
-
message?: string;
|
|
38
|
-
} | undefined>;
|
|
40
|
+
errors?: FieldErrorItem[];
|
|
39
41
|
}): import("react").JSX.Element | null;
|
|
40
42
|
export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle, };
|
|
43
|
+
export type { FieldErrorItem };
|
package/dist/field.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import { labelText } from "./internal/styles.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
import { Label } from "./label.js";
|
|
7
8
|
import { Separator } from "./separator.js";
|
|
@@ -9,7 +10,7 @@ function FieldSet({ className, ...props }) {
|
|
|
9
10
|
return (_jsx("fieldset", { "data-slot": "field-set", className: cn("flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", className), ...props }));
|
|
10
11
|
}
|
|
11
12
|
function FieldLegend({ className, variant = "legend", ...props }) {
|
|
12
|
-
return (_jsx("legend", { "data-slot": "field-legend", "data-variant": variant, className: cn("mb-3
|
|
13
|
+
return (_jsx("legend", { "data-slot": "field-legend", "data-variant": variant, className: cn("mb-3", labelText, className), ...props }));
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Stack of fields and the `@container/field-group` that responsive fields
|
|
@@ -49,7 +50,7 @@ function FieldLabel({ className, ...props }) {
|
|
|
49
50
|
return (_jsx(Label, { "data-slot": "field-label", className: cn("group/field-label peer/field-label flex w-fit gap-2 leading-relaxed group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-selected has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-4 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-selected", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className), ...props }));
|
|
50
51
|
}
|
|
51
52
|
function FieldTitle({ className, ...props }) {
|
|
52
|
-
return (_jsx("div", { "data-slot": "field-label", className: cn("flex w-fit items-center gap-2
|
|
53
|
+
return (_jsx("div", { "data-slot": "field-label", className: cn(labelText, "flex w-fit items-center gap-2 group-data-[disabled=true]/field:opacity-50 in-data-[slot=field-label]:font-semibold", className), ...props }));
|
|
53
54
|
}
|
|
54
55
|
function FieldDescription({ className, ...props }) {
|
|
55
56
|
return (_jsx("p", { "data-slot": "field-description", className: cn("text-left text-sm leading-normal font-normal tracking-normal text-muted-foreground normal-case group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-selected-foreground", className), ...props }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FormFieldBaseProps, FormFieldDescriptionProps, FormFieldMetaProps } from "./field-error.js";
|
|
2
2
|
type CheckboxFieldProps = FormFieldBaseProps & FormFieldMetaProps & FormFieldDescriptionProps & {
|
|
3
3
|
/** Whether the box is checked. */
|
|
4
4
|
checked: boolean;
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { Checkbox } from "../checkbox.js";
|
|
5
|
-
import { Field, FieldContent,
|
|
5
|
+
import { Field, FieldContent, FieldLabel, FieldLegend } from "../field.js";
|
|
6
6
|
import { cn } from "../utils.js";
|
|
7
|
-
import {
|
|
8
|
-
import { describedBy, descriptionIdFor, errorIdFor, invalidWhen, metaErrorText, } from "./field-shell.js";
|
|
7
|
+
import { fieldAria, FormFieldDescription, FormFieldFrame, metaErrorText, } from "./field-shell.js";
|
|
9
8
|
/**
|
|
10
9
|
* One boolean as a labelled checkbox, with an optional hint and the same
|
|
11
10
|
* error text and timing as the other `form/*` fields.
|
|
@@ -14,16 +13,14 @@ export function FormCheckboxField({ checked, onCheckedChange, onBlur, label, des
|
|
|
14
13
|
const generatedId = useId();
|
|
15
14
|
const id = idProp ?? generatedId;
|
|
16
15
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
17
|
-
const
|
|
18
|
-
const checkbox = (_jsx(Checkbox, { id: id, checked: checked, onCheckedChange: (next) => onCheckedChange(next === true), onBlur: onBlur, disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText, descriptionId), className: className }));
|
|
16
|
+
const checkbox = (_jsx(Checkbox, { id: id, checked: checked, onCheckedChange: (next) => onCheckedChange(next === true), onBlur: onBlur, disabled: disabled, required: required, ...fieldAria(id, errorText, { description, required }), className: className }));
|
|
19
17
|
const labelNode = (_jsx(FieldLabel, { htmlFor: id, required: required, children: label }));
|
|
20
|
-
return (
|
|
18
|
+
return (_jsx(FormFieldFrame, { id: id, errorText: errorText, className: fieldClassName, children: _jsxs(Field, { orientation: "horizontal", children: [controlPosition === "start" ? checkbox : null, description ? (_jsxs(FieldContent, { children: [labelNode, _jsx(FormFieldDescription, { id: id, children: description })] })) : (labelNode), controlPosition === "end" ? checkbox : null] }) }));
|
|
21
19
|
}
|
|
22
20
|
/** Several values from a short list, as a labelled set of checkboxes. */
|
|
23
21
|
export function FormCheckboxGroupField({ label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
|
|
24
22
|
const id = useId();
|
|
25
23
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
26
|
-
const descriptionId = description ? descriptionIdFor(id) : undefined;
|
|
27
24
|
function toggle(option, on) {
|
|
28
25
|
const next = new Set(value);
|
|
29
26
|
if (on)
|
|
@@ -32,8 +29,8 @@ export function FormCheckboxGroupField({ label, description, options, value, onV
|
|
|
32
29
|
next.delete(option);
|
|
33
30
|
onValueChange(options.map((o) => o.value).filter((v) => next.has(v)));
|
|
34
31
|
}
|
|
35
|
-
return (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
return (_jsx(FormFieldFrame, { asChild: true, id: id, errorText: errorText, className: fieldClassName, children: _jsxs("fieldset", { ...fieldAria(id, errorText, { description }), children: [_jsx(FieldLegend, { variant: "label", className: "mb-0", children: label }), description ? (_jsx(FormFieldDescription, { id: id, children: description })) : null, _jsx("div", { "data-slot": "form-checkbox-group-options", className: cn("flex flex-wrap gap-x-6 gap-y-3 pt-1", className), children: options.map((option) => {
|
|
33
|
+
const optionId = `${id}-${option.value}`;
|
|
34
|
+
return (_jsxs(Field, { orientation: "horizontal", children: [_jsx(Checkbox, { id: optionId, checked: value.includes(option.value), onCheckedChange: (next) => toggle(option.value, next === true), disabled: disabled }), _jsx(FieldLabel, { htmlFor: optionId, children: option.label })] }, option.value));
|
|
35
|
+
}) })] }) }));
|
|
39
36
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { Combobox } from "../combobox.js";
|
|
5
|
-
import {
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
6
6
|
/**
|
|
7
7
|
* `Combobox` with a label, optional hint, and the `form/*` error text and
|
|
8
8
|
* timing. Pass the field's blur handler as `onBlur` so touch state updates.
|
|
@@ -10,5 +10,5 @@ import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorTe
|
|
|
10
10
|
export function FormComboboxField({ label, description, meta, submitted, showError, required, fieldClassName, ...props }) {
|
|
11
11
|
const id = useId();
|
|
12
12
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
13
|
-
return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Combobox, { ...props, id: id,
|
|
13
|
+
return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Combobox, { ...props, id: id, ...fieldAria(id, errorText, { description, required }) }) }));
|
|
14
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
5
5
|
/**
|
|
6
6
|
* The `form/*` field layout for a control the kit does not ship (a
|
|
7
7
|
* combobox, a segmented picker): label with the required marker, optional
|
|
@@ -13,10 +13,5 @@ export function FormControlField({ id: idProp, label, description, required, met
|
|
|
13
13
|
const generatedId = useId();
|
|
14
14
|
const id = idProp ?? generatedId;
|
|
15
15
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
16
|
-
return (_jsx(FormFieldShell, { ...props, id: id, label: label, description: description, errorText: errorText, required: required, children: children({
|
|
17
|
-
id,
|
|
18
|
-
"aria-invalid": invalidWhen(errorText),
|
|
19
|
-
"aria-describedby": describedBy(id, errorText, description ? descriptionIdFor(id) : undefined),
|
|
20
|
-
"aria-required": required || undefined,
|
|
21
|
-
}) }));
|
|
16
|
+
return (_jsx(FormFieldShell, { ...props, id: id, label: label, description: description, errorText: errorText, required: required, children: children({ id, ...fieldAria(id, errorText, { description, required }) }) }));
|
|
22
17
|
}
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { DatePickerButton } from "../date-picker.js";
|
|
5
|
-
import { FormFieldShell, metaErrorText,
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText, } from "./field-shell.js";
|
|
6
6
|
/** Date picker with built-in field error text and invalid styling. */
|
|
7
7
|
export function FormDatePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }) {
|
|
8
8
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
9
9
|
const generatedId = useId();
|
|
10
10
|
const id = idProp ?? generatedId;
|
|
11
|
-
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsx(DatePickerButton, { ...picker, id: id, onValueChange: onValueChange, ...
|
|
11
|
+
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsx(DatePickerButton, { ...picker, id: id, onValueChange: onValueChange, ...fieldAria(id, errorText, {
|
|
12
|
+
required: required && "described",
|
|
13
|
+
labelledBy: "label-value",
|
|
14
|
+
}) }) }));
|
|
12
15
|
}
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { DateTimePickerButton } from "../date-time-picker.js";
|
|
5
|
-
import { FormFieldShell, metaErrorText,
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText, } from "./field-shell.js";
|
|
6
6
|
/** Date-time picker with built-in field error text and invalid styling. */
|
|
7
7
|
export function FormDateTimePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }) {
|
|
8
8
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
9
9
|
const generatedId = useId();
|
|
10
10
|
const id = idProp ?? generatedId;
|
|
11
|
-
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsx(DateTimePickerButton, { ...picker, id: id, onValueChange: onValueChange, ...
|
|
11
|
+
return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, required: required, className: fieldClassName, children: _jsx(DateTimePickerButton, { ...picker, id: id, onValueChange: onValueChange, ...fieldAria(id, errorText, {
|
|
12
|
+
required: required && "described",
|
|
13
|
+
labelledBy: "label-value",
|
|
14
|
+
}) }) }));
|
|
12
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { FieldErrorItem } from "../field.js";
|
|
3
|
+
/** An error as TanStack Form reports it: text, or an error with a `message`. */
|
|
4
|
+
export type FieldErrorMessage = string | FieldErrorItem;
|
|
5
5
|
export type FieldMetaState = {
|
|
6
6
|
/** Validation errors for the field; the `form/*` fields show the first. */
|
|
7
7
|
errors: FieldErrorMessage[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentProps, ReactNode } from "react";
|
|
2
2
|
import type { DateLike, DatePickerCalendarProps } from "../date-picker.js";
|
|
3
|
+
import type { AsChildProps } from "../internal/as-child.js";
|
|
3
4
|
import { type ErrorTimingProps, type FieldMetaState, type FormFieldBaseProps, type FormFieldMetaProps } from "./field-error.js";
|
|
4
5
|
/**
|
|
5
6
|
* Not a public entry. The layout and error wiring every `form/*` field
|
|
@@ -12,19 +13,25 @@ import { type ErrorTimingProps, type FieldMetaState, type FormFieldBaseProps, ty
|
|
|
12
13
|
* and the form has not been submitted.
|
|
13
14
|
*/
|
|
14
15
|
export declare function metaErrorText(meta: FieldMetaState | undefined, { submitted, showError }?: ErrorTimingProps): string | undefined;
|
|
15
|
-
/** `aria-invalid` for the control: `true` while there is error text. */
|
|
16
|
-
export declare function invalidWhen(errorText: string | undefined): true | undefined;
|
|
17
|
-
/** Id of the label `FormFieldShell` renders for control `id`. */
|
|
18
|
-
export declare function labelIdFor(id: string): string;
|
|
19
|
-
/** Id of the hint `FormFieldShell` renders for control `id`. */
|
|
20
|
-
export declare function descriptionIdFor(id: string): string;
|
|
21
|
-
/** Id of the error text `FormFieldShell` renders for control `id`. */
|
|
22
|
-
export declare function errorIdFor(id: string): string;
|
|
23
16
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
17
|
+
* ARIA for a field's control, from the ids `FormFieldShell` renders:
|
|
18
|
+
* invalid while there is error text, described by the hint, the hidden
|
|
19
|
+
* "Required" text, and the error. `required: "described"` is for a button
|
|
20
|
+
* trigger, which cannot take `aria-required`; leave `required` out where
|
|
21
|
+
* the control carries native `required`. `labelledBy: "label"` names a
|
|
22
|
+
* group; `"label-value"` reads a picker "label, value".
|
|
26
23
|
*/
|
|
27
|
-
export declare function
|
|
24
|
+
export declare function fieldAria(id: string, errorText: string | undefined, { description, required, labelledBy, }?: {
|
|
25
|
+
/** The field's hint; when set, the control is described by it. */
|
|
26
|
+
description?: ReactNode;
|
|
27
|
+
required?: boolean | "described";
|
|
28
|
+
labelledBy?: "label" | "label-value";
|
|
29
|
+
}): {
|
|
30
|
+
"aria-invalid": true | undefined;
|
|
31
|
+
"aria-describedby": string | undefined;
|
|
32
|
+
"aria-required": true | undefined;
|
|
33
|
+
"aria-labelledby"?: string | undefined;
|
|
34
|
+
};
|
|
28
35
|
/**
|
|
29
36
|
* Props the date and date-time fields share; each adds its own `value`,
|
|
30
37
|
* `onValueChange`, and `placeholder`.
|
|
@@ -52,29 +59,27 @@ export type PickerFieldBaseProps = FormFieldBaseProps & FormFieldMetaProps & {
|
|
|
52
59
|
calendarProps?: DatePickerCalendarProps;
|
|
53
60
|
};
|
|
54
61
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `aria-required`) and the error.
|
|
62
|
+
* Every `form/*` field's wrapper (`data-slot="form-field"`): children with
|
|
63
|
+
* the field spacing, then the error text. `asChild` makes the one child the
|
|
64
|
+
* wrapper (the checkbox group's fieldset).
|
|
59
65
|
*/
|
|
60
|
-
export declare function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
export declare function FormFieldFrame({ id, errorText, asChild, className, children, ...props }: Omit<ComponentProps<"div">, "id"> & AsChildProps & {
|
|
67
|
+
/** Id of the control; the error id derives from it. */
|
|
68
|
+
id: string;
|
|
69
|
+
/** Error text under the children; `undefined` shows none. */
|
|
70
|
+
errorText: string | undefined;
|
|
71
|
+
}): import("react").JSX.Element;
|
|
72
|
+
/** A field's hint, with id `descriptionIdFor(id)`. */
|
|
73
|
+
export declare function FormFieldDescription({ id, children, }: {
|
|
74
|
+
id: string;
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
}): import("react").JSX.Element;
|
|
65
77
|
/**
|
|
66
|
-
* Label, control, and error
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* preview), so the label does not point at a missing element.
|
|
70
|
-
* `description` is a hint between the label and the control, with id
|
|
71
|
-
* `descriptionIdFor(id)`. `className` and other div props go to the
|
|
72
|
-
* wrapper (`data-slot="form-field"`), e.g. a width.
|
|
73
|
-
* `required` marks the label; the control still sets its own `required`.
|
|
74
|
-
* The error sits below the control and takes no space while there is none,
|
|
75
|
-
* so put fields in a grid row with `items-start` to keep controls aligned.
|
|
78
|
+
* Label, hint, control, and error in a `FormFieldFrame`. `labelAction` sits
|
|
79
|
+
* on the label's row; `labelFor={false}` when the control is not rendered
|
|
80
|
+
* (Markdown preview). `required` marks the label only.
|
|
76
81
|
*/
|
|
77
|
-
export declare function FormFieldShell({ id, label, labelAction, description, errorText, labelFor, required,
|
|
82
|
+
export declare function FormFieldShell({ id, label, labelAction, description, errorText, labelFor, required, children, ...props }: Omit<ComponentProps<"div">, "id" | "children"> & {
|
|
78
83
|
/** Id of the control; the label, hint, and error ids derive from it. */
|
|
79
84
|
id: string;
|
|
80
85
|
/** Visible label text. */
|
package/dist/form/field-shell.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "radix-ui";
|
|
2
3
|
import { pickerValueId } from "../internal/form-control.js";
|
|
3
4
|
import { Label } from "../label.js";
|
|
4
5
|
import { cn } from "../utils.js";
|
|
@@ -20,20 +21,16 @@ export function metaErrorText(meta, { submitted, showError = true } = {}) {
|
|
|
20
21
|
return undefined;
|
|
21
22
|
return getFieldErrorMessage(meta.errors[0]);
|
|
22
23
|
}
|
|
23
|
-
/** `aria-invalid` for the control: `true` while there is error text. */
|
|
24
|
-
export function invalidWhen(errorText) {
|
|
25
|
-
return errorText ? true : undefined;
|
|
26
|
-
}
|
|
27
24
|
/** Id of the label `FormFieldShell` renders for control `id`. */
|
|
28
|
-
|
|
25
|
+
function labelIdFor(id) {
|
|
29
26
|
return `${id}-label`;
|
|
30
27
|
}
|
|
31
28
|
/** Id of the hint `FormFieldShell` renders for control `id`. */
|
|
32
|
-
|
|
29
|
+
function descriptionIdFor(id) {
|
|
33
30
|
return `${id}-description`;
|
|
34
31
|
}
|
|
35
32
|
/** Id of the error text `FormFieldShell` renders for control `id`. */
|
|
36
|
-
|
|
33
|
+
function errorIdFor(id) {
|
|
37
34
|
return `${id}-error`;
|
|
38
35
|
}
|
|
39
36
|
/**
|
|
@@ -44,39 +41,49 @@ function requiredIdFor(id) {
|
|
|
44
41
|
return `${id}-required`;
|
|
45
42
|
}
|
|
46
43
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* ARIA for a field's control, from the ids `FormFieldShell` renders:
|
|
45
|
+
* invalid while there is error text, described by the hint, the hidden
|
|
46
|
+
* "Required" text, and the error. `required: "described"` is for a button
|
|
47
|
+
* trigger, which cannot take `aria-required`; leave `required` out where
|
|
48
|
+
* the control carries native `required`. `labelledBy: "label"` names a
|
|
49
|
+
* group; `"label-value"` reads a picker "label, value".
|
|
49
50
|
*/
|
|
50
|
-
export function
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
export function fieldAria(id, errorText, { description, required, labelledBy, } = {}) {
|
|
52
|
+
const describedBy = [
|
|
53
|
+
description ? descriptionIdFor(id) : undefined,
|
|
54
|
+
required === "described" ? requiredIdFor(id) : undefined,
|
|
55
|
+
errorText ? errorIdFor(id) : undefined,
|
|
56
|
+
].filter(Boolean);
|
|
57
|
+
return {
|
|
58
|
+
"aria-invalid": errorText ? true : undefined,
|
|
59
|
+
"aria-describedby": describedBy.length ? describedBy.join(" ") : undefined,
|
|
60
|
+
"aria-required": required === true ? true : undefined,
|
|
61
|
+
...(labelledBy && {
|
|
62
|
+
"aria-labelledby": labelledBy === "label"
|
|
63
|
+
? labelIdFor(id)
|
|
64
|
+
: `${labelIdFor(id)} ${pickerValueId(id)}`,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
53
67
|
}
|
|
54
68
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `aria-required`) and the error.
|
|
69
|
+
* Every `form/*` field's wrapper (`data-slot="form-field"`): children with
|
|
70
|
+
* the field spacing, then the error text. `asChild` makes the one child the
|
|
71
|
+
* wrapper (the checkbox group's fieldset).
|
|
59
72
|
*/
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
export function FormFieldFrame({ id, errorText, asChild, className, children, ...props }) {
|
|
74
|
+
const Comp = asChild ? Slot.Root : "div";
|
|
75
|
+
return (_jsxs(Comp, { "data-slot": "form-field", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Slot.Slottable, { children: children }), _jsx(FieldError, { id: errorIdFor(id), text: errorText })] }));
|
|
76
|
+
}
|
|
77
|
+
/** A field's hint, with id `descriptionIdFor(id)`. */
|
|
78
|
+
export function FormFieldDescription({ id, children, }) {
|
|
79
|
+
return (_jsx("p", { id: descriptionIdFor(id), "data-slot": "form-field-description", className: "text-xs text-muted-foreground", children: children }));
|
|
66
80
|
}
|
|
67
81
|
/**
|
|
68
|
-
* Label, control, and error
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* preview), so the label does not point at a missing element.
|
|
72
|
-
* `description` is a hint between the label and the control, with id
|
|
73
|
-
* `descriptionIdFor(id)`. `className` and other div props go to the
|
|
74
|
-
* wrapper (`data-slot="form-field"`), e.g. a width.
|
|
75
|
-
* `required` marks the label; the control still sets its own `required`.
|
|
76
|
-
* The error sits below the control and takes no space while there is none,
|
|
77
|
-
* so put fields in a grid row with `items-start` to keep controls aligned.
|
|
82
|
+
* Label, hint, control, and error in a `FormFieldFrame`. `labelAction` sits
|
|
83
|
+
* on the label's row; `labelFor={false}` when the control is not rendered
|
|
84
|
+
* (Markdown preview). `required` marks the label only.
|
|
78
85
|
*/
|
|
79
|
-
export function FormFieldShell({ id, label, labelAction, description, errorText, labelFor = true, required,
|
|
86
|
+
export function FormFieldShell({ id, label, labelAction, description, errorText, labelFor = true, required, children, ...props }) {
|
|
80
87
|
const labelNode = (_jsx(Label, { id: labelIdFor(id), htmlFor: labelFor ? id : undefined, required: required, children: label }));
|
|
81
|
-
return (_jsxs(
|
|
88
|
+
return (_jsxs(FormFieldFrame, { ...props, id: id, errorText: errorText, children: [labelAction ? (_jsxs("div", { "data-slot": "form-field-header", className: "flex items-center justify-between gap-2", children: [labelNode, labelAction] })) : (labelNode), description ? (_jsx(FormFieldDescription, { id: id, children: description })) : null, children, required ? (_jsx("span", { id: requiredIdFor(id), hidden: true, children: "Required" })) : null] }));
|
|
82
89
|
}
|
|
@@ -6,7 +6,7 @@ import { Textarea } from "../textarea.js";
|
|
|
6
6
|
import { ToggleGroup, ToggleGroupItem } from "../toggle-group.js";
|
|
7
7
|
import { fieldSurface } from "../internal/styles.js";
|
|
8
8
|
import { cn, onInputChange } from "../utils.js";
|
|
9
|
-
import {
|
|
9
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
10
10
|
// The renderer (react-markdown + remark-gfm) is fetched the first time
|
|
11
11
|
// Preview is shown or hovered, so a form in Write mode stays light.
|
|
12
12
|
const loadMarkdown = () => import("../markdown.js");
|
|
@@ -25,5 +25,5 @@ export function FormMarkdownField({ field, id = field.name, label, placeholder =
|
|
|
25
25
|
if (value === "write" || value === "preview")
|
|
26
26
|
setMode(value);
|
|
27
27
|
}
|
|
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,
|
|
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" })) })) }));
|
|
29
29
|
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { Select, SelectContent, SelectTrigger, SelectValue } from "../select.js";
|
|
5
|
-
import {
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
6
6
|
/** Select control with built-in field error text and invalid styling. */
|
|
7
7
|
export function FormSelectField({ id: idProp, value, meta, submitted, showError, label, onValueChange, placeholder, required, className, fieldClassName, contentClassName, children, }) {
|
|
8
8
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
9
9
|
const generatedId = useId();
|
|
10
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,
|
|
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 })] }) }));
|
|
12
12
|
}
|
package/dist/form/text-field.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { Input } from "../input.js";
|
|
4
4
|
import { Textarea } from "../textarea.js";
|
|
5
5
|
import { onInputChange } from "../utils.js";
|
|
6
|
-
import {
|
|
6
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
7
7
|
/**
|
|
8
8
|
* Text input, or a textarea with `multiline`, bound to a TanStack Form
|
|
9
9
|
* `field`, with a label, optional hint, and the `form/*` error text.
|
|
@@ -11,7 +11,6 @@ import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorTe
|
|
|
11
11
|
export function FormTextField({ field, id = field.name, label, placeholder, description, autoFocus, multiline, rows = 3, inputType = "text", showError, submitted, autoComplete, required, minLength, className, fieldClassName, }) {
|
|
12
12
|
const onChange = onInputChange(field.handleChange);
|
|
13
13
|
const errorText = metaErrorText(field.state.meta, { submitted, showError });
|
|
14
|
-
const descriptionId = description ? descriptionIdFor(id) : undefined;
|
|
15
14
|
const controlProps = {
|
|
16
15
|
id,
|
|
17
16
|
value: field.state.value,
|
|
@@ -22,8 +21,7 @@ export function FormTextField({ field, id = field.name, label, placeholder, desc
|
|
|
22
21
|
required,
|
|
23
22
|
minLength,
|
|
24
23
|
className,
|
|
25
|
-
|
|
26
|
-
"aria-describedby": describedBy(id, errorText, descriptionId),
|
|
24
|
+
...fieldAria(id, errorText, { description }),
|
|
27
25
|
};
|
|
28
26
|
return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: multiline ? (_jsx(Textarea, { ...controlProps, rows: rows })) : (_jsx(Input, { ...controlProps, type: inputType, autoFocus: autoFocus })) }));
|
|
29
27
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { ToggleGroup, ToggleGroupItem } from "../toggle-group.js";
|
|
5
|
-
import {
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText } 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.
|
|
@@ -15,5 +15,5 @@ export function FormToggleGroupField({ label, description, value, onValueChange,
|
|
|
15
15
|
if (option)
|
|
16
16
|
onValueChange(option.value);
|
|
17
17
|
}
|
|
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,
|
|
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))) }) }));
|
|
19
19
|
}
|
package/dist/info-tip.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ export type InfoTipProps = {
|
|
|
11
11
|
/** Extra body after the title and description. */
|
|
12
12
|
children?: React.ReactNode;
|
|
13
13
|
/**
|
|
14
|
-
* `inline`
|
|
15
|
-
*
|
|
14
|
+
* `inline` sits in a line of text: the glyph is 1em, a quarter em after
|
|
15
|
+
* the word, on the text's cap height, and keeps the line height; the 44px
|
|
16
|
+
* hit area overflows. Default `box`.
|
|
16
17
|
*/
|
|
17
18
|
layout?: InfoTipLayout;
|
|
18
19
|
/** Panel side. Default `top`; Radix still flips on collision. */
|
|
@@ -29,8 +30,8 @@ export type InfoTipProps = {
|
|
|
29
30
|
contentClassName?: string;
|
|
30
31
|
} & Omit<React.ComponentProps<"button">, "title" | "children" | "type" | "onMouseEnter" | "onMouseLeave" | "onClick">;
|
|
31
32
|
/**
|
|
32
|
-
* Longer help than a Tooltip: a Popover with a 20px glyph
|
|
33
|
-
* area. Hover opens only on a fine pointer; click, tap, and keyboard always
|
|
33
|
+
* Longer help than a Tooltip: a Popover with a 20px glyph (1em inline) and
|
|
34
|
+
* a 44px hit area. Hover opens only on a fine pointer; click, tap, and keyboard always
|
|
34
35
|
* toggle it. Not a glossary or "coming soon" chip — pass that copy in.
|
|
35
36
|
*/
|
|
36
37
|
declare function InfoTip({ label, title, description, children, layout, side, align, badge, icon, className, contentClassName, ...props }: InfoTipProps): React.JSX.Element;
|
package/dist/info-tip.js
CHANGED
|
@@ -3,18 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { CircleHelpIcon } from "lucide-react";
|
|
5
5
|
import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, } from "./popover.js";
|
|
6
|
-
import { focusRing, hitArea44 } from "./internal/styles.js";
|
|
6
|
+
import { focusRing, hitArea44, quietTextHover } from "./internal/styles.js";
|
|
7
|
+
import { HOVER_MEDIA_QUERY, useMediaQuery } from "./internal/use-media-query.js";
|
|
7
8
|
import { cn } from "./utils.js";
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* a
|
|
11
|
-
*/
|
|
12
|
-
function canHoverOpen() {
|
|
13
|
-
return window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Longer help than a Tooltip: a Popover with a 20px glyph and a 44px hit
|
|
17
|
-
* area. Hover opens only on a fine pointer; click, tap, and keyboard always
|
|
10
|
+
* Longer help than a Tooltip: a Popover with a 20px glyph (1em inline) and
|
|
11
|
+
* a 44px hit area. Hover opens only on a fine pointer; click, tap, and keyboard always
|
|
18
12
|
* toggle it. Not a glossary or "coming soon" chip — pass that copy in.
|
|
19
13
|
*/
|
|
20
14
|
function InfoTip({ label, title, description, children, layout = "box", side = "top", align = "center", badge, icon, className, contentClassName, ...props }) {
|
|
@@ -25,6 +19,8 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
25
19
|
const openedBy = React.useRef(null);
|
|
26
20
|
const titleId = React.useId();
|
|
27
21
|
const inline = layout === "inline";
|
|
22
|
+
// Hover opens only for a pointer that can hover; touch must click.
|
|
23
|
+
const canHoverOpen = useMediaQuery(HOVER_MEDIA_QUERY);
|
|
28
24
|
const cancelClose = () => {
|
|
29
25
|
if (closeTimer.current !== null) {
|
|
30
26
|
window.clearTimeout(closeTimer.current);
|
|
@@ -32,7 +28,7 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
32
28
|
}
|
|
33
29
|
};
|
|
34
30
|
const openFromHover = () => {
|
|
35
|
-
if (!canHoverOpen
|
|
31
|
+
if (!canHoverOpen)
|
|
36
32
|
return;
|
|
37
33
|
cancelClose();
|
|
38
34
|
if (open)
|
|
@@ -41,7 +37,7 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
41
37
|
setOpen(true);
|
|
42
38
|
};
|
|
43
39
|
const closeFromHover = () => {
|
|
44
|
-
if (!canHoverOpen
|
|
40
|
+
if (!canHoverOpen || openedBy.current !== "hover")
|
|
45
41
|
return;
|
|
46
42
|
cancelClose();
|
|
47
43
|
// Leave a beat so the pointer can travel from the glyph into the panel.
|
|
@@ -55,7 +51,12 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
55
51
|
if (next)
|
|
56
52
|
openedBy.current = "click";
|
|
57
53
|
setOpen(next);
|
|
58
|
-
}, 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
|
|
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
|
+
// Inline: 1em scales with the sentence; -0.125em centres the
|
|
56
|
+
// glyph on the cap height without growing the line box.
|
|
57
|
+
inline
|
|
58
|
+
? cn("ml-[0.25em] size-[1em] align-[-0.125em]", hitArea44)
|
|
59
|
+
: "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, onClick: (event) => {
|
|
59
60
|
// Hover already opened the panel; this click pins it instead of
|
|
60
61
|
// toggling it closed (Playwright and real mice both hover before
|
|
61
62
|
// they click). Preventing default skips Radix's toggle.
|
|
@@ -64,7 +65,7 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
|
|
|
64
65
|
cancelClose();
|
|
65
66
|
openedBy.current = "click";
|
|
66
67
|
}
|
|
67
|
-
}, children: icon ?? (_jsx(CircleHelpIcon, { className: "size-5", "data-icon": "info-tip", "aria-hidden": true })) }) }), _jsxs(PopoverContent, { side: side, align: align, className: cn("w-72", contentClassName), "aria-labelledby": title ? titleId : undefined, "aria-label": title ? undefined : label,
|
|
68
|
+
}, children: icon ?? (_jsx(CircleHelpIcon, { className: inline ? "size-full" : "size-5", "data-icon": "info-tip", "aria-hidden": true })) }) }), _jsxs(PopoverContent, { side: side, align: align, className: cn("w-72", contentClassName), "aria-labelledby": title ? titleId : undefined, "aria-label": title ? undefined : label,
|
|
68
69
|
// A pointer passing over the glyph must not take focus from where
|
|
69
70
|
// the user is typing, nor hand it to the trigger on close.
|
|
70
71
|
onOpenAutoFocus: (event) => {
|