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.
Files changed (67) hide show
  1. package/COMPONENTS.md +22 -26
  2. package/DESIGN_RULES.md +14 -40
  3. package/README.md +7 -6
  4. package/dist/accordion.js +2 -2
  5. package/dist/alert.js +6 -5
  6. package/dist/badge.js +5 -2
  7. package/dist/breadcrumb.js +2 -2
  8. package/dist/button.js +7 -4
  9. package/dist/calendar.js +5 -4
  10. package/dist/combobox.js +7 -13
  11. package/dist/date-time-picker.js +2 -1
  12. package/dist/dropdown-menu.js +4 -4
  13. package/dist/field.d.ts +6 -3
  14. package/dist/field.js +3 -2
  15. package/dist/form/checkbox-field.d.ts +1 -1
  16. package/dist/form/checkbox-field.js +8 -11
  17. package/dist/form/combobox-field.js +2 -2
  18. package/dist/form/control-field.js +2 -7
  19. package/dist/form/date-picker-field.js +5 -2
  20. package/dist/form/date-time-picker-field.js +5 -2
  21. package/dist/form/field-error.d.ts +3 -3
  22. package/dist/form/field-shell.d.ts +36 -31
  23. package/dist/form/field-shell.js +41 -34
  24. package/dist/form/markdown-field.js +2 -2
  25. package/dist/form/select-field.js +2 -2
  26. package/dist/form/text-field.js +2 -4
  27. package/dist/form/toggle-group-field.js +2 -2
  28. package/dist/info-tip.d.ts +5 -4
  29. package/dist/info-tip.js +15 -14
  30. package/dist/internal/active-link.d.ts +7 -0
  31. package/dist/internal/active-link.js +9 -0
  32. package/dist/internal/calendar-size.d.ts +5 -0
  33. package/dist/internal/calendar-size.js +5 -0
  34. package/dist/internal/form-control.d.ts +1 -1
  35. package/dist/internal/form-control.js +2 -1
  36. package/dist/internal/listbox-navigation.d.ts +0 -5
  37. package/dist/internal/listbox-navigation.js +0 -7
  38. package/dist/internal/listbox-option.d.ts +16 -3
  39. package/dist/internal/listbox-option.js +22 -5
  40. package/dist/internal/overlay-styles.d.ts +10 -7
  41. package/dist/internal/overlay-styles.js +10 -7
  42. package/dist/internal/search.d.ts +9 -1
  43. package/dist/internal/search.js +14 -5
  44. package/dist/internal/styles.d.ts +43 -0
  45. package/dist/internal/styles.js +43 -0
  46. package/dist/internal/tabs-styles.d.ts +8 -7
  47. package/dist/internal/tabs-styles.js +15 -13
  48. package/dist/internal/use-media-query.d.ts +5 -0
  49. package/dist/internal/use-media-query.js +5 -0
  50. package/dist/kanban.d.ts +1 -1
  51. package/dist/kanban.js +6 -4
  52. package/dist/label.js +2 -1
  53. package/dist/lazy-calendar.js +3 -2
  54. package/dist/nav-link.js +2 -1
  55. package/dist/popover.js +4 -3
  56. package/dist/search-picker.d.ts +1 -1
  57. package/dist/search-picker.js +7 -12
  58. package/dist/select.js +6 -6
  59. package/dist/skip-link.js +2 -1
  60. package/dist/tabs.d.ts +3 -3
  61. package/dist/tabs.js +7 -6
  62. package/dist/toast.js +11 -10
  63. package/dist/toggle.d.ts +2 -0
  64. package/dist/toggle.js +8 -3
  65. package/dist/tooltip.js +2 -2
  66. package/package.json +1 -1
  67. package/skills/atlas-react-kit/SKILL.md +46 -42
@@ -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, optionIcon, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
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, optionIcon, "pr-3 data-inset:pl-9");
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 = 4, collisionPadding = 8, ...props }) {
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?: Array<{
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 font-semibold uppercase data-[variant=label]:text-xs data-[variant=legend]:text-xs", className), ...props }));
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 text-xs font-semibold uppercase group-data-[disabled=true]/field:opacity-50 in-data-[slot=field-label]:font-semibold", className), ...props }));
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 { type FormFieldBaseProps, type FormFieldDescriptionProps, type FormFieldMetaProps } from "./field-error.js";
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, FieldDescription, FieldLabel, FieldLegend, FieldSet, } from "../field.js";
5
+ import { Field, FieldContent, FieldLabel, FieldLegend } from "../field.js";
6
6
  import { cn } from "../utils.js";
7
- import { FieldError, } from "./field-error.js";
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 descriptionId = description ? descriptionIdFor(id) : undefined;
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 (_jsxs("div", { "data-slot": "form-field", className: cn("flex flex-col gap-1.5", fieldClassName), children: [_jsxs(Field, { orientation: "horizontal", children: [controlPosition === "start" ? checkbox : null, description ? (_jsxs(FieldContent, { children: [labelNode, _jsx(FieldDescription, { id: descriptionId, children: description })] })) : (labelNode), controlPosition === "end" ? checkbox : null] }), _jsx(FieldError, { id: errorIdFor(id), text: errorText })] }));
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 (_jsxs(FieldSet, { "data-slot": "form-field", "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText, descriptionId), className: cn("gap-1.5", fieldClassName), children: [_jsx(FieldLegend, { variant: "label", className: "mb-0", children: label }), description ? (_jsx("p", { id: descriptionId, "data-slot": "form-field-description", className: "text-xs text-muted-foreground", 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) => {
36
- const optionId = `${id}-${option.value}`;
37
- 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));
38
- }) }), _jsx(FieldError, { id: errorIdFor(id), text: errorText })] }));
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 { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
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, "aria-required": required || undefined, "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText, description ? descriptionIdFor(id) : undefined) }) }));
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 { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
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, pickerFieldAria, } from "./field-shell.js";
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, ...pickerFieldAria(id, errorText, required) }) }));
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, pickerFieldAria, } from "./field-shell.js";
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, ...pickerFieldAria(id, errorText, required) }) }));
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
- export type FieldErrorMessage = string | {
3
- message?: string;
4
- } | undefined;
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
- * `aria-describedby` for the control: the given hint ids, plus the error
25
- * text while there is one, or `undefined` when there is nothing to read.
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 describedBy(id: string, errorText: string | undefined, ...hintIds: Array<string | undefined>): string | undefined;
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
- * Trigger ARIA for the date and date-time fields: invalid while there is
56
- * error text, named "label, value" (a `<label for>` alone would hide the
57
- * picked value), and described by "Required" (a button cannot take
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 pickerFieldAria(id: string, errorText: string | undefined, required?: boolean): {
61
- "aria-invalid": boolean | undefined;
62
- "aria-labelledby": string;
63
- "aria-describedby": string | undefined;
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 text stacked with the kit's field spacing.
67
- * `labelAction` sits on the label's row (the Markdown field's mode toggle).
68
- * Set `labelFor` to false when the control is not rendered (Markdown
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, className, children, ...props }: Omit<ComponentProps<"div">, "id" | "children"> & {
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. */
@@ -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
- export function labelIdFor(id) {
25
+ function labelIdFor(id) {
29
26
  return `${id}-label`;
30
27
  }
31
28
  /** Id of the hint `FormFieldShell` renders for control `id`. */
32
- export function descriptionIdFor(id) {
29
+ function descriptionIdFor(id) {
33
30
  return `${id}-description`;
34
31
  }
35
32
  /** Id of the error text `FormFieldShell` renders for control `id`. */
36
- export function errorIdFor(id) {
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
- * `aria-describedby` for the control: the given hint ids, plus the error
48
- * text while there is one, or `undefined` when there is nothing to read.
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 describedBy(id, errorText, ...hintIds) {
51
- const ids = [...hintIds, errorText ? errorIdFor(id) : undefined].filter(Boolean);
52
- return ids.length ? ids.join(" ") : undefined;
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
- * Trigger ARIA for the date and date-time fields: invalid while there is
56
- * error text, named "label, value" (a `<label for>` alone would hide the
57
- * picked value), and described by "Required" (a button cannot take
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 pickerFieldAria(id, errorText, required) {
61
- return {
62
- "aria-invalid": invalidWhen(errorText),
63
- "aria-labelledby": `${labelIdFor(id)} ${pickerValueId(id)}`,
64
- "aria-describedby": describedBy(id, errorText, required ? requiredIdFor(id) : undefined),
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 text stacked with the kit's field spacing.
69
- * `labelAction` sits on the label's row (the Markdown field's mode toggle).
70
- * Set `labelFor` to false when the control is not rendered (Markdown
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, className, children, ...props }) {
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("div", { "data-slot": "form-field", className: cn("flex flex-col gap-1.5", className), ...props, children: [labelAction ? (_jsxs("div", { "data-slot": "form-field-header", className: "flex items-center justify-between gap-2", children: [labelNode, labelAction] })) : (labelNode), description ? (_jsx("p", { id: descriptionIdFor(id), "data-slot": "form-field-description", className: "text-xs text-muted-foreground", children: description })) : null, children, required ? (_jsx("span", { id: requiredIdFor(id), hidden: true, children: "Required" })) : null, _jsx(FieldError, { id: errorIdFor(id), text: errorText })] }));
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 { describedBy, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
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, "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(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" })) })) }));
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 { describedBy, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
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, "aria-required": required || undefined, "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText), children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { className: contentClassName, children: children })] }) }));
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
  }
@@ -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 { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
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
- "aria-invalid": invalidWhen(errorText),
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 { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, labelIdFor, metaErrorText, } from "./field-shell.js";
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, "aria-labelledby": labelIdFor(id), "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText, description ? descriptionIdFor(id) : undefined), className: className, children: options.map((option) => (_jsx(ToggleGroupItem, { value: option.value, children: option.label }, option.value))) }) }));
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
  }
@@ -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` keeps the surrounding line height; the 44px hit area overflows.
15
- * Default `box`.
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 and a 44px hit
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
- * True when a mouse can hover. Touch and coarse pointers must use click;
10
- * a hover-open popover would trap them after the first tap.
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() || openedBy.current !== "hover")
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 hover:text-selected-foreground focus-visible:text-selected-foreground", inline ? cn("size-5 align-middle", hitArea44) : "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, onClick: (event) => {
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) => {