atlas-react-kit 1.2.0 → 1.2.2

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 (72) hide show
  1. package/COMPONENTS.md +40 -40
  2. package/DESIGN_RULES.md +14 -40
  3. package/README.md +7 -6
  4. package/dist/alert.js +7 -6
  5. package/dist/calendar.js +5 -4
  6. package/dist/card.js +2 -1
  7. package/dist/combobox.d.ts +4 -3
  8. package/dist/combobox.js +8 -17
  9. package/dist/date-time-picker.js +2 -1
  10. package/dist/dialog.js +2 -1
  11. package/dist/dropdown-menu.js +7 -5
  12. package/dist/field.d.ts +6 -3
  13. package/dist/field.js +3 -2
  14. package/dist/form/checkbox-field.d.ts +3 -5
  15. package/dist/form/checkbox-field.js +13 -19
  16. package/dist/form/combobox-field.d.ts +2 -2
  17. package/dist/form/combobox-field.js +5 -6
  18. package/dist/form/control-field.d.ts +5 -5
  19. package/dist/form/control-field.js +6 -9
  20. package/dist/form/date-picker-field.d.ts +1 -1
  21. package/dist/form/date-picker-field.js +3 -7
  22. package/dist/form/date-time-picker-field.d.ts +1 -1
  23. package/dist/form/date-time-picker-field.js +3 -7
  24. package/dist/form/field-error.d.ts +13 -4
  25. package/dist/form/field-shell.d.ts +63 -47
  26. package/dist/form/field-shell.js +67 -35
  27. package/dist/form/focus-first-invalid.d.ts +7 -4
  28. package/dist/form/focus-first-invalid.js +10 -6
  29. package/dist/form/markdown-field.d.ts +1 -1
  30. package/dist/form/markdown-field.js +8 -4
  31. package/dist/form/select-field.d.ts +2 -8
  32. package/dist/form/select-field.js +5 -7
  33. package/dist/form/text-field.d.ts +2 -8
  34. package/dist/form/text-field.js +9 -6
  35. package/dist/form/toggle-group-field.d.ts +2 -2
  36. package/dist/form/toggle-group-field.js +5 -6
  37. package/dist/info-tip.d.ts +7 -4
  38. package/dist/info-tip.js +12 -4
  39. package/dist/internal/active-link.d.ts +7 -0
  40. package/dist/internal/active-link.js +9 -0
  41. package/dist/internal/calendar-size.d.ts +5 -0
  42. package/dist/internal/calendar-size.js +5 -0
  43. package/dist/internal/form-control.d.ts +7 -1
  44. package/dist/internal/form-control.js +8 -1
  45. package/dist/internal/kanban-layout.js +2 -1
  46. package/dist/internal/listbox-navigation.d.ts +8 -8
  47. package/dist/internal/listbox-navigation.js +23 -23
  48. package/dist/internal/listbox-option.d.ts +16 -3
  49. package/dist/internal/listbox-option.js +22 -5
  50. package/dist/internal/overlay-styles.d.ts +15 -5
  51. package/dist/internal/overlay-styles.js +15 -5
  52. package/dist/internal/search.d.ts +4 -0
  53. package/dist/internal/search.js +6 -0
  54. package/dist/internal/styles.d.ts +21 -0
  55. package/dist/internal/styles.js +21 -0
  56. package/dist/kanban.js +3 -2
  57. package/dist/label.js +2 -1
  58. package/dist/lazy-calendar.js +3 -2
  59. package/dist/nav-link.js +2 -1
  60. package/dist/picker-shell.d.ts +3 -1
  61. package/dist/picker-shell.js +3 -3
  62. package/dist/popover.js +6 -5
  63. package/dist/search-picker.js +8 -16
  64. package/dist/select.js +5 -5
  65. package/dist/skip-link.js +2 -1
  66. package/dist/tabs.js +3 -2
  67. package/dist/toast.js +11 -10
  68. package/dist/toggle-group.d.ts +1 -1
  69. package/dist/toggle-group.js +36 -2
  70. package/dist/tooltip.js +2 -2
  71. package/package.json +1 -1
  72. package/skills/atlas-react-kit/SKILL.md +46 -42
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { format } from "date-fns";
5
+ import { labelText } from "./internal/styles.js";
5
6
  import { PickerShell } from "./picker-shell.js";
6
7
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
7
8
  /** Default clock increment, in minutes. Must divide 60. */
@@ -71,7 +72,7 @@ export function DateTimePickerButton({ value, onValueChange, onChange, timeStep,
71
72
  return;
72
73
  emit?.(combineDateAndTime(next, date?.getHours() ?? 0, date?.getMinutes() ?? 0));
73
74
  },
74
- }, onClear: () => emit?.(null), clearDisabled: empty, children: _jsxs("div", { role: "group", "aria-label": "Time", "data-slot": "date-time-picker-time", className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [_jsx("p", { className: "text-xs font-semibold tracking-wide uppercase", children: "Time" }), _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsxs(Select, { value: date ? padTimePart(date.getHours()) : undefined, onValueChange: (nextHour) => {
75
+ }, onClear: () => emit?.(null), clearDisabled: empty, children: _jsxs("div", { role: "group", "aria-label": "Time", "data-slot": "date-time-picker-time", className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [_jsx("p", { className: labelText, children: "Time" }), _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsxs(Select, { value: date ? padTimePart(date.getHours()) : undefined, onValueChange: (nextHour) => {
75
76
  if (!date)
76
77
  return;
77
78
  emit?.(combineDateAndTime(date, Number(nextHour), date.getMinutes()));
package/dist/dialog.js CHANGED
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Dialog as DialogPrimitive } from "radix-ui";
5
5
  import { overlayFade, overlayFadeZoom, overlaySurface, } from "./internal/overlay-styles.js";
6
+ import { descriptionText } from "./internal/styles.js";
6
7
  import { cn } from "./utils.js";
7
8
  import { Button } from "./button.js";
8
9
  import { XIcon } from "lucide-react";
@@ -36,6 +37,6 @@ function DialogTitle({ className, ...props }) {
36
37
  return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn("pr-[var(--dialog-close-gutter,0px)] font-heading text-lg leading-none font-semibold tracking-wider uppercase", className), ...props }));
37
38
  }
38
39
  function DialogDescription({ className, ...props }) {
39
- return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground", className), ...props }));
40
+ return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("mt-0.5", descriptionText, "*:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground", className), ...props }));
40
41
  }
41
42
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, };
@@ -3,12 +3,14 @@ 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, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
6
+ import { anchoredPanel, menuLabel, optionRow, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, } from "./internal/overlay-styles.js";
7
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
10
  const menuItemClassName = cn(optionRow, optionState.focus, dataDisabledState, inlineIcon.default, "pr-3 data-inset:pl-9");
11
- const menuContentClassName = cn(overlaySurface, overlaySideSlide, overlayFadeZoom, "z-50 overflow-x-hidden overflow-y-auto p-1");
11
+ /** The check or dot in the inset of a checkbox or radio item. */
12
+ const menuIndicatorClassName = "pointer-events-none absolute left-3 flex size-3.5 items-center justify-center";
13
+ const menuContentClassName = cn(anchoredPanel, "z-50 overflow-x-hidden overflow-y-auto p-1");
12
14
  /** Action menu for commands (row actions, overflow menus). Use `Select` to pick a value. */
13
15
  function DropdownMenu({ ...props }) {
14
16
  return _jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
@@ -19,7 +21,7 @@ function DropdownMenuPortal({ ...props }) {
19
21
  function DropdownMenuTrigger({ ...props }) {
20
22
  return (_jsx(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props }));
21
23
  }
22
- function DropdownMenuContent({ className, align = "start", sideOffset = 4, collisionPadding = 8, ...props }) {
24
+ function DropdownMenuContent({ className, align = "start", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
23
25
  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
26
  }
25
27
  function DropdownMenuGroup({ ...props }) {
@@ -29,13 +31,13 @@ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
29
31
  return (_jsx(DropdownMenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset || undefined, "data-variant": variant, className: cn(menuItemClassName, "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive", className), ...props }));
30
32
  }
31
33
  function DropdownMenuCheckboxItem({ className, children, checked, ...props }) {
32
- return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", className: cn(menuItemClassName, "pl-9", className), checked: checked, ...props, children: [_jsx("span", { className: "pointer-events-none absolute left-3 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, {}) }) }), children] }));
34
+ return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", className: cn(menuItemClassName, "pl-9", className), checked: checked, ...props, children: [_jsx("span", { className: menuIndicatorClassName, children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, {}) }) }), children] }));
33
35
  }
34
36
  function DropdownMenuRadioGroup({ ...props }) {
35
37
  return (_jsx(DropdownMenuPrimitive.RadioGroup, { "data-slot": "dropdown-menu-radio-group", ...props }));
36
38
  }
37
39
  function DropdownMenuRadioItem({ className, children, ...props }) {
38
- return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: cn(menuItemClassName, "pl-9", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute left-3 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx("span", { className: "block size-1.5 rounded-full bg-current" }) }) }), children] }));
40
+ return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: cn(menuItemClassName, "pl-9", className), ...props, children: [_jsx("span", { className: menuIndicatorClassName, children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx("span", { className: "block size-1.5 rounded-full bg-current" }) }) }), children] }));
39
41
  }
40
42
  function DropdownMenuLabel({ className, inset, ...props }) {
41
43
  return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset || undefined, className: cn(menuLabel, "data-inset:pl-9", className), ...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;
@@ -6,8 +6,6 @@ type CheckboxFieldProps = FormFieldBaseProps & FormFieldMetaProps & FormFieldDes
6
6
  onCheckedChange: (checked: boolean) => void;
7
7
  /** Called when the checkbox loses focus; pass the field's blur handler. */
8
8
  onBlur?: () => void;
9
- /** Checkbox id the label points at. Defaults to a generated id. */
10
- id?: string;
11
9
  /** Sets `required` on the checkbox and marks the label. */
12
10
  required?: boolean;
13
11
  /** Disables the checkbox. */
@@ -21,7 +19,7 @@ type CheckboxFieldProps = FormFieldBaseProps & FormFieldMetaProps & FormFieldDes
21
19
  * One boolean as a labelled checkbox, with an optional hint and the same
22
20
  * error text and timing as the other `form/*` fields.
23
21
  */
24
- export declare function FormCheckboxField({ checked, onCheckedChange, onBlur, label, description, id: idProp, meta, submitted, showError, required, disabled, controlPosition, className, fieldClassName, }: CheckboxFieldProps): import("react").JSX.Element;
22
+ export declare function FormCheckboxField({ checked, onCheckedChange, onBlur, fieldName, label, description, meta, submitted, showError, required, disabled, controlPosition, className, fieldClassName, }: CheckboxFieldProps): import("react").JSX.Element;
25
23
  export type CheckboxGroupOption<T extends string | number> = {
26
24
  /** Value added to the group's `value` while this box is checked. */
27
25
  value: T;
@@ -41,5 +39,5 @@ type CheckboxGroupFieldProps<T extends string | number> = FormFieldBaseProps & F
41
39
  className?: string;
42
40
  };
43
41
  /** Several values from a short list, as a labelled set of checkboxes. */
44
- export declare function FormCheckboxGroupField<T extends string | number>({ label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }: CheckboxGroupFieldProps<T>): import("react").JSX.Element;
42
+ export declare function FormCheckboxGroupField<T extends string | number>({ fieldName, label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }: CheckboxGroupFieldProps<T>): import("react").JSX.Element;
45
43
  export {};
@@ -1,29 +1,23 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useId } from "react";
4
3
  import { Checkbox } from "../checkbox.js";
5
- import { Field, FieldContent, FieldDescription, FieldLabel, FieldLegend, FieldSet, } from "../field.js";
4
+ import { Field, FieldContent, FieldLabel, FieldLegend } from "../field.js";
5
+ import { fieldNameProps } from "../internal/form-control.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, useFormField, } 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.
12
11
  */
13
- export function FormCheckboxField({ checked, onCheckedChange, onBlur, label, description, id: idProp, meta, submitted, showError, required, disabled, controlPosition = "start", className, fieldClassName, }) {
14
- const generatedId = useId();
15
- const id = idProp ?? generatedId;
16
- 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 }));
12
+ export function FormCheckboxField({ checked, onCheckedChange, onBlur, fieldName, label, description, meta, submitted, showError, required, disabled, controlPosition = "start", className, fieldClassName, }) {
13
+ const { id, errorText } = useFormField(meta, { submitted, showError });
14
+ const checkbox = (_jsx(Checkbox, { id: id, ...fieldNameProps(fieldName), checked: checked, onCheckedChange: (next) => onCheckedChange(next === true), onBlur: onBlur, disabled: disabled, required: required, ...fieldAria(id, errorText, { description, required }), className: className }));
19
15
  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 })] }));
16
+ 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
17
  }
22
18
  /** Several values from a short list, as a labelled set of checkboxes. */
23
- export function FormCheckboxGroupField({ label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
24
- const id = useId();
25
- const errorText = metaErrorText(meta, { submitted, showError });
26
- const descriptionId = description ? descriptionIdFor(id) : undefined;
19
+ export function FormCheckboxGroupField({ fieldName, label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
20
+ const { id, errorText } = useFormField(meta, { submitted, showError });
27
21
  function toggle(option, on) {
28
22
  const next = new Set(value);
29
23
  if (on)
@@ -32,8 +26,8 @@ export function FormCheckboxGroupField({ label, description, options, value, onV
32
26
  next.delete(option);
33
27
  onValueChange(options.map((o) => o.value).filter((v) => next.has(v)));
34
28
  }
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 })] }));
29
+ 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, index) => {
30
+ const optionId = `${id}-${option.value}`;
31
+ return (_jsxs(Field, { orientation: "horizontal", children: [_jsx(Checkbox, { id: optionId, ...fieldNameProps(index === 0 ? fieldName : undefined), checked: value.includes(option.value), onCheckedChange: (next) => toggle(option.value, next === true), disabled: disabled }), _jsx(FieldLabel, { htmlFor: optionId, children: option.label })] }, option.value));
32
+ }) })] }) }));
39
33
  }
@@ -1,4 +1,4 @@
1
- import { type ComponentProps } from "react";
1
+ import type { ComponentProps } from "react";
2
2
  import { Combobox } from "../combobox.js";
3
3
  import type { FormFieldBaseProps, FormFieldDescriptionProps, FormFieldMetaProps } from "./field-error.js";
4
4
  type Props = FormFieldBaseProps & FormFieldMetaProps & FormFieldDescriptionProps & Omit<ComponentProps<typeof Combobox>, "id" | "aria-invalid" | "aria-describedby" | "aria-required"> & {
@@ -9,5 +9,5 @@ type Props = FormFieldBaseProps & FormFieldMetaProps & FormFieldDescriptionProps
9
9
  * `Combobox` with a label, optional hint, and the `form/*` error text and
10
10
  * timing. Pass the field's blur handler as `onBlur` so touch state updates.
11
11
  */
12
- export declare function FormComboboxField({ label, description, meta, submitted, showError, required, fieldClassName, ...props }: Props): import("react").JSX.Element;
12
+ export declare function FormComboboxField({ fieldName, label, description, meta, submitted, showError, required, fieldClassName, ...props }: Props): import("react").JSX.Element;
13
13
  export {};
@@ -1,14 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useId } from "react";
4
3
  import { Combobox } from "../combobox.js";
5
- import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
4
+ import { fieldNameProps } from "../internal/form-control.js";
5
+ import { fieldAria, FormFieldShell, useFormField } 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.
9
9
  */
10
- export function FormComboboxField({ label, description, meta, submitted, showError, required, fieldClassName, ...props }) {
11
- const id = useId();
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) }) }));
10
+ export function FormComboboxField({ fieldName, label, description, meta, submitted, showError, required, fieldClassName, ...props }) {
11
+ const { id, errorText } = useFormField(meta, { submitted, showError });
12
+ return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Combobox, { ...props, id: id, ...fieldNameProps(fieldName), ...fieldAria(id, errorText, { description, required }) }) }));
14
13
  }
@@ -1,9 +1,11 @@
1
- import { type ComponentProps, type ReactNode } from "react";
1
+ import type { ComponentProps, ReactNode } from "react";
2
2
  import type { FormFieldBaseProps, FormFieldDescriptionProps, FormFieldMetaProps } from "./field-error.js";
3
3
  /** Props `FormControlField` hands its control: spread them onto it. */
4
4
  export type FormControlProps = {
5
- /** Id the label points at. */
5
+ /** Generated id the label points at. */
6
6
  id: string;
7
+ /** The field's `fieldName`, for `focusFirstInvalid`; `undefined` without one. */
8
+ "data-field-name": string | undefined;
7
9
  /** `true` while the field shows error text. */
8
10
  "aria-invalid": true | undefined;
9
11
  /** Ids of the hint and the error text, when shown. */
@@ -12,8 +14,6 @@ export type FormControlProps = {
12
14
  "aria-required": true | undefined;
13
15
  };
14
16
  type Props = Omit<FormFieldBaseProps, "fieldClassName"> & FormFieldMetaProps & FormFieldDescriptionProps & Omit<ComponentProps<"div">, "id" | "children"> & {
15
- /** Control id the label points at. Defaults to a generated id. */
16
- id?: string;
17
17
  /** Marks the label and passes `aria-required` to the control. */
18
18
  required?: boolean;
19
19
  /** Renders the control from the id and ARIA props it should carry. */
@@ -26,5 +26,5 @@ type Props = Omit<FormFieldBaseProps, "fieldClassName"> & FormFieldMetaProps & F
26
26
  * timing as `FormTextField`. `className` and other div props go to the
27
27
  * wrapper (`data-slot="form-field"`).
28
28
  */
29
- export declare function FormControlField({ id: idProp, label, description, required, meta, submitted, showError, children, ...props }: Props): import("react").JSX.Element;
29
+ export declare function FormControlField({ fieldName, label, description, required, meta, submitted, showError, children, ...props }: Props): import("react").JSX.Element;
30
30
  export {};
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useId } from "react";
4
- import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorText, } from "./field-shell.js";
3
+ import { fieldNameProps } from "../internal/form-control.js";
4
+ import { fieldAria, FormFieldShell, useFormField } 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
@@ -9,14 +9,11 @@ import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorTe
9
9
  * timing as `FormTextField`. `className` and other div props go to the
10
10
  * wrapper (`data-slot="form-field"`).
11
11
  */
12
- export function FormControlField({ id: idProp, label, description, required, meta, submitted, showError, children, ...props }) {
13
- const generatedId = useId();
14
- const id = idProp ?? generatedId;
15
- const errorText = metaErrorText(meta, { submitted, showError });
12
+ export function FormControlField({ fieldName, label, description, required, meta, submitted, showError, children, ...props }) {
13
+ const { id, errorText } = useFormField(meta, { submitted, showError });
16
14
  return (_jsx(FormFieldShell, { ...props, id: id, label: label, description: description, errorText: errorText, required: required, children: children({
17
15
  id,
18
- "aria-invalid": invalidWhen(errorText),
19
- "aria-describedby": describedBy(id, errorText, description ? descriptionIdFor(id) : undefined),
20
- "aria-required": required || undefined,
16
+ ...fieldNameProps(fieldName),
17
+ ...fieldAria(id, errorText, { description, required }),
21
18
  }) }));
22
19
  }
@@ -14,5 +14,5 @@ type Props = PickerFieldBaseProps & {
14
14
  clearable?: boolean;
15
15
  };
16
16
  /** Date picker with built-in field error text and invalid styling. */
17
- export declare function FormDatePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }: Props): import("react").JSX.Element;
17
+ export declare function FormDatePickerField(props: Props): import("react").JSX.Element;
18
18
  export {};
@@ -1,12 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useId } from "react";
4
3
  import { DatePickerButton } from "../date-picker.js";
5
- import { FormFieldShell, metaErrorText, pickerFieldAria, } from "./field-shell.js";
4
+ import { FormPickerField } from "./field-shell.js";
6
5
  /** Date picker with built-in field error text and invalid styling. */
7
- export function FormDatePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }) {
8
- const errorText = metaErrorText(meta, { submitted, showError });
9
- const generatedId = useId();
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) }) }));
6
+ export function FormDatePickerField(props) {
7
+ return _jsx(FormPickerField, { field: props, picker: DatePickerButton });
12
8
  }
@@ -10,5 +10,5 @@ type Props = PickerFieldBaseProps & {
10
10
  timeStep?: number;
11
11
  };
12
12
  /** Date-time picker with built-in field error text and invalid styling. */
13
- export declare function FormDateTimePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }: Props): import("react").JSX.Element;
13
+ export declare function FormDateTimePickerField(props: Props): import("react").JSX.Element;
14
14
  export {};
@@ -1,12 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useId } from "react";
4
3
  import { DateTimePickerButton } from "../date-time-picker.js";
5
- import { FormFieldShell, metaErrorText, pickerFieldAria, } from "./field-shell.js";
4
+ import { FormPickerField } from "./field-shell.js";
6
5
  /** Date-time picker with built-in field error text and invalid styling. */
7
- export function FormDateTimePickerField({ id: idProp, onValueChange, label, meta, submitted, showError, required, fieldClassName, ...picker }) {
8
- const errorText = metaErrorText(meta, { submitted, showError });
9
- const generatedId = useId();
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) }) }));
6
+ export function FormDateTimePickerField(props) {
7
+ return _jsx(FormPickerField, { field: props, picker: DateTimePickerButton });
12
8
  }
@@ -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[];
@@ -38,13 +38,22 @@ export type FormFieldBaseProps = ErrorTimingProps & {
38
38
  */
39
39
  fieldClassName?: string;
40
40
  };
41
- /** Error meta for the `form/*` fields that are not bound to a `field` API. */
41
+ /**
42
+ * What the `form/*` fields that are not bound to a `field` API take in its
43
+ * place: its meta and its name.
44
+ */
42
45
  export type FormFieldMetaProps = {
43
46
  /**
44
47
  * The field's meta (TanStack Form's `field.state.meta`); its first error
45
48
  * shows under the control. Leave it out for no error text.
46
49
  */
47
50
  meta?: FieldMetaState;
51
+ /**
52
+ * The field's name (TanStack Form's `field.name`). Put on the control as
53
+ * `data-field-name`, so `focusFirstInvalid` can find it. Leave it out and
54
+ * the field is not a focus target.
55
+ */
56
+ fieldName?: string;
48
57
  };
49
58
  /** The optional hint the `form/*` fields render under the label. */
50
59
  export type FormFieldDescriptionProps = {
@@ -1,41 +1,41 @@
1
- import type { ComponentProps, ReactNode } from "react";
1
+ import { type ComponentProps, type ComponentType, type 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
- * Not a public entry. The layout and error wiring every `form/*` field
6
- * shares, so a field only says which control it renders. Custom controls
7
- * get the same shell through the public `FormControlField`.
6
+ * The error text a `form/*` field shows (`metaErrorText`) and its control
7
+ * id. The id is generated per field, so two forms that share a field name
8
+ * (login and sign-up `email`) never share an id, and each label, hint, and
9
+ * error links to its own control.
8
10
  */
11
+ export declare function useFormField(meta: FieldMetaState | undefined, timing: ErrorTimingProps): {
12
+ id: string;
13
+ errorText: string | undefined;
14
+ };
9
15
  /**
10
- * First error in `meta`, or `undefined` when there is no meta, no error,
11
- * `showError` is false, or the field is untouched (`isTouched: false`)
12
- * and the form has not been submitted.
13
- */
14
- 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
- /**
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.
16
+ * ARIA for a field's control, from the ids `FormFieldShell` renders:
17
+ * invalid while there is error text, described by the hint, the hidden
18
+ * "Required" text, and the error. `required: "described"` is for a button
19
+ * trigger, which cannot take `aria-required`; leave `required` out where
20
+ * the control carries native `required`. `labelledBy: "label"` names a
21
+ * group; `"label-value"` reads a picker "label, value".
26
22
  */
27
- export declare function describedBy(id: string, errorText: string | undefined, ...hintIds: Array<string | undefined>): string | undefined;
23
+ export declare function fieldAria(id: string, errorText: string | undefined, { description, required, labelledBy, }?: {
24
+ /** The field's hint; when set, the control is described by it. */
25
+ description?: ReactNode;
26
+ required?: boolean | "described";
27
+ labelledBy?: "label" | "label-value";
28
+ }): {
29
+ "aria-invalid": true | undefined;
30
+ "aria-describedby": string | undefined;
31
+ "aria-required": true | undefined;
32
+ "aria-labelledby"?: string | undefined;
33
+ };
28
34
  /**
29
35
  * Props the date and date-time fields share; each adds its own `value`,
30
36
  * `onValueChange`, and `placeholder`.
31
37
  */
32
38
  export type PickerFieldBaseProps = FormFieldBaseProps & FormFieldMetaProps & {
33
- /**
34
- * Id of the trigger the label points at. Defaults to a generated id;
35
- * pass the field name (`id={field.name}`) so a submit handler such as
36
- * `focusFirstInvalid` can find the control.
37
- */
38
- id?: string;
39
39
  /** Disables the trigger so no date can be picked. */
40
40
  disabled?: boolean;
41
41
  /** Marks the label and adds "Required" to the trigger's description. */
@@ -51,30 +51,45 @@ export type PickerFieldBaseProps = FormFieldBaseProps & FormFieldMetaProps & {
51
51
  /** Day Picker options forwarded to the calendar (disabled days, etc.). */
52
52
  calendarProps?: DatePickerCalendarProps;
53
53
  };
54
+ /** The props a date or date-time field keeps for its shell, not the picker. */
55
+ type PickerFieldOwnKey = "fieldName" | "label" | "meta" | "submitted" | "showError" | "required" | "fieldClassName";
54
56
  /**
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.
57
+ * The date and date-time fields: `picker` (the trigger) in a
58
+ * `FormFieldShell`, given every prop of `field` the shell does not take. A
59
+ * button cannot carry `required`, so the trigger is described by the hidden
60
+ * "Required" text and reads "label, value".
59
61
  */
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
- };
62
+ export declare function FormPickerField<P extends PickerFieldBaseProps>({ field, picker: Picker, }: {
63
+ /** The field's props. */
64
+ field: P;
65
+ /** The picker trigger. */
66
+ picker: ComponentType<Omit<P, PickerFieldOwnKey> & {
67
+ id: string;
68
+ "data-field-name"?: string;
69
+ } & ReturnType<typeof fieldAria>>;
70
+ }): import("react").JSX.Element;
71
+ /**
72
+ * Every `form/*` field's wrapper (`data-slot="form-field"`): children with
73
+ * the field spacing, then the error text. `asChild` makes the one child the
74
+ * wrapper (the checkbox group's fieldset).
75
+ */
76
+ export declare function FormFieldFrame({ id, errorText, asChild, className, children, ...props }: Omit<ComponentProps<"div">, "id"> & AsChildProps & {
77
+ /** Id of the control; the error id derives from it. */
78
+ id: string;
79
+ /** Error text under the children; `undefined` shows none. */
80
+ errorText: string | undefined;
81
+ }): import("react").JSX.Element;
82
+ /** A field's hint, with id `descriptionIdFor(id)`. */
83
+ export declare function FormFieldDescription({ id, children, }: {
84
+ id: string;
85
+ children: ReactNode;
86
+ }): import("react").JSX.Element;
65
87
  /**
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.
88
+ * Label, hint, control, and error in a `FormFieldFrame`. `labelAction` sits
89
+ * on the label's row; `labelFor={false}` when the control is not rendered
90
+ * (Markdown preview). `required` marks the label only.
76
91
  */
77
- export declare function FormFieldShell({ id, label, labelAction, description, errorText, labelFor, required, className, children, ...props }: Omit<ComponentProps<"div">, "id" | "children"> & {
92
+ export declare function FormFieldShell({ id, label, labelAction, description, errorText, labelFor, required, children, ...props }: Omit<ComponentProps<"div">, "id" | "children"> & {
78
93
  /** Id of the control; the label, hint, and error ids derive from it. */
79
94
  id: string;
80
95
  /** Visible label text. */
@@ -91,3 +106,4 @@ export declare function FormFieldShell({ id, label, labelAction, description, er
91
106
  required?: boolean;
92
107
  children: ReactNode;
93
108
  }): import("react").JSX.Element;
109
+ export {};