atlas-react-kit 0.10.0 → 0.11.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 (102) hide show
  1. package/COMPONENTS.md +50 -8
  2. package/DESIGN_RULES.md +12 -0
  3. package/README.md +2 -2
  4. package/dist/accordion.d.ts +8 -1
  5. package/dist/accordion.js +8 -3
  6. package/dist/alert.js +1 -1
  7. package/dist/avatar.d.ts +5 -1
  8. package/dist/avatar.js +7 -2
  9. package/dist/badge.js +1 -1
  10. package/dist/breadcrumb.js +3 -2
  11. package/dist/button.js +6 -6
  12. package/dist/calendar.js +39 -21
  13. package/dist/card.d.ts +1 -1
  14. package/dist/checkbox.js +2 -2
  15. package/dist/color-legend.d.ts +6 -4
  16. package/dist/color-legend.js +7 -4
  17. package/dist/combobox.d.ts +7 -2
  18. package/dist/combobox.js +57 -38
  19. package/dist/date-picker.d.ts +7 -13
  20. package/dist/date-picker.js +14 -20
  21. package/dist/date-time-picker.d.ts +5 -13
  22. package/dist/date-time-picker.js +26 -30
  23. package/dist/dialog.d.ts +5 -1
  24. package/dist/dialog.js +4 -3
  25. package/dist/dropdown-menu.js +5 -4
  26. package/dist/empty-panel.d.ts +4 -3
  27. package/dist/empty-panel.js +3 -3
  28. package/dist/error-boundary.d.ts +5 -3
  29. package/dist/error-boundary.js +9 -4
  30. package/dist/field.d.ts +14 -1
  31. package/dist/field.js +16 -3
  32. package/dist/form/date-picker-field.d.ts +8 -2
  33. package/dist/form/date-picker-field.js +4 -4
  34. package/dist/form/date-time-picker-field.d.ts +8 -1
  35. package/dist/form/date-time-picker-field.js +3 -3
  36. package/dist/form/feedback-field.d.ts +2 -1
  37. package/dist/form/feedback-field.js +3 -2
  38. package/dist/form/field-error.d.ts +4 -1
  39. package/dist/form/field-error.js +3 -2
  40. package/dist/form/field-shell.d.ts +25 -1
  41. package/dist/form/field-shell.js +36 -3
  42. package/dist/form/markdown-field.d.ts +7 -1
  43. package/dist/form/markdown-field.js +3 -3
  44. package/dist/form/select-field.d.ts +7 -1
  45. package/dist/form/select-field.js +3 -3
  46. package/dist/form/text-field.d.ts +5 -1
  47. package/dist/form/text-field.js +6 -3
  48. package/dist/info-tip.d.ts +2 -2
  49. package/dist/info-tip.js +34 -16
  50. package/dist/internal/form-control.d.ts +5 -0
  51. package/dist/internal/form-control.js +7 -0
  52. package/dist/internal/listbox-navigation.d.ts +32 -0
  53. package/dist/internal/listbox-navigation.js +38 -0
  54. package/dist/internal/listbox-option.d.ts +41 -0
  55. package/dist/internal/listbox-option.js +29 -0
  56. package/dist/internal/math.d.ts +5 -0
  57. package/dist/internal/math.js +7 -0
  58. package/dist/internal/overlay-styles.d.ts +28 -0
  59. package/dist/internal/overlay-styles.js +28 -0
  60. package/dist/internal/search.d.ts +8 -0
  61. package/dist/internal/search.js +16 -0
  62. package/dist/internal/styles.d.ts +27 -0
  63. package/dist/internal/styles.js +27 -0
  64. package/dist/internal/tabs-styles.d.ts +10 -1
  65. package/dist/internal/tabs-styles.js +27 -5
  66. package/dist/lazy-calendar.js +9 -1
  67. package/dist/markdown.js +10 -4
  68. package/dist/meter.d.ts +3 -2
  69. package/dist/meter.js +6 -3
  70. package/dist/pagination.d.ts +8 -1
  71. package/dist/pagination.js +15 -9
  72. package/dist/picker-shell.d.ts +59 -0
  73. package/dist/picker-shell.js +21 -0
  74. package/dist/popover.js +2 -1
  75. package/dist/progress.d.ts +7 -3
  76. package/dist/progress.js +10 -4
  77. package/dist/radio-group.js +2 -2
  78. package/dist/search-picker.d.ts +4 -6
  79. package/dist/search-picker.js +35 -43
  80. package/dist/select.js +12 -6
  81. package/dist/separator.d.ts +4 -0
  82. package/dist/separator.js +5 -1
  83. package/dist/slider.d.ts +8 -3
  84. package/dist/slider.js +7 -5
  85. package/dist/sortable-table-head.d.ts +3 -1
  86. package/dist/sortable-table-head.js +3 -2
  87. package/dist/stat-tile.d.ts +6 -4
  88. package/dist/stat-tile.js +6 -4
  89. package/dist/switch.js +2 -2
  90. package/dist/table-body-skeleton.d.ts +8 -2
  91. package/dist/table-body-skeleton.js +7 -3
  92. package/dist/table.d.ts +7 -1
  93. package/dist/table.js +6 -2
  94. package/dist/tabs.d.ts +13 -3
  95. package/dist/tabs.js +29 -10
  96. package/dist/theme-provider.d.ts +2 -1
  97. package/dist/theme-provider.js +1 -1
  98. package/dist/toggle.js +4 -4
  99. package/dist/tooltip.d.ts +7 -3
  100. package/dist/tooltip.js +17 -6
  101. package/package.json +1 -1
  102. package/theme.css +43 -50
@@ -3,44 +3,40 @@ 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 { listboxOptionId, moveListboxHighlight, scrollOptionIntoView, } from "./internal/listbox-navigation.js";
7
- import { listboxEmpty, optionState } from "./internal/styles.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";
8
9
  import { cn } from "./utils.js";
9
- /**
10
- * Case-fold and strip combining marks so "nino" matches "Niño".
11
- */
12
- export function foldSearchText(value) {
13
- return value.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
14
- }
10
+ export { foldSearchText };
15
11
  /**
16
12
  * Default SearchPicker filter: the folded label includes the folded query.
17
13
  */
18
14
  export function defaultSearchFilter(item, query, getLabel) {
19
- const needle = foldSearchText(query.trim());
15
+ const needle = normalizeSearchQuery(query);
20
16
  if (!needle)
21
17
  return true;
22
- return foldSearchText(getLabel(item)).includes(needle);
18
+ return matchesSearchLabel(foldSearchText(getLabel(item)), needle);
23
19
  }
20
+ /** Frame shared by the listbox and the empty state that replaces it. */
21
+ const listFrame = "border border-input bg-background";
24
22
  /**
25
23
  * Always-visible combobox: a labelled input and a listbox. Unlike Combobox,
26
24
  * the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
27
25
  * Enter, Escape.
28
26
  */
29
- function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, listClassName, }) {
27
+ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, listClassName, ...props }) {
30
28
  const [query, setQuery] = React.useState("");
31
29
  const listId = React.useId();
32
30
  const inputId = React.useId();
33
31
  const normalizedItems = React.useMemo(() => filter === defaultSearchFilter
34
- ? items.map((item) => [item, foldSearchText(getLabel(item))])
32
+ ? createSearchIndex(items, getLabel)
35
33
  : null, [filter, getLabel, items]);
36
- const foldedQuery = filter === defaultSearchFilter ? foldSearchText(query.trim()) : "";
34
+ const foldedQuery = filter === defaultSearchFilter ? normalizeSearchQuery(query) : "";
37
35
  const filtered = React.useMemo(() => {
38
36
  if (normalizedItems) {
39
37
  if (!foldedQuery)
40
38
  return items;
41
- return normalizedItems
42
- .filter(([, label]) => label.includes(foldedQuery))
43
- .map(([item]) => item);
39
+ return filterSearchIndex(normalizedItems, foldedQuery);
44
40
  }
45
41
  return items.filter((item) => filter(item, query, getLabel));
46
42
  }, [items, filter, foldedQuery, getLabel, normalizedItems, query]);
@@ -48,6 +44,7 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
48
44
  const index = filtered.findIndex((item) => getValue(item) === value);
49
45
  return index >= 0 ? index : 0;
50
46
  }, [filtered, getValue, value]);
47
+ const rows = React.useMemo(() => filtered.map((item) => ({ item, value: getValue(item) })), [filtered, getValue]);
51
48
  const [highlight, setHighlight] = React.useState(selectedIndex);
52
49
  // Reset the highlight while rendering, not in an effect, when the query or
53
50
  // the selected row changes: an effect commits every keystroke twice, once
@@ -62,38 +59,33 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
62
59
  setHighlight(query ? 0 : selectedIndex);
63
60
  }
64
61
  const active = filtered[highlight];
65
- const selectItem = (item) => {
62
+ const empty = filtered.length === 0;
63
+ const selectItem = React.useCallback((item) => {
66
64
  onSelect(item);
67
65
  setQuery("");
68
- };
66
+ }, [onSelect]);
69
67
  const onKeyDown = (event) => {
70
- const nextHighlight = moveListboxHighlight(event.key, highlight, filtered.length);
71
- if (nextHighlight !== undefined) {
72
- event.preventDefault();
73
- setHighlight(nextHighlight);
68
+ const handled = handleListboxKey(event, {
69
+ highlight,
70
+ length: filtered.length,
71
+ setHighlight,
72
+ onEnter: (index) => {
73
+ const item = filtered[index];
74
+ if (item)
75
+ selectItem(item);
76
+ },
77
+ });
78
+ if (handled || event.key !== "Escape")
79
+ return;
80
+ event.preventDefault();
81
+ if (query) {
82
+ setQuery("");
74
83
  return;
75
84
  }
76
- if (event.key === "Enter") {
77
- event.preventDefault();
78
- if (active)
79
- selectItem(active);
80
- }
81
- if (event.key === "Escape") {
82
- event.preventDefault();
83
- if (query) {
84
- setQuery("");
85
- return;
86
- }
87
- event.currentTarget.blur();
88
- }
85
+ event.currentTarget.blur();
89
86
  };
90
- return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), 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: cn("max-h-60 overflow-y-auto border border-input bg-background", listClassName), children: filtered.length === 0 ? (_jsx("li", { className: listboxEmpty, children: emptyText })) : (filtered.map((item, index) => {
91
- const itemValue = getValue(item);
92
- const isSelected = itemValue === value;
93
- const isActive = index === highlight;
94
- return (_jsx("li", { role: "none", children: _jsx("button", { ref: isActive ? scrollOptionIntoView : undefined, id: listboxOptionId(listId, index), type: "button", role: "option", "aria-selected": isSelected, disabled: disabled, className: cn("relative flex w-full cursor-default items-center rounded-none py-2 pr-3 pl-3 text-left text-sm outline-hidden select-none", isSelected && optionState.selected, isActive && optionState.active, isActive &&
95
- !isSelected &&
96
- "bg-accent text-accent-foreground"), onMouseEnter: () => setHighlight(index), onClick: () => selectItem(item), children: _jsx("span", { className: "truncate", children: getLabel(item) }) }) }, itemValue));
97
- })) })] }));
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 })] }));
98
90
  }
99
91
  export { SearchPicker };
package/dist/select.js CHANGED
@@ -3,8 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Select as SelectPrimitive } from "radix-ui";
5
5
  import { dataDisabledState, disabledState, fieldSurface, focusRing, invalidState, optionState, } from "./internal/styles.js";
6
+ import { menuLabel, optionIcon, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
6
7
  import { cn } from "./utils.js";
7
8
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
9
+ /**
10
+ * Trigger heights as plain classes, so a consumer `className` such as `h-8`
11
+ * replaces them (a `data-[size=*]:` variant would outrank it).
12
+ */
13
+ const triggerHeight = { default: "h-10", sm: "h-9" };
8
14
  const scrollButtonClassName = "z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5";
9
15
  function Select({ ...props }) {
10
16
  return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
@@ -16,20 +22,20 @@ function SelectValue({ ...props }) {
16
22
  return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
17
23
  }
18
24
  function SelectTrigger({ className, size = "default", children, ...props }) {
19
- return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, "flex items-center justify-between gap-1.5 whitespace-nowrap data-placeholder:text-muted-foreground data-[size=default]:h-10 data-[size=sm]:h-9 *: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 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", className), ...props, children: [children, _jsx(SelectPrimitive.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, 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" }) })] }));
20
26
  }
21
27
  function SelectContent({ className, children, position = "popper", align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
22
- return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn("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 rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position === "popper" &&
23
- "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-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, {})] }) }));
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
+ "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, {})] }) }));
24
30
  }
25
31
  function SelectLabel({ className, ...props }) {
26
- return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn("px-3 py-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase", className), ...props }));
32
+ return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn(menuLabel, className), ...props }));
27
33
  }
28
34
  function SelectItem({ className, children, ...props }) {
29
- return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionState.focus, optionState.checked, dataDisabledState, "relative flex w-full cursor-default items-center gap-2.5 rounded-none py-2 pr-8 pl-3 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:**:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[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, { 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, 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 })] }));
30
36
  }
31
37
  function SelectSeparator({ className, ...props }) {
32
- return (_jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn("pointer-events-none -mx-1.5 my-1.5 h-px bg-border/50", className), ...props }));
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 }));
33
39
  }
34
40
  function SelectScrollUpButton({ className, ...props }) {
35
41
  return (_jsx(SelectPrimitive.ScrollUpButton, { "data-slot": "select-scroll-up-button", className: cn(scrollButtonClassName, className), ...props, children: _jsx(ChevronUpIcon, {}) }));
@@ -1,4 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { Separator as SeparatorPrimitive } from "radix-ui";
3
+ /**
4
+ * Hairline divider. Its thickness and length are plain classes picked from
5
+ * `orientation`, so `className="w-12"` or `"w-0.5"` replaces them.
6
+ */
3
7
  declare function Separator({ className, orientation, decorative, ...props }: React.ComponentProps<typeof SeparatorPrimitive.Root>): React.JSX.Element;
4
8
  export { Separator };
package/dist/separator.js CHANGED
@@ -3,7 +3,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Separator as SeparatorPrimitive } from "radix-ui";
5
5
  import { cn } from "./utils.js";
6
+ /**
7
+ * Hairline divider. Its thickness and length are plain classes picked from
8
+ * `orientation`, so `className="w-12"` or `"w-0.5"` replaces them.
9
+ */
6
10
  function Separator({ className, orientation = "horizontal", decorative = true, ...props }) {
7
- return (_jsx(SeparatorPrimitive.Root, { "data-slot": "separator", decorative: decorative, orientation: orientation, className: cn("shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", className), ...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 }));
8
12
  }
9
13
  export { Separator };
package/dist/slider.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export type SliderProps = {
1
+ import type * as React from "react";
2
+ import { Slider as SliderPrimitive } from "radix-ui";
3
+ export type SliderProps = Omit<React.ComponentProps<typeof SliderPrimitive.Root>, "value" | "defaultValue" | "onValueChange" | "onValueCommit" | "children"> & {
2
4
  value: number;
3
5
  onValueChange: (value: number) => void;
4
6
  onValueCommit?: (value: number) => void;
@@ -6,6 +8,7 @@ export type SliderProps = {
6
8
  max?: number;
7
9
  step?: number;
8
10
  disabled?: boolean;
11
+ /** Goes on the thumb, so a `<Label htmlFor>` names the slider. */
9
12
  id?: string;
10
13
  name?: string;
11
14
  className?: string;
@@ -14,7 +17,9 @@ export type SliderProps = {
14
17
  };
15
18
  /**
16
19
  * Single-thumb range on Radix Slider. Square track and thumb, primary fill,
17
- * solid focus ring, 44px thumb hit area. Pair with a visible `Label`.
20
+ * solid focus ring, 44px thumb hit area. Pair with a visible `Label`. Other
21
+ * props (`orientation`, `inverted`, `data-*`) go to the Radix root; `id`
22
+ * and the accessible name go to the thumb.
18
23
  */
19
- declare function Slider({ value, onValueChange, onValueCommit, min, max, step, disabled, id, name, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: SliderProps): import("react").JSX.Element;
24
+ declare function Slider({ value, onValueChange, onValueCommit, min, max, step, id, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }: SliderProps): React.JSX.Element;
20
25
  export { Slider };
package/dist/slider.js CHANGED
@@ -1,14 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Slider as SliderPrimitive } from "radix-ui";
4
- import { dataDisabledState, focusRing } from "./internal/styles.js";
4
+ import { dataDisabledState, focusRing, hitArea44 } from "./internal/styles.js";
5
5
  import { cn } from "./utils.js";
6
6
  /**
7
7
  * Single-thumb range on Radix Slider. Square track and thumb, primary fill,
8
- * solid focus ring, 44px thumb hit area. Pair with a visible `Label`.
8
+ * solid focus ring, 44px thumb hit area. Pair with a visible `Label`. Other
9
+ * props (`orientation`, `inverted`, `data-*`) go to the Radix root; `id`
10
+ * and the accessible name go to the thumb.
9
11
  */
10
- function Slider({ value, onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled, id, name, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }) {
11
- return (_jsxs(SliderPrimitive.Root, { "data-slot": "slider", name: name, min: min, max: max, step: step, disabled: disabled, value: [value], onValueChange: (next) => {
12
+ function Slider({ value, onValueChange, onValueCommit, min = 0, max = 100, step = 1, id, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
13
+ return (_jsxs(SliderPrimitive.Root, { "data-slot": "slider", ...props, min: min, max: max, step: step, value: [value], onValueChange: (next) => {
12
14
  const amount = next[0];
13
15
  if (amount !== undefined)
14
16
  onValueChange(amount);
@@ -16,6 +18,6 @@ function Slider({ value, onValueChange, onValueCommit, min = 0, max = 100, step
16
18
  const amount = next[0];
17
19
  if (amount !== undefined)
18
20
  onValueCommit?.(amount);
19
- }, 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, "relative block size-4 shrink-0 rounded-none border border-primary bg-background shadow-xs after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-x-1/2 after:-translate-y-1/2") })] }));
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") })] }));
20
22
  }
21
23
  export { Slider };
@@ -6,10 +6,12 @@ type SortableTableHeadProps = ComponentProps<typeof TableHead> & {
6
6
  sorted?: boolean;
7
7
  direction?: SortDirection;
8
8
  onClick?: () => void;
9
+ /** Classes for the inner sort button (`data-slot="sortable-table-head-button"`). */
10
+ buttonClassName?: string;
9
11
  };
10
12
  /**
11
13
  * Table header cell with `aria-sort` and a button affordance.
12
14
  * Column keys and comparators stay in the application.
13
15
  */
14
- export declare function SortableTableHead({ children, sorted, direction, onClick, className, ...props }: SortableTableHeadProps): import("react").JSX.Element;
16
+ export declare function SortableTableHead({ children, sorted, direction, onClick, className, buttonClassName, ...props }: SortableTableHeadProps): import("react").JSX.Element;
15
17
  export {};
@@ -2,15 +2,16 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDownIcon, ChevronUpIcon, ChevronsUpDownIcon, } from "lucide-react";
4
4
  import { TableHead } from "./table.js";
5
+ import { cn } from "./utils.js";
5
6
  /**
6
7
  * Table header cell with `aria-sort` and a button affordance.
7
8
  * Column keys and comparators stay in the application.
8
9
  */
9
- export function SortableTableHead({ children, sorted = false, direction = "asc", onClick, className, ...props }) {
10
+ export function SortableTableHead({ children, sorted = false, direction = "asc", onClick, className, buttonClassName, ...props }) {
10
11
  const ariaSort = sorted
11
12
  ? direction === "asc"
12
13
  ? "ascending"
13
14
  : "descending"
14
15
  : "none";
15
- return (_jsx(TableHead, { "aria-sort": ariaSort, className: className, ...props, children: _jsxs("button", { type: "button", onClick: onClick, className: "inline-flex items-center gap-1 rounded-none hover:text-foreground", children: [children, sorted ? (direction === "asc" ? (_jsx(ChevronUpIcon, { className: "size-3.5", "aria-hidden": "true" })) : (_jsx(ChevronDownIcon, { className: "size-3.5", "aria-hidden": "true" }))) : (_jsx(ChevronsUpDownIcon, { className: "size-3.5 opacity-40", "aria-hidden": "true" }))] }) }));
16
+ return (_jsx(TableHead, { "aria-sort": ariaSort, className: className, ...props, children: _jsxs("button", { type: "button", onClick: onClick, "data-slot": "sortable-table-head-button", className: cn("inline-flex items-center gap-1 rounded-none hover:text-foreground", buttonClassName), children: [children, sorted ? (direction === "asc" ? (_jsx(ChevronUpIcon, { className: "size-3.5", "aria-hidden": "true" })) : (_jsx(ChevronDownIcon, { className: "size-3.5", "aria-hidden": "true" }))) : (_jsx(ChevronsUpDownIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }))] }) }));
16
17
  }
@@ -1,12 +1,13 @@
1
1
  import type * as React from "react";
2
+ import { sectionLabel } from "./internal/styles.js";
2
3
  /**
3
4
  * Uppercase muted label used on tiles and thin section chrome. Apps that
4
5
  * only needed a heading class can import this instead of a wrapper.
5
6
  */
6
- export declare const sectionLabel = "text-xs font-semibold tracking-widest text-muted-foreground uppercase";
7
+ export { sectionLabel };
7
8
  /** Row of tiles: stacked on phones, divided columns from `sm`. */
8
9
  export declare const statTileGrid = "grid ring-1 ring-foreground/5 sm:divide-x";
9
- export type StatTileProps = {
10
+ export type StatTileProps = Omit<React.ComponentProps<"div">, "children" | "title"> & {
10
11
  label: string;
11
12
  value: string;
12
13
  sub?: React.ReactNode;
@@ -15,7 +16,8 @@ export type StatTileProps = {
15
16
  };
16
17
  /**
17
18
  * Label, big tabular figure, optional extra (a meter), muted sub line.
18
- * Domain copy and status colors stay in the app.
19
+ * Domain copy and status colors stay in the app. Other props go to the
20
+ * root `div`; parts carry `data-slot="stat-tile-label|value|sub"`.
19
21
  */
20
- declare function StatTile({ label, value, sub, children, className }: StatTileProps): React.JSX.Element;
22
+ declare function StatTile({ label, value, sub, children, className, ...props }: StatTileProps): React.JSX.Element;
21
23
  export { StatTile };
package/dist/stat-tile.js CHANGED
@@ -1,17 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sectionLabel } from "./internal/styles.js";
2
3
  import { cn } from "./utils.js";
3
4
  /**
4
5
  * Uppercase muted label used on tiles and thin section chrome. Apps that
5
6
  * only needed a heading class can import this instead of a wrapper.
6
7
  */
7
- export const sectionLabel = "text-xs font-semibold tracking-widest text-muted-foreground uppercase";
8
+ export { sectionLabel };
8
9
  /** Row of tiles: stacked on phones, divided columns from `sm`. */
9
10
  export const statTileGrid = "grid ring-1 ring-foreground/5 sm:divide-x";
10
11
  /**
11
12
  * Label, big tabular figure, optional extra (a meter), muted sub line.
12
- * Domain copy and status colors stay in the app.
13
+ * Domain copy and status colors stay in the app. Other props go to the
14
+ * root `div`; parts carry `data-slot="stat-tile-label|value|sub"`.
13
15
  */
14
- function StatTile({ label, value, sub, children, className }) {
15
- return (_jsxs("div", { "data-slot": "stat-tile", className: cn("flex flex-col gap-1 px-3 py-2", className), children: [_jsx("span", { className: sectionLabel, children: label }), _jsx("span", { className: "text-lg font-semibold tracking-tight tabular-nums", children: value }), children, sub ? (_jsx("span", { className: "text-xs leading-snug text-muted-foreground", children: sub })) : null] }));
16
+ function StatTile({ label, value, sub, children, className, ...props }) {
17
+ return (_jsxs("div", { "data-slot": "stat-tile", className: cn("flex flex-col gap-1 px-3 py-2", className), ...props, children: [_jsx("span", { "data-slot": "stat-tile-label", className: sectionLabel, children: label }), _jsx("span", { "data-slot": "stat-tile-value", className: "text-lg font-semibold tracking-tight tabular-nums", children: value }), children, sub ? (_jsx("span", { "data-slot": "stat-tile-sub", className: "text-xs leading-snug text-muted-foreground", children: sub })) : null] }));
16
18
  }
17
19
  export { StatTile };
package/dist/switch.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Switch as SwitchPrimitive } from "radix-ui";
5
- import { disabledState, focusRing, invalidState } from "./internal/styles.js";
5
+ import { choiceHitArea, disabledState, focusRing, invalidState, } from "./internal/styles.js";
6
6
  import { cn } from "./utils.js";
7
7
  /**
8
8
  * On/off setting that applies immediately (notifications, dark mode). Use
@@ -10,6 +10,6 @@ import { cn } from "./utils.js";
10
10
  * primitives; checked uses the primary fill, as `Checkbox` does.
11
11
  */
12
12
  function Switch({ className, ...props }) {
13
- return (_jsx(SwitchPrimitive.Root, { "data-slot": "switch", className: cn(focusRing, invalidState, disabledState, "peer inline-flex h-5 w-9 shrink-0 items-center rounded-none border border-transparent bg-input p-0.5 shadow-xs transition-colors data-checked:bg-primary dark:data-checked:bg-primary", className), ...props, children: _jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "pointer-events-none block size-3.5 rounded-none bg-background shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground" }) }));
13
+ return (_jsx(SwitchPrimitive.Root, { "data-slot": "switch", className: cn(focusRing, invalidState, disabledState, choiceHitArea, "peer relative inline-flex h-5 w-9 shrink-0 items-center rounded-none border border-transparent bg-input p-0.5 shadow-xs transition-colors data-checked:bg-primary dark:data-checked:bg-primary", className), ...props, children: _jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "pointer-events-none block size-3.5 rounded-none bg-background shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground" }) }));
14
14
  }
15
15
  export { Switch };
@@ -1,7 +1,13 @@
1
1
  type Props = {
2
2
  columns: number;
3
3
  rows?: number;
4
+ /** Classes for the `tbody`. */
5
+ className?: string;
4
6
  };
5
- /** Skeleton table body rows for client query loading states. */
6
- export declare function TableBodySkeleton({ columns, rows }: Props): import("react").JSX.Element;
7
+ /**
8
+ * Skeleton table body rows for client query loading states. The body is
9
+ * `aria-busy` and the placeholder cells are hidden, so a screen reader hears
10
+ * that the table is loading instead of rows of empty cells.
11
+ */
12
+ export declare function TableBodySkeleton({ columns, rows, className }: Props): import("react").JSX.Element;
7
13
  export {};
@@ -1,7 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Skeleton } from "./skeleton.js";
3
3
  import { TableBody, TableCell, TableRow } from "./table.js";
4
- /** Skeleton table body rows for client query loading states. */
5
- export function TableBodySkeleton({ columns, rows = 5 }) {
6
- return (_jsx(TableBody, { children: Array.from({ length: rows }, (_, row) => (_jsx(TableRow, { children: Array.from({ length: columns }, (_, col) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-full" }) }, col))) }, row))) }));
4
+ /**
5
+ * Skeleton table body rows for client query loading states. The body is
6
+ * `aria-busy` and the placeholder cells are hidden, so a screen reader hears
7
+ * that the table is loading instead of rows of empty cells.
8
+ */
9
+ export function TableBodySkeleton({ columns, rows = 5, className }) {
10
+ return (_jsx(TableBody, { "aria-busy": "true", className: className, children: Array.from({ length: rows }, (_, row) => (_jsx(TableRow, { "aria-hidden": "true", children: Array.from({ length: columns }, (_, col) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-full" }) }, col))) }, row))) }));
7
11
  }
package/dist/table.d.ts CHANGED
@@ -5,8 +5,14 @@ import * as React from "react";
5
5
  * While it overflows, the scroller joins the tab order so keyboard users can
6
6
  * scroll it with the arrow keys (WCAG 2.1.1).
7
7
  * Cell padding is `px-2` below `sm` and the original `p-3` from `sm` up.
8
+ *
9
+ * `className` styles the `<table>` (`w-full` by default; `w-auto` sizes it
10
+ * to its columns). `containerClassName` styles the scroller, for a height
11
+ * cap (`max-h-96 overflow-y-auto`) or a border.
8
12
  */
9
- declare function Table({ className, ...props }: React.ComponentProps<"table">): React.JSX.Element;
13
+ declare function Table({ className, containerClassName, ...props }: React.ComponentProps<"table"> & {
14
+ containerClassName?: string;
15
+ }): React.JSX.Element;
10
16
  declare function TableHeader({ className, ...props }: React.ComponentProps<"thead">): React.JSX.Element;
11
17
  declare function TableBody({ className, ...props }: React.ComponentProps<"tbody">): React.JSX.Element;
12
18
  declare function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">): React.JSX.Element;
package/dist/table.js CHANGED
@@ -10,11 +10,15 @@ import { cn } from "./utils.js";
10
10
  * While it overflows, the scroller joins the tab order so keyboard users can
11
11
  * scroll it with the arrow keys (WCAG 2.1.1).
12
12
  * Cell padding is `px-2` below `sm` and the original `p-3` from `sm` up.
13
+ *
14
+ * `className` styles the `<table>` (`w-full` by default; `w-auto` sizes it
15
+ * to its columns). `containerClassName` styles the scroller, for a height
16
+ * cap (`max-h-96 overflow-y-auto`) or a border.
13
17
  */
14
- function Table({ className, ...props }) {
18
+ function Table({ className, containerClassName, ...props }) {
15
19
  const containerRef = React.useRef(null);
16
20
  const scrollable = useHorizontalOverflow(containerRef);
17
- return (_jsx("div", { ref: containerRef, "data-slot": "table-container", tabIndex: scrollable ? 0 : undefined, className: cn(focusRing, "relative w-full min-w-0 max-w-full overflow-x-auto overscroll-x-contain"), children: _jsx("table", { "data-slot": "table", className: cn("w-full min-w-full caption-bottom text-sm", className), ...props }) }));
21
+ return (_jsx("div", { ref: containerRef, "data-slot": "table-container", tabIndex: scrollable ? 0 : undefined, className: cn(focusRing, "relative min-w-0 max-w-full overflow-x-auto overscroll-x-contain", containerClassName), children: _jsx("table", { "data-slot": "table", className: cn("w-full caption-bottom text-sm", className), ...props }) }));
18
22
  }
19
23
  function TableHeader({ className, ...props }) {
20
24
  return (_jsx("thead", { "data-slot": "table-header", className: cn("[&_tr]:border-b", className), ...props }));
package/dist/tabs.d.ts CHANGED
@@ -3,7 +3,14 @@ import { type VariantProps } from "class-variance-authority";
3
3
  import { Tabs as TabsPrimitive } from "radix-ui";
4
4
  import { tabsListVariants, tabsTriggerVariants } from "./internal/tabs-styles.js";
5
5
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
6
- declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
6
+ /**
7
+ * The tab row inside its scroller. `className` styles the list (its `h-11`
8
+ * height, fill, gap); `scrollerClassName` styles the scroller around it
9
+ * (`h-13` with `p-1` for the active border and focus ring).
10
+ */
11
+ declare function TabsList({ className, scrollerClassName, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & Omit<VariantProps<typeof tabsListVariants>, "orientation"> & {
12
+ scrollerClassName?: string;
13
+ }): React.JSX.Element;
7
14
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
8
15
  declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
9
16
  /**
@@ -18,9 +25,12 @@ declare function TabsContent({ className, ...props }: React.ComponentProps<typeo
18
25
  * for active borders and focus rings counted in the scroller height (so the
19
26
  * list cannot overflow on y), an edge fade while it overflows, and the
20
27
  * active link scrolled into view on mount. Do not wrap it in
21
- * `overflow-x-auto`.
28
+ * `overflow-x-auto`. `scrollerClassName` styles that scroller and
29
+ * `listClassName` the row of links inside it.
22
30
  */
23
- declare function TabsNav({ className, children, ...props }: React.ComponentProps<"nav"> & {
31
+ declare function TabsNav({ className, scrollerClassName, listClassName, children, ...props }: React.ComponentProps<"nav"> & {
24
32
  "aria-label": string;
33
+ scrollerClassName?: string;
34
+ listClassName?: string;
25
35
  }): React.JSX.Element;
26
36
  export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
package/dist/tabs.js CHANGED
@@ -7,8 +7,14 @@ import { focusRing } from "./internal/styles.js";
7
7
  import { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
8
8
  import { useHorizontalOverflow } from "./internal/use-horizontal-overflow.js";
9
9
  import { cn } from "./utils.js";
10
+ /**
11
+ * The root's orientation, so the list can pick plain size classes (`h-11`)
12
+ * that a consumer `className` replaces, instead of `group-data-*` variants
13
+ * that would beat it.
14
+ */
15
+ const TabsOrientationContext = React.createContext("horizontal");
10
16
  function Tabs({ className, orientation = "horizontal", ...props }) {
11
- return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, orientation: orientation, className: cn('group/tabs flex gap-2 data-[orientation="horizontal"]:flex-col', className), ...props }));
17
+ return (_jsx(TabsOrientationContext.Provider, { value: orientation, children: _jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, orientation: orientation, className: cn('group/tabs flex gap-2 data-[orientation="horizontal"]:flex-col', className), ...props }) }));
12
18
  }
13
19
  /**
14
20
  * Horizontal scroller around a tab row. `overflow-x: auto` alone would
@@ -18,24 +24,36 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
18
24
  * y axis as a backstop, and fade the edges while the row overflows —
19
25
  * same cue as `Table`.
20
26
  */
21
- function TabsScroller({ variant = "default", children, }) {
27
+ function TabsScroller({ variant = "default", orientation = "horizontal", className, children, }) {
22
28
  const ref = React.useRef(null);
29
+ const revealed = React.useRef(null);
23
30
  const overflowing = useHorizontalOverflow(ref);
31
+ // Runs after every render, not just mount: TabsNav usually sits in a
32
+ // persistent layout, so a route change updates the active link in place.
24
33
  React.useLayoutEffect(() => {
25
34
  const scroller = ref.current;
26
35
  if (!scroller)
27
36
  return;
28
37
  const active = scroller.querySelector('[aria-current="page"], [data-state="active"]');
29
- active?.scrollIntoView({
38
+ if (!active || active === revealed.current)
39
+ return;
40
+ revealed.current = active;
41
+ active.scrollIntoView({
30
42
  block: "nearest",
31
43
  inline: "nearest",
32
44
  behavior: "auto",
33
45
  });
34
- }, []);
35
- return (_jsx("div", { ref: ref, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing, tabsScrollerVariants({ variant })), children: children }));
46
+ });
47
+ 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 }));
36
48
  }
37
- function TabsList({ className, variant = "default", ...props }) {
38
- return (_jsx(TabsScroller, { variant: variant ?? "default", children: _jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }),
49
+ /**
50
+ * The tab row inside its scroller. `className` styles the list (its `h-11`
51
+ * height, fill, gap); `scrollerClassName` styles the scroller around it
52
+ * (`h-13` with `p-1` for the active border and focus ring).
53
+ */
54
+ function TabsList({ className, scrollerClassName, variant = "default", ...props }) {
55
+ const orientation = React.useContext(TabsOrientationContext);
56
+ return (_jsx(TabsScroller, { variant: variant ?? "default", orientation: orientation, className: scrollerClassName, children: _jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant, orientation }),
39
57
  // The scroller owns the muted fill so edge fades show through.
40
58
  variant === "default" && "bg-transparent", className), ...props }) }));
41
59
  }
@@ -57,9 +75,10 @@ function TabsContent({ className, ...props }) {
57
75
  * for active borders and focus rings counted in the scroller height (so the
58
76
  * list cannot overflow on y), an edge fade while it overflows, and the
59
77
  * active link scrolled into view on mount. Do not wrap it in
60
- * `overflow-x-auto`.
78
+ * `overflow-x-auto`. `scrollerClassName` styles that scroller and
79
+ * `listClassName` the row of links inside it.
61
80
  */
62
- function TabsNav({ className, children, ...props }) {
63
- return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs min-w-0 max-w-full", className), ...props, children: _jsx(TabsScroller, { children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: cn(tabsListVariants(), "bg-transparent"), children: children }) }) }));
81
+ function TabsNav({ className, scrollerClassName, listClassName, children, ...props }) {
82
+ return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs min-w-0 max-w-full", className), ...props, children: _jsx(TabsScroller, { className: scrollerClassName, children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: cn(tabsListVariants({ orientation: "horizontal" }), "bg-transparent", listClassName), children: children }) }) }));
64
83
  }
65
84
  export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
@@ -4,7 +4,8 @@ export type ThemeProviderProps = NextThemesProviderProps & {
4
4
  /**
5
5
  * When true, pressing `d` toggles dark/light unless focus is in an editable
6
6
  * field or a widget with letter typeahead (such as `Select`). Defaults to
7
- * true.
7
+ * false: a single-key shortcut fires by accident for speech-input users, so
8
+ * an app that turns it on should also let people switch it off (WCAG 2.1.4).
8
9
  */
9
10
  enableShortcut?: boolean;
10
11
  };
@@ -55,7 +55,7 @@ function ThemeShortcut() {
55
55
  * Class-based theme wrapper around `next-themes`. Does not require Next.js;
56
56
  * pass `storageKey` from the application (`atlas-theme` is the kit default).
57
57
  */
58
- function ThemeProvider({ children, storageKey = "atlas-theme", attribute = "class", defaultTheme = "system", enableSystem = true, disableTransitionOnChange = true, enableShortcut = true, ...props }) {
58
+ function ThemeProvider({ children, storageKey = "atlas-theme", attribute = "class", defaultTheme = "system", enableSystem = true, disableTransitionOnChange = true, enableShortcut = false, ...props }) {
59
59
  return (_jsxs(NextThemesProvider, { storageKey: storageKey, attribute: attribute, defaultTheme: defaultTheme, enableSystem: enableSystem, disableTransitionOnChange: disableTransitionOnChange, ...props, children: [enableShortcut ? _jsx(ThemeShortcut, {}) : null, children] }));
60
60
  }
61
61
  export { ThemeProvider, useTheme };
package/dist/toggle.js CHANGED
@@ -3,7 +3,7 @@ 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 { disabledState, focusRing, invalidState, selectedState, } from "./internal/styles.js";
6
+ import { controlSize, disabledState, focusRing, 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
@@ -25,9 +25,9 @@ const toggleVariants = cva([
25
25
  outline: "border border-input bg-transparent",
26
26
  },
27
27
  size: {
28
- default: "h-10 min-w-10 px-6 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4",
29
- sm: "h-9 min-w-9 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
30
- lg: "h-11 min-w-11 px-8 has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5",
28
+ default: `${controlSize.default} min-w-10`,
29
+ sm: `${controlSize.sm} min-w-9`,
30
+ lg: `${controlSize.lg} min-w-11`,
31
31
  },
32
32
  },
33
33
  defaultVariants: {