@dreamtree-org/twreact-ui 1.3.8 → 1.3.10

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 (109) hide show
  1. package/DESIGN.md +11 -3
  2. package/README.md +9 -1
  3. package/ai-skills/dreamtree-ui.md +2 -2
  4. package/dist/ai-schema.json +19 -8
  5. package/dist/charts/index.js +69 -24
  6. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  7. package/dist/components/charts/chartTheme.d.ts +2 -5
  8. package/dist/components/charts/chartTheme.d.ts.map +1 -1
  9. package/dist/components/core/Accordion.d.ts.map +1 -1
  10. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  11. package/dist/components/core/DatePicker.d.ts.map +1 -1
  12. package/dist/components/core/Form.d.ts.map +1 -1
  13. package/dist/components/core/Input.d.ts.map +1 -1
  14. package/dist/components/core/PriceRangePicker.d.ts +0 -29
  15. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  16. package/dist/components/core/Radio.d.ts.map +1 -1
  17. package/dist/components/core/Rate.d.ts.map +1 -1
  18. package/dist/components/core/RoundedTag.d.ts.map +1 -1
  19. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  20. package/dist/components/core/Table/Pagination.d.ts.map +1 -1
  21. package/dist/components/core/Table/Table.d.ts.map +1 -1
  22. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  23. package/dist/components/core/Table/helpers.d.ts +2 -0
  24. package/dist/components/core/Table/helpers.d.ts.map +1 -1
  25. package/dist/components/core/Table/useClickOutside.d.ts +5 -2
  26. package/dist/components/core/Table/useClickOutside.d.ts.map +1 -1
  27. package/dist/components/core/Tabs.d.ts.map +1 -1
  28. package/dist/components/core/TextToSpeech.d.ts +2 -7
  29. package/dist/components/core/TextToSpeech.d.ts.map +1 -1
  30. package/dist/components/core/Tooltip.d.ts.map +1 -1
  31. package/dist/components/feedback/Alert.d.ts.map +1 -1
  32. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  33. package/dist/components/feedback/Toast.d.ts.map +1 -1
  34. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  35. package/dist/components/utility/Avatar.d.ts.map +1 -1
  36. package/dist/components/utility/Carousel.d.ts.map +1 -1
  37. package/dist/components/utility/Portal.d.ts +6 -0
  38. package/dist/components/utility/Portal.d.ts.map +1 -0
  39. package/dist/esm/components/blocks/AppShell.js +24 -18
  40. package/dist/esm/components/charts/CartesianChart.js +2 -1
  41. package/dist/esm/components/charts/DonutChart.js +2 -1
  42. package/dist/esm/components/charts/Sparkline.js +2 -1
  43. package/dist/esm/components/charts/chartTheme.js +1 -22
  44. package/dist/esm/components/core/Accordion.js +40 -9
  45. package/dist/esm/components/core/Button.js +8 -8
  46. package/dist/esm/components/core/ColorPicker.js +19 -7
  47. package/dist/esm/components/core/DatePicker.js +97 -58
  48. package/dist/esm/components/core/Form.js +35 -8
  49. package/dist/esm/components/core/Input.js +3 -1
  50. package/dist/esm/components/core/PriceRangePicker.js +205 -218
  51. package/dist/esm/components/core/Radio.js +36 -6
  52. package/dist/esm/components/core/Rate.js +41 -50
  53. package/dist/esm/components/core/RoundedTag.js +25 -8
  54. package/dist/esm/components/core/Table/ColumnMenu.js +2 -4
  55. package/dist/esm/components/core/Table/Pagination.js +15 -18
  56. package/dist/esm/components/core/Table/Table.js +11 -5
  57. package/dist/esm/components/core/Table/TableRow.js +2 -2
  58. package/dist/esm/components/core/Table/helpers.js +33 -1
  59. package/dist/esm/components/core/Tabs.js +6 -5
  60. package/dist/esm/components/core/TextToSpeech.js +121 -28
  61. package/dist/esm/components/core/Tooltip.js +100 -28
  62. package/dist/esm/components/feedback/Alert.js +21 -11
  63. package/dist/esm/components/feedback/Dialog.js +44 -100
  64. package/dist/esm/components/feedback/Toast.js +24 -5
  65. package/dist/esm/components/navigation/Sidebar.js +13 -61
  66. package/dist/esm/components/utility/Avatar.js +23 -10
  67. package/dist/esm/components/utility/Carousel.js +34 -17
  68. package/dist/esm/components/utility/Pagination.js +2 -2
  69. package/dist/esm/hooks/useClickOutside.js +51 -0
  70. package/dist/esm/hooks/useFocusTrap.js +10 -2
  71. package/dist/esm/hooks/useMediaQuery.js +61 -0
  72. package/dist/esm/hooks/useReducedMotion.js +17 -0
  73. package/dist/esm/hooks/useScrollLock.js +76 -9
  74. package/dist/esm/hooks/useTheme.js +6 -1
  75. package/dist/esm/utils/position.js +6 -0
  76. package/dist/hooks/useClickOutside.d.ts +14 -0
  77. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  78. package/dist/hooks/useFloatingPosition.d.ts +37 -0
  79. package/dist/hooks/useFloatingPosition.d.ts.map +1 -0
  80. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  81. package/dist/hooks/useMediaQuery.d.ts +22 -0
  82. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  83. package/dist/hooks/usePresence.d.ts +16 -0
  84. package/dist/hooks/usePresence.d.ts.map +1 -0
  85. package/dist/hooks/usePresentation.d.ts +9 -0
  86. package/dist/hooks/usePresentation.d.ts.map +1 -0
  87. package/dist/hooks/useReducedMotion.d.ts +10 -0
  88. package/dist/hooks/useReducedMotion.d.ts.map +1 -0
  89. package/dist/hooks/useScrollLock.d.ts +11 -1
  90. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  91. package/dist/hooks/useTheme.d.ts.map +1 -1
  92. package/dist/index.css +1 -1
  93. package/dist/index.esm.css +1 -1
  94. package/dist/index.esm.js +1719 -1201
  95. package/dist/index.js +1718 -1200
  96. package/dist/tokens/tailwind3-preset.cjs +101 -8
  97. package/dist/tokens/theme.css +27 -0
  98. package/dist/tokens/tokens.json +175 -0
  99. package/dist/tokens/vars.css +2 -0
  100. package/dist/types/index.d.ts +8 -2
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/dist/utils/position.d.ts.map +1 -1
  103. package/dist/utils/touchTarget.d.ts +1 -0
  104. package/dist/utils/touchTarget.d.ts.map +1 -1
  105. package/mcp/catalog.snapshot.json +83 -53
  106. package/package.json +2 -1
  107. package/tokens/lib.cjs +35 -0
  108. package/tokens/tokens.json +58 -2
  109. package/dist/esm/components/core/Table/useClickOutside.js +0 -26
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
- import { forwardRef, useState } from 'react';
4
+ import { forwardRef, useState, useRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
 
7
8
  var _excluded = ["count", "value", "defaultValue", "readOnly", "icon", "toggledIcon", "activeColor", "inactiveColor", "size", "onChange", "onClick", "text", "className", "id", "name"];
8
9
  function StarFilled(_ref) {
@@ -69,45 +70,39 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
69
70
  _useState4 = _slicedToArray(_useState3, 2),
70
71
  hoverValue = _useState4[0],
71
72
  setHoverValue = _useState4[1];
72
- var _useState5 = useState(null),
73
- _useState6 = _slicedToArray(_useState5, 2),
74
- focusValue = _useState6[0],
75
- setFocusValue = _useState6[1];
73
+ var starRefs = useRef([]);
76
74
  var isControlled = value != null;
77
75
  var selectedValue = isControlled ? value : internalValue;
78
- var displayValue = hoverValue != null ? hoverValue : focusValue != null ? focusValue : selectedValue;
76
+ // Hover preview (pointer devices only) wins over the committed value.
77
+ var displayValue = hoverValue != null ? hoverValue : selectedValue;
78
+ // Roving tabindex (DTUI-23): the group is a single tab stop — the star
79
+ // equal to the value, or the first star when nothing is selected.
80
+ var tabStop = selectedValue >= 1 ? Math.min(count, selectedValue) : 1;
79
81
  var InactiveIcon = icon !== null && icon !== void 0 ? icon : jsx(StarOutline, {
80
82
  size: size
81
83
  });
82
84
  var ActiveIcon = toggledIcon !== null && toggledIcon !== void 0 ? toggledIcon : jsx(StarFilled, {
83
85
  size: size
84
86
  });
85
- var commit = function commit(n) {
86
- if (readOnly) return;
87
+ var setValue = function setValue(n) {
87
88
  if (!isControlled) setInternalValue(n);
88
89
  if (typeof onChange === "function") onChange(n);
89
- if (typeof onClick === "function") onClick(n);
90
90
  };
91
- var handleMouseEnter = function handleMouseEnter(n) {
91
+ var commit = function commit(n) {
92
92
  if (readOnly) return;
93
- setHoverValue(n);
93
+ setValue(n);
94
+ if (typeof onClick === "function") onClick(n);
94
95
  };
95
- var handleMouseMove = function handleMouseMove(n) {
96
- if (readOnly) return;
96
+ // Touch taps emit pointer/compat-mouse events with no matching "leave",
97
+ // which used to leave the preview stuck. Only real hover devices preview.
98
+ var handlePointerHover = function handlePointerHover(e, n) {
99
+ if (readOnly || e.pointerType === "touch") return;
97
100
  setHoverValue(n);
98
101
  };
99
- var handleMouseLeave = function handleMouseLeave() {
102
+ var clearHover = function clearHover() {
100
103
  if (readOnly) return;
101
104
  setHoverValue(null);
102
105
  };
103
- var handleFocus = function handleFocus(n) {
104
- if (readOnly) return;
105
- setFocusValue(n);
106
- };
107
- var handleBlur = function handleBlur() {
108
- if (readOnly) return;
109
- setFocusValue(null);
110
- };
111
106
  var handleKeyDown = function handleKeyDown(e, n) {
112
107
  if (readOnly) return;
113
108
  if (e.key === "Enter" || e.key === " ") {
@@ -115,22 +110,16 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
115
110
  commit(n);
116
111
  return;
117
112
  }
118
- if (e.key === "ArrowLeft") {
119
- e.preventDefault();
120
- setFocusValue(function (prev) {
121
- var base = prev != null ? prev : selectedValue || 0;
122
- return Math.max(0, base - 1);
123
- });
124
- return;
125
- }
126
- if (e.key === "ArrowRight") {
127
- e.preventDefault();
128
- setFocusValue(function (prev) {
129
- var base = prev != null ? prev : selectedValue || 0;
130
- return Math.min(count, base + 1);
131
- });
132
- return;
133
- }
113
+ var next = null;
114
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") next = n + 1;else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = n - 1;else if (e.key === "Home") next = 1;else if (e.key === "End") next = count;
115
+ if (next == null) return;
116
+ e.preventDefault();
117
+ next = Math.max(1, Math.min(count, next));
118
+ // Radio-group semantics: arrows move real focus AND the selection.
119
+ setHoverValue(null);
120
+ var target = starRefs.current[next - 1];
121
+ if (target) target.focus();
122
+ if (next !== selectedValue) setValue(next);
134
123
  };
135
124
  return jsxs("div", _objectSpread2(_objectSpread2({
136
125
  ref: ref,
@@ -141,41 +130,43 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
141
130
  children: [jsx("div", {
142
131
  role: "radiogroup",
143
132
  "aria-label": name || "Rating",
133
+ "aria-readonly": readOnly ? "true" : undefined,
144
134
  className: "inline-flex gap-1",
145
- onMouseLeave: handleMouseLeave,
135
+ onPointerLeave: clearHover,
136
+ onPointerCancel: clearHover,
146
137
  children: Array.from({
147
138
  length: count
148
139
  }, function (_, i) {
149
140
  var starNumber = i + 1;
150
141
  var filled = starNumber <= (displayValue || 0);
142
+ var checked = starNumber === selectedValue;
151
143
  var color = filled ? activeColor : inactiveColor;
152
144
  // Token classes unless a colour override is passed, so brands and dark mode apply (STUDIO-59).
153
145
  var colorClass = color ? "" : filled ? " text-warning-400" : " text-gray-200";
154
146
  return jsx("button", {
147
+ ref: function ref(el) {
148
+ starRefs.current[i] = el;
149
+ },
155
150
  type: "button",
156
151
  role: "radio",
157
- "aria-checked": filled,
152
+ "aria-checked": checked,
158
153
  "aria-label": "".concat(starNumber, " star").concat(starNumber > 1 ? "s" : ""),
159
- tabIndex: readOnly ? -1 : 0,
154
+ tabIndex: !readOnly && starNumber === tabStop ? 0 : -1,
160
155
  onClick: function onClick() {
161
156
  return commit(starNumber);
162
157
  },
163
158
  onKeyDown: function onKeyDown(e) {
164
159
  return handleKeyDown(e, starNumber);
165
160
  },
166
- onMouseEnter: function onMouseEnter() {
167
- return handleMouseEnter(starNumber);
168
- },
169
- onMouseMove: function onMouseMove() {
170
- return handleMouseMove(starNumber);
161
+ onPointerEnter: function onPointerEnter(e) {
162
+ return handlePointerHover(e, starNumber);
171
163
  },
172
- onFocus: function onFocus() {
173
- return handleFocus(starNumber);
164
+ onPointerMove: function onPointerMove(e) {
165
+ return handlePointerHover(e, starNumber);
174
166
  },
175
- onBlur: handleBlur,
176
167
  disabled: readOnly,
177
168
  title: "".concat(starNumber, " of ").concat(count),
178
- className: "p-0 m-0 bg-transparent border-0 inline-flex items-center justify-center leading-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-slate-300 rounded".concat(colorClass),
169
+ className: cn("p-0 m-0 bg-transparent border-0 inline-flex items-center justify-center leading-0 rounded", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500", "transition-colors motion-reduce:transition-none", TOUCH_TARGET, colorClass.trim()),
179
170
  style: color ? {
180
171
  color: color
181
172
  } : undefined,
@@ -3,8 +3,9 @@ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import { forwardRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
 
7
- var _excluded = ["label", "avatarSrc", "avatarAlt", "initials", "size", "onRemove", "onClick", "className", "closeClass", "ariaLabel", "avatarPosition", "avatarClassname", "initialClassname"];
8
+ var _excluded = ["label", "avatarSrc", "avatarAlt", "initials", "size", "onRemove", "onClick", "className", "closeClass", "ariaLabel", "onKeyDown", "avatarPosition", "avatarClassname", "initialClassname"];
8
9
  var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
9
10
  var label = _ref.label,
10
11
  _ref$avatarSrc = _ref.avatarSrc,
@@ -23,8 +24,8 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
23
24
  className = _ref$className === void 0 ? "" : _ref$className,
24
25
  _ref$closeClass = _ref.closeClass,
25
26
  closeClass = _ref$closeClass === void 0 ? "" : _ref$closeClass,
26
- _ref$ariaLabel = _ref.ariaLabel,
27
- ariaLabel = _ref$ariaLabel === void 0 ? "tag" : _ref$ariaLabel,
27
+ ariaLabel = _ref.ariaLabel,
28
+ onKeyDown = _ref.onKeyDown,
28
29
  _ref$avatarPosition = _ref.avatarPosition,
29
30
  avatarPosition = _ref$avatarPosition === void 0 ? "left" : _ref$avatarPosition,
30
31
  _ref$avatarClassname = _ref.avatarClassname,
@@ -63,6 +64,19 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
63
64
  e && e.stopPropagation();
64
65
  onRemove && onRemove({});
65
66
  }
67
+ var clickable = typeof _onClick === "function";
68
+ // Enter / Space activate a clickable tag like a native button. Keys from
69
+ // nested controls (the remove button) are left alone.
70
+ function handleKeyDown(e) {
71
+ onKeyDown && onKeyDown(e);
72
+ if (!clickable || e.defaultPrevented || e.target !== e.currentTarget) return;
73
+ if (e.key === "Enter" || e.key === " ") {
74
+ e.preventDefault();
75
+ _onClick(e);
76
+ }
77
+ }
78
+ var labelText = typeof label === "string" || typeof label === "number" ? String(label) : "";
79
+ var removeLabel = labelText ? "Remove ".concat(labelText) : "Remove";
66
80
  var s = sizes[size] || sizes.md;
67
81
  var paddingClass = showAvatar ? s.paddingWithAvatar : s.paddingNoAvatar;
68
82
  var gapClass = showAvatar ? s.gapWithAvatar : s.gapNoAvatar;
@@ -83,22 +97,25 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
83
97
  });
84
98
  return jsxs("div", _objectSpread2(_objectSpread2({
85
99
  ref: ref,
86
- className: cn("inline-flex items-center rounded-full bg-gray-100 text-primary-800", paddingClass, gapClass, s.text, className),
87
- role: "group",
100
+ className: cn("inline-flex items-center rounded-full bg-gray-100 text-primary-800", paddingClass, gapClass, s.text, clickable && "cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1", className),
101
+ role: clickable ? "button" : "group",
102
+ tabIndex: clickable ? 0 : undefined,
88
103
  "aria-label": ariaLabel,
89
104
  onClick: function onClick(e) {
90
105
  return _onClick && _onClick(e);
91
- }
106
+ },
107
+ onKeyDown: handleKeyDown
92
108
  }, rest), {}, {
93
109
  children: [avatarPosition === "left" && AvatarNode, jsx("div", {
94
110
  className: "truncate max-w-[10rem]",
95
111
  children: label
96
112
  }), avatarPosition === "right" && AvatarNode, onRemove && jsx("button", {
97
113
  type: "button",
98
- "aria-label": "Remove",
114
+ "aria-label": removeLabel,
99
115
  onClick: handleRemove,
100
- className: "inline-flex items-center justify-center rounded-full p-1 hover:bg-gray-200 focus:outline-none ".concat(closeClass),
116
+ className: cn("inline-flex items-center justify-center rounded-full p-1 hover:bg-gray-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", TOUCH_TARGET, closeClass),
101
117
  children: jsx("svg", {
118
+ "aria-hidden": "true",
102
119
  className: "w-3 h-3",
103
120
  viewBox: "0 0 24 24",
104
121
  fill: "none",
@@ -1,8 +1,8 @@
1
1
  'use client';
2
- import { defineProperty as _defineProperty } from '../../../_virtual/_rollupPluginBabelHelpers.js';
3
2
  import { jsxs, jsx } from 'react/jsx-runtime';
4
3
  import 'react';
5
4
  import { cn } from '../../../utils/cn.js';
5
+ import { getHoverClass } from './helpers.js';
6
6
 
7
7
  var DEFAULT_ROW_HOVER = "bg-background";
8
8
  /**
@@ -32,9 +32,7 @@ function ColumnMenu(_ref) {
32
32
  className: "max-h-56 overflow-auto",
33
33
  children: columns.map(function (col) {
34
34
  return jsxs("label", {
35
- className: cn(_defineProperty({
36
- "flex cursor-pointer items-center gap-2 px-2 py-1.5 rounded": true
37
- }, "hover:" + rowHover, true)),
35
+ className: cn("flex cursor-pointer items-center gap-2 px-2 py-1.5 rounded", getHoverClass(rowHover)),
38
36
  children: [jsx("input", {
39
37
  type: "checkbox",
40
38
  checked: col.isVisible !== false,
@@ -1,27 +1,27 @@
1
1
  'use client';
2
- import { defineProperty as _defineProperty } from '../../../_virtual/_rollupPluginBabelHelpers.js';
3
2
  import { jsxs, jsx } from 'react/jsx-runtime';
4
3
  import 'react';
5
4
  import { cn } from '../../../utils/cn.js';
5
+ import { TOUCH_TARGET } from '../../../utils/touchTarget.js';
6
+ import { getHoverClass } from './helpers.js';
6
7
  import { ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight } from 'lucide-react';
7
8
 
8
9
  /**
9
10
  * Pagination controls component for the table
10
11
  * Supports both header (icon-only) and footer (with page numbers) variants
11
12
  */
12
- var DEFAULT_ROW_HOVER = "bg-background";
13
+ var NAV_BUTTON_BASE = "inline-flex items-center justify-center rounded-lg md:rounded-md border border-gray-300 disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
13
14
  // Header pagination (compact, icon-based)
14
15
  function PaginationHeader(_ref) {
15
- var _theme$rowHover;
16
16
  var currentPage = _ref.currentPage,
17
17
  totalPages = _ref.totalPages,
18
18
  onPageChange = _ref.onPageChange,
19
19
  theme = _ref.theme;
20
- var rowHover = (_theme$rowHover = theme === null || theme === void 0 ? void 0 : theme.rowHover) !== null && _theme$rowHover !== void 0 ? _theme$rowHover : DEFAULT_ROW_HOVER;
20
+ var hoverClass = getHoverClass(theme === null || theme === void 0 ? void 0 : theme.rowHover);
21
21
  return jsxs("div", {
22
22
  className: "flex items-center gap-1",
23
23
  children: [totalPages >= 4 && jsx("button", {
24
- className: cn("rounded-lg md:rounded-md border border-gray-300 p-2 disabled:opacity-50", ["hover:" + rowHover]),
24
+ className: cn(NAV_BUTTON_BASE, "p-2", TOUCH_TARGET, hoverClass),
25
25
  disabled: currentPage === 1,
26
26
  onClick: function onClick() {
27
27
  return onPageChange(1);
@@ -31,7 +31,7 @@ function PaginationHeader(_ref) {
31
31
  className: "size-4 md:size-5 text-primary-800"
32
32
  })
33
33
  }), jsx("button", {
34
- className: cn("rounded-lg md:rounded-md border border-gray-300 p-2 disabled:opacity-50", ["hover:" + rowHover]),
34
+ className: cn(NAV_BUTTON_BASE, "p-2", TOUCH_TARGET, hoverClass),
35
35
  disabled: currentPage === 1,
36
36
  onClick: function onClick() {
37
37
  return onPageChange(Math.max(1, currentPage - 1));
@@ -41,7 +41,7 @@ function PaginationHeader(_ref) {
41
41
  className: "size-4 md:size-5 text-primary-800"
42
42
  })
43
43
  }), jsx("button", {
44
- className: cn("rounded-lg md:rounded-md border border-gray-300 p-2 disabled:opacity-50", ["hover:" + rowHover]),
44
+ className: cn(NAV_BUTTON_BASE, "p-2", TOUCH_TARGET, hoverClass),
45
45
  disabled: currentPage === totalPages,
46
46
  onClick: function onClick() {
47
47
  return onPageChange(Math.min(totalPages, currentPage + 1));
@@ -51,7 +51,7 @@ function PaginationHeader(_ref) {
51
51
  className: "size-4 md:size-5 text-primary-800"
52
52
  })
53
53
  }), totalPages >= 4 && jsx("button", {
54
- className: cn("rounded-lg md:rounded-md border border-gray-300 p-2 disabled:opacity-50", ["hover:" + rowHover]),
54
+ className: cn(NAV_BUTTON_BASE, "p-2", TOUCH_TARGET, hoverClass),
55
55
  disabled: currentPage === totalPages,
56
56
  onClick: function onClick() {
57
57
  return onPageChange(totalPages);
@@ -65,13 +65,12 @@ function PaginationHeader(_ref) {
65
65
  }
66
66
  // Footer pagination (with page numbers)
67
67
  function PaginationFooter(_ref2) {
68
- var _theme$rowHover2;
69
68
  var currentPage = _ref2.currentPage,
70
69
  totalPages = _ref2.totalPages,
71
70
  onPageChange = _ref2.onPageChange,
72
71
  isMobileView = _ref2.isMobileView,
73
72
  theme = _ref2.theme;
74
- var rowHover = (_theme$rowHover2 = theme === null || theme === void 0 ? void 0 : theme.rowHover) !== null && _theme$rowHover2 !== void 0 ? _theme$rowHover2 : DEFAULT_ROW_HOVER;
73
+ var hoverClass = getHoverClass(theme === null || theme === void 0 ? void 0 : theme.rowHover);
75
74
  var visiblePageCount = isMobileView ? 5 : 3;
76
75
  return jsx("div", {
77
76
  className: "p-4 border-t",
@@ -83,7 +82,7 @@ function PaginationFooter(_ref2) {
83
82
  }), jsxs("div", {
84
83
  className: "flex items-center gap-1 sm:gap-2",
85
84
  children: [totalPages >= 4 && jsx("button", {
86
- className: cn("rounded-lg md:rounded-md border border-gray-300 disabled:opacity-50", isMobileView ? "p-2" : "px-4 py-2 text-sm", ["hover:" + rowHover]),
85
+ className: cn(NAV_BUTTON_BASE, isMobileView ? "p-2" : "px-4 py-2 text-sm", TOUCH_TARGET, hoverClass),
87
86
  disabled: currentPage === 1,
88
87
  onClick: function onClick() {
89
88
  return onPageChange(1);
@@ -93,7 +92,7 @@ function PaginationFooter(_ref2) {
93
92
  className: "size-4 text-primary-800"
94
93
  }) : "First"
95
94
  }), jsx("button", {
96
- className: cn("rounded-lg md:rounded-md border border-gray-300 disabled:opacity-50", isMobileView ? "p-2" : "px-4 py-2 text-sm", ["hover:" + rowHover]),
95
+ className: cn(NAV_BUTTON_BASE, isMobileView ? "p-2" : "px-4 py-2 text-sm", TOUCH_TARGET, hoverClass),
97
96
  disabled: currentPage === 1,
98
97
  onClick: function onClick() {
99
98
  return onPageChange(Math.max(1, currentPage - 1));
@@ -118,10 +117,8 @@ function PaginationFooter(_ref2) {
118
117
  pageNum = currentPage - 2 + i;
119
118
  }
120
119
  return jsx("button", {
121
- className: cn("h-8 w-8 rounded-lg md:rounded-md text-sm", _defineProperty({
122
- "bg-primary-600 text-white": pageNum === currentPage,
123
- "border border-gray-300": pageNum !== currentPage
124
- }, "hover:" + rowHover, pageNum !== currentPage)),
120
+ className: cn("inline-flex items-center justify-center h-8 w-8 rounded-lg md:rounded-md text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", TOUCH_TARGET, pageNum === currentPage ? "bg-primary-600 text-white" : ["border border-gray-300", hoverClass]),
121
+ "aria-current": pageNum === currentPage ? "page" : undefined,
125
122
  onClick: function onClick() {
126
123
  return onPageChange(pageNum);
127
124
  },
@@ -129,7 +126,7 @@ function PaginationFooter(_ref2) {
129
126
  }, pageNum);
130
127
  })
131
128
  }), jsx("button", {
132
- className: cn("rounded-lg md:rounded-md border border-gray-300 disabled:opacity-50", isMobileView ? "p-2" : "px-4 py-2 text-sm", ["hover:" + rowHover]),
129
+ className: cn(NAV_BUTTON_BASE, isMobileView ? "p-2" : "px-4 py-2 text-sm", TOUCH_TARGET, hoverClass),
133
130
  disabled: currentPage === totalPages,
134
131
  onClick: function onClick() {
135
132
  return onPageChange(Math.min(totalPages, currentPage + 1));
@@ -139,7 +136,7 @@ function PaginationFooter(_ref2) {
139
136
  className: "size-4 text-primary-800"
140
137
  }) : "Next"
141
138
  }), totalPages >= 4 && jsx("button", {
142
- className: cn("rounded-lg md:rounded-md border border-gray-300 disabled:opacity-50", isMobileView ? "p-2" : "px-4 py-2 text-sm", ["hover:" + rowHover]),
139
+ className: cn(NAV_BUTTON_BASE, isMobileView ? "p-2" : "px-4 py-2 text-sm", TOUCH_TARGET, hoverClass),
143
140
  disabled: currentPage === totalPages,
144
141
  onClick: function onClick() {
145
142
  return onPageChange(totalPages);
@@ -8,7 +8,7 @@ import { Plus, RefreshCcw, Save, X, AlertCircle, Search, ChevronUp, ChevronDown,
8
8
  import Select from '../Select.js';
9
9
  import { DEFAULT_ACTIONS } from './constants.js';
10
10
  import { getRowKey, mergeDefaultActions } from './helpers.js';
11
- import { useClickOutside } from './useClickOutside.js';
11
+ import { useClickOutside } from '../../../hooks/useClickOutside.js';
12
12
  import { PaginationHeader, PaginationFooter } from './Pagination.js';
13
13
  import ColumnMenu from './ColumnMenu.js';
14
14
  import MobileFilters from './MobileFilters.js';
@@ -524,8 +524,10 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
524
524
  });
525
525
  }
526
526
  }, []);
527
- useClickOutside([tableContainerRef], interactive && Boolean(editingCell), function () {
528
- setEditingCell(null);
527
+ useClickOutside([tableContainerRef], function () {
528
+ return setEditingCell(null);
529
+ }, {
530
+ enabled: interactive && Boolean(editingCell)
529
531
  });
530
532
  var actionButtonRefs = useRef({});
531
533
  // Row components register their kebab buttons through this callback rather
@@ -603,8 +605,12 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
603
605
  var closeActionMenu = useCallback(function () {
604
606
  return setActiveActionRowKey(null);
605
607
  }, []);
606
- useClickOutside([columnMenuRef, columnToggleBtnRef], showColumnMenu, closeColumnMenu);
607
- useClickOutside([actionMenuRef], activeActionRowKey !== null, closeActionMenu);
608
+ useClickOutside([columnMenuRef, columnToggleBtnRef], closeColumnMenu, {
609
+ enabled: showColumnMenu
610
+ });
611
+ useClickOutside([actionMenuRef], closeActionMenu, {
612
+ enabled: activeActionRowKey !== null
613
+ });
608
614
  // Computed values
609
615
  var activeAnchorEl = useMemo(function () {
610
616
  if (activeActionRowKey === null) return null;
@@ -5,7 +5,7 @@ import React from 'react';
5
5
  import { cn } from '../../../utils/cn.js';
6
6
  import { Trash2, MoreVertical } from 'lucide-react';
7
7
  import ActionMenu from './ActionMenu.js';
8
- import { getRowKey, resolveActions, rowHasDetails, getActionLabelText, getStripeProps } from './helpers.js';
8
+ import { getRowKey, resolveActions, rowHasDetails, getActionLabelText, getHoverClass, getStripeProps } from './helpers.js';
9
9
 
10
10
  /**
11
11
  * Desktop table row component
@@ -96,7 +96,7 @@ function TableRow(_ref) {
96
96
  return jsxs(React.Fragment, {
97
97
  children: [jsxs("tr", {
98
98
  style: stripeProps.style,
99
- className: cn(stripeProps.className, theme !== null && theme !== void 0 && theme.rowHover ? ["hover:" + theme.rowHover] : [], {
99
+ className: cn(stripeProps.className, theme !== null && theme !== void 0 && theme.rowHover ? getHoverClass(theme.rowHover) : [], {
100
100
  "cursor-pointer": !!onRowClick,
101
101
  "bg-primary-50": isSelected,
102
102
  "bg-primary-50/20": isRowActive && !isSelected,
@@ -135,5 +135,37 @@ var resolveActions = function resolveActions(actions, row, index) {
135
135
  menuActions: menuActions
136
136
  };
137
137
  };
138
+ /**
139
+ * Tailwind only generates classes it can see as complete literals in source, so a
140
+ * runtime-built `"hover:" + token` is purged from the compiled CSS. Bare `rowHover`
141
+ * tokens (the shipped defaults, past and present) map to static literals here.
142
+ */
143
+ var HOVER_BG_CLASSES = {
144
+ "bg-background": "hover:bg-background",
145
+ "bg-surface": "hover:bg-surface",
146
+ "bg-gray-50": "hover:bg-gray-50",
147
+ "bg-gray-100": "hover:bg-gray-100",
148
+ "bg-primary-50": "hover:bg-primary-50",
149
+ "bg-primary-100": "hover:bg-primary-100"
150
+ };
151
+ var DEFAULT_HOVER_CLASS = HOVER_BG_CLASSES["bg-background"];
152
+ /**
153
+ * Resolve `theme.rowHover` to a full hover class string.
154
+ * - A value that already contains a `hover:` variant is used as-is (preferred: pass
155
+ * the full literal, e.g. `"hover:bg-gray-100"`, so your Tailwind build sees it).
156
+ * - A bare token from the static map resolves to its literal.
157
+ * - Any other bare token falls back to a `hover:` prefix for backward compatibility;
158
+ * it only works if the consumer's own Tailwind build generates that class.
159
+ */
160
+ var getHoverClass = function getHoverClass(rowHover) {
161
+ if (!rowHover || typeof rowHover !== "string") return DEFAULT_HOVER_CLASS;
162
+ var value = rowHover.trim();
163
+ if (!value) return DEFAULT_HOVER_CLASS;
164
+ if (/(^|\s)hover:/.test(value)) return value;
165
+ if (HOVER_BG_CLASSES[value]) return HOVER_BG_CLASSES[value];
166
+ return value.split(/\s+/).map(function (cls) {
167
+ return "hover:".concat(cls);
168
+ }).join(" ");
169
+ };
138
170
 
139
- export { getActionLabelText, getRowKey, getStripeProps, mergeDefaultActions, resolveActions, rowHasDetails };
171
+ export { DEFAULT_HOVER_CLASS, getActionLabelText, getHoverClass, getRowKey, getStripeProps, mergeDefaultActions, resolveActions, rowHasDetails };
@@ -156,7 +156,7 @@ function Tab(_ref3) {
156
156
  var idx = typeof index === "number" ? index : -1;
157
157
  var selected = idx === activeIndex;
158
158
  var id = tabId(baseId, idx);
159
- var base = "inline-flex items-center gap-2 outline-none select-none";
159
+ var base = "inline-flex items-center gap-2 outline-none select-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1";
160
160
  var sizeClasses = {
161
161
  sm: "px-2 py-1 text-sm",
162
162
  md: "px-3 py-2 text-sm",
@@ -269,15 +269,16 @@ function Panel(_ref5) {
269
269
  // Remember that the panel has been shown once (adjust-during-render pattern).
270
270
  if (isActive && !wasActive) setWasActive(true);
271
271
  if (!wasActive && !isActive) return null;
272
- var base = "w-full transition-all duration-200 ease-in-out";
273
- var activeClasses = animated ? "opacity-100 translate-y-0 pointer-events-auto relative" : "opacity-100 pointer-events-auto relative";
274
- var inactiveClasses = animated ? "opacity-0 -translate-y-2 pointer-events-none absolute inset-0" : "hidden pointer-events-none absolute inset-0";
272
+ // Inactive panels are `hidden` (display: none), so a CSS *transition* can
273
+ // never run. A keyframe animation does replay each time the panel leaves
274
+ // display:none, so the active panel fades in on every switch.
275
+ var activeClasses = animated ? "animate-fade-in motion-reduce:animate-none" : "";
275
276
  return jsx("div", {
276
277
  id: panelId(baseId, index),
277
278
  role: "tabpanel",
278
279
  "aria-labelledby": tabId(baseId, index),
279
280
  hidden: !isActive,
280
- className: "".concat(base, " ").concat(isActive ? activeClasses : inactiveClasses, " ").concat(className),
281
+ className: cn("w-full", isActive && activeClasses, className),
281
282
  children: children
282
283
  });
283
284
  }