atlas-react-kit 1.2.3 → 1.2.5

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.
package/DESIGN_RULES.md CHANGED
@@ -120,8 +120,8 @@ Primitives have square corners. Reserve `rounded-full` for dots, avatars,
120
120
  and other circles. Card and dialog titles already use
121
121
  uppercase, tracked `font-heading` styling. Toasts use the same square corners
122
122
  and Alert's left padding, with a square close button inside the top-right
123
- corner. Mount the toaster in its own notification area; it occupies space
124
- in the page so notifications do not cover content. A plain `toast()` has no icon — status icons
123
+ corner. The toaster floats over the page at its `position`, anchored to the
124
+ viewport; mount one near the app root. A plain `toast()` has no icon — status icons
125
125
  belong to `success`, `info`, `warning`, `error`, and `loading`. Tone colors
126
126
  the icon, title, and border, not the body.
127
127
 
package/dist/badge.js CHANGED
@@ -21,7 +21,7 @@ const badgeVariants = cva([
21
21
  ghost: "text-muted-foreground hover:text-foreground",
22
22
  link: "text-foreground underline-offset-4 hover:underline",
23
23
  /** Tinted chip; pick its color with `tone`. */
24
- soft: "px-2 py-0.5",
24
+ soft: "px-2 py-0.5 text-xs font-medium tracking-normal normal-case",
25
25
  },
26
26
  /** Only `soft` reads this. */
27
27
  tone: {
package/dist/button.js CHANGED
@@ -21,7 +21,7 @@ const buttonVariants = cva([
21
21
  disabledState,
22
22
  "group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding",
23
23
  controlText,
24
- "transition-all select-none active:not-aria-[haspopup]:translate-y-px disabled:opacity-100 disabled:saturate-0",
24
+ "transition-all select-none active:not-aria-[haspopup]:translate-y-px",
25
25
  inlineIcon.default,
26
26
  ], {
27
27
  variants: {
package/dist/combobox.js CHANGED
@@ -6,6 +6,7 @@ import { Button } from "./button.js";
6
6
  import { Input } from "./input.js";
7
7
  import { formControlTriggerClassName } from "./internal/form-control.js";
8
8
  import { handleListboxKey } from "./internal/listbox-navigation.js";
9
+ import { clamp } from "./internal/math.js";
9
10
  import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
10
11
  import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
11
12
  import { emptyValueText } from "./internal/styles.js";
@@ -53,7 +54,7 @@ function resolveCreateValue(query, existingValues, normalize) {
53
54
  export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText, disabled, id, className, listClassName, contentClassName, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, creatable = false, normalizeCreateValue = defaultNormalizeCreateValue, createLabel = defaultCreateLabel, ...props }) {
54
55
  const [open, setOpen] = React.useState(false);
55
56
  const [query, setQuery] = React.useState("");
56
- const [highlight, setHighlight] = React.useState(0);
57
+ const [highlightIndex, setHighlight] = React.useState(0);
57
58
  const listId = React.useId();
58
59
  const generatedId = React.useId();
59
60
  const triggerId = id ?? generatedId;
@@ -84,6 +85,7 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
84
85
  : null;
85
86
  }, [creatable, query, existingValues, normalizeCreateValue, createLabel]);
86
87
  const list = createOption ? [...filtered, createOption] : filtered;
88
+ const highlight = clamp(highlightIndex, 0, Math.max(0, list.length - 1));
87
89
  const onOpenChange = (next) => {
88
90
  setOpen(next);
89
91
  if (!next) {
@@ -10,9 +10,7 @@ type ErrorFallbackProps = {
10
10
  className?: string;
11
11
  };
12
12
  /**
13
- * Presentational recovery UI used by `ErrorBoundary`: a large `EmptyPanel`
14
- * with the message and recovery buttons. Exported so the markup can be
15
- * tested without throwing during render.
13
+ * Recovery UI used by `ErrorBoundary`: a large `EmptyPanel` with recovery buttons.
16
14
  */
17
15
  export declare function ErrorFallback({ message, onRetry, onReload, className, }: ErrorFallbackProps): import("react").JSX.Element;
18
16
  type ErrorBoundaryProps = {
@@ -24,15 +22,14 @@ type ErrorBoundaryProps = {
24
22
  onError?: (error: Error, info: ErrorInfo) => void;
25
23
  };
26
24
  type ErrorBoundaryState = {
27
- error: Error | null;
25
+ hasError: boolean;
28
26
  };
29
27
  /**
30
28
  * Generic React error boundary with retry (reset) and full-page reload.
31
- * Not tied to any data library.
32
29
  */
33
30
  export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
34
31
  state: ErrorBoundaryState;
35
- static getDerivedStateFromError(error: Error): ErrorBoundaryState;
32
+ static getDerivedStateFromError(): ErrorBoundaryState;
36
33
  componentDidCatch(error: Error, info: ErrorInfo): void;
37
34
  retry: () => void;
38
35
  reload: () => void;
@@ -5,9 +5,7 @@ import { Button } from "./button.js";
5
5
  import { EmptyPanel } from "./empty-panel.js";
6
6
  import { cn } from "./utils.js";
7
7
  /**
8
- * Presentational recovery UI used by `ErrorBoundary`: a large `EmptyPanel`
9
- * with the message and recovery buttons. Exported so the markup can be
10
- * tested without throwing during render.
8
+ * Recovery UI used by `ErrorBoundary`: a large `EmptyPanel` with recovery buttons.
11
9
  */
12
10
  export function ErrorFallback({ message = "Something went wrong.", onRetry, onReload, className, }) {
13
11
  return (_jsxs(EmptyPanel, { role: "alert", "data-slot": "error-boundary", size: "lg",
@@ -16,24 +14,23 @@ export function ErrorFallback({ message = "Something went wrong.", onRetry, onRe
16
14
  }
17
15
  /**
18
16
  * Generic React error boundary with retry (reset) and full-page reload.
19
- * Not tied to any data library.
20
17
  */
21
18
  export class ErrorBoundary extends Component {
22
- state = { error: null };
23
- static getDerivedStateFromError(error) {
24
- return { error };
19
+ state = { hasError: false };
20
+ static getDerivedStateFromError() {
21
+ return { hasError: true };
25
22
  }
26
23
  componentDidCatch(error, info) {
27
24
  this.props.onError?.(error, info);
28
25
  }
29
26
  retry = () => {
30
- this.setState({ error: null });
27
+ this.setState({ hasError: false });
31
28
  };
32
29
  reload = () => {
33
30
  window.location.reload();
34
31
  };
35
32
  render() {
36
- if (!this.state.error)
33
+ if (!this.state.hasError)
37
34
  return this.props.children;
38
35
  return (_jsx(ErrorFallback, { message: this.props.message, onRetry: this.retry, onReload: this.reload }));
39
36
  }
@@ -5,5 +5,5 @@ import { cn } from "../utils.js";
5
5
  export function FormFeedbackField({ message, className, }) {
6
6
  if (!message)
7
7
  return null;
8
- return (_jsx("p", { "data-slot": "form-feedback", className: cn("border-t border-destructive pt-3", errorText, className), role: "alert", children: message }));
8
+ return (_jsx("p", { "data-slot": "form-feedback", className: cn(errorText, className), role: "alert", children: message }));
9
9
  }
@@ -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: the selected wash plus a 2px inset ring bar. */
63
+ /** Highlight as focus moves: grey fill plus a 2px ring bar, unlike checked. */
64
64
  focus: string;
65
65
  /** The same highlight for lists that track the active option in state. */
66
66
  active: string;
@@ -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: 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)]",
63
+ /** Highlight as focus moves: grey fill plus a 2px ring bar, unlike checked. */
64
+ focus: "focus:bg-accent focus:text-accent-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-selected text-selected-foreground shadow-[inset_2px_0_0_var(--color-ring)]",
66
+ active: "bg-accent text-accent-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. */
@@ -5,3 +5,5 @@ import * as React from "react";
5
5
  * join the tab order only while it overflows (WCAG 2.1.1).
6
6
  */
7
7
  export declare function useHorizontalOverflow(ref: React.RefObject<HTMLElement | null>): boolean;
8
+ /** Scrolls a keyboard-focused `element` and its ring into `scroller`'s view. */
9
+ export declare function revealInline(scroller: HTMLElement, element: Element): void;
@@ -21,3 +21,17 @@ export function useHorizontalOverflow(ref) {
21
21
  }, [ref]);
22
22
  return overflowing;
23
23
  }
24
+ /** Scrolls a keyboard-focused `element` and its ring into `scroller`'s view. */
25
+ export function revealInline(scroller, element) {
26
+ const rect = element.getBoundingClientRect();
27
+ const focused = scroller.ownerDocument.activeElement;
28
+ if (!rect.width || !focused?.matches(":focus-visible"))
29
+ return;
30
+ const box = scroller.getBoundingClientRect();
31
+ const before = rect.left - 8 - box.left;
32
+ const after = rect.right + 8 - box.right;
33
+ if (before < 0)
34
+ scroller.scrollLeft += before;
35
+ else if (after > 0)
36
+ scroller.scrollLeft += Math.min(after, before);
37
+ }
package/dist/kanban.js CHANGED
@@ -7,7 +7,8 @@ import { CheckCircle2, ChevronLeft, ChevronRight, GripVertical, Trash2, } from "
7
7
  import { Button } from "./button.js";
8
8
  import { KANBAN_COMPLETE_ID, KANBAN_DELETE_ID, buildKanbanGroups, kanbanOverflowEdges, kanbanScrollBy, resolveKanbanDrop, } from "./internal/kanban-layout.js";
9
9
  import { configureCardSortablePlugins, configureKanbanPlugins, } from "./internal/kanban-plugins.js";
10
- import { focusRingWithin } from "./internal/styles.js";
10
+ import { focusRing } from "./internal/styles.js";
11
+ import { revealInline } from "./internal/use-horizontal-overflow.js";
11
12
  import { Skeleton } from "./skeleton.js";
12
13
  import { cn } from "./utils.js";
13
14
  /** Column droppables lose to cards, so a drop lands between cards. */
@@ -77,7 +78,11 @@ function KanbanBoard({ columns, items, getColumnId, onMove, onComplete, onDelete
77
78
  }, [cardClassName, itemById, renderCard]);
78
79
  // `min-w-0` lets a flex or grid parent shrink the board so the scroller,
79
80
  // 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 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
+ 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, onFocus: (event) => {
82
+ const card = event.target.closest('[data-slot="kanban-card"]');
83
+ if (card)
84
+ revealInline(event.currentTarget, card);
85
+ }, className: cn("flex min-w-0 gap-4 overflow-x-auto pb-2",
81
86
  // AutoScroller scrolls in small steps near the edge; mandatory
82
87
  // snap would pull each one back, so it is off mid-drag.
83
88
  "snap-x snap-mandatory scroll-px-2 has-data-dragging:snap-none", "data-loading:opacity-60", scrollerClassName), children: columns.map((column) => {
@@ -129,6 +134,7 @@ function KanbanColumnView({ column, count, emptyLabel, loading, className, child
129
134
  }
130
135
  const cardChrome = cn("flex border border-border bg-card text-card-foreground");
131
136
  const handleChrome = cn("flex shrink-0 items-center self-stretch border-r border-border", "bg-muted/30 px-1.5 text-muted-foreground");
137
+ const stopFocus = cn(focusRing, "relative focus-visible:z-10");
132
138
  function stopClick(event) {
133
139
  event.stopPropagation();
134
140
  }
@@ -145,9 +151,7 @@ function KanbanCardViewImpl({ item, index, column, onCardClick, className, rende
145
151
  });
146
152
  return (_jsxs("div", { ref: ref, "data-slot": "kanban-card", "data-dragging": isDragging || undefined, className: cn(cardChrome, "shadow-sm transition-[box-shadow,opacity,border-color]",
147
153
  // The overlay moves; the source keeps its slot.
148
- "data-dragging:pointer-events-none data-dragging:invisible",
149
- // One ring on the card for the grip and the body alike.
150
- focusRingWithin, onCardClick && "hover:border-foreground/15", className), children: [_jsx("button", { type: "button", ref: handleRef, "data-slot": "kanban-card-handle", "aria-label": "Drag to move", onClick: stopClick, className: cn(handleChrome, "cursor-grab outline-none hover:bg-muted/50 active:cursor-grabbing"), children: _jsx(GripVertical, { className: "size-4", "aria-hidden": true }) }), onCardClick ? (_jsx("button", { type: "button", "data-slot": "kanban-card-body", onClick: () => onCardClick(item), className: "min-w-0 flex-1 cursor-pointer p-3 text-left outline-none hover:bg-muted/20", children: children })) : (_jsx("div", { "data-slot": "kanban-card-body", className: "min-w-0 flex-1 p-3", children: children }))] }));
154
+ "data-dragging:pointer-events-none data-dragging:invisible", onCardClick && "hover:border-foreground/15", className), children: [_jsx("button", { type: "button", ref: handleRef, "data-slot": "kanban-card-handle", "aria-label": "Drag to move", onClick: stopClick, className: cn(handleChrome, stopFocus, "cursor-grab hover:bg-muted/50 active:cursor-grabbing"), children: _jsx(GripVertical, { className: "size-4", "aria-hidden": true }) }), onCardClick ? (_jsx("button", { type: "button", "data-slot": "kanban-card-body", onClick: () => onCardClick(item), className: cn("min-w-0 flex-1 cursor-pointer p-3 text-left hover:bg-muted/20", stopFocus), children: children })) : (_jsx("div", { "data-slot": "kanban-card-body", className: "min-w-0 flex-1 p-3", children: children }))] }));
151
155
  }
152
156
  /**
153
157
  * Memoized so a board update re-renders only the cards whose item, index or
@@ -185,7 +189,7 @@ function ActionZone({ id, label, regionLabel, icon: Icon, tone, visible, }) {
185
189
  }
186
190
  function ScrollButton({ side, onClick, }) {
187
191
  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 bottom-1 z-20", side === "start" ? "left-1" : "right-1"), children: _jsx(Icon, {}) }));
192
+ 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, {}) }));
189
193
  }
190
194
  function OverflowFade({ side, visible, }) {
191
195
  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/markdown.js CHANGED
@@ -35,7 +35,7 @@ const markdownComponents = {
35
35
  h3: ({ children }) => (_jsx("h3", { className: headingClass("text-base"), children: children })),
36
36
  p: ({ children }) => _jsx("p", { className: "text-sm leading-relaxed", children: children }),
37
37
  ul: ({ children }) => (_jsx("ul", { className: "list-disc space-y-1 pl-5 text-sm", children: children })),
38
- ol: ({ children }) => (_jsx("ol", { className: "list-decimal space-y-1 pl-5 text-sm", children: children })),
38
+ ol: ({ children, start }) => (_jsx("ol", { start: start, className: "list-decimal space-y-1 pl-5 text-sm", children: children })),
39
39
  li: ({ children }) => _jsx("li", { className: "leading-relaxed", children: children }),
40
40
  blockquote: ({ children }) => (_jsx("blockquote", { className: "border-l-2 border-input pl-3 text-sm text-muted-foreground", children: children })),
41
41
  a: ({ href, children }) => href ? (_jsx("a", { href: href, className: "text-selected-foreground underline underline-offset-2", ...(href.startsWith("http")
@@ -11,7 +11,7 @@ import { cn } from "./utils.js";
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("@container/pagination flex w-full min-w-0 p-1", className), ...props }));
14
+ return (_jsx("nav", { "aria-label": "Pagination", "data-slot": "pagination", className: cn("@container/pagination flex w-full min-w-0", className), ...props }));
15
15
  }
16
16
  function PaginationContent({ className, ...props }) {
17
17
  return (_jsx("ul", { "data-slot": "pagination-content", className: cn("flex flex-row flex-wrap items-center gap-1", className), ...props }));
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Input } from "./input.js";
5
5
  import { Label } from "./label.js";
6
6
  import { handleListboxKey } from "./internal/listbox-navigation.js";
7
+ import { clamp } from "./internal/math.js";
7
8
  import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
8
9
  import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
9
10
  import { cn, onInputChange } from "./utils.js";
@@ -42,10 +43,9 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
42
43
  return index >= 0 ? index : 0;
43
44
  }, [filtered, getValue, value]);
44
45
  const rows = React.useMemo(() => filtered.map((item) => ({ item, value: getValue(item) })), [filtered, getValue]);
45
- const [highlight, setHighlight] = React.useState(selectedIndex);
46
- // Reset the highlight while rendering, not in an effect, when the query or
47
- // the selected row changes: an effect commits every keystroke twice, once
48
- // with the stale highlight and again with the reset.
46
+ const [highlightIndex, setHighlight] = React.useState(selectedIndex);
47
+ const highlight = clamp(highlightIndex, 0, Math.max(0, filtered.length - 1));
48
+ // Reset during render so a query or selection change commits only once.
49
49
  const [highlightFor, setHighlightFor] = React.useState({
50
50
  query,
51
51
  selectedIndex,
package/dist/select.js CHANGED
@@ -32,7 +32,7 @@ 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 *:[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 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 })] }));
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/tabs.js CHANGED
@@ -7,7 +7,7 @@ import { activeLinkAttributes, activeLinkSelector, } from "./internal/active-lin
7
7
  import { focusRing } from "./internal/styles.js";
8
8
  import { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
9
9
  import { useDragScroll } from "./internal/use-drag-scroll.js";
10
- import { useHorizontalOverflow } from "./internal/use-horizontal-overflow.js";
10
+ import { revealInline, useHorizontalOverflow, } from "./internal/use-horizontal-overflow.js";
11
11
  import { cn } from "./utils.js";
12
12
  /**
13
13
  * The root's orientation, so the list can pick plain size classes (`h-7`)
@@ -48,7 +48,10 @@ function TabsScroller({ variant = "default", orientation = "horizontal", classNa
48
48
  behavior: "auto",
49
49
  });
50
50
  });
51
- return (_jsx("div", { ref: ref, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing, tabsScrollerVariants({ variant, orientation }), className), children: children }));
51
+ return (_jsx("div", { ref: ref, onFocus: (event) => {
52
+ if (event.target !== event.currentTarget)
53
+ revealInline(event.currentTarget, event.target);
54
+ }, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing, tabsScrollerVariants({ variant, orientation }), className), children: children }));
52
55
  }
53
56
  /**
54
57
  * The tab row inside its scroller. `className` styles the list (its `h-7`
@@ -65,7 +68,7 @@ function TabsTrigger({ className, ...props }) {
65
68
  return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn(tabsTriggerVariants(), className), ...props }));
66
69
  }
67
70
  function TabsContent({ className, ...props }) {
68
- return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 p-2 text-sm", focusRing, className), ...props }));
71
+ return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 text-sm", focusRing, className), ...props }));
69
72
  }
70
73
  /**
71
74
  * Tab-styled link for `TabsNav`. Pass `active` from the route: the active
package/dist/toast.js CHANGED
@@ -48,16 +48,15 @@ const toast = Object.assign(show(sonnerToast), {
48
48
  * Close button: a 24px square inside the top-right corner. Important kit
49
49
  * utilities beat Sonner's unlayered close-button positioning and colors.
50
50
  *
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`).
51
+ * Stacking: the toaster floats at `position`; collapsed back toasts are
52
+ * `invisible`. Hovering or focusing expands it (see `Toaster`).
54
53
  *
55
54
  * Padding is asymmetric: `pl-4` matches Alert; `pr-10` clears the close
56
55
  * button. Icon-to-text is `gap-x-2.5`
57
56
  * so the status icon and copy read as one unit.
58
57
  */
59
58
  const classNames = {
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"),
59
+ toast: cn(focusRing, "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-10 text-sm text-popover-foreground shadow-md focus-visible:!shadow-[var(--tw-ring-offset-shadow),var(--tw-ring-shadow)] data-[visible=false]:invisible data-[expanded=false]:data-[front=false]:*:invisible"),
61
60
  // Fills the row beside the icon, so action buttons wrap to their own row
62
61
  // (aligned right by the toast's justify-end). Without an icon the basis
63
62
  // falls back to the title's width, so a one-word toast stays compact.
@@ -96,6 +95,6 @@ const Toaster = ({ position, closeButton = true, expand, visibleToasts, duration
96
95
  setFocusWithin(false);
97
96
  }
98
97
  };
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 } }) }));
98
+ return (_jsx("div", { "data-slot": "toaster", className: "contents", onFocus: () => setFocusWithin(true), onBlur: onBlur, children: _jsx(Sonner, { theme: theme, className: "toaster group overflow-visible", style: { fontFamily: "var(--font-sans)" }, position: position, closeButton: closeButton, expand: expand || focusWithin, visibleToasts: visibleToasts, duration: duration, icons: icons, toastOptions: { unstyled: true, classNames } }) }));
100
99
  };
101
100
  export { Toaster, toast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/theme.css CHANGED
@@ -227,9 +227,302 @@
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"])) {
230
+ /*
231
+ * Pagination that no longer fits its container drops pages in two steps,
232
+ * sized for 36px page links, 38px icon-only Previous / Next and a 4px gap
233
+ * (40px per item; `n` items fit in n x 40px). The first and last <li> are
234
+ * Previous and Next.
235
+ * 1. A list with an ellipsis keeps Previous, the first and last pages, the
236
+ * current page, the ellipses and Next (plus a lone page between the
237
+ * first or last page and the current one), so every gap is still marked.
238
+ * 2. Narrower still, or for a list without an ellipsis, only Previous, the
239
+ * current page and Next stay. The list also wraps rather than spill.
240
+ */
241
+ @container pagination (width < 10rem) {
242
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
243
+ [data-slot="pagination-ellipsis"]
244
+ ):has(> li:nth-child(4)):not(:has(> li:nth-child(5)))
245
+ > li:not(
246
+ :first-child,
247
+ :last-child,
248
+ :nth-child(2),
249
+ :nth-last-child(2),
250
+ :has([aria-current="page"]),
251
+ :has([data-slot="pagination-ellipsis"]),
252
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
253
+ + li [aria-current="page"]
254
+ ),
255
+ :has([aria-current="page"])
256
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
257
+ ),
258
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
259
+ :has([data-slot="pagination-ellipsis"])
260
+ ):has(> li:nth-child(4)):not(:has(> li:nth-child(5)))
261
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
262
+ display: none;
263
+ }
264
+ }
265
+ @container pagination (width < 7.5rem) {
266
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
267
+ [data-slot="pagination-ellipsis"]
268
+ ):has(> li:nth-child(4)):not(:has(> li:nth-child(5)))
269
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
270
+ display: none;
271
+ }
272
+ }
273
+ @container pagination (width < 12.5rem) {
274
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
275
+ [data-slot="pagination-ellipsis"]
276
+ ):has(> li:nth-child(5)):not(:has(> li:nth-child(6)))
277
+ > li:not(
278
+ :first-child,
279
+ :last-child,
280
+ :nth-child(2),
281
+ :nth-last-child(2),
282
+ :has([aria-current="page"]),
283
+ :has([data-slot="pagination-ellipsis"]),
284
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
285
+ + li [aria-current="page"]
286
+ ),
287
+ :has([aria-current="page"])
288
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
289
+ ),
290
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
291
+ :has([data-slot="pagination-ellipsis"])
292
+ ):has(> li:nth-child(5)):not(:has(> li:nth-child(6)))
293
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
294
+ display: none;
295
+ }
296
+ }
297
+ @container pagination (width < 10rem) {
298
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
299
+ [data-slot="pagination-ellipsis"]
300
+ ):has(> li:nth-child(5)):not(:has(> li:nth-child(6)))
301
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
302
+ display: none;
303
+ }
304
+ }
305
+ @container pagination (width < 15rem) {
306
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
307
+ [data-slot="pagination-ellipsis"]
308
+ ):has(> li:nth-child(6)):not(:has(> li:nth-child(7)))
309
+ > li:not(
310
+ :first-child,
311
+ :last-child,
312
+ :nth-child(2),
313
+ :nth-last-child(2),
314
+ :has([aria-current="page"]),
315
+ :has([data-slot="pagination-ellipsis"]),
316
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
317
+ + li [aria-current="page"]
318
+ ),
319
+ :has([aria-current="page"])
320
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
321
+ ),
322
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
323
+ :has([data-slot="pagination-ellipsis"])
324
+ ):has(> li:nth-child(6)):not(:has(> li:nth-child(7)))
325
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
326
+ display: none;
327
+ }
328
+ }
329
+ @container pagination (width < 12.5rem) {
330
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
331
+ [data-slot="pagination-ellipsis"]
332
+ ):has(> li:nth-child(6)):not(:has(> li:nth-child(7)))
333
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
334
+ display: none;
335
+ }
336
+ }
337
+ @container pagination (width < 17.5rem) {
338
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
339
+ [data-slot="pagination-ellipsis"]
340
+ ):has(> li:nth-child(7)):not(:has(> li:nth-child(8)))
341
+ > li:not(
342
+ :first-child,
343
+ :last-child,
344
+ :nth-child(2),
345
+ :nth-last-child(2),
346
+ :has([aria-current="page"]),
347
+ :has([data-slot="pagination-ellipsis"]),
348
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
349
+ + li [aria-current="page"]
350
+ ),
351
+ :has([aria-current="page"])
352
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
353
+ ),
354
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
355
+ :has([data-slot="pagination-ellipsis"])
356
+ ):has(> li:nth-child(7)):not(:has(> li:nth-child(8)))
357
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
358
+ display: none;
359
+ }
360
+ }
361
+ @container pagination (width < 15rem) {
362
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
363
+ [data-slot="pagination-ellipsis"]
364
+ ):has(> li:nth-child(7)):not(:has(> li:nth-child(8)))
365
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
366
+ display: none;
367
+ }
368
+ }
369
+ @container pagination (width < 20rem) {
370
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
371
+ [data-slot="pagination-ellipsis"]
372
+ ):has(> li:nth-child(8)):not(:has(> li:nth-child(9)))
373
+ > li:not(
374
+ :first-child,
375
+ :last-child,
376
+ :nth-child(2),
377
+ :nth-last-child(2),
378
+ :has([aria-current="page"]),
379
+ :has([data-slot="pagination-ellipsis"]),
380
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
381
+ + li [aria-current="page"]
382
+ ),
383
+ :has([aria-current="page"])
384
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
385
+ ),
386
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
387
+ :has([data-slot="pagination-ellipsis"])
388
+ ):has(> li:nth-child(8)):not(:has(> li:nth-child(9)))
389
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
390
+ display: none;
391
+ }
392
+ }
393
+ @container pagination (width < 17.5rem) {
394
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
395
+ [data-slot="pagination-ellipsis"]
396
+ ):has(> li:nth-child(8)):not(:has(> li:nth-child(9)))
397
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
398
+ display: none;
399
+ }
400
+ }
401
+ @container pagination (width < 22.5rem) {
402
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
403
+ [data-slot="pagination-ellipsis"]
404
+ ):has(> li:nth-child(9)):not(:has(> li:nth-child(10)))
405
+ > li:not(
406
+ :first-child,
407
+ :last-child,
408
+ :nth-child(2),
409
+ :nth-last-child(2),
410
+ :has([aria-current="page"]),
411
+ :has([data-slot="pagination-ellipsis"]),
412
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
413
+ + li [aria-current="page"]
414
+ ),
415
+ :has([aria-current="page"])
416
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
417
+ ),
418
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
419
+ :has([data-slot="pagination-ellipsis"])
420
+ ):has(> li:nth-child(9)):not(:has(> li:nth-child(10)))
421
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
422
+ display: none;
423
+ }
424
+ }
425
+ @container pagination (width < 17.5rem) {
426
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
427
+ [data-slot="pagination-ellipsis"]
428
+ ):has(> li:nth-child(9)):not(:has(> li:nth-child(10)))
429
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
430
+ display: none;
431
+ }
432
+ }
433
+ @container pagination (width < 25rem) {
434
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
435
+ [data-slot="pagination-ellipsis"]
436
+ ):has(> li:nth-child(10)):not(:has(> li:nth-child(11)))
437
+ > li:not(
438
+ :first-child,
439
+ :last-child,
440
+ :nth-child(2),
441
+ :nth-last-child(2),
442
+ :has([aria-current="page"]),
443
+ :has([data-slot="pagination-ellipsis"]),
444
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
445
+ + li [aria-current="page"]
446
+ ),
447
+ :has([aria-current="page"])
448
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
449
+ ),
450
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
451
+ :has([data-slot="pagination-ellipsis"])
452
+ ):has(> li:nth-child(10)):not(:has(> li:nth-child(11)))
453
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
454
+ display: none;
455
+ }
456
+ }
457
+ @container pagination (width < 17.5rem) {
458
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
459
+ [data-slot="pagination-ellipsis"]
460
+ ):has(> li:nth-child(10)):not(:has(> li:nth-child(11)))
461
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
462
+ display: none;
463
+ }
464
+ }
465
+ @container pagination (width < 27.5rem) {
466
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
467
+ [data-slot="pagination-ellipsis"]
468
+ ):has(> li:nth-child(11)):not(:has(> li:nth-child(12)))
469
+ > li:not(
470
+ :first-child,
471
+ :last-child,
472
+ :nth-child(2),
473
+ :nth-last-child(2),
474
+ :has([aria-current="page"]),
475
+ :has([data-slot="pagination-ellipsis"]),
476
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
477
+ + li [aria-current="page"]
478
+ ),
479
+ :has([aria-current="page"])
480
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
481
+ ),
482
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
483
+ :has([data-slot="pagination-ellipsis"])
484
+ ):has(> li:nth-child(11)):not(:has(> li:nth-child(12)))
485
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
486
+ display: none;
487
+ }
488
+ }
489
+ @container pagination (width < 17.5rem) {
490
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
491
+ [data-slot="pagination-ellipsis"]
492
+ ):has(> li:nth-child(11)):not(:has(> li:nth-child(12)))
493
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
494
+ display: none;
495
+ }
496
+ }
497
+ @container pagination (width < 30rem) {
498
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
499
+ [data-slot="pagination-ellipsis"]
500
+ ):has(> li:nth-child(12))
501
+ > li:not(
502
+ :first-child,
503
+ :last-child,
504
+ :nth-child(2),
505
+ :nth-last-child(2),
506
+ :has([aria-current="page"]),
507
+ :has([data-slot="pagination-ellipsis"]),
508
+ :nth-child(3):not(:has([data-slot="pagination-ellipsis"]) + li):has(
509
+ + li [aria-current="page"]
510
+ ),
511
+ :has([aria-current="page"])
512
+ + :nth-last-child(3):not(:has(+ li [data-slot="pagination-ellipsis"]))
513
+ ),
514
+ [data-slot="pagination-content"]:has([aria-current="page"]):not(
515
+ :has([data-slot="pagination-ellipsis"])
516
+ ):has(> li:nth-child(12))
517
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
518
+ display: none;
519
+ }
520
+ }
521
+ @container pagination (width < 17.5rem) {
522
+ [data-slot="pagination-content"]:has([aria-current="page"]):has(
523
+ [data-slot="pagination-ellipsis"]
524
+ ):has(> li:nth-child(12))
525
+ > li:not(:first-child, :last-child, :has([aria-current="page"])) {
233
526
  display: none;
234
527
  }
235
528
  }