atlas-react-kit 1.1.0 → 1.2.1

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 (67) hide show
  1. package/COMPONENTS.md +22 -26
  2. package/DESIGN_RULES.md +14 -40
  3. package/README.md +7 -6
  4. package/dist/accordion.js +2 -2
  5. package/dist/alert.js +6 -5
  6. package/dist/badge.js +5 -2
  7. package/dist/breadcrumb.js +2 -2
  8. package/dist/button.js +7 -4
  9. package/dist/calendar.js +5 -4
  10. package/dist/combobox.js +7 -13
  11. package/dist/date-time-picker.js +2 -1
  12. package/dist/dropdown-menu.js +4 -4
  13. package/dist/field.d.ts +6 -3
  14. package/dist/field.js +3 -2
  15. package/dist/form/checkbox-field.d.ts +1 -1
  16. package/dist/form/checkbox-field.js +8 -11
  17. package/dist/form/combobox-field.js +2 -2
  18. package/dist/form/control-field.js +2 -7
  19. package/dist/form/date-picker-field.js +5 -2
  20. package/dist/form/date-time-picker-field.js +5 -2
  21. package/dist/form/field-error.d.ts +3 -3
  22. package/dist/form/field-shell.d.ts +36 -31
  23. package/dist/form/field-shell.js +41 -34
  24. package/dist/form/markdown-field.js +2 -2
  25. package/dist/form/select-field.js +2 -2
  26. package/dist/form/text-field.js +2 -4
  27. package/dist/form/toggle-group-field.js +2 -2
  28. package/dist/info-tip.d.ts +5 -4
  29. package/dist/info-tip.js +15 -14
  30. package/dist/internal/active-link.d.ts +7 -0
  31. package/dist/internal/active-link.js +9 -0
  32. package/dist/internal/calendar-size.d.ts +5 -0
  33. package/dist/internal/calendar-size.js +5 -0
  34. package/dist/internal/form-control.d.ts +1 -1
  35. package/dist/internal/form-control.js +2 -1
  36. package/dist/internal/listbox-navigation.d.ts +0 -5
  37. package/dist/internal/listbox-navigation.js +0 -7
  38. package/dist/internal/listbox-option.d.ts +16 -3
  39. package/dist/internal/listbox-option.js +22 -5
  40. package/dist/internal/overlay-styles.d.ts +10 -7
  41. package/dist/internal/overlay-styles.js +10 -7
  42. package/dist/internal/search.d.ts +9 -1
  43. package/dist/internal/search.js +14 -5
  44. package/dist/internal/styles.d.ts +43 -0
  45. package/dist/internal/styles.js +43 -0
  46. package/dist/internal/tabs-styles.d.ts +8 -7
  47. package/dist/internal/tabs-styles.js +15 -13
  48. package/dist/internal/use-media-query.d.ts +5 -0
  49. package/dist/internal/use-media-query.js +5 -0
  50. package/dist/kanban.d.ts +1 -1
  51. package/dist/kanban.js +6 -4
  52. package/dist/label.js +2 -1
  53. package/dist/lazy-calendar.js +3 -2
  54. package/dist/nav-link.js +2 -1
  55. package/dist/popover.js +4 -3
  56. package/dist/search-picker.d.ts +1 -1
  57. package/dist/search-picker.js +7 -12
  58. package/dist/select.js +6 -6
  59. package/dist/skip-link.js +2 -1
  60. package/dist/tabs.d.ts +3 -3
  61. package/dist/tabs.js +7 -6
  62. package/dist/toast.js +11 -10
  63. package/dist/toggle.d.ts +2 -0
  64. package/dist/toggle.js +8 -3
  65. package/dist/tooltip.js +2 -2
  66. package/package.json +1 -1
  67. package/skills/atlas-react-kit/SKILL.md +46 -42
package/dist/nav-link.js CHANGED
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { Slot } from "radix-ui";
5
+ import { activeLinkAttributes } from "./internal/active-link.js";
5
6
  import { focusRing, selectedState } from "./internal/styles.js";
6
7
  import { cn } from "./utils.js";
7
8
  /**
@@ -36,6 +37,6 @@ function isNavActive(pathname, href) {
36
37
  */
37
38
  function NavLink({ asChild, active, className, ...props }) {
38
39
  const Comp = asChild ? Slot.Root : "a";
39
- return (_jsx(Comp, { "data-slot": "nav-link", "data-state": active ? "active" : "inactive", "aria-current": active ? "page" : undefined, className: cn(navLinkVariants(), className), ...props }));
40
+ return (_jsx(Comp, { "data-slot": "nav-link", ...activeLinkAttributes(active), className: cn(navLinkVariants(), className), ...props }));
40
41
  }
41
42
  export { NavLink, isNavActive, navLinkVariants };
package/dist/popover.js CHANGED
@@ -2,7 +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 { overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
5
+ import { OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
6
+ import { labelText } from "./internal/styles.js";
6
7
  import { cn } from "./utils.js";
7
8
  function Popover({ ...props }) {
8
9
  return _jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
@@ -10,7 +11,7 @@ function Popover({ ...props }) {
10
11
  function PopoverTrigger({ ...props }) {
11
12
  return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
12
13
  }
13
- function PopoverContent({ className, align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
14
+ function PopoverContent({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
14
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
16
  }
16
17
  function PopoverAnchor({ ...props }) {
@@ -20,7 +21,7 @@ function PopoverHeader({ className, ...props }) {
20
21
  return (_jsx("div", { "data-slot": "popover-header", className: cn("flex flex-col gap-1 text-sm", className), ...props }));
21
22
  }
22
23
  function PopoverTitle({ className, ...props }) {
23
- return (_jsx("h2", { "data-slot": "popover-title", className: cn("text-xs font-semibold uppercase", className), ...props }));
24
+ return (_jsx("h2", { "data-slot": "popover-title", className: cn(labelText, className), ...props }));
24
25
  }
25
26
  function PopoverDescription({ className, ...props }) {
26
27
  return (_jsx("p", { "data-slot": "popover-description", className: cn("mt-0.5 text-sm leading-relaxed text-muted-foreground", className), ...props }));
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { foldSearchText } from "./internal/listbox-navigation.js";
2
+ import { foldSearchText } from "./internal/search.js";
3
3
  export { foldSearchText };
4
4
  /**
5
5
  * Default SearchPicker filter: the folded label includes the folded query.
@@ -3,9 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Input } from "./input.js";
5
5
  import { Label } from "./label.js";
6
- import { foldSearchText, handleListboxKey, listboxOptionId, } from "./internal/listbox-navigation.js";
7
- import { ListboxEmptyStatus, PickerOption } from "./internal/listbox-option.js";
8
- import { createSearchIndex, filterSearchIndex, matchesSearchLabel, normalizeSearchQuery, } from "./internal/search.js";
6
+ import { handleListboxKey } from "./internal/listbox-navigation.js";
7
+ import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
8
+ import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
9
9
  import { cn } from "./utils.js";
10
10
  export { foldSearchText };
11
11
  /**
@@ -15,7 +15,7 @@ export function defaultSearchFilter(item, query, getLabel) {
15
15
  const needle = normalizeSearchQuery(query);
16
16
  if (!needle)
17
17
  return true;
18
- return matchesSearchLabel(foldSearchText(getLabel(item)), needle);
18
+ return foldSearchText(getLabel(item)).includes(needle);
19
19
  }
20
20
  /** Frame shared by the listbox and the empty state that replaces it. */
21
21
  const listFrame = "border border-input bg-background";
@@ -24,7 +24,7 @@ const listFrame = "border border-input bg-background";
24
24
  * the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
25
25
  * Enter, Escape.
26
26
  */
27
- function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, listClassName, ...props }) {
27
+ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText, listLabel, disabled, className, listClassName, ...props }) {
28
28
  const [query, setQuery] = React.useState("");
29
29
  const listId = React.useId();
30
30
  const inputId = React.useId();
@@ -33,11 +33,8 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
33
33
  : null, [filter, getLabel, items]);
34
34
  const foldedQuery = filter === defaultSearchFilter ? normalizeSearchQuery(query) : "";
35
35
  const filtered = React.useMemo(() => {
36
- if (normalizedItems) {
37
- if (!foldedQuery)
38
- return items;
36
+ if (normalizedItems)
39
37
  return filterSearchIndex(normalizedItems, foldedQuery);
40
- }
41
38
  return items.filter((item) => filter(item, query, getLabel));
42
39
  }, [items, filter, foldedQuery, getLabel, normalizedItems, query]);
43
40
  const selectedIndex = React.useMemo(() => {
@@ -84,8 +81,6 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
84
81
  }
85
82
  event.currentTarget.blur();
86
83
  };
87
- 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, "aria-autocomplete": "list", "aria-activedescendant": active ? listboxOptionId(listId, highlight) : undefined, value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? label, className: empty
88
- ? "sr-only"
89
- : cn("max-h-60 overflow-y-auto", 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 })] }));
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 })] }));
90
85
  }
91
86
  export { SearchPicker };
package/dist/select.js CHANGED
@@ -2,8 +2,8 @@
2
2
  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
- import { dataDisabledState, disabledState, fieldSurface, focusRing, invalidState, optionState, } from "./internal/styles.js";
6
- import { menuLabel, optionIcon, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
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";
7
7
  import { cn } from "./utils.js";
8
8
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
9
9
  /**
@@ -11,7 +11,7 @@ import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
11
11
  * replaces them (a `data-[size=*]:` variant would outrank it).
12
12
  */
13
13
  const triggerHeight = { default: "h-10", sm: "h-9" };
14
- const scrollButtonClassName = "z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5";
14
+ const scrollButtonClassName = cn("z-10 flex cursor-default items-center justify-center bg-popover py-1", inlineIcon.default);
15
15
  function Select({ ...props }) {
16
16
  return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
17
17
  }
@@ -22,9 +22,9 @@ 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, optionIcon, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap data-placeholder:text-muted-foreground *: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, 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
- function SelectContent({ className, children, position = "popper", align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
27
+ function SelectContent({ className, children, position = "popper", align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
28
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" &&
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
  }
@@ -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, optionIcon, "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 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/skip-link.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { focusRing } from "./internal/styles.js";
2
3
  import { cn } from "./utils.js";
3
4
  /**
4
5
  * First focusable link on the page: hidden until a keyboard user tabs to
5
6
  * it, then it jumps past the navigation to `href` (the main landmark's id).
6
7
  */
7
8
  function SkipLink({ href = "#main-content", className, children = "Skip to content", ...props }) {
8
- return (_jsx("a", { "data-slot": "skip-link", href: href, className: cn("sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-background focus:px-3 focus:py-2 focus:text-sm focus:ring-2 focus:ring-ring", className), ...props, children: children }));
9
+ return (_jsx("a", { "data-slot": "skip-link", href: href, className: cn("sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-background focus:px-3 focus:py-2 focus:text-sm", focusRing, className), ...props, children: children }));
9
10
  }
10
11
  export { SkipLink };
package/dist/tabs.d.ts CHANGED
@@ -5,14 +5,14 @@ import type { AsChildProps } from "./internal/as-child.js";
5
5
  import { tabsListVariants, tabsTriggerVariants } from "./internal/tabs-styles.js";
6
6
  /** Props `TabsList` and `TabsNav` share for the scroller around their row. */
7
7
  type TabsScrollerClassNameProps = {
8
- /** Classes for the scroller around the tab row (`h-13` with `p-1` by default). */
8
+ /** Classes for the scroller around the tab row (`h-9` with `p-1` by default). */
9
9
  scrollerClassName?: string;
10
10
  };
11
11
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
12
12
  /**
13
- * The tab row inside its scroller. `className` styles the list (its `h-11`
13
+ * The tab row inside its scroller. `className` styles the list (its `h-7`
14
14
  * height, fill, gap); `scrollerClassName` styles the scroller around it
15
- * (`h-13` with `p-1` for the active border and focus ring).
15
+ * (`h-9` with `p-1` for the active border and focus ring).
16
16
  */
17
17
  declare function TabsList({ className, scrollerClassName, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & Omit<VariantProps<typeof tabsListVariants>, "orientation"> & TabsScrollerClassNameProps): React.JSX.Element;
18
18
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
package/dist/tabs.js CHANGED
@@ -3,13 +3,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import {} from "class-variance-authority";
5
5
  import { Slot, Tabs as TabsPrimitive } from "radix-ui";
6
+ import { activeLinkAttributes, activeLinkSelector, } from "./internal/active-link.js";
6
7
  import { focusRing } from "./internal/styles.js";
7
8
  import { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
8
9
  import { useDragScroll } from "./internal/use-drag-scroll.js";
9
10
  import { useHorizontalOverflow } from "./internal/use-horizontal-overflow.js";
10
11
  import { cn } from "./utils.js";
11
12
  /**
12
- * The root's orientation, so the list can pick plain size classes (`h-11`)
13
+ * The root's orientation, so the list can pick plain size classes (`h-7`)
13
14
  * that a consumer `className` replaces, instead of `group-data-*` variants
14
15
  * that would beat it.
15
16
  */
@@ -21,7 +22,7 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
21
22
  * Horizontal scroller around a tab row. `overflow-x: auto` alone would
22
23
  * compute `overflow-y` to `auto` as well (CSS overflow pairing), so the
23
24
  * active tab's border and focus ring grow a vertical scrollbar. Pad for
24
- * those strokes *inside* the box (`h-13` = `h-11` list + `p-1`), clip the
25
+ * those strokes *inside* the box (`h-9` = `h-7` list + `p-1`), clip the
25
26
  * y axis as a backstop, and fade the edges while the row overflows —
26
27
  * same cue as `Table`. While it overflows, a mouse can also drag the row
27
28
  * sideways (`useDragScroll`); touch, wheel and keyboard scroll as before.
@@ -37,7 +38,7 @@ function TabsScroller({ variant = "default", orientation = "horizontal", classNa
37
38
  const scroller = ref.current;
38
39
  if (!scroller)
39
40
  return;
40
- const active = scroller.querySelector('[aria-current="page"], [data-state="active"]');
41
+ const active = scroller.querySelector(activeLinkSelector);
41
42
  if (!active || active === revealed.current)
42
43
  return;
43
44
  revealed.current = active;
@@ -50,9 +51,9 @@ function TabsScroller({ variant = "default", orientation = "horizontal", classNa
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
52
  }
52
53
  /**
53
- * The tab row inside its scroller. `className` styles the list (its `h-11`
54
+ * The tab row inside its scroller. `className` styles the list (its `h-7`
54
55
  * height, fill, gap); `scrollerClassName` styles the scroller around it
55
- * (`h-13` with `p-1` for the active border and focus ring).
56
+ * (`h-9` with `p-1` for the active border and focus ring).
56
57
  */
57
58
  function TabsList({ className, scrollerClassName, variant = "default", ...props }) {
58
59
  const orientation = React.useContext(TabsOrientationContext);
@@ -74,7 +75,7 @@ function TabsContent({ className, ...props }) {
74
75
  */
75
76
  function TabsNavLink({ asChild, active, className, ...props }) {
76
77
  const Comp = asChild ? Slot.Root : "a";
77
- return (_jsx(Comp, { "data-slot": "tabs-nav-link", "data-state": active ? "active" : "inactive", "aria-current": active ? "page" : undefined, className: cn(tabsTriggerVariants(), className), ...props }));
78
+ return (_jsx(Comp, { "data-slot": "tabs-nav-link", ...activeLinkAttributes(active), className: cn(tabsTriggerVariants(), className), ...props }));
78
79
  }
79
80
  /**
80
81
  * Tab-styled links for a view switcher that changes the URL. Use this, not
package/dist/toast.js CHANGED
@@ -5,6 +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
9
  import { cn } from "./utils.js";
9
10
  /**
10
11
  * Copies only the kit's options, so plain JavaScript callers cannot hand the
@@ -42,7 +43,7 @@ const toast = Object.assign(show(sonnerToast), {
42
43
  *
43
44
  * Icon policy: a plain `toast()` has no icon. `success`, `info`, `warning`,
44
45
  * `error`, and `loading` each show a status icon. Tone colors the icon, title,
45
- * and border (same approach as Alert), not the body text.
46
+ * and border (`statusTone`, shared with Alert), not the body text.
46
47
  *
47
48
  * Close button: a circular badge centred on the top-right corner (`-top-2
48
49
  * -right-2`, half outside the border). Ghost / icon-xs still supplies the
@@ -70,22 +71,22 @@ const classNames = {
70
71
  // falls back to the title's width, so a one-word toast stays compact.
71
72
  // 1.625rem = 16px icon + 10px gap-x-2.5.
72
73
  content: "flex min-w-0 flex-1 basis-full flex-col gap-1 [[data-icon]+&]:basis-[calc(100%-1.625rem)]",
73
- title: "font-medium leading-5",
74
+ title: "font-medium leading-5 text-(--status-tone)",
74
75
  description: "leading-5 text-muted-foreground",
75
76
  icon: "flex size-4 shrink-0 items-center justify-center [&_svg]:size-4",
76
77
  actionButton: cn(buttonVariants({ size: "xs" }), "shrink-0"),
77
78
  cancelButton: cn(buttonVariants({ variant: "outline", size: "xs" }), "shrink-0"),
78
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"),
79
- success: "border-success/40 [&_[data-title]]:text-success",
80
- info: "[&_[data-title]]:text-foreground",
81
- warning: "border-warning/40 [&_[data-title]]:text-warning",
82
- error: "border-destructive/40 [&_[data-title]]:text-destructive",
80
+ success: statusTone.success,
81
+ info: statusTone.neutral,
82
+ warning: statusTone.warning,
83
+ error: statusTone.destructive,
83
84
  };
84
85
  const icons = {
85
- success: _jsx(CircleCheckIcon, { "aria-hidden": true, className: "text-success" }),
86
- info: _jsx(InfoIcon, { "aria-hidden": true, className: "text-foreground" }),
87
- warning: _jsx(CircleAlertIcon, { "aria-hidden": true, className: "text-warning" }),
88
- error: _jsx(CircleAlertIcon, { "aria-hidden": true, className: "text-destructive" }),
86
+ success: _jsx(CircleCheckIcon, { "aria-hidden": true, className: "text-(--status-tone)" }),
87
+ info: _jsx(InfoIcon, { "aria-hidden": true, className: "text-(--status-tone)" }),
88
+ warning: _jsx(CircleAlertIcon, { "aria-hidden": true, className: "text-(--status-tone)" }),
89
+ error: _jsx(CircleAlertIcon, { "aria-hidden": true, className: "text-(--status-tone)" }),
89
90
  loading: (_jsx(LoaderCircleIcon, { "aria-hidden": true, className: "animate-spin text-muted-foreground" })),
90
91
  close: _jsx(XIcon, { "aria-hidden": true, className: "size-3" }),
91
92
  };
package/dist/toggle.d.ts CHANGED
@@ -5,6 +5,8 @@ import { Toggle as TogglePrimitive } from "radix-ui";
5
5
  * Selected chrome shared by Toggle and ToggleGroupItem (see
6
6
  * `selectedState.toggle`): the wash, selected text, and a selected border
7
7
  * for `data-state=on`, `aria-pressed`, and `aria-checked`.
8
+ *
9
+ * @deprecated `toggleVariants()` already includes this chrome. Removed in 2.0.
8
10
  */
9
11
  declare const selectedToggleChrome: string;
10
12
  declare const toggleVariants: (props?: ({
package/dist/toggle.js CHANGED
@@ -3,21 +3,26 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { Toggle as TogglePrimitive } from "radix-ui";
6
- import { controlSize, disabledState, focusRing, invalidState, selectedState, } from "./internal/styles.js";
6
+ import { controlSize, controlText, disabledState, focusRing, inlineIcon, invalidState, selectedState, } from "./internal/styles.js";
7
7
  import { cn } from "./utils.js";
8
8
  /**
9
9
  * Selected chrome shared by Toggle and ToggleGroupItem (see
10
10
  * `selectedState.toggle`): the wash, selected text, and a selected border
11
11
  * for `data-state=on`, `aria-pressed`, and `aria-checked`.
12
+ *
13
+ * @deprecated `toggleVariants()` already includes this chrome. Removed in 2.0.
12
14
  */
13
15
  const selectedToggleChrome = selectedState.toggle;
14
16
  const toggleVariants = cva([
15
17
  focusRing,
16
18
  selectedState.hover,
17
- selectedToggleChrome,
19
+ selectedState.toggle,
18
20
  invalidState,
19
21
  disabledState,
20
- "group/toggle inline-flex items-center justify-center gap-1.5 rounded-none text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-colors focus-visible:text-selected-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
22
+ "group/toggle inline-flex items-center justify-center gap-1.5 rounded-none",
23
+ controlText,
24
+ "transition-colors focus-visible:text-selected-foreground",
25
+ inlineIcon.default,
21
26
  ], {
22
27
  variants: {
23
28
  variant: {
package/dist/tooltip.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Tooltip as TooltipPrimitive } from "radix-ui";
5
- import { overlaySideSlide } from "./internal/overlay-styles.js";
5
+ import { OVERLAY_SIDE_OFFSET, overlaySideSlide, } from "./internal/overlay-styles.js";
6
6
  import { cn } from "./utils.js";
7
7
  /** True inside a TooltipProvider, so `Tooltip` does not add its own. */
8
8
  const HasTooltipProvider = React.createContext(false);
@@ -28,7 +28,7 @@ function Tooltip({ ...props }) {
28
28
  function TooltipTrigger({ ...props }) {
29
29
  return _jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
30
30
  }
31
- function TooltipContent({ className, arrowClassName, sideOffset = 4, children, ...props }) {
31
+ function TooltipContent({ className, arrowClassName, sideOffset = OVERLAY_SIDE_OFFSET, children, ...props }) {
32
32
  return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn(overlaySideSlide,
33
33
  // Radix tooltips open as "delayed-open" or "instant-open", which
34
34
  // the shared data-open fade does not match.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,23 +28,21 @@ than guessing.
28
28
 
29
29
  ## 2. Check the project setup once
30
30
 
31
- Components render unstyled unless the app's global stylesheet imports the theme
32
- and tells Tailwind to scan the kit's compiled files:
31
+ Components render unstyled unless the app's global stylesheet imports the
32
+ theme and tells Tailwind to scan the kit's compiled files. Compare the app
33
+ with the setup in the package's README (the stylesheet lines and dark mode):
33
34
 
34
- ```css
35
- @import "tailwindcss";
36
- @import "atlas-react-kit/theme.css";
37
- @source "../node_modules/atlas-react-kit/dist"; /* relative to this CSS file */
35
+ ```sh
36
+ cat "$KIT/README.md"
38
37
  ```
39
38
 
40
- Dark mode needs `ThemeProvider` from `atlas-react-kit/theme-provider` near the
41
- root (or a `dark` class on an ancestor). Toasts need one `<Toaster />` from
42
- `atlas-react-kit/toast`; show them with `toast()` from the same module, using
43
- only the options it declares. Never import a toast library such as `sonner`
44
- directly: the kit owns the toast API, and a second copy never reaches its
45
- toaster. Some entry points need optional peers; install the extras from the
46
- "Optional peer dependencies" table in `COMPONENTS.md` when you import them.
47
- Fix missing setup rather than working around unstyled components.
39
+ Toasts need one `<Toaster />` from `atlas-react-kit/toast`; show them with
40
+ `toast()` from the same module, using only the options it declares. Never
41
+ import a toast library such as `sonner` directly: the kit owns the toast API,
42
+ and a second copy never reaches its toaster. Some entry points need optional
43
+ peers; install the extras from the "Optional peer dependencies" table in
44
+ `COMPONENTS.md` when you import them. Fix missing setup rather than working
45
+ around unstyled components.
48
46
 
49
47
  ## 3. Pick the kit component for the job
50
48
 
@@ -54,48 +52,54 @@ Import each component from its subpath, e.g.
54
52
 
55
53
  | Need | Use |
56
54
  | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
57
- | Short fixed list (a handful of options) | `Select` family from `select` |
58
- | Long or searchable list (timezones, currencies) | `Combobox` from `combobox` with `{ value, label }[]` options; set `creatable` to invent a value the catalog lacks |
55
+ | Short fixed list (a handful of options) | `Select` from `select` |
56
+ | Long or searchable list (timezones, currencies) | `Combobox` from `combobox` |
59
57
  | Always-visible searchable list (places) | `SearchPicker` from `search-picker` |
60
- | One date, or a from/to range | `DatePickerButton` from `date-picker` (`mode="range"` for ranges) |
58
+ | One date, or a from/to range | `DatePickerButton` from `date-picker` |
61
59
  | Date and time (not `datetime-local`) | `DateTimePickerButton` from `date-time-picker` |
62
60
  | Labelled field with error text | `FormTextField`, `FormSelectField`, `FormDatePickerField`, `FormDateTimePickerField`, `FormMarkdownField` from `form/*` |
63
61
  | Checkbox, checkbox group, toggle group, combobox in a form | `FormCheckboxField`, `FormCheckboxGroupField`, `FormToggleGroupField`, `FormComboboxField` from `form/*` |
64
62
  | Custom control the kit has no form field for | `FormControlField` from `form/control-field` |
65
63
  | Form-level error (save failed) | `FormFeedbackField` from `form/feedback-field` |
66
- | Content panels on one page | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` from `tabs` |
67
- | View switch that changes the URL | `TabsNav` with `TabsNavLink` from `tabs` (`asChild` for a router link); Next: `next/tabs-nav-link` |
68
- | App shell nav (Calendar, Tasks, Settings) | `NavLink` from `nav-link` (`asChild` for a router link); Next: `next/nav-link`; `active={isNavActive(pathname, href)}` |
69
- | Focus the first invalid field when a submit fails | `focusFirstInvalid(formId)` from `form/focus-first-invalid`; give fields `id={field.name}` |
64
+ | Focus the first invalid field when a submit fails | `focusFirstInvalid` from `form/focus-first-invalid` |
65
+ | Content panels on one page | `Tabs` from `tabs` |
66
+ | View switch that changes the URL | `TabsNav` and `TabsNavLink` from `tabs`; in Next, `next/tabs-nav-link` |
67
+ | App shell nav (Calendar, Tasks, Settings) | `NavLink` from `nav-link`; in Next, `next/nav-link` |
70
68
  | Skip-to-content link | `SkipLink` from `skip-link` |
71
69
  | Page title row with actions | `PageHeader` from `page-header` |
72
- | Previous / today / next period | `PeriodStepper` from `period-stepper`; `next/period-stepper` for URL periods |
73
- | Cards in columns that drag | `KanbanBoard` from `kanban`; `KanbanSkeleton` while loading |
74
- | SVG chart axes, grid, tooltip, legend | `chart` helpers (`chartFrame`, `ChartYAxisGrid`, `ChartTooltip`, `ChartLegend`) |
75
- | Status chip (paid, pending, failed) | `Badge variant="soft"` with `tone="success"`, `"warning"`, `"destructive"`, or `"neutral"` |
76
- | Data table | `Table` family from `table`; `SortableTableHead`; `TableBodySkeleton` while loading |
70
+ | Previous / today / next period | `PeriodStepper` from `period-stepper`; for URL periods in Next, `next/period-stepper` |
71
+ | Cards in columns that drag | `KanbanBoard` from `kanban` |
72
+ | SVG chart axes, grid, tooltip, legend | `chartFrame` and the other helpers from `chart` |
73
+ | Status chip (paid, pending, failed) | `Badge` from `badge` |
74
+ | Data table | `Table` from `table` |
75
+ | Sortable column header | `SortableTableHead` from `sortable-table-head` |
76
+ | Table body while loading | `TableBodySkeleton` from `table-body-skeleton` |
77
77
  | Nothing to show yet | `EmptyPanel` from `empty-panel` |
78
- | Grouped content | `Card` family from `card`; `<Card flush>` for an edge-to-edge table or list |
79
- | Modal | `Dialog` family from `dialog` |
80
- | Label for an icon button, or a short hint | `Tooltip` from `tooltip`; keep `aria-label` on icon buttons |
81
- | Longer help from a glyph | `InfoTip` / `InlineTip` from `info-tip` (not `Tooltip`) |
78
+ | Grouped content | `Card` from `card` |
79
+ | Modal | `Dialog` from `dialog` |
80
+ | Label for an icon button, or a short hint | `Tooltip` from `tooltip` |
81
+ | Longer help from a glyph | `InfoTip` from `info-tip` |
82
82
  | Color scale key | `ColorLegend` from `color-legend` |
83
- | GitHub-flavoured Markdown | `Markdown` from `markdown`; write/preview with `FormMarkdownField` |
84
- | Summary figure in a strip | `StatTile` from `stat-tile` (`statTileGrid`, `sectionLabel`) |
85
- | Value against a scale, optional ticks | `Meter` from `meter` (not `Progress`) |
83
+ | GitHub-flavoured Markdown | `Markdown` from `markdown` |
84
+ | Summary figure in a strip | `StatTile` from `stat-tile` |
85
+ | Value against a scale, optional ticks | `Meter` from `meter` |
86
86
  | Number on a continuous scale | `Slider` from `slider` |
87
- | Row actions or an overflow menu of commands | `DropdownMenu` family from `dropdown-menu` |
88
- | Setting that applies immediately | `Switch` from `switch`; `Checkbox` for choices submitted with a form |
89
- | One choice from a few visible options | `RadioGroup` and `RadioGroupItem` from `radio-group` |
90
- | Inline message about a page or section | `Alert` from `alert` (`success`, `warning`, `destructive` variants) |
91
- | Person or workspace image | `Avatar`, `AvatarImage`, `AvatarFallback` from `avatar` |
92
- | Sections that expand in place | `Accordion` family from `accordion` |
93
- | Pages of a long list | `Pagination` family from `pagination` |
94
- | Where the page sits in a hierarchy | `Breadcrumb` family from `breadcrumb` |
95
- | Work in progress | `Progress` from `progress` when the percent is known, else `Spinner` from `spinner` |
87
+ | Row actions or an overflow menu of commands | `DropdownMenu` from `dropdown-menu` |
88
+ | Setting that applies immediately | `Switch` from `switch` |
89
+ | On/off choice submitted with a form | `Checkbox` from `checkbox` |
90
+ | One choice from a few visible options | `RadioGroup` from `radio-group` |
91
+ | Inline message about a page or section | `Alert` from `alert` |
92
+ | Person or workspace image | `Avatar` from `avatar` |
93
+ | Sections that expand in place | `Accordion` from `accordion` |
94
+ | Pages of a long list | `Pagination` from `pagination` |
95
+ | Where the page sits in a hierarchy | `Breadcrumb` from `breadcrumb` |
96
+ | Job with a known percentage | `Progress` from `progress` |
97
+ | Wait of unknown length | `Spinner` from `spinner` |
96
98
  | Crash recovery around a section | `ErrorBoundary` from `error-boundary` |
97
99
  | Conditional classes | `cn` from `utils` |
98
100
 
101
+ The parts, props, and options of each are in its `COMPONENTS.md` row.
102
+
99
103
  If nothing fits, build the piece in the app from kit primitives and semantic
100
104
  tokens. Do not copy kit source into the app; a missing shared component belongs
101
105
  in the atlas-design-system repository.