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
@@ -17,6 +17,11 @@ export const overlaySideSlide = "data-[side=bottom]:slide-in-from-top-2 data-[si
17
17
  export const overlayFade = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0";
18
18
  /** Fade plus a slight zoom, for panels (the backdrop only fades). */
19
19
  export const overlayFadeZoom = `${overlayFade} data-open:zoom-in-95 data-closed:zoom-out-95`;
20
+ /**
21
+ * A panel opened from a trigger (Popover, Select, DropdownMenu): the
22
+ * surface, sliding in from the trigger's side with a fade and zoom.
23
+ */
24
+ export const anchoredPanel = `${overlaySurface} ${overlaySideSlide} ${overlayFadeZoom}`;
20
25
  /**
21
26
  * Base of a menu or listbox row (SelectItem, DropdownMenuItem, Combobox and
22
27
  * SearchPicker options). Each component adds its own right padding and
@@ -10,8 +10,8 @@
10
10
  export declare const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
11
11
  /** The same ring on a wrapper with keyboard focus inside it. */
12
12
  export declare const focusRingWithin = "has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-background";
13
- /** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
14
- export declare const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
13
+ /** `aria-invalid`: a destructive border; keyboard focus keeps the shared solid ring. */
14
+ export declare const invalidState = "aria-invalid:border-destructive";
15
15
  /** Muted trigger text while empty (`data-placeholder` or `data-empty`). */
16
16
  export declare const emptyValueText = "data-placeholder:text-muted-foreground data-[empty=true]:text-muted-foreground";
17
17
  /** Native `disabled`: dimmed, with no hover or click. */
@@ -60,7 +60,7 @@ export declare const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-fo
60
60
  export declare const listboxScroll = "max-h-60 overflow-y-auto";
61
61
  /** Options in menus and listboxes. */
62
62
  export declare const optionState: {
63
- /** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
63
+ /** Highlight as focus moves: the selected wash plus a 2px inset ring bar. */
64
64
  focus: string;
65
65
  /** The same highlight for lists that track the active option in state. */
66
66
  active: string;
@@ -97,6 +97,13 @@ export declare const controlSize: {
97
97
  * tailwind-merge then drops the `text-xs`.
98
98
  */
99
99
  export declare const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
100
+ /**
101
+ * Secondary text under a title (CardDescription, PopoverDescription,
102
+ * DialogDescription, AlertDescription).
103
+ */
104
+ export declare const descriptionText = "text-sm leading-relaxed text-muted-foreground";
105
+ /** Field and form-level error text share one size, weight, and color. */
106
+ export declare const errorText = "text-sm font-normal text-destructive";
100
107
  /** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
101
108
  export declare const labelText = "text-xs font-semibold tracking-wide uppercase";
102
109
  /**
@@ -10,8 +10,8 @@
10
10
  export const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
11
11
  /** The same ring on a wrapper with keyboard focus inside it. */
12
12
  export const focusRingWithin = "has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-background";
13
- /** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
14
- export const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
13
+ /** `aria-invalid`: a destructive border; keyboard focus keeps the shared solid ring. */
14
+ export const invalidState = "aria-invalid:border-destructive";
15
15
  /** Muted trigger text while empty (`data-placeholder` or `data-empty`). */
16
16
  export const emptyValueText = "data-placeholder:text-muted-foreground data-[empty=true]:text-muted-foreground";
17
17
  /** Native `disabled`: dimmed, with no hover or click. */
@@ -60,10 +60,10 @@ export const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-foreground
60
60
  export const listboxScroll = "max-h-60 overflow-y-auto";
61
61
  /** Options in menus and listboxes. */
62
62
  export const optionState = {
63
- /** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
64
- focus: "focus:bg-accent focus:text-accent-foreground focus:shadow-[inset_2px_0_0_var(--color-ring)]",
63
+ /** Highlight as focus moves: the selected wash plus a 2px inset ring bar. */
64
+ focus: "focus:bg-selected focus:text-selected-foreground focus:shadow-[inset_2px_0_0_var(--color-ring)]",
65
65
  /** The same highlight for lists that track the active option in state. */
66
- active: "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--color-ring)]",
66
+ active: "bg-selected text-selected-foreground shadow-[inset_2px_0_0_var(--color-ring)]",
67
67
  /** The chosen value (`data-state="checked"`); a check icon marks it too. */
68
68
  checked: "data-[state=checked]:bg-selected data-[state=checked]:text-selected-foreground",
69
69
  /** The chosen value in lists that track selection in state. */
@@ -97,6 +97,13 @@ export const controlSize = {
97
97
  * tailwind-merge then drops the `text-xs`.
98
98
  */
99
99
  export const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
100
+ /**
101
+ * Secondary text under a title (CardDescription, PopoverDescription,
102
+ * DialogDescription, AlertDescription).
103
+ */
104
+ export const descriptionText = "text-sm leading-relaxed text-muted-foreground";
105
+ /** Field and form-level error text share one size, weight, and color. */
106
+ export const errorText = "text-sm font-normal text-destructive";
100
107
  /** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
101
108
  export const labelText = "text-xs font-semibold tracking-wide uppercase";
102
109
  /**
@@ -6,7 +6,7 @@ import { disabledState, focusRing, inlineIcon, selectedState, } from "./styles.j
6
6
  * without it (`auto`), the row reads the orientation from the `group/tabs`
7
7
  * root instead, as it always has for apps building their own list.
8
8
  */
9
- const tabsListVariants = cva("group/tabs-list inline-flex w-max min-w-full items-stretch justify-start p-0 text-muted-foreground", {
9
+ const tabsListVariants = cva("group/tabs-list inline-flex w-max min-w-full items-stretch justify-start p-0 after:w-1 after:shrink-0 after:content-[''] text-muted-foreground", {
10
10
  variants: {
11
11
  variant: {
12
12
  default: "bg-muted",
package/dist/kanban.js CHANGED
@@ -77,7 +77,7 @@ function KanbanBoard({ columns, items, getColumnId, onMove, onComplete, onDelete
77
77
  }, [cardClassName, itemById, renderCard]);
78
78
  // `min-w-0` lets a flex or grid parent shrink the board so the scroller,
79
79
  // not the page, overflows.
80
- return (_jsx("div", { "data-slot": "kanban-board", className: cn("min-w-0", className), ...props, children: _jsxs(DragDropProvider, { plugins: configureKanbanPlugins, onDragOver: handleDragOver, onDragEnd: handleDragEnd, children: [_jsxs("div", { className: "relative min-w-0", children: [_jsx("div", { ref: scrollerRef, "data-slot": "kanban-scroller", "data-loading": dimmed || undefined, "data-overflow-start": overflow.start || undefined, "data-overflow-end": overflow.end || undefined, onScroll: overflow.measure, className: cn("flex min-w-0 gap-4 overflow-x-auto pb-2",
80
+ return (_jsx("div", { "data-slot": "kanban-board", className: cn("min-w-0", className), ...props, children: _jsxs(DragDropProvider, { plugins: configureKanbanPlugins, onDragOver: handleDragOver, onDragEnd: handleDragEnd, children: [_jsxs("div", { className: "relative min-w-0 pb-12", children: [_jsx("div", { ref: scrollerRef, "data-slot": "kanban-scroller", "data-loading": dimmed || undefined, "data-overflow-start": overflow.start || undefined, "data-overflow-end": overflow.end || undefined, onScroll: overflow.measure, className: cn("flex min-w-0 gap-4 overflow-x-auto pb-2",
81
81
  // AutoScroller scrolls in small steps near the edge; mandatory
82
82
  // snap would pull each one back, so it is off mid-drag.
83
83
  "snap-x snap-mandatory scroll-px-2 has-data-dragging:snap-none", "data-loading:opacity-60", scrollerClassName), children: columns.map((column) => {
@@ -125,7 +125,7 @@ function KanbanColumnView({ column, count, emptyLabel, loading, className, child
125
125
  collisionPriority: COLUMN_DROP_PRIORITY,
126
126
  });
127
127
  const empty = count === 0;
128
- return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex snap-start flex-col gap-3 p-3", "bg-muted/40 ring-1 ring-foreground/5", "data-drop-target:bg-muted/60 data-drop-target:ring-2 data-drop-target:ring-ring/60", columnSize, className), children: [_jsxs("header", { "data-slot": "kanban-column-header", className: "flex items-start justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-widest uppercase", children: [column.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full", style: { backgroundColor: column.color } })) : null, _jsx("span", { className: "leading-tight", children: column.label })] }), _jsxs("span", { className: "flex shrink-0 flex-col items-end gap-0.5 text-muted-foreground tabular-nums", children: [_jsx("span", { className: "text-xs", children: count }), column.badge ? (_jsx("span", { className: "text-[10px] font-medium", children: column.badge })) : null] })] }), _jsx("div", { className: "flex min-h-32 flex-1 flex-col gap-2 overflow-y-auto pr-1", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20" }), _jsx(Skeleton, { className: "h-16" })] })) : (_jsxs(_Fragment, { children: [children, _jsx("p", { "data-slot": "kanban-column-empty", "aria-hidden": !empty, className: cn("border border-dashed px-3 py-6 text-center text-xs text-muted-foreground", !empty && "hidden"), children: emptyLabel })] })) })] }));
128
+ return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex snap-start flex-col gap-3 p-3", "bg-muted/40 ring-1 ring-foreground/5", "data-drop-target:bg-muted/60 data-drop-target:ring-2 data-drop-target:ring-ring/60", columnSize, className), children: [_jsxs("div", { "data-slot": "kanban-column-header", className: "flex flex-wrap items-start justify-between gap-x-2 gap-y-0.5", children: [_jsxs("span", { className: "flex max-w-full min-w-0 items-center gap-2 text-xs font-semibold tracking-widest uppercase", children: [column.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full", style: { backgroundColor: column.color } })) : null, _jsx("span", { className: "truncate leading-tight", title: column.label, children: column.label })] }), _jsxs("span", { className: "ml-auto flex max-w-full min-w-0 flex-col items-end gap-0.5 text-muted-foreground tabular-nums", children: [_jsx("span", { className: "text-xs", children: count }), column.badge ? (_jsx("span", { className: "max-w-full truncate text-[10px] font-medium", children: column.badge })) : null] })] }), _jsx("div", { className: "-m-1 mr-0 flex min-h-32 flex-1 flex-col gap-2 overflow-y-auto p-1", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20" }), _jsx(Skeleton, { className: "h-16" })] })) : (_jsxs(_Fragment, { children: [children, _jsx("p", { "data-slot": "kanban-column-empty", "aria-hidden": !empty, className: cn("border border-dashed px-3 py-6 text-center text-xs text-muted-foreground", !empty && "hidden"), children: emptyLabel })] })) })] }));
129
129
  }
130
130
  const cardChrome = cn("flex border border-border bg-card text-card-foreground");
131
131
  const handleChrome = cn("flex shrink-0 items-center self-stretch border-r border-border", "bg-muted/30 px-1.5 text-muted-foreground");
@@ -185,7 +185,7 @@ function ActionZone({ id, label, regionLabel, icon: Icon, tone, visible, }) {
185
185
  }
186
186
  function ScrollButton({ side, onClick, }) {
187
187
  const Icon = side === "start" ? ChevronLeft : ChevronRight;
188
- return (_jsx(Button, { type: "button", variant: "outline", size: "icon-sm", "data-slot": "kanban-scroll-button", "aria-label": side === "start" ? "Show previous columns" : "Show next columns", onClick: onClick, className: cn("absolute top-1/2 z-20 -translate-y-1/2", side === "start" ? "left-1" : "right-1"), children: _jsx(Icon, {}) }));
188
+ return (_jsx(Button, { type: "button", variant: "outline", size: "icon-sm", "data-slot": "kanban-scroll-button", "aria-label": side === "start" ? "Show previous columns" : "Show next columns", onClick: onClick, className: cn("absolute bottom-1 z-20", side === "start" ? "left-1" : "right-1"), children: _jsx(Icon, {}) }));
189
189
  }
190
190
  function OverflowFade({ side, visible, }) {
191
191
  return (_jsx("div", { "aria-hidden": true, "data-slot": "kanban-overflow-fade", "data-side": side, className: cn("pointer-events-none absolute inset-y-0 z-10 w-10 to-transparent transition-opacity", side === "start"
package/dist/meter.js CHANGED
@@ -15,6 +15,6 @@ function Meter({ value, min = 0, max, fillClassName = "bg-primary", markers, "ar
15
15
  return 0;
16
16
  return clamp(((amount - min) / span) * 100, 0, 100);
17
17
  };
18
- return (_jsxs("div", { ...props, role: "meter", "data-slot": "meter", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": clamp(current, min, max), "aria-label": ariaLabel, className: cn("relative h-1.5 w-full overflow-hidden bg-muted", className), children: [_jsx("div", { "data-slot": "meter-fill", className: cn("h-full", fillClassName), style: { width: `${toPercent(current)}%` } }), markers?.map((marker) => (_jsx("div", { "aria-hidden": true, "data-slot": "meter-marker", className: cn("absolute inset-y-0 w-px bg-foreground/40", marker.className), style: { left: `${toPercent(marker.position)}%` } }, marker.position)))] }));
18
+ return (_jsxs("div", { ...props, role: "meter", "data-slot": "meter", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": clamp(current, min, max), "aria-label": ariaLabel, className: cn("relative h-1.5 w-full overflow-hidden bg-input", className), children: [_jsx("div", { "data-slot": "meter-fill", className: cn("h-full", fillClassName), style: { width: `${toPercent(current)}%` } }), markers?.map((marker) => (_jsx("div", { "aria-hidden": true, "data-slot": "meter-marker", className: cn("absolute inset-y-0 w-px bg-foreground/40", marker.className), style: { left: `${toPercent(marker.position)}%` } }, marker.position)))] }));
19
19
  }
20
20
  export { Meter };
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  /**
3
3
  * Page links for long lists and tables. Links are plain anchors so the page
4
4
  * lives in the URL; pass `onClick` with `href` for client-side routing.
5
- * The nav is a plain flex row: place it with `className` (`justify-center`,
5
+ * The nav fills its container: place its links with `className` (`justify-center`,
6
6
  * `justify-end`, `mx-auto`) to suit the layout.
7
7
  */
8
8
  declare function Pagination({ className, ...props }: React.ComponentProps<"nav">): React.JSX.Element;
@@ -7,14 +7,14 @@ import { cn } from "./utils.js";
7
7
  /**
8
8
  * Page links for long lists and tables. Links are plain anchors so the page
9
9
  * lives in the URL; pass `onClick` with `href` for client-side routing.
10
- * The nav is a plain flex row: place it with `className` (`justify-center`,
10
+ * The nav fills its container: place its links with `className` (`justify-center`,
11
11
  * `justify-end`, `mx-auto`) to suit the layout.
12
12
  */
13
13
  function Pagination({ className, ...props }) {
14
- return (_jsx("nav", { "aria-label": "Pagination", "data-slot": "pagination", className: cn("flex", className), ...props }));
14
+ return (_jsx("nav", { "aria-label": "Pagination", "data-slot": "pagination", className: cn("@container/pagination flex w-full min-w-0 p-1", className), ...props }));
15
15
  }
16
16
  function PaginationContent({ className, ...props }) {
17
- return (_jsx("ul", { "data-slot": "pagination-content", className: cn("flex flex-row items-center gap-1", className), ...props }));
17
+ return (_jsx("ul", { "data-slot": "pagination-content", className: cn("flex flex-row flex-wrap items-center gap-1", className), ...props }));
18
18
  }
19
19
  function PaginationItem({ ...props }) {
20
20
  return _jsx("li", { "data-slot": "pagination-item", ...props });
@@ -64,6 +64,8 @@ export type PickerTriggerProps = {
64
64
  "aria-labelledby"?: string;
65
65
  /** Forwarded to the trigger, e.g. the id of a hint or error text. */
66
66
  "aria-describedby"?: string;
67
+ /** Forwarded to the trigger; a form field sets it for `focusFirstInvalid`. */
68
+ "data-field-name"?: string;
67
69
  /** Popover side. Defaults to `bottom`; Radix still flips on collision. */
68
70
  side?: React.ComponentProps<typeof PopoverContent>["side"];
69
71
  /** Month shown when the popover opens; falls back to the selected date. */
@@ -112,5 +114,5 @@ type PickerShellProps = Omit<PickerTriggerProps, "placeholder"> & {
112
114
  * Form-control trigger plus the viewport-clamped popover holding the
113
115
  * calendar, any extra rows, and an optional Clear row.
114
116
  */
115
- export declare function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled, children, }: PickerShellProps): React.JSX.Element;
117
+ export declare function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-field-name": fieldName, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled, children, }: PickerShellProps): React.JSX.Element;
116
118
  export {};
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { CalendarIcon } from "lucide-react";
5
5
  import { Button } from "./button.js";
6
6
  import { toDate, } from "./internal/date-like.js";
7
- import { PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, pickerValueId, } from "./internal/form-control.js";
7
+ import { fieldNameProps, PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, pickerValueId, } from "./internal/form-control.js";
8
8
  import { LazyCalendar, preloadCalendar } from "./lazy-calendar.js";
9
9
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
10
10
  import { cn } from "./utils.js";
@@ -39,8 +39,8 @@ function limitCalendarProps(calendarProps, minDate, maxDate) {
39
39
  * Form-control trigger plus the viewport-clamped popover holding the
40
40
  * calendar, any extra rows, and an optional Clear row.
41
41
  */
42
- export function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled = false, children, }) {
43
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), _jsx("span", { "data-slot": "date-picker-value", className: "min-w-0 text-left whitespace-normal", id: id ? pickerValueId(id) : undefined, children: label })] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, "aria-label": dialogLabel,
42
+ export function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-field-name": fieldName, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled = false, children, }) {
43
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...fieldNameProps(fieldName), "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), _jsx("span", { "data-slot": "date-picker-value", className: "min-w-0 text-left whitespace-normal", id: id ? pickerValueId(id) : undefined, children: label })] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, "aria-label": dialogLabel,
44
44
  // The calendar's `autoFocus` puts focus on the selected day (or
45
45
  // today) once it loads, as the APG date picker dialog does; Radix
46
46
  // focusing the popover first would leave it on the container.
package/dist/popover.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Popover as PopoverPrimitive } from "radix-ui";
5
- import { OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
6
- import { labelText } from "./internal/styles.js";
5
+ import { anchoredPanel, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, } from "./internal/overlay-styles.js";
6
+ import { descriptionText, labelText } from "./internal/styles.js";
7
7
  import { cn } from "./utils.js";
8
8
  function Popover({ ...props }) {
9
9
  return _jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
@@ -12,7 +12,7 @@ function PopoverTrigger({ ...props }) {
12
12
  return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
13
13
  }
14
14
  function PopoverContent({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
15
- return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(overlaySurface, overlaySideSlide, overlayFadeZoom, "z-50 flex max-w-(--radix-popover-content-available-width) origin-(--radix-popover-content-transform-origin) flex-col gap-4 p-4 text-sm outline-hidden", className), ...props }) }));
15
+ return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(anchoredPanel, "z-50 flex max-w-(--radix-popover-content-available-width) origin-(--radix-popover-content-transform-origin) flex-col gap-4 p-4 text-sm outline-hidden", className), ...props }) }));
16
16
  }
17
17
  function PopoverAnchor({ ...props }) {
18
18
  return _jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
@@ -24,6 +24,6 @@ function PopoverTitle({ className, ...props }) {
24
24
  return (_jsx("h2", { "data-slot": "popover-title", className: cn(labelText, className), ...props }));
25
25
  }
26
26
  function PopoverDescription({ className, ...props }) {
27
- return (_jsx("p", { "data-slot": "popover-description", className: cn("mt-0.5 text-sm leading-relaxed text-muted-foreground", className), ...props }));
27
+ return (_jsx("p", { "data-slot": "popover-description", className: cn("mt-0.5", descriptionText, className), ...props }));
28
28
  }
29
29
  export { Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, };
package/dist/progress.js CHANGED
@@ -14,6 +14,6 @@ function Progress({ className, indicatorClassName, value, max = 100, ...props })
14
14
  const scale = Number.isFinite(max) && max > 0 ? max : 100;
15
15
  const current = value != null && Number.isFinite(value) ? clamp(value, 0, scale) : null;
16
16
  const filled = current == null ? 0 : (current / scale) * 100;
17
- return (_jsx(ProgressPrimitive.Root, { "data-slot": "progress", value: current, max: scale, className: cn("relative h-2 w-full overflow-hidden rounded-none bg-primary/15", className), ...props, children: _jsx(ProgressPrimitive.Indicator, { "data-slot": "progress-indicator", className: cn("h-full w-full flex-1 bg-primary transition-transform", indicatorClassName), style: { transform: `translateX(-${100 - filled}%)` } }) }));
17
+ return (_jsx(ProgressPrimitive.Root, { "data-slot": "progress", value: current, max: scale, className: cn("relative h-2 w-full overflow-hidden rounded-none bg-input", className), ...props, children: _jsx(ProgressPrimitive.Indicator, { "data-slot": "progress-indicator", className: cn("h-full w-full flex-1 bg-primary transition-transform", indicatorClassName), style: { transform: `translateX(-${100 - filled}%)` } }) }));
18
18
  }
19
19
  export { Progress };
@@ -6,7 +6,7 @@ import { Label } from "./label.js";
6
6
  import { handleListboxKey } from "./internal/listbox-navigation.js";
7
7
  import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
8
8
  import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
9
- import { cn } from "./utils.js";
9
+ import { cn, onInputChange } from "./utils.js";
10
10
  export { foldSearchText };
11
11
  /**
12
12
  * Default SearchPicker filter: the folded label includes the folded query.
@@ -63,14 +63,10 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
63
63
  }, [onSelect]);
64
64
  const onKeyDown = (event) => {
65
65
  const handled = handleListboxKey(event, {
66
+ items: filtered,
66
67
  highlight,
67
- length: filtered.length,
68
68
  setHighlight,
69
- onEnter: (index) => {
70
- const item = filtered[index];
71
- if (item)
72
- selectItem(item);
73
- },
69
+ onPick: selectItem,
74
70
  });
75
71
  if (handled || event.key !== "Escape")
76
72
  return;
@@ -81,6 +77,6 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
81
77
  }
82
78
  event.currentTarget.blur();
83
79
  };
84
- return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, ...listboxInputAria(listId, active ? highlight : undefined), value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx(Listbox, { id: listId, empty: empty, "aria-label": listLabel ?? label, className: cn(listFrame, listClassName), children: rows.map(({ item, value: itemValue }, index) => (_jsx(PickerOption, { "data-slot": "search-picker-option", listId: listId, index: index, active: index === highlight, selected: itemValue === value, disabled: disabled, item: item, getLabel: getLabel, onHighlight: setHighlight, onSelect: selectItem }, itemValue))) }), _jsx(ListboxEmptyStatus, { "data-slot": "search-picker-empty", empty: empty, className: listFrame, children: emptyText })] }));
80
+ return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, ...listboxInputAria(listId, active ? highlight : undefined), value: query, disabled: disabled, placeholder: placeholder, onChange: onInputChange(setQuery), onKeyDown: onKeyDown }), _jsx(Listbox, { id: listId, empty: empty, "aria-label": listLabel ?? label, className: cn(listFrame, listClassName), children: rows.map(({ item, value: itemValue }, index) => (_jsx(PickerOption, { "data-slot": "search-picker-option", listId: listId, index: index, active: index === highlight, selected: itemValue === value, disabled: disabled, item: item, getLabel: getLabel, onHighlight: setHighlight, onSelect: selectItem }, itemValue))) }), _jsx(ListboxEmptyStatus, { "data-slot": "search-picker-empty", empty: empty, className: listFrame, children: emptyText })] }));
85
81
  }
86
82
  export { SearchPicker };
package/dist/select.js CHANGED
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Select as SelectPrimitive } from "radix-ui";
5
5
  import { dataDisabledState, disabledState, emptyValueText, fieldSurface, focusRing, inlineIcon, invalidState, optionState, } from "./internal/styles.js";
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 { cn } from "./utils.js";
8
8
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
9
9
  /**
@@ -22,17 +22,17 @@ function SelectValue({ ...props }) {
22
22
  return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
23
23
  }
24
24
  function SelectTrigger({ className, size = "default", children, ...props }) {
25
- return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, emptyValueText, emptyValueText, inlineIcon.default, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { "data-slot": "select-icon", asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })] }));
25
+ return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, emptyValueText, inlineIcon.default, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { "data-slot": "select-icon", asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })] }));
26
26
  }
27
27
  function SelectContent({ className, children, position = "popper", align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
28
- return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn(overlaySurface, overlaySideSlide, overlayFadeZoom, "relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" &&
28
+ return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn(anchoredPanel, "relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" &&
29
29
  "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { "data-slot": "select-viewport", "data-position": position, className: "data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)", children: children }), _jsx(SelectScrollDownButton, {})] }) }));
30
30
  }
31
31
  function SelectLabel({ className, ...props }) {
32
32
  return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn(menuLabel, className), ...props }));
33
33
  }
34
34
  function SelectItem({ className, children, ...props }) {
35
- return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionRow, optionState.focus, optionState.checked, dataDisabledState, inlineIcon.default, "pr-8 not-data-[variant=destructive]:focus:**:text-accent-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { "data-slot": "select-item-indicator", children: _jsx(CheckIcon, { className: "pointer-events-none" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
35
+ return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionRow, optionState.focus, optionState.checked, dataDisabledState, inlineIcon.default, "pr-8 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { "data-slot": "select-item-indicator", children: _jsx(CheckIcon, { className: "pointer-events-none" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
36
36
  }
37
37
  function SelectSeparator({ className, ...props }) {
38
38
  return (_jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn("pointer-events-none -mx-1.5 my-1.5 h-px bg-border", className), ...props }));
package/dist/separator.js CHANGED
@@ -8,6 +8,6 @@ import { cn } from "./utils.js";
8
8
  * `orientation`, so `className="w-12"` or `"w-0.5"` replaces them.
9
9
  */
10
10
  function Separator({ className, orientation = "horizontal", decorative = true, ...props }) {
11
- return (_jsx(SeparatorPrimitive.Root, { "data-slot": "separator", decorative: decorative, orientation: orientation, className: cn("shrink-0 bg-border", orientation === "vertical" ? "w-px self-stretch" : "h-px w-full", className), ...props }));
11
+ return (_jsx(SeparatorPrimitive.Root, { "data-slot": "separator", decorative: decorative, orientation: orientation, className: cn("shrink-0 bg-input", orientation === "vertical" ? "w-px self-stretch" : "h-px w-full", className), ...props }));
12
12
  }
13
13
  export { Separator };
package/dist/skeleton.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cn } from "./utils.js";
3
3
  function Skeleton({ className, ...props }) {
4
- return (_jsx("div", { "data-slot": "skeleton", className: cn("animate-pulse bg-muted", className), ...props }));
4
+ return (_jsx("div", { "data-slot": "skeleton", className: cn("animate-pulse bg-input", className), ...props }));
5
5
  }
6
6
  export { Skeleton };
package/dist/slider.js CHANGED
@@ -18,6 +18,6 @@ function Slider({ value, onValueChange, onValueCommit, min = 0, max = 100, step
18
18
  const amount = next[0];
19
19
  if (amount !== undefined)
20
20
  onValueCommit?.(amount);
21
- }, className: cn(dataDisabledState, "relative flex w-full touch-none items-center select-none", className), children: [_jsx(SliderPrimitive.Track, { "data-slot": "slider-track", className: "relative h-1.5 w-full grow overflow-hidden rounded-none bg-muted", children: _jsx(SliderPrimitive.Range, { "data-slot": "slider-range", className: "absolute h-full bg-primary" }) }), _jsx(SliderPrimitive.Thumb, { id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-slot": "slider-thumb", className: cn(focusRing, hitArea44, "relative block size-4 shrink-0 rounded-none border border-primary bg-background shadow-xs") })] }));
21
+ }, className: cn(dataDisabledState, "relative flex w-full touch-none items-center select-none", className), children: [_jsx(SliderPrimitive.Track, { "data-slot": "slider-track", className: "relative h-1.5 w-full grow overflow-hidden rounded-none bg-input", children: _jsx(SliderPrimitive.Range, { "data-slot": "slider-range", className: "absolute h-full bg-primary" }) }), _jsx(SliderPrimitive.Thumb, { id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-slot": "slider-thumb", className: cn(focusRing, hitArea44, "relative block size-4 shrink-0 rounded-none border border-primary bg-background shadow-xs") })] }));
22
22
  }
23
23
  export { Slider };
package/dist/tabs.js CHANGED
@@ -65,7 +65,7 @@ function TabsTrigger({ className, ...props }) {
65
65
  return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn(tabsTriggerVariants(), className), ...props }));
66
66
  }
67
67
  function TabsContent({ className, ...props }) {
68
- return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 text-sm", focusRing, className), ...props }));
68
+ return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 p-2 text-sm", focusRing, className), ...props }));
69
69
  }
70
70
  /**
71
71
  * Tab-styled link for `TabsNav`. Pass `active` from the route: the active
package/dist/toast.js CHANGED
@@ -5,7 +5,7 @@ import { CircleAlertIcon, CircleCheckIcon, InfoIcon, LoaderCircleIcon, XIcon, }
5
5
  import { useTheme } from "next-themes";
6
6
  import { Toaster as Sonner, toast as sonnerToast, } from "sonner";
7
7
  import { buttonVariants } from "./button.js";
8
- import { statusTone } from "./internal/styles.js";
8
+ import { focusRing, statusTone } from "./internal/styles.js";
9
9
  import { cn } from "./utils.js";
10
10
  /**
11
11
  * Copies only the kit's options, so plain JavaScript callers cannot hand the
@@ -45,27 +45,19 @@ const toast = Object.assign(show(sonnerToast), {
45
45
  * `error`, and `loading` each show a status icon. Tone colors the icon, title,
46
46
  * and border (`statusTone`, shared with Alert), not the body text.
47
47
  *
48
- * Close button: a circular badge centred on the top-right corner (`-top-2
49
- * -right-2`, half outside the border). Ghost / icon-xs still supplies the
50
- * shared focus ring, but Sonner's unlayered `[data-button]` default
51
- * (`background: var(--normal-text)`) and the dark `[data-close-button]` rule
52
- * (not gated on `data-styled`) would paint a foreground square. Important
53
- * popover fill, border, and muted text beat those fills. The 24px hit area
54
- * is the badge itself; the X is 12px. `overflow-visible` on the toast and
55
- * toaster keeps stacked badges from being clipped.
48
+ * Close button: a 24px square inside the top-right corner. Important kit
49
+ * utilities beat Sonner's unlayered close-button positioning and colors.
56
50
  *
57
- * Stacking: while the stack is collapsed, every part of the toasts behind
58
- * the front one is `invisible`. Their badges would otherwise peek out above
59
- * the front toast, closer together than the 24px target spacing, and their
60
- * buttons would stay in the tab order unseen. Hovering or focusing the stack
61
- * expands it (see `Toaster`), which shows them again.
51
+ * Stacking: notifications occupy normal flow in their mount area. Hidden
52
+ * and overflow notifications take no space or keyboard stops. Hovering or
53
+ * focusing the stack expands the visible notifications (see `Toaster`).
62
54
  *
63
- * Padding is asymmetric: `pl-4` matches Alert; `pr-6` clears the badge
64
- * (about 16px of it sits inside the border). Icon-to-text is `gap-x-2.5`
55
+ * Padding is asymmetric: `pl-4` matches Alert; `pr-10` clears the close
56
+ * button. Icon-to-text is `gap-x-2.5`
65
57
  * so the status icon and copy read as one unit.
66
58
  */
67
59
  const classNames = {
68
- toast: "relative flex w-max max-w-[min(356px,calc(100vw-2rem))] flex-wrap items-center justify-end gap-x-2.5 gap-y-2 overflow-visible rounded-none border border-border bg-popover py-3 pl-4 pr-6 text-sm text-popover-foreground shadow-md data-[expanded=false]:data-[front=false]:*:invisible",
60
+ toast: cn(focusRing, "!relative !transform-none flex w-max max-w-[min(356px,calc(100vw-2rem))] flex-wrap items-center justify-end gap-x-2.5 gap-y-2 overflow-visible rounded-none border border-border bg-popover py-3 pl-4 pr-10 text-sm text-popover-foreground shadow-md focus-visible:!shadow-[var(--tw-ring-offset-shadow),var(--tw-ring-shadow)] data-[visible=false]:hidden data-[expanded=false]:data-[front=false]:hidden"),
69
61
  // Fills the row beside the icon, so action buttons wrap to their own row
70
62
  // (aligned right by the toast's justify-end). Without an icon the basis
71
63
  // falls back to the title's width, so a one-word toast stays compact.
@@ -76,7 +68,7 @@ const classNames = {
76
68
  icon: "flex size-4 shrink-0 items-center justify-center [&_svg]:size-4",
77
69
  actionButton: cn(buttonVariants({ size: "xs" }), "shrink-0"),
78
70
  cancelButton: cn(buttonVariants({ variant: "outline", size: "xs" }), "shrink-0"),
79
- closeButton: cn(buttonVariants({ variant: "ghost", size: "icon-xs" }), "!absolute !-top-2 !-right-2 !left-auto !z-10 !size-6 !rounded-full !border !border-border !bg-popover !p-0 !text-muted-foreground !shadow-xs !translate-x-0 !translate-y-0 hover:!border-border hover:!bg-popover hover:!text-foreground focus-visible:!text-foreground active:!translate-y-0 [&_svg]:!size-3"),
71
+ closeButton: cn(buttonVariants({ variant: "ghost", size: "icon-xs" }), "!absolute !top-2 !right-2 !left-auto !z-10 !size-6 !rounded-none !border !border-border !bg-popover !p-0 !text-muted-foreground !translate-x-0 !translate-y-0 hover:!border-border hover:!bg-popover hover:!text-foreground focus-visible:!text-foreground active:!translate-y-0 [&_svg]:!size-3"),
80
72
  success: statusTone.success,
81
73
  info: statusTone.neutral,
82
74
  warning: statusTone.warning,
@@ -104,6 +96,6 @@ const Toaster = ({ position, closeButton = true, expand, visibleToasts, duration
104
96
  setFocusWithin(false);
105
97
  }
106
98
  };
107
- return (_jsx("div", { "data-slot": "toaster", className: "contents", onFocus: () => setFocusWithin(true), onBlur: onBlur, children: _jsx(Sonner, { theme: theme, className: "toaster group overflow-visible", position: position, closeButton: closeButton, expand: expand || focusWithin, visibleToasts: visibleToasts, duration: duration, icons: icons, toastOptions: { unstyled: true, classNames } }) }));
99
+ return (_jsx("div", { "data-slot": "toaster", className: "min-w-0", onFocus: () => setFocusWithin(true), onBlur: onBlur, children: _jsx(Sonner, { theme: theme, className: "toaster group !relative !inset-auto !transform-none flex !w-full flex-col gap-3 overflow-visible p-2 data-[x-position=right]:items-end data-[x-position=center]:items-center data-[y-position=bottom]:flex-col-reverse", style: { fontFamily: "var(--font-sans)", height: "auto" }, position: position, closeButton: closeButton, expand: expand || focusWithin, visibleToasts: visibleToasts, duration: duration, icons: icons, toastOptions: { unstyled: true, classNames } }) }));
108
100
  };
109
101
  export { Toaster, toast };
@@ -12,6 +12,6 @@ type ToggleGroupLayoutProps = {
12
12
  /** Lay items out in a row or a column. Default `horizontal`. */
13
13
  orientation?: "horizontal" | "vertical";
14
14
  };
15
- declare function ToggleGroup({ className, variant, size, spacing, orientation, children, ...props }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof toggleVariants> & ToggleGroupLayoutProps): React.JSX.Element;
15
+ declare function ToggleGroup({ className, variant, size, spacing, orientation, children, onKeyDown, onPointerDown, onFocus, ...props }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof toggleVariants> & ToggleGroupLayoutProps): React.JSX.Element;
16
16
  declare function ToggleGroupItem({ className, children, variant, size, ...props }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>): React.JSX.Element;
17
17
  export { ToggleGroup, ToggleGroupItem };
@@ -5,15 +5,49 @@ import {} from "class-variance-authority";
5
5
  import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
6
6
  import { cn } from "./utils.js";
7
7
  import { toggleVariants } from "./toggle.js";
8
+ /** The keys Radix's roving focus moves between items with. */
9
+ const arrowKeys = new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]);
10
+ function isItem(target) {
11
+ return (target instanceof HTMLElement && target.dataset.slot === "toggle-group-item");
12
+ }
8
13
  const ToggleGroupContext = React.createContext({
9
14
  size: "default",
10
15
  variant: "default",
11
16
  spacing: 2,
12
17
  orientation: "horizontal",
13
18
  });
14
- function ToggleGroup({ className, variant, size, spacing = 2, orientation = "horizontal", children, ...props }) {
19
+ function ToggleGroup({ className, variant, size, spacing = 2, orientation = "horizontal", children, onKeyDown, onPointerDown, onFocus, ...props }) {
20
+ // The item an arrow key was pressed on, until focus lands on the next one.
21
+ // A single group's items are radios, and focus that arrives by arrow key
22
+ // selects, as in any radio group; Tab and clicks do not.
23
+ const arrowFrom = React.useRef(null);
24
+ const selectsOnArrow = props.type === "single" && props.rovingFocus !== false;
15
25
  const context = React.useMemo(() => ({ variant, size, spacing, orientation }), [variant, size, spacing, orientation]);
16
- return (_jsx(ToggleGroupPrimitive.Root, { "data-slot": "toggle-group", "data-variant": variant, "data-size": size, "data-spacing": spacing, "data-orientation": orientation, style: { "--gap": spacing }, className: cn("group/toggle-group flex w-fit max-w-full flex-row flex-wrap items-center gap-[--spacing(var(--gap))] data-[spacing=0]:data-[variant=outline]:rounded-none data-vertical:flex-col data-vertical:flex-nowrap data-vertical:items-stretch", className), ...props, children: _jsx(ToggleGroupContext.Provider, { value: context, children: children }) }));
26
+ return (_jsx(ToggleGroupPrimitive.Root, { "data-slot": "toggle-group", "data-variant": variant, "data-size": size, "data-spacing": spacing, "data-orientation": orientation, onKeyDown: (event) => {
27
+ onKeyDown?.(event);
28
+ // Radix moves focus to the next item once the key has bubbled up.
29
+ if (selectsOnArrow &&
30
+ arrowKeys.has(event.key) &&
31
+ isItem(event.target) &&
32
+ !(event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)) {
33
+ arrowFrom.current = event.target;
34
+ }
35
+ }, onPointerDown: (event) => {
36
+ onPointerDown?.(event);
37
+ arrowFrom.current = null;
38
+ }, onFocus: (event) => {
39
+ onFocus?.(event);
40
+ const from = arrowFrom.current;
41
+ arrowFrom.current = null;
42
+ const item = event.target;
43
+ // Clicking runs the selection Radix already owns, onValueChange included.
44
+ if (from &&
45
+ from !== item &&
46
+ isItem(item) &&
47
+ item.getAttribute("aria-checked") !== "true") {
48
+ item.click();
49
+ }
50
+ }, style: { "--gap": spacing }, className: cn("group/toggle-group flex w-fit max-w-full flex-row flex-wrap items-center gap-[--spacing(var(--gap))] data-[spacing=0]:data-[variant=outline]:rounded-none data-vertical:flex-col data-vertical:flex-nowrap data-vertical:items-stretch", className), ...props, children: _jsx(ToggleGroupContext.Provider, { value: context, children: children }) }));
17
51
  }
18
52
  function ToggleGroupItem({ className, children, variant = "default", size = "default", ...props }) {
19
53
  const context = React.useContext(ToggleGroupContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@ Import each component from its subpath, e.g.
61
61
  | Checkbox, checkbox group, toggle group, combobox in a form | `FormCheckboxField`, `FormCheckboxGroupField`, `FormToggleGroupField`, `FormComboboxField` from `form/*` |
62
62
  | Custom control the kit has no form field for | `FormControlField` from `form/control-field` |
63
63
  | Form-level error (save failed) | `FormFeedbackField` from `form/feedback-field` |
64
- | Focus the first invalid field when a submit fails | `focusFirstInvalid` from `form/focus-first-invalid` |
64
+ | Focus the first invalid field when a submit fails | `focusFirstInvalid` from `form/focus-first-invalid`; fields without a `field` prop take `fieldName={field.name}` |
65
65
  | Content panels on one page | `Tabs` from `tabs` |
66
66
  | View switch that changes the URL | `TabsNav` and `TabsNavLink` from `tabs`; in Next, `next/tabs-nav-link` |
67
67
  | App shell nav (Calendar, Tasks, Settings) | `NavLink` from `nav-link`; in Next, `next/nav-link` |
package/theme.css CHANGED
@@ -227,6 +227,12 @@
227
227
  * without `!`. Size and flush still set the variable on `[data-slot=card]`.
228
228
  */
229
229
  @layer components {
230
+ @container pagination (max-width: 36rem) {
231
+ [data-slot="pagination-content"]:has([aria-current="page"])
232
+ > li:not(:first-child):not(:last-child):not(:has([aria-current="page"])) {
233
+ display: none;
234
+ }
235
+ }
230
236
  [data-slot="card"] {
231
237
  padding-block: var(--card-p);
232
238
  }