atlas-react-kit 1.2.2 → 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.
package/DESIGN_RULES.md CHANGED
@@ -117,11 +117,11 @@ still read normal words.
117
117
  ## Shape and composition
118
118
 
119
119
  Primitives have square corners. Reserve `rounded-full` for dots, avatars,
120
- other circles, and the toast close badge. Card and dialog titles already use
121
- uppercase, tracked `font-heading` styling. Toasts are overlay popovers with
122
- the same square corners and Alert's left padding. The close control is a
123
- circular badge centred on the top-right corner (half outside the border),
124
- not a control in the text row. A plain `toast()` has no icon — status icons
120
+ and other circles. Card and dialog titles already use
121
+ uppercase, tracked `font-heading` styling. Toasts use the same square corners
122
+ and Alert's left padding, with a square close button inside the top-right
123
+ corner. Mount the toaster in its own notification area; it occupies space
124
+ in the page so notifications do not cover content. A plain `toast()` has no icon — status icons
125
125
  belong to `success`, `info`, `warning`, `error`, and `loading`. Tone colors
126
126
  the icon, title, and border, not the body.
127
127
 
@@ -146,8 +146,8 @@ toasts keep their width.
146
146
 
147
147
  Parents do not force sizes on their children. A vertical `Field` stretches
148
148
  its controls through the flex column, so a control's own width class (such
149
- as `w-32`) wins; `Field`, `Alert`, and `Pagination` set no width of their
150
- own. `FieldGroup` keeps `w-full` because its inline-size container has no
149
+ as `w-32`) wins; `Field` and `Alert` set no width of their
150
+ own. `FieldGroup` and `Pagination` keep `w-full` because an inline-size container has no
151
151
  intrinsic width. Size classes that `className` should be able to replace are
152
152
  plain classes picked from props (`Avatar` `size`, `Separator` orientation,
153
153
  `SelectTrigger` `size`, tab orientation), never `data-*` variants, which sort
package/dist/badge.js CHANGED
@@ -21,7 +21,7 @@ const badgeVariants = cva([
21
21
  ghost: "text-muted-foreground hover:text-foreground",
22
22
  link: "text-foreground underline-offset-4 hover:underline",
23
23
  /** Tinted chip; pick its color with `tone`. */
24
- soft: "px-2 py-0.5 text-xs font-medium tracking-normal normal-case",
24
+ soft: "px-2 py-0.5",
25
25
  },
26
26
  /** Only `soft` reads this. */
27
27
  tone: {
package/dist/button.js CHANGED
@@ -21,7 +21,7 @@ const buttonVariants = cva([
21
21
  disabledState,
22
22
  "group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding",
23
23
  controlText,
24
- "transition-all select-none active:not-aria-[haspopup]:translate-y-px",
24
+ "transition-all select-none active:not-aria-[haspopup]:translate-y-px disabled:opacity-100 disabled:saturate-0",
25
25
  inlineIcon.default,
26
26
  ], {
27
27
  variants: {
package/dist/chart.js CHANGED
@@ -144,7 +144,7 @@ export function ChartYAxisGrid({ frame, yMax, tickCount = 4, format = compactNum
144
144
  const ticks = niceTicks(yMax, tickCount);
145
145
  const top = ticks[ticks.length - 1] ?? 1;
146
146
  const y = (value) => frame.bottom - (value / top) * frame.plotHeight;
147
- return (_jsx("g", { "data-slot": "chart-y-axis", children: ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: frame.left, y1: y(tick), x2: frame.right, y2: y(tick), strokeWidth: 1, className: "stroke-border" }), _jsx("text", { x: frame.left - 6, y: y(tick) + 3, textAnchor: "end", className: "fill-muted-foreground text-[10px] tabular-nums", children: format(tick) })] }, tick))) }));
147
+ return (_jsx("g", { "data-slot": "chart-y-axis", children: ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: frame.left, y1: y(tick), x2: frame.right, y2: y(tick), strokeWidth: 1, className: "stroke-input" }), _jsx("text", { x: frame.left - 6, y: y(tick) + 3, textAnchor: "end", className: "fill-muted-foreground text-xs tabular-nums", children: format(tick) })] }, tick))) }));
148
148
  }
149
149
  /**
150
150
  * Readout for the hovered or focused point, placed in percent of the chart
package/dist/field.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useMemo } from "react";
4
4
  import { cva } from "class-variance-authority";
5
- import { labelText } from "./internal/styles.js";
5
+ import { errorText, labelText } from "./internal/styles.js";
6
6
  import { cn } from "./utils.js";
7
7
  import { Label } from "./label.js";
8
8
  import { Separator } from "./separator.js";
@@ -77,6 +77,6 @@ function FieldError({ className, children, errors, ...props }) {
77
77
  if (!content) {
78
78
  return null;
79
79
  }
80
- return (_jsx("div", { role: "alert", "data-slot": "field-error", className: cn("text-sm font-normal text-destructive", className), ...props, children: content }));
80
+ return (_jsx("div", { role: "alert", "data-slot": "field-error", className: cn(errorText, className), ...props, children: content }));
81
81
  }
82
82
  export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle, };
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { errorText } from "../internal/styles.js";
2
3
  import { cn } from "../utils.js";
3
4
  /** Form-level submit or mutation error (not tied to a single field). */
4
5
  export function FormFeedbackField({ message, className, }) {
5
6
  if (!message)
6
7
  return null;
7
- return (_jsx("p", { "data-slot": "form-feedback", className: cn("text-sm text-destructive", className), role: "alert", children: message }));
8
+ return (_jsx("p", { "data-slot": "form-feedback", className: cn("border-t border-destructive pt-3", errorText, className), role: "alert", children: message }));
8
9
  }
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { errorText } from "../internal/styles.js";
2
3
  import { cn } from "../utils.js";
3
4
  /** First TanStack Form error message for a field, if any. */
4
5
  export function getFieldErrorMessage(message) {
@@ -10,5 +11,5 @@ export function FieldError({ id, message, text, className, }) {
10
11
  const resolved = text ?? getFieldErrorMessage(message);
11
12
  if (!resolved)
12
13
  return null;
13
- return (_jsx("p", { id: id, "data-slot": "form-field-error", className: cn("text-xs text-destructive", className), role: "alert", children: resolved }));
14
+ return (_jsx("p", { id: id, "data-slot": "form-field-error", className: cn(errorText, className), role: "alert", children: resolved }));
14
15
  }
@@ -2,4 +2,4 @@
2
2
  * Calendar cell size and padding, shared with LazyCalendar's placeholder
3
3
  * (here, not in calendar.tsx, so the placeholder skips Day Picker).
4
4
  */
5
- export declare const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(3)]";
5
+ export declare const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(2)]";
@@ -2,4 +2,4 @@
2
2
  * Calendar cell size and padding, shared with LazyCalendar's placeholder
3
3
  * (here, not in calendar.tsx, so the placeholder skips Day Picker).
4
4
  */
5
- export const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(3)]";
5
+ export const calendarSize = "[--cell-size:--spacing(8)] [--calendar-padding:--spacing(2)]";
@@ -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;
@@ -102,6 +102,8 @@ export declare const controlText = "text-xs font-semibold tracking-widest whites
102
102
  * DialogDescription, AlertDescription).
103
103
  */
104
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";
105
107
  /** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
106
108
  export declare const labelText = "text-xs font-semibold tracking-wide uppercase";
107
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. */
@@ -102,6 +102,8 @@ export const controlText = "text-xs font-semibold tracking-widest whitespace-now
102
102
  * DialogDescription, AlertDescription).
103
103
  */
104
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";
105
107
  /** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
106
108
  export const labelText = "text-xs font-semibold tracking-wide uppercase";
107
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 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 })] })) })] }));
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 });
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 };
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 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",
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
  }