atlas-react-kit 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/COMPONENTS.md +22 -18
  2. package/DESIGN_RULES.md +7 -7
  3. package/dist/alert.js +2 -2
  4. package/dist/badge.js +1 -1
  5. package/dist/button.js +1 -1
  6. package/dist/card.js +2 -1
  7. package/dist/chart.js +1 -1
  8. package/dist/combobox.d.ts +4 -3
  9. package/dist/combobox.js +3 -7
  10. package/dist/dialog.js +2 -1
  11. package/dist/dropdown-menu.js +6 -4
  12. package/dist/field.js +2 -2
  13. package/dist/form/checkbox-field.d.ts +2 -4
  14. package/dist/form/checkbox-field.js +9 -12
  15. package/dist/form/combobox-field.d.ts +2 -2
  16. package/dist/form/combobox-field.js +5 -6
  17. package/dist/form/control-field.d.ts +5 -5
  18. package/dist/form/control-field.js +9 -7
  19. package/dist/form/date-picker-field.d.ts +1 -1
  20. package/dist/form/date-picker-field.js +3 -10
  21. package/dist/form/date-time-picker-field.d.ts +1 -1
  22. package/dist/form/date-time-picker-field.js +3 -10
  23. package/dist/form/feedback-field.js +2 -1
  24. package/dist/form/field-error.d.ts +10 -1
  25. package/dist/form/field-error.js +2 -1
  26. package/dist/form/field-shell.d.ts +27 -16
  27. package/dist/form/field-shell.js +27 -2
  28. package/dist/form/focus-first-invalid.d.ts +7 -4
  29. package/dist/form/focus-first-invalid.js +10 -6
  30. package/dist/form/markdown-field.d.ts +1 -1
  31. package/dist/form/markdown-field.js +8 -4
  32. package/dist/form/select-field.d.ts +2 -8
  33. package/dist/form/select-field.js +5 -7
  34. package/dist/form/text-field.d.ts +2 -8
  35. package/dist/form/text-field.js +8 -3
  36. package/dist/form/toggle-group-field.d.ts +2 -2
  37. package/dist/form/toggle-group-field.js +5 -6
  38. package/dist/info-tip.d.ts +3 -1
  39. package/dist/info-tip.js +4 -1
  40. package/dist/internal/calendar-size.d.ts +1 -1
  41. package/dist/internal/calendar-size.js +1 -1
  42. package/dist/internal/form-control.d.ts +6 -0
  43. package/dist/internal/form-control.js +6 -0
  44. package/dist/internal/kanban-layout.js +2 -1
  45. package/dist/internal/listbox-navigation.d.ts +8 -8
  46. package/dist/internal/listbox-navigation.js +23 -23
  47. package/dist/internal/overlay-styles.d.ts +5 -0
  48. package/dist/internal/overlay-styles.js +5 -0
  49. package/dist/internal/styles.d.ts +10 -3
  50. package/dist/internal/styles.js +12 -5
  51. package/dist/internal/tabs-styles.js +1 -1
  52. package/dist/kanban.js +3 -3
  53. package/dist/meter.js +1 -1
  54. package/dist/pagination.d.ts +1 -1
  55. package/dist/pagination.js +3 -3
  56. package/dist/picker-shell.d.ts +3 -1
  57. package/dist/picker-shell.js +3 -3
  58. package/dist/popover.js +4 -4
  59. package/dist/progress.js +1 -1
  60. package/dist/search-picker.js +4 -8
  61. package/dist/select.js +4 -4
  62. package/dist/separator.js +1 -1
  63. package/dist/skeleton.js +1 -1
  64. package/dist/slider.js +1 -1
  65. package/dist/tabs.js +1 -1
  66. package/dist/toast.js +11 -19
  67. package/dist/toggle-group.d.ts +1 -1
  68. package/dist/toggle-group.js +36 -2
  69. package/package.json +1 -1
  70. package/skills/atlas-react-kit/SKILL.md +1 -1
  71. package/theme.css +6 -0
package/COMPONENTS.md CHANGED
@@ -50,7 +50,7 @@ theme from `next-themes`, so toast consumers install that too.
50
50
  | `empty-panel` | `EmptyPanel`; required `children`; optional `size` (`sm`, `default`, `lg`; `md` is a deprecated alias of `default`), `muted`, `className` |
51
51
  | `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
52
52
  | `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
53
- | `info-tip` | `InfoTip`, `InlineTip`; required `label` (aria-label); `title`, `description`, `children`, `layout` `box` \| `inline`, `side`, `align`, `badge`, `icon`, `className`. Popover, 20px glyph (1em inline, on the text's cap height), 44px hit. Hover opens only on a fine pointer. Not a Tooltip. |
53
+ | `info-tip` | `InfoTip`, `InlineTip`; required `label` (aria-label); `title`, `description`, `children`, `layout` `box` \| `inline`, `side`, `align`, `badge`, `icon`, `className`. Popover, 20px glyph (1em inline, on the text's cap height), 44px hit. After a label the glyph sits a quarter em from the text in both layouts, so leave the gap off the parent. Hover opens only on a fine pointer. Not a Tooltip. |
54
54
  | `kanban` | `KanbanBoard<T>`, `KanbanSkeleton`, `KanbanColumn` and `KanbanDropTarget` types; `columns` (`{ id, label, color?, badge?, emptyLabel? }`), `items` (`{ id, position }`), `getColumnId`, `onMove(item, toColumnId, { beforePosition, afterPosition })` for every column change or reorder, `renderCard`; optional `onCardClick` (body becomes a button), `onComplete` / `onDelete` (Finish / Remove drop zones while dragging), `isLoading` (skeleton cards on a first load, dimmed board on a refetch), `emptyLabel`, `completeLabel`, `deleteLabel`. Layout comes from `items`, so an optimistic cache update is the only state. Keep `renderCard` and `onCardClick` stable (`useCallback`) and only the changed card re-renders. Cards drag by the grip (`Drag to move`); dnd-kit edge-scrolls while dragging, and off-screen columns get fades and scroll buttons. `KanbanSkeleton` takes `columnCount` (default 4). |
55
55
  | `input`, `textarea`, `label` | `Input`, `Textarea`, `Label`; `Textarea` starts at `min-h-16` (override with `className`); `Label` `required` shows a visible marker (set `required` on the control too) |
56
56
  | `markdown` | `Markdown`, `MarkdownContent`; `content`, optional `className`. GFM via `react-markdown` + `remark-gfm` in this subpath only. Sanitized (no raw HTML; http/https/mailto/#/`/` URLs). Token styles, no typography plugin. |
@@ -76,21 +76,21 @@ theme from `next-themes`, so toast consumers install that too.
76
76
  | `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `TabsNavLink`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; `TabsNavLink` is the link inside `TabsNav` (required `active`, `asChild` for a router link); `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, `p-1` counted in the scroller height, edge fade, active item into view, mouse drag scrolls the row) — do not wrap them in `overflow-x-auto` |
77
77
  | `toast` | `Toaster`, `toast`; mount one `Toaster` (`position`, `closeButton` (default on), `expand`, `visibleToasts`, `duration`), then `toast(message, options)`. The stack expands on hover and while keyboard focus is inside it; collapsed toasts behind the front one are hidden, `.success`, `.info`, `.warning`, `.error`, `.loading`, `.dismiss(id?)`. Options: `id` (replaces that toast), `description`, `duration`, `action` / `cancel` as `{ label, onClick }`. Kit-owned API: no toast library is exposed |
78
78
  | `toggle` | `Toggle`, `toggleVariants`; `variant`, `size`. `selectedToggleChrome` is deprecated: `toggleVariants()` already includes it. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
79
- | `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle` |
79
+ | `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle`. In a single group the arrow keys select as they move focus, like radios; Tab and clicks do not. A multiple group only moves focus |
80
80
  | `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
81
81
  | `tooltip` | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`; opens on hover and focus; short labels only; content keeps a `max-w-xs` reading width (override with `className`) |
82
- | `form/checkbox-field` | `FormCheckboxField` (`checked`, `onCheckedChange(boolean)`, `label`, optional `description`, `controlPosition` `start` \| `end`, `meta`, `required`, `disabled`, `onBlur`) and `FormCheckboxGroupField<T>` (`label` as legend, `options` `{ value, label }[]`, `value: T[]`, `onValueChange` in option order, `className` lays out the options). |
83
- | `form/combobox-field` | `FormComboboxField`; every `Combobox` prop plus `label`, `description`, `meta`, `required`; pass the field's `onBlur`. |
84
- | `form/control-field` | `FormControlField`, type `FormControlProps`; `label`, optional `id`, `description`, `required`, `meta`, `submitted`, `showError`; `children` renders the control from `{ id, aria-invalid, aria-describedby, aria-required }` |
85
- | `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, optional `id`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
82
+ | `form/checkbox-field` | `FormCheckboxField` (`checked`, `onCheckedChange(boolean)`, `label`, optional `description`, `controlPosition` `start` \| `end`, `meta`, `fieldName`, `required`, `disabled`, `onBlur`) and `FormCheckboxGroupField<T>` (`label` as legend, `options` `{ value, label }[]`, `value: T[]`, `onValueChange` in option order, `className` lays out the options). |
83
+ | `form/combobox-field` | `FormComboboxField`; every `Combobox` prop (but `id`) plus `label`, `description`, `meta`, `fieldName`, `required`; pass the field's `onBlur`. |
84
+ | `form/control-field` | `FormControlField`, type `FormControlProps`; `label`, optional `fieldName`, `description`, `required`, `meta`, `submitted`, `showError`; `children` renders the control from `{ id, data-field-name, aria-invalid, aria-describedby, aria-required }` |
85
+ | `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
86
86
  | `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints; `required` also marks the label |
87
- | `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `id` (default generated; pass the field name for `focusFirstInvalid`), `meta`, placeholder, and `required`; select item children |
88
- | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `id` (default generated), `meta`, placeholder, `required`, `clearable` (a Clear row that emits `undefined`), `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
89
- | `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; optional `id` (default generated), `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
87
+ | `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `fieldName` (for `focusFirstInvalid`), `meta`, placeholder, and `required`; select item children |
88
+ | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `fieldName`, `meta`, placeholder, `required`, `clearable` (a Clear row that emits `undefined`), `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
89
+ | `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; optional `fieldName`, `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
90
90
  | `form/field-error` | `FieldError` (optional `id`), `getFieldErrorMessage`; types `FieldErrorMessage`, `FieldMetaState`, `ErrorTimingProps` |
91
- | `form/focus-first-invalid` | `focusFirstInvalid(formId)`: an `onSubmitInvalid` handler that focuses the first control in the form whose field (matched by control id, so pass `id={field.name}`) has errors; type `InvalidSubmitEvent` |
91
+ | `form/focus-first-invalid` | `focusFirstInvalid(formId)`: an `onSubmitInvalid` handler that focuses the first control in the form whose field has errors, matched by the control's `data-field-name` (`field.name` for the text and Markdown fields, the `fieldName` prop on the others) and never outside that form; type `InvalidSubmitEvent` |
92
92
  | `form/feedback-field` | `FormFeedbackField`; nullable `message` |
93
- | `form/toggle-group-field` | `FormToggleGroupField<T>`; one choice from `options` `{ value, label }[]` as a joined outline toggle group named by its label; `value` (`""` for none), `onValueChange(T)` (pressing the active item keeps it), optional `description`, `meta`, `size` (`sm` default). |
93
+ | `form/toggle-group-field` | `FormToggleGroupField<T>`; one choice from `options` `{ value, label }[]` as a joined outline toggle group named by its label; `value` (`""` for none), `onValueChange(T)` (pressing the active item keeps it), optional `description`, `meta`, `fieldName`, `size` (`sm` default). |
94
94
  | `next/nav-link` | `NavLink`; Next Link props and required `active` boolean; the `nav-link` pill with `asChild` around a Next `Link` |
95
95
  | `next/period-stepper` | `PeriodStepper` with prefetched Next links: `previousHref`, `nextHref`, optional `todayHref`. |
96
96
  | `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; the `tabs` link with `asChild` around a Next `Link` |
@@ -121,7 +121,7 @@ each part carries a `data-slot` for parent selectors such as
121
121
  | `DialogContent` | `overlayClassName`, `closeButtonClassName` | `dialog-close-button` (the built-in X; your own `DialogClose` keeps `dialog-close`) |
122
122
  | `SelectTrigger` | a `className` height replaces `size` | `select-icon`, `select-viewport`, `select-item-indicator` |
123
123
  | `TooltipContent` | `arrowClassName` | `tooltip-arrow` |
124
- | `Combobox` | `contentClassName` (panel, default trigger width), `listClassName` (default `max-h-60`); button props go to trigger | `combobox-search`, `combobox-list`, `combobox-option` |
124
+ | `Combobox` | `contentClassName` (panel, default at least the trigger width, wider for a long option), `listClassName` (default `max-h-60`); button props go to trigger | `combobox-search`, `combobox-list`, `combobox-option` |
125
125
  | `SearchPicker` | `listClassName` (default `max-h-60`); div props go to the root | `search-picker-option`, `search-picker-empty` |
126
126
  | `InfoTip` | `contentClassName` (default `w-72`); button props go to the trigger | |
127
127
  | `KanbanBoard` | `scrollerClassName`, `columnClassName` (default `flex-1 basis-72 min-w-72` and a drop-area min height; columns grow to fill the board and keep an 18rem minimum so a long row still scrolls), `cardClassName` (cards and the drag overlay); div props go to the root | `kanban-board`, `kanban-scroller`, `kanban-column`, `kanban-column-header`, `kanban-column-empty`, `kanban-card`, `kanban-card-handle`, `kanban-card-body`, `kanban-drag-overlay`, `kanban-action-rail`, `kanban-action-zone`, `kanban-scroll-button`, `kanban-overflow-fade`, `kanban-skeleton` |
@@ -168,12 +168,16 @@ for a Write / Preview GFM body. `FormSelectField`, `FormDatePickerField`, and
168
168
  `FormDateTimePickerField` accept the same error meta shape.
169
169
  `FormDatePickerField` takes a `Date` or `yyyy-MM-dd` string and calls
170
170
  `onValueChange` with `Date | undefined`. `FormDateTimePickerField` takes a
171
- `Date` or `null` and calls `onValueChange` with `Date | null`. Pass
172
- `id={field.name}` to the select and date fields (the text field defaults to
173
- it) so a field is addressable by name, for example by `focusFirstInvalid`
174
- from `form/focus-first-invalid`, which focuses the first field with errors
175
- when a submit fails. `FieldError`
176
- accepts either `message` or pre-resolved `text`, and an `id` to point the
171
+ `Date` or `null` and calls `onValueChange` with `Date | null`. Every
172
+ `form/*` field generates its own control id, so two forms on a page can
173
+ share a field name (login and sign-up `email`) and each label, hint, and
174
+ error still points at its own control; there is no `id` prop. To make a
175
+ field addressable by name, for example by `focusFirstInvalid` from
176
+ `form/focus-first-invalid`, which focuses the first field with errors when
177
+ a submit fails, pass `fieldName={field.name}` to every field that is not
178
+ bound to a `field` (the text and Markdown fields read `field.name`
179
+ themselves); the name is put on the control as `data-field-name`.
180
+ `FieldError` accepts either `message` or pre-resolved `text`, and an `id` to point the
177
181
  control's `aria-describedby` at. Every `form/*` field does that for you: the
178
182
  error text (and `FormTextField`'s `description`) is the control's accessible
179
183
  description. The date fields label their trigger with `aria-labelledby` (the
package/DESIGN_RULES.md CHANGED
@@ -117,11 +117,11 @@ still read normal words.
117
117
  ## Shape and composition
118
118
 
119
119
  Primitives have square corners. Reserve `rounded-full` for dots, avatars,
120
- other circles, and the toast close badge. Card and dialog titles already use
121
- uppercase, tracked `font-heading` styling. Toasts are overlay popovers with
122
- the same square corners and Alert's left padding. The close control is a
123
- circular badge centred on the top-right corner (half outside the border),
124
- not a control in the text row. A plain `toast()` has no icon — status icons
120
+ and other circles. Card and dialog titles already use
121
+ uppercase, tracked `font-heading` styling. Toasts use the same square corners
122
+ and Alert's left padding, with a square close button inside the top-right
123
+ corner. Mount the toaster in its own notification area; it occupies space
124
+ in the page so notifications do not cover content. A plain `toast()` has no icon — status icons
125
125
  belong to `success`, `info`, `warning`, `error`, and `loading`. Tone colors
126
126
  the icon, title, and border, not the body.
127
127
 
@@ -146,8 +146,8 @@ toasts keep their width.
146
146
 
147
147
  Parents do not force sizes on their children. A vertical `Field` stretches
148
148
  its controls through the flex column, so a control's own width class (such
149
- as `w-32`) wins; `Field`, `Alert`, and `Pagination` set no width of their
150
- own. `FieldGroup` keeps `w-full` because its inline-size container has no
149
+ as `w-32`) wins; `Field` and `Alert` set no width of their
150
+ own. `FieldGroup` and `Pagination` keep `w-full` because an inline-size container has no
151
151
  intrinsic width. Size classes that `className` should be able to replace are
152
152
  plain classes picked from props (`Avatar` `size`, `Separator` orientation,
153
153
  `SelectTrigger` `size`, tab orientation), never `data-*` variants, which sort
package/dist/alert.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cva } from "class-variance-authority";
4
- import { statusTone } from "./internal/styles.js";
4
+ import { descriptionText, statusTone } from "./internal/styles.js";
5
5
  import { cn } from "./utils.js";
6
6
  /**
7
7
  * Status tone sits on the title and icon. The description stays
@@ -33,6 +33,6 @@ function AlertTitle({ className, ...props }) {
33
33
  return (_jsx("div", { "data-slot": "alert-title", className: cn("col-start-2 min-h-4 text-xs font-semibold tracking-wider uppercase", className), ...props }));
34
34
  }
35
35
  function AlertDescription({ className, ...props }) {
36
- return (_jsx("div", { "data-slot": "alert-description", className: cn("col-start-2 grid justify-items-start gap-1 text-sm leading-relaxed text-muted-foreground [&_p]:leading-relaxed", className), ...props }));
36
+ return (_jsx("div", { "data-slot": "alert-description", className: cn("col-start-2 grid justify-items-start gap-1", descriptionText, "[&_p]:leading-relaxed", className), ...props }));
37
37
  }
38
38
  export { Alert, AlertDescription, AlertTitle, alertVariants };
package/dist/badge.js CHANGED
@@ -21,7 +21,7 @@ const badgeVariants = cva([
21
21
  ghost: "text-muted-foreground hover:text-foreground",
22
22
  link: "text-foreground underline-offset-4 hover:underline",
23
23
  /** Tinted chip; pick its color with `tone`. */
24
- soft: "px-2 py-0.5 text-xs font-medium tracking-normal normal-case",
24
+ soft: "px-2 py-0.5",
25
25
  },
26
26
  /** Only `soft` reads this. */
27
27
  tone: {
package/dist/button.js CHANGED
@@ -21,7 +21,7 @@ const buttonVariants = cva([
21
21
  disabledState,
22
22
  "group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding",
23
23
  controlText,
24
- "transition-all select-none active:not-aria-[haspopup]:translate-y-px",
24
+ "transition-all select-none active:not-aria-[haspopup]:translate-y-px disabled:opacity-100 disabled:saturate-0",
25
25
  inlineIcon.default,
26
26
  ], {
27
27
  variants: {
package/dist/card.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
+ import { descriptionText } from "./internal/styles.js";
3
4
  import { cn } from "./utils.js";
4
5
  /**
5
6
  * Size and flush padding live in `--card-p` (set here, read from `theme.css`
@@ -32,7 +33,7 @@ function CardTitle({ as: Component = "div", className, ...props }) {
32
33
  return (_jsx(Component, { "data-slot": "card-title", className: cn("font-heading text-base font-semibold tracking-wider uppercase", className), ...props }));
33
34
  }
34
35
  function CardDescription({ className, ...props }) {
35
- return (_jsx("div", { "data-slot": "card-description", className: cn("text-sm leading-relaxed text-muted-foreground", className), ...props }));
36
+ return (_jsx("div", { "data-slot": "card-description", className: cn(descriptionText, className), ...props }));
36
37
  }
37
38
  function CardAction({ className, ...props }) {
38
39
  return (_jsx("div", { "data-slot": "card-action", className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className), ...props }));
package/dist/chart.js CHANGED
@@ -144,7 +144,7 @@ export function ChartYAxisGrid({ frame, yMax, tickCount = 4, format = compactNum
144
144
  const ticks = niceTicks(yMax, tickCount);
145
145
  const top = ticks[ticks.length - 1] ?? 1;
146
146
  const y = (value) => frame.bottom - (value / top) * frame.plotHeight;
147
- return (_jsx("g", { "data-slot": "chart-y-axis", children: ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: frame.left, y1: y(tick), x2: frame.right, y2: y(tick), strokeWidth: 1, className: "stroke-border" }), _jsx("text", { x: frame.left - 6, y: y(tick) + 3, textAnchor: "end", className: "fill-muted-foreground text-[10px] tabular-nums", children: format(tick) })] }, tick))) }));
147
+ return (_jsx("g", { "data-slot": "chart-y-axis", children: ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: frame.left, y1: y(tick), x2: frame.right, y2: y(tick), strokeWidth: 1, className: "stroke-input" }), _jsx("text", { x: frame.left - 6, y: y(tick) + 3, textAnchor: "end", className: "fill-muted-foreground text-xs tabular-nums", children: format(tick) })] }, tick))) }));
148
148
  }
149
149
  /**
150
150
  * Readout for the hovered or focused point, placed in percent of the chart
@@ -21,11 +21,12 @@ type ComboboxProps = {
21
21
  disabled?: boolean;
22
22
  /** Goes on the trigger, so a `<Label htmlFor>` names it. Generated when omitted. */
23
23
  id?: string;
24
- /** Classes for the trigger, e.g. its width; the list matches it. */
24
+ /** Classes for the trigger, e.g. its width; the list is at least as wide. */
25
25
  className?: string;
26
26
  /**
27
- * Classes for the popover panel, e.g. a width other than the trigger's.
28
- * Defaults to the trigger width.
27
+ * Classes for the popover panel, e.g. a fixed width. By default the panel
28
+ * is at least as wide as the trigger and grows to fit the longest option,
29
+ * up to the space the viewport leaves.
29
30
  */
30
31
  contentClassName?: string;
31
32
  /** Marks the trigger invalid, drawing the error border. */
package/dist/combobox.js CHANGED
@@ -120,17 +120,13 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
120
120
  };
121
121
  const onSearchKeyDown = (event) => {
122
122
  handleListboxKey(event, {
123
+ items: list,
123
124
  highlight,
124
- length: list.length,
125
125
  setHighlight,
126
- onEnter: (index) => {
127
- const option = list[index];
128
- if (option)
129
- selectOption(option);
130
- },
126
+ onPick: selectOption,
131
127
  });
132
128
  };
133
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ...props, id: triggerId, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-empty": !selected, disabled: disabled, onKeyDown: onTriggerKeyDown, className: cn(formControlTriggerClassName, "justify-between", emptyValueText, className), children: [_jsx("span", { className: "truncate", children: selected?.label ?? placeholder }), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "text-muted-foreground" })] }) }), _jsxs(PopoverContent, { align: "start", className: cn("w-[var(--radix-popover-trigger-width)] gap-0 rounded-none p-0", contentClassName), children: [_jsx("div", { "data-slot": "combobox-search", className: "border-b p-2", children: _jsx(Input, { value: query, onChange: onQueryChange, onKeyDown: onSearchKeyDown, placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listId, ...listboxInputAria(listId, activeOption ? highlight : undefined) }) }), _jsx(Listbox, { id: listId, empty: empty, "data-slot": "combobox-list", "aria-label": ariaLabelledBy ? undefined : ariaLabel, "aria-labelledby": ariaLabelledBy ?? (ariaLabel ? undefined : triggerId), className: cn("p-1", listClassName), children: list.map((option, index) => {
129
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ...props, id: triggerId, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-empty": !selected, disabled: disabled, onKeyDown: onTriggerKeyDown, className: cn(formControlTriggerClassName, "justify-between", emptyValueText, className), children: [_jsx("span", { className: "truncate", children: selected?.label ?? placeholder }), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "text-muted-foreground" })] }) }), _jsxs(PopoverContent, { align: "start", className: cn("w-max min-w-(--radix-popover-trigger-width) gap-0 rounded-none p-0", contentClassName), children: [_jsx("div", { "data-slot": "combobox-search", className: "border-b p-2", children: _jsx(Input, { value: query, onChange: onQueryChange, onKeyDown: onSearchKeyDown, placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listId, ...listboxInputAria(listId, activeOption ? highlight : undefined) }) }), _jsx(Listbox, { id: listId, empty: empty, "data-slot": "combobox-list", "aria-label": ariaLabelledBy ? undefined : ariaLabel, "aria-labelledby": ariaLabelledBy ?? (ariaLabel ? undefined : triggerId), className: cn("p-1", listClassName), children: list.map((option, index) => {
134
130
  const isCreate = createOption != null && option === createOption;
135
131
  const isSelected = option.value === value && !isCreate;
136
132
  return (_jsx(PickerOption, { "data-slot": "combobox-option", listId: listId, index: index, active: index === highlight, selected: isSelected, showCheck: true, item: option, getLabel: getOptionLabel, onHighlight: setHighlight, onSelect: selectOption }, isCreate ? CREATE_OPTION_KEY : option.value));
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, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, 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 });
@@ -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.js CHANGED
@@ -2,7 +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
+ import { errorText, labelText } from "./internal/styles.js";
6
6
  import { cn } from "./utils.js";
7
7
  import { Label } from "./label.js";
8
8
  import { Separator } from "./separator.js";
@@ -77,6 +77,6 @@ function FieldError({ className, children, errors, ...props }) {
77
77
  if (!content) {
78
78
  return null;
79
79
  }
80
- return (_jsx("div", { role: "alert", "data-slot": "field-error", className: cn("text-sm font-normal text-destructive", className), ...props, children: content }));
80
+ return (_jsx("div", { role: "alert", "data-slot": "field-error", className: cn(errorText, className), ...props, children: content }));
81
81
  }
82
82
  export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle, };
@@ -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,26 +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
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 { fieldAria, FormFieldDescription, FormFieldFrame, metaErrorText, } from "./field-shell.js";
7
+ import { fieldAria, FormFieldDescription, FormFieldFrame, useFormField, } from "./field-shell.js";
8
8
  /**
9
9
  * One boolean as a labelled checkbox, with an optional hint and the same
10
10
  * error text and timing as the other `form/*` fields.
11
11
  */
12
- export function FormCheckboxField({ checked, onCheckedChange, onBlur, label, description, id: idProp, meta, submitted, showError, required, disabled, controlPosition = "start", className, fieldClassName, }) {
13
- const generatedId = useId();
14
- const id = idProp ?? generatedId;
15
- const errorText = metaErrorText(meta, { submitted, showError });
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 }));
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 }));
17
15
  const labelNode = (_jsx(FieldLabel, { htmlFor: id, required: required, children: label }));
18
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] }) }));
19
17
  }
20
18
  /** Several values from a short list, as a labelled set of checkboxes. */
21
- export function FormCheckboxGroupField({ label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
22
- const id = useId();
23
- const errorText = metaErrorText(meta, { submitted, showError });
19
+ export function FormCheckboxGroupField({ fieldName, label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
20
+ const { id, errorText } = useFormField(meta, { submitted, showError });
24
21
  function toggle(option, on) {
25
22
  const next = new Set(value);
26
23
  if (on)
@@ -29,8 +26,8 @@ export function FormCheckboxGroupField({ label, description, options, value, onV
29
26
  next.delete(option);
30
27
  onValueChange(options.map((o) => o.value).filter((v) => next.has(v)));
31
28
  }
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) => {
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) => {
33
30
  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));
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));
35
32
  }) })] }) }));
36
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 { fieldAria, FormFieldShell, 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, ...fieldAria(id, errorText, { description, required }) }) }));
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 { fieldAria, FormFieldShell, 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,9 +9,11 @@ import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
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 });
16
- return (_jsx(FormFieldShell, { ...props, id: id, label: label, description: description, errorText: errorText, required: required, children: children({ id, ...fieldAria(id, errorText, { description, required }) }) }));
12
+ export function FormControlField({ fieldName, label, description, required, meta, submitted, showError, children, ...props }) {
13
+ const { id, errorText } = useFormField(meta, { submitted, showError });
14
+ return (_jsx(FormFieldShell, { ...props, id: id, label: label, description: description, errorText: errorText, required: required, children: children({
15
+ id,
16
+ ...fieldNameProps(fieldName),
17
+ ...fieldAria(id, errorText, { description, required }),
18
+ }) }));
17
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,15 +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 { fieldAria, FormFieldShell, metaErrorText, } 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, ...fieldAria(id, errorText, {
12
- required: required && "described",
13
- labelledBy: "label-value",
14
- }) }) }));
6
+ export function FormDatePickerField(props) {
7
+ return _jsx(FormPickerField, { field: props, picker: DatePickerButton });
15
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,15 +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 { fieldAria, FormFieldShell, metaErrorText, } 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, ...fieldAria(id, errorText, {
12
- required: required && "described",
13
- labelledBy: "label-value",
14
- }) }) }));
6
+ export function FormDateTimePickerField(props) {
7
+ return _jsx(FormPickerField, { field: props, picker: DateTimePickerButton });
15
8
  }