atlas-react-kit 1.2.1 → 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 (51) hide show
  1. package/COMPONENTS.md +22 -18
  2. package/dist/alert.js +2 -2
  3. package/dist/card.js +2 -1
  4. package/dist/combobox.d.ts +4 -3
  5. package/dist/combobox.js +3 -7
  6. package/dist/dialog.js +2 -1
  7. package/dist/dropdown-menu.js +6 -4
  8. package/dist/form/checkbox-field.d.ts +2 -4
  9. package/dist/form/checkbox-field.js +9 -12
  10. package/dist/form/combobox-field.d.ts +2 -2
  11. package/dist/form/combobox-field.js +5 -6
  12. package/dist/form/control-field.d.ts +5 -5
  13. package/dist/form/control-field.js +9 -7
  14. package/dist/form/date-picker-field.d.ts +1 -1
  15. package/dist/form/date-picker-field.js +3 -10
  16. package/dist/form/date-time-picker-field.d.ts +1 -1
  17. package/dist/form/date-time-picker-field.js +3 -10
  18. package/dist/form/field-error.d.ts +10 -1
  19. package/dist/form/field-shell.d.ts +27 -16
  20. package/dist/form/field-shell.js +27 -2
  21. package/dist/form/focus-first-invalid.d.ts +7 -4
  22. package/dist/form/focus-first-invalid.js +10 -6
  23. package/dist/form/markdown-field.d.ts +1 -1
  24. package/dist/form/markdown-field.js +8 -4
  25. package/dist/form/select-field.d.ts +2 -8
  26. package/dist/form/select-field.js +5 -7
  27. package/dist/form/text-field.d.ts +2 -8
  28. package/dist/form/text-field.js +8 -3
  29. package/dist/form/toggle-group-field.d.ts +2 -2
  30. package/dist/form/toggle-group-field.js +5 -6
  31. package/dist/info-tip.d.ts +3 -1
  32. package/dist/info-tip.js +4 -1
  33. package/dist/internal/form-control.d.ts +6 -0
  34. package/dist/internal/form-control.js +6 -0
  35. package/dist/internal/kanban-layout.js +2 -1
  36. package/dist/internal/listbox-navigation.d.ts +8 -8
  37. package/dist/internal/listbox-navigation.js +23 -23
  38. package/dist/internal/overlay-styles.d.ts +5 -0
  39. package/dist/internal/overlay-styles.js +5 -0
  40. package/dist/internal/styles.d.ts +5 -0
  41. package/dist/internal/styles.js +5 -0
  42. package/dist/kanban.js +1 -1
  43. package/dist/picker-shell.d.ts +3 -1
  44. package/dist/picker-shell.js +3 -3
  45. package/dist/popover.js +4 -4
  46. package/dist/search-picker.js +4 -8
  47. package/dist/select.js +3 -3
  48. package/dist/toggle-group.d.ts +1 -1
  49. package/dist/toggle-group.js +36 -2
  50. package/package.json +1 -1
  51. package/skills/atlas-react-kit/SKILL.md +1 -1
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/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/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 }));
@@ -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 }));
@@ -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
  }
@@ -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 = {