@dreamtree-org/twreact-ui 1.3.9 → 1.3.11

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 (128) hide show
  1. package/README.md +32 -0
  2. package/ai-skills/dreamtree-ui.md +12 -7
  3. package/dist/ai-schema.json +156 -29
  4. package/dist/charts/index.js +195 -66
  5. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  6. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  7. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  8. package/dist/components/charts/useChartLayout.d.ts +25 -0
  9. package/dist/components/charts/useChartLayout.d.ts.map +1 -0
  10. package/dist/components/core/Accordion.d.ts.map +1 -1
  11. package/dist/components/core/Button.d.ts.map +1 -1
  12. package/dist/components/core/Checkbox.d.ts.map +1 -1
  13. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  14. package/dist/components/core/DatePicker.d.ts.map +1 -1
  15. package/dist/components/core/Form.d.ts.map +1 -1
  16. package/dist/components/core/Input.d.ts.map +1 -1
  17. package/dist/components/core/Loader.d.ts.map +1 -1
  18. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  19. package/dist/components/core/PriceRangePicker.d.ts +0 -29
  20. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  21. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  22. package/dist/components/core/Radio.d.ts.map +1 -1
  23. package/dist/components/core/Rate.d.ts.map +1 -1
  24. package/dist/components/core/RoundedTag.d.ts.map +1 -1
  25. package/dist/components/core/Skeleton.d.ts.map +1 -1
  26. package/dist/components/core/Slider.d.ts.map +1 -1
  27. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  28. package/dist/components/core/Switch.d.ts.map +1 -1
  29. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  30. package/dist/components/core/Table/Pagination.d.ts.map +1 -1
  31. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  32. package/dist/components/core/Table/helpers.d.ts +2 -0
  33. package/dist/components/core/Table/helpers.d.ts.map +1 -1
  34. package/dist/components/core/Tabs.d.ts +2 -5
  35. package/dist/components/core/Tabs.d.ts.map +1 -1
  36. package/dist/components/core/TextToSpeech.d.ts +2 -7
  37. package/dist/components/core/TextToSpeech.d.ts.map +1 -1
  38. package/dist/components/core/Textarea.d.ts.map +1 -1
  39. package/dist/components/core/Tooltip.d.ts.map +1 -1
  40. package/dist/components/feedback/Alert.d.ts.map +1 -1
  41. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  42. package/dist/components/feedback/Toast.d.ts.map +1 -1
  43. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  44. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  45. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  46. package/dist/components/utility/Avatar.d.ts +8 -0
  47. package/dist/components/utility/Avatar.d.ts.map +1 -1
  48. package/dist/components/utility/AvatarGroup.d.ts +4 -0
  49. package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
  50. package/dist/components/utility/Badge.d.ts.map +1 -1
  51. package/dist/components/utility/Card.d.ts.map +1 -1
  52. package/dist/components/utility/Carousel.d.ts.map +1 -1
  53. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  54. package/dist/components/utility/Pagination.d.ts.map +1 -1
  55. package/dist/components/utility/Sortable.d.ts.map +1 -1
  56. package/dist/components/utility/Stepper.d.ts.map +1 -1
  57. package/dist/esm/components/blocks/AppShell.js +24 -18
  58. package/dist/esm/components/blocks/EmptyState.js +1 -1
  59. package/dist/esm/components/blocks/ErrorState.js +1 -1
  60. package/dist/esm/components/blocks/LoadingState.js +4 -4
  61. package/dist/esm/components/blocks/StatCard.js +6 -6
  62. package/dist/esm/components/blocks/StatusPill.js +5 -8
  63. package/dist/esm/components/charts/CartesianChart.js +85 -62
  64. package/dist/esm/components/charts/ChartFrame.js +12 -5
  65. package/dist/esm/components/charts/useChartLayout.js +105 -0
  66. package/dist/esm/components/core/Accordion.js +40 -9
  67. package/dist/esm/components/core/Button.js +49 -25
  68. package/dist/esm/components/core/Checkbox.js +71 -45
  69. package/dist/esm/components/core/ColorPicker.js +49 -48
  70. package/dist/esm/components/core/DatePicker.js +97 -58
  71. package/dist/esm/components/core/Form.js +35 -8
  72. package/dist/esm/components/core/Input.js +89 -20
  73. package/dist/esm/components/core/Loader.js +30 -44
  74. package/dist/esm/components/core/LocationPicker.js +82 -16
  75. package/dist/esm/components/core/PriceRangePicker.js +205 -218
  76. package/dist/esm/components/core/ProgressBar.js +8 -12
  77. package/dist/esm/components/core/Radio.js +43 -15
  78. package/dist/esm/components/core/Rate.js +109 -50
  79. package/dist/esm/components/core/RoundedTag.js +27 -10
  80. package/dist/esm/components/core/Skeleton.js +13 -14
  81. package/dist/esm/components/core/Slider.js +24 -6
  82. package/dist/esm/components/core/SpeechToText.js +68 -16
  83. package/dist/esm/components/core/Switch.js +88 -16
  84. package/dist/esm/components/core/Table/ColumnMenu.js +2 -4
  85. package/dist/esm/components/core/Table/Pagination.js +15 -18
  86. package/dist/esm/components/core/Table/TableRow.js +2 -2
  87. package/dist/esm/components/core/Table/helpers.js +33 -1
  88. package/dist/esm/components/core/Tabs.js +185 -37
  89. package/dist/esm/components/core/TextToSpeech.js +121 -28
  90. package/dist/esm/components/core/Textarea.js +10 -2
  91. package/dist/esm/components/core/Tooltip.js +100 -28
  92. package/dist/esm/components/feedback/Alert.js +21 -11
  93. package/dist/esm/components/feedback/Dialog.js +44 -100
  94. package/dist/esm/components/feedback/Toast.js +24 -5
  95. package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
  96. package/dist/esm/components/navigation/FootNav.js +102 -13
  97. package/dist/esm/components/navigation/Sidebar.js +13 -61
  98. package/dist/esm/components/utility/Avatar.js +37 -22
  99. package/dist/esm/components/utility/AvatarGroup.js +56 -0
  100. package/dist/esm/components/utility/Badge.js +43 -23
  101. package/dist/esm/components/utility/Card.js +32 -8
  102. package/dist/esm/components/utility/Carousel.js +165 -38
  103. package/dist/esm/components/utility/FileUpload.js +106 -31
  104. package/dist/esm/components/utility/Pagination.js +33 -15
  105. package/dist/esm/components/utility/Sortable.js +6 -2
  106. package/dist/esm/components/utility/Stepper.js +110 -47
  107. package/dist/esm/hooks/useFocusTrap.js +10 -2
  108. package/dist/esm/hooks/useSliderValues.js +8 -2
  109. package/dist/esm/hooks/useSortable.js +107 -5
  110. package/dist/esm/index.js +1 -0
  111. package/dist/esm/utils/touchTarget.js +4 -1
  112. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  113. package/dist/hooks/useSliderValues.d.ts +6 -2
  114. package/dist/hooks/useSliderValues.d.ts.map +1 -1
  115. package/dist/hooks/useSortable.d.ts +11 -1
  116. package/dist/hooks/useSortable.d.ts.map +1 -1
  117. package/dist/index.css +1 -1
  118. package/dist/index.d.ts +3 -2
  119. package/dist/index.esm.css +1 -1
  120. package/dist/index.esm.js +3734 -2174
  121. package/dist/index.js +3732 -2171
  122. package/dist/types/charts.d.ts +3 -1
  123. package/dist/types/charts.d.ts.map +1 -1
  124. package/dist/types/index.d.ts +64 -9
  125. package/dist/types/index.d.ts.map +1 -1
  126. package/doc/README.md +1 -0
  127. package/mcp/catalog.snapshot.json +350 -121
  128. package/package.json +1 -1
@@ -1,10 +1,19 @@
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 React, { forwardRef, useState, useRef, useId, useEffect, createContext, useContext } from 'react';
4
+ import React, { forwardRef, useState, useRef, useId, useEffect, createContext, useContext, useLayoutEffect } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
6
 
7
- var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation", "size", "variant", "animated", "className"];
7
+ var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation", "size", "variant", "animated", "swipeable", "className"],
8
+ _excluded2 = ["children", "className"];
9
+ // useLayoutEffect warns during SSR; fall back to useEffect on the server.
10
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
11
+ // Minimum horizontal travel (px) for a swipe to change tabs, and how much more
12
+ // horizontal than vertical it must be, so vertical page scrolls never switch tabs.
13
+ var SWIPE_THRESHOLD = 50;
14
+ var SWIPE_DOMINANCE = 1.5;
15
+ // Variants whose active state is drawn by the sliding indicator (horizontal only).
16
+ var INDICATOR_VARIANTS = ["line", "segmented"];
8
17
  /*
9
18
  Tabs.jsx
10
19
  - Default export: Tabs (provider wrapper)
@@ -14,7 +23,10 @@ var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation",
14
23
  - Controlled / uncontrolled usage
15
24
  - Keyboard navigation (Arrow keys, Home, End)
16
25
  - ARIA attributes for accessibility
17
- - Variants: "line" | "pills" | "unstyled"
26
+ - Variants: "line" | "pills" | "segmented" | "unstyled"
27
+ - Horizontal lists scroll with snap; the active tab is kept in view
28
+ - Animated active indicator for "line" / "segmented" (off under reduced motion)
29
+ - Optional `swipeable` panels (touch / pen pointer swipes)
18
30
  - Sizes: "sm" | "md" | "lg"
19
31
  - Simple fade animation
20
32
  - Tailwind CSS only
@@ -60,6 +72,8 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
60
72
  variant = _ref$variant === void 0 ? "line" : _ref$variant,
61
73
  _ref$animated = _ref.animated,
62
74
  animated = _ref$animated === void 0 ? true : _ref$animated,
75
+ _ref$swipeable = _ref.swipeable,
76
+ swipeable = _ref$swipeable === void 0 ? false : _ref$swipeable,
63
77
  _ref$className = _ref.className,
64
78
  className = _ref$className === void 0 ? "" : _ref$className,
65
79
  rest = _objectWithoutProperties(_ref, _excluded);
@@ -71,6 +85,12 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
71
85
  var activeIndex = isControlled ? controlledIndex : internalIndex;
72
86
  var tabsRef = useRef([]); // DOM refs for keyboard navigation
73
87
  var baseId = useId();
88
+ // Set by TabList once the sliding indicator has been measured; until then
89
+ // (SSR, first paint) tabs draw their own active styling.
90
+ var _useState3 = useState(false),
91
+ _useState4 = _slicedToArray(_useState3, 2),
92
+ indicatorReady = _useState4[0],
93
+ setIndicatorReady = _useState4[1];
74
94
  var changeTab = function changeTab(idx) {
75
95
  if (!isControlled) setActiveIndex(idx);
76
96
  onChange === null || onChange === void 0 || onChange(idx);
@@ -87,7 +107,10 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
87
107
  orientation: orientation,
88
108
  size: size,
89
109
  variant: variant,
90
- animated: animated
110
+ animated: animated,
111
+ swipeable: swipeable,
112
+ indicatorReady: indicatorReady,
113
+ setIndicatorReady: setIndicatorReady
91
114
  };
92
115
  return jsx(TabsContext.Provider, {
93
116
  value: value,
@@ -101,25 +124,96 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
101
124
  });
102
125
  Tabs.displayName = "Tabs";
103
126
  /* ------------------------- TabList ------------------------- */
104
- function TabList(_ref2) {
127
+ var TabList = /*#__PURE__*/forwardRef(function TabList(_ref2, ref) {
105
128
  var children = _ref2.children,
106
129
  _ref2$className = _ref2.className,
107
- className = _ref2$className === void 0 ? "" : _ref2$className;
130
+ className = _ref2$className === void 0 ? "" : _ref2$className,
131
+ rest = _objectWithoutProperties(_ref2, _excluded2);
108
132
  var _useTabsContext = useTabsContext("Tabs.List"),
109
- orientation = _useTabsContext.orientation;
110
- return jsx("div", {
133
+ orientation = _useTabsContext.orientation,
134
+ variant = _useTabsContext.variant,
135
+ activeIndex = _useTabsContext.activeIndex,
136
+ tabsRef = _useTabsContext.tabsRef,
137
+ indicatorReady = _useTabsContext.indicatorReady,
138
+ setIndicatorReady = _useTabsContext.setIndicatorReady;
139
+ var listRef = useRef(null);
140
+ var _useState5 = useState(null),
141
+ _useState6 = _slicedToArray(_useState5, 2),
142
+ indicator = _useState6[0],
143
+ setIndicator = _useState6[1];
144
+ var horizontal = orientation !== "vertical";
145
+ var hasIndicator = horizontal && INDICATOR_VARIANTS.includes(variant);
146
+ var childCount = React.Children.count(children);
147
+ var setRefs = function setRefs(node) {
148
+ listRef.current = node;
149
+ if (typeof ref === "function") ref(node);else if (ref) ref.current = node;
150
+ };
151
+ // Measure the active tab: position the indicator and scroll the tab into view.
152
+ useIsomorphicLayoutEffect(function () {
153
+ var list = listRef.current;
154
+ var tab = tabsRef.current[activeIndex];
155
+ if (!list || !tab) return undefined;
156
+ var measure = function measure() {
157
+ if (horizontal) {
158
+ var left = tab.offsetLeft;
159
+ var right = left + tab.offsetWidth;
160
+ if (left < list.scrollLeft) list.scrollLeft = left;else if (right > list.scrollLeft + list.clientWidth) list.scrollLeft = right - list.clientWidth;
161
+ }
162
+ // jsdom / display:none report 0; keep the tabs' own active styling then.
163
+ if (!hasIndicator || !tab.offsetWidth) return;
164
+ setIndicator({
165
+ left: tab.offsetLeft,
166
+ top: tab.offsetTop,
167
+ width: tab.offsetWidth,
168
+ height: tab.offsetHeight
169
+ });
170
+ setIndicatorReady(true);
171
+ };
172
+ measure();
173
+ if (typeof ResizeObserver === "undefined") return undefined;
174
+ var ro = new ResizeObserver(measure);
175
+ ro.observe(list);
176
+ ro.observe(tab);
177
+ return function () {
178
+ return ro.disconnect();
179
+ };
180
+ }, [activeIndex, hasIndicator, horizontal, childCount]); // eslint-disable-line react-hooks/exhaustive-deps
181
+ // Leaving an indicator variant hands the active styling back to the tabs.
182
+ useEffect(function () {
183
+ if (!hasIndicator && indicatorReady) setIndicatorReady(false);
184
+ }, [hasIndicator, indicatorReady, setIndicatorReady]);
185
+ var showIndicator = hasIndicator && indicatorReady && indicator;
186
+ return jsxs("div", _objectSpread2(_objectSpread2({
187
+ ref: setRefs,
111
188
  role: "tablist",
112
189
  "aria-orientation": orientation,
113
- className: "flex ".concat(orientation === "vertical" ? "flex-col" : "flex-row", " gap-1 ").concat(className),
114
- children: React.Children.map(children, function (child, i) {
190
+ className: cn("relative flex gap-1", horizontal ?
191
+ // Scroll with snap, scrollbar hidden; native smooth scrolling unless reduced motion.
192
+ "flex-row overflow-x-auto snap-x snap-mandatory scroll-smooth motion-reduce:scroll-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" : "flex-col", variant === "segmented" && "rounded-lg bg-gray-100 p-1 dark:bg-gray-800", className)
193
+ }, rest), {}, {
194
+ children: [showIndicator && jsx("span", {
195
+ "aria-hidden": "true",
196
+ "data-tabs-indicator": "",
197
+ className: cn("pointer-events-none absolute left-0 top-0", "transition-[transform,width,height] duration-base ease-standard motion-reduce:transition-none", variant === "line" ? "bg-primary-500" : "rounded-md bg-surface shadow-sm"),
198
+ style: variant === "line" ? {
199
+ width: indicator.width,
200
+ height: 2,
201
+ transform: "translate(".concat(indicator.left, "px, ").concat(indicator.top + indicator.height - 2, "px)")
202
+ } : {
203
+ width: indicator.width,
204
+ height: indicator.height,
205
+ transform: "translate(".concat(indicator.left, "px, ").concat(indicator.top, "px)")
206
+ }
207
+ }), React.Children.map(children, function (child, i) {
115
208
  var _child$props$index;
116
209
  if (! /*#__PURE__*/React.isValidElement(child)) return child;
117
210
  return /*#__PURE__*/React.cloneElement(child, {
118
211
  index: (_child$props$index = child.props.index) !== null && _child$props$index !== void 0 ? _child$props$index : i
119
212
  });
120
- })
121
- });
122
- }
213
+ })]
214
+ }));
215
+ });
216
+ TabList.displayName = "Tabs.List";
123
217
  /* ------------------------- Tab ------------------------- */
124
218
  function Tab(_ref3) {
125
219
  var children = _ref3.children,
@@ -139,32 +233,45 @@ function Tab(_ref3) {
139
233
  tabsRef = _useTabsContext2.tabsRef,
140
234
  baseId = _useTabsContext2.baseId,
141
235
  size = _useTabsContext2.size,
142
- variant = _useTabsContext2.variant;
236
+ variant = _useTabsContext2.variant,
237
+ orientation = _useTabsContext2.orientation,
238
+ indicatorReady = _useTabsContext2.indicatorReady;
143
239
  var internalRef = useRef(null);
144
240
  // If the user didn't provide `index` we derive from DOM position by registering nulls until mount.
145
241
  // However it's recommended to always keep Tabs.Tab order consistent with Panels.
146
- useEffect(function () {
242
+ // Registered from a ref callback (runs at commit, before any layout effect) so
243
+ // Tabs.List can measure the active tab on mount without a flash.
244
+ var setTabRef = function setTabRef(el) {
245
+ internalRef.current = el;
246
+ if (!el) return;
147
247
  // find index in tabsRef if index not provided
148
- var idx = typeof index === "number" ? index : tabsRef.current.findIndex(function (el) {
149
- return el === internalRef.current;
248
+ var slot = typeof index === "number" ? index : tabsRef.current.findIndex(function (node) {
249
+ return node === el;
150
250
  });
151
- if (idx === -1) idx = tabsRef.current.length; // fallback
152
- registerTab(internalRef.current, idx);
153
- // ensure the tab has the correct array slot
154
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
251
+ if (slot === -1) slot = tabsRef.current.length; // fallback
252
+ registerTab(el, slot);
253
+ };
155
254
  // TabList always injects `index`; a Tab rendered outside it is never selected.
156
255
  var idx = typeof index === "number" ? index : -1;
157
256
  var selected = idx === activeIndex;
158
257
  var id = tabId(baseId, idx);
159
- var base = "inline-flex items-center gap-2 outline-none select-none";
258
+ var horizontal = orientation !== "vertical";
259
+ // Once the sliding indicator is measured it draws the active state; until then
260
+ // (SSR / first paint) the tab draws it itself so nothing flashes.
261
+ var indicatorDraws = indicatorReady && horizontal && INDICATOR_VARIANTS.includes(variant);
262
+ var base = cn("relative inline-flex items-center gap-2 whitespace-nowrap outline-none select-none", "focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1", "transition-colors duration-fast motion-reduce:transition-none", "disabled:cursor-not-allowed disabled:opacity-50",
263
+ // The scrolling list clips outside rings, so horizontal tabs draw theirs inset.
264
+ horizontal && "shrink-0 snap-start focus-visible:ring-inset focus-visible:ring-offset-0");
160
265
  var sizeClasses = {
161
266
  sm: "px-2 py-1 text-sm",
162
267
  md: "px-3 py-2 text-sm",
163
268
  lg: "px-4 py-3 text-base"
164
269
  };
165
270
  var variantClasses = {
166
- line: selected ? "border-b-2 border-primary-500" : "border-b-2 border-transparent hover:border-gray-200",
167
- pills: selected ? "bg-primary-600 text-white rounded-full shadow-sm" : "bg-transparent rounded-full hover:bg-gray-100",
271
+ line: selected ? cn("border-b-2", indicatorDraws ? "border-transparent" : "border-primary-500") : "border-b-2 border-transparent hover:border-gray-200 dark:hover:border-gray-700",
272
+ pills: selected ? "bg-primary-600 text-white rounded-full shadow-sm" : "bg-transparent rounded-full hover:bg-gray-100 dark:hover:bg-gray-800",
273
+ // iOS-style segmented control: equal-width segments on a tinted track.
274
+ segmented: cn("z-10 flex-1 justify-center rounded-md font-medium", selected ? cn("text-foreground", !indicatorDraws && "bg-surface shadow-sm") : "text-muted hover:text-foreground"),
168
275
  unstyled: ""
169
276
  };
170
277
  var computedClass = cn(base, sizeClasses[size] || sizeClasses.md, variantClasses[variant], tabClassName, className);
@@ -208,7 +315,7 @@ function Tab(_ref3) {
208
315
  }
209
316
  };
210
317
  return jsxs("button", {
211
- ref: internalRef,
318
+ ref: setTabRef,
212
319
  id: id,
213
320
  role: "tab",
214
321
  "aria-selected": selected,
@@ -237,9 +344,49 @@ function Panels(_ref4) {
237
344
  _ref4$className = _ref4.className,
238
345
  className = _ref4$className === void 0 ? "" : _ref4$className;
239
346
  var _useTabsContext3 = useTabsContext("Tabs.Panels"),
240
- animated = _useTabsContext3.animated;
241
- return jsx("div", {
242
- className: "relative w-full ".concat(className),
347
+ animated = _useTabsContext3.animated,
348
+ swipeable = _useTabsContext3.swipeable,
349
+ activeIndex = _useTabsContext3.activeIndex,
350
+ changeTab = _useTabsContext3.changeTab,
351
+ tabsRef = _useTabsContext3.tabsRef;
352
+ var start = useRef(null);
353
+ // Next enabled tab in `dir`, or -1 at either end (no wrap-around on swipe).
354
+ var neighbour = function neighbour(dir) {
355
+ var tabs = tabsRef.current;
356
+ for (var i = activeIndex + dir; i >= 0 && i < tabs.length; i += dir) {
357
+ if (tabs[i] && !tabs[i].disabled) return i;
358
+ }
359
+ return -1;
360
+ };
361
+ var swipeHandlers = swipeable ? {
362
+ onPointerDown: function onPointerDown(e) {
363
+ // Touch and pen only: a mouse drag selects text instead.
364
+ if (e.pointerType === "mouse" || e.isPrimary === false) return;
365
+ start.current = {
366
+ x: e.clientX,
367
+ y: e.clientY
368
+ };
369
+ },
370
+ onPointerUp: function onPointerUp(e) {
371
+ var from = start.current;
372
+ start.current = null;
373
+ if (!from) return;
374
+ var dx = e.clientX - from.x;
375
+ var dy = e.clientY - from.y;
376
+ if (!Number.isFinite(dx) || !Number.isFinite(dy)) return;
377
+ if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) < Math.abs(dy) * SWIPE_DOMINANCE) return;
378
+ // Swipe left -> next tab, swipe right -> previous tab.
379
+ var next = neighbour(dx < 0 ? 1 : -1);
380
+ if (next !== -1) changeTab(next);
381
+ },
382
+ onPointerCancel: function onPointerCancel() {
383
+ start.current = null;
384
+ }
385
+ } : {};
386
+ return jsx("div", _objectSpread2(_objectSpread2({
387
+ // touch-pan-y: the browser keeps vertical scrolling; horizontal moves reach us.
388
+ className: cn("relative w-full", swipeable && "touch-pan-y", className)
389
+ }, swipeHandlers), {}, {
243
390
  children: React.Children.map(children, function (child, i) {
244
391
  var _child$props$index2;
245
392
  // inject index if missing
@@ -249,7 +396,7 @@ function Panels(_ref4) {
249
396
  animated: animated
250
397
  });
251
398
  })
252
- });
399
+ }));
253
400
  }
254
401
  function Panel(_ref5) {
255
402
  var children = _ref5.children,
@@ -262,22 +409,23 @@ function Panel(_ref5) {
262
409
  activeIndex = _useTabsContext4.activeIndex,
263
410
  baseId = _useTabsContext4.baseId;
264
411
  var isActive = index === activeIndex;
265
- var _useState3 = useState(isActive),
266
- _useState4 = _slicedToArray(_useState3, 2),
267
- wasActive = _useState4[0],
268
- setWasActive = _useState4[1];
412
+ var _useState7 = useState(isActive),
413
+ _useState8 = _slicedToArray(_useState7, 2),
414
+ wasActive = _useState8[0],
415
+ setWasActive = _useState8[1];
269
416
  // Remember that the panel has been shown once (adjust-during-render pattern).
270
417
  if (isActive && !wasActive) setWasActive(true);
271
418
  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";
419
+ // Inactive panels are `hidden` (display: none), so a CSS *transition* can
420
+ // never run. A keyframe animation does replay each time the panel leaves
421
+ // display:none, so the active panel fades in on every switch.
422
+ var activeClasses = animated ? "animate-fade-in motion-reduce:animate-none" : "";
275
423
  return jsx("div", {
276
424
  id: panelId(baseId, index),
277
425
  role: "tabpanel",
278
426
  "aria-labelledby": tabId(baseId, index),
279
427
  hidden: !isActive,
280
- className: "".concat(base, " ").concat(isActive ? activeClasses : inactiveClasses, " ").concat(className),
428
+ className: cn("w-full", isActive && activeClasses, className),
281
429
  children: children
282
430
  });
283
431
  }
@@ -1,9 +1,19 @@
1
1
  'use client';
2
- import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsx } from 'react/jsx-runtime';
4
- import { useState } from 'react';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { forwardRef, useState, useRef, useEffect, useCallback } from 'react';
5
+ import { Square, Volume2 } from 'lucide-react';
6
+ import { cn } from '../../utils/cn.js';
5
7
 
6
- var TextToSpeech = function TextToSpeech(_ref) {
8
+ var _excluded = ["text", "rate", "pitch", "onSpeak", "onError", "renderButton", "disabled", "className"];
9
+ var UNSUPPORTED_MESSAGE = "Text-to-speech is not supported in this browser";
10
+ // `interrupted` / `canceled` fire when playback is stopped on purpose (our own
11
+ // cancel(), or another utterance taking over) — not failures worth surfacing.
12
+ var NON_FATAL_ERRORS = new Set(["interrupted", "canceled"]);
13
+ var detectSupport = function detectSupport() {
14
+ return typeof window !== "undefined" && "speechSynthesis" in window && !!window.speechSynthesis && typeof window.SpeechSynthesisUtterance !== "undefined";
15
+ };
16
+ var TextToSpeech = /*#__PURE__*/forwardRef(function (_ref, ref) {
7
17
  var _ref$text = _ref.text,
8
18
  text = _ref$text === void 0 ? "" : _ref$text,
9
19
  _ref$rate = _ref.rate,
@@ -12,41 +22,124 @@ var TextToSpeech = function TextToSpeech(_ref) {
12
22
  pitch = _ref$pitch === void 0 ? 0.5 : _ref$pitch,
13
23
  _ref$onSpeak = _ref.onSpeak,
14
24
  onSpeak = _ref$onSpeak === void 0 ? function () {} : _ref$onSpeak,
15
- renderButton = _ref.renderButton;
16
- var _useState = useState(text),
17
- _useState2 = _slicedToArray(_useState, 1),
18
- inputText = _useState2[0];
19
- var _useState3 = useState(false),
25
+ _ref$onError = _ref.onError,
26
+ onError = _ref$onError === void 0 ? function () {} : _ref$onError,
27
+ renderButton = _ref.renderButton,
28
+ _ref$disabled = _ref.disabled,
29
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
30
+ className = _ref.className,
31
+ rest = _objectWithoutProperties(_ref, _excluded);
32
+ var _useState = useState(false),
33
+ _useState2 = _slicedToArray(_useState, 2),
34
+ isSpeaking = _useState2[0],
35
+ setIsSpeaking = _useState2[1];
36
+ var _useState3 = useState(true),
20
37
  _useState4 = _slicedToArray(_useState3, 2),
21
- isSpeaking = _useState4[0],
22
- setIsSpeaking = _useState4[1];
23
- var handleSpeak = function handleSpeak() {
24
- var utterance = new SpeechSynthesisUtterance(inputText);
38
+ isSupported = _useState4[0],
39
+ setIsSupported = _useState4[1];
40
+ var isMountedRef = useRef(true);
41
+ var utteranceRef = useRef(null);
42
+ useEffect(function () {
43
+ setIsSupported(detectSupport());
44
+ }, []);
45
+ var stop = useCallback(function () {
46
+ if (detectSupport()) window.speechSynthesis.cancel();
47
+ utteranceRef.current = null;
48
+ setIsSpeaking(false);
49
+ }, []);
50
+ var speak = useCallback(function () {
51
+ if (!detectSupport()) {
52
+ setIsSupported(false);
53
+ return;
54
+ }
55
+ // Always speak the *current* `text` prop, not a value captured on mount.
56
+ var spokenText = text;
57
+ var utterance = new window.SpeechSynthesisUtterance(spokenText);
25
58
  utterance.rate = rate;
26
59
  utterance.pitch = pitch;
60
+ var isCurrent = function isCurrent() {
61
+ return isMountedRef.current && utteranceRef.current === utterance;
62
+ };
27
63
  utterance.onstart = function () {
28
- onSpeak(inputText);
64
+ if (!isCurrent()) return;
65
+ onSpeak(spokenText);
29
66
  setIsSpeaking(true);
30
67
  };
31
68
  utterance.onend = function () {
69
+ if (!isCurrent()) return;
70
+ utteranceRef.current = null;
32
71
  setIsSpeaking(false);
33
72
  };
34
- if (isSpeaking) {
35
- window.speechSynthesis.cancel();
73
+ utterance.onerror = function (event) {
74
+ if (!isCurrent()) return;
75
+ utteranceRef.current = null;
36
76
  setIsSpeaking(false);
37
- } else {
77
+ if (!NON_FATAL_ERRORS.has(event === null || event === void 0 ? void 0 : event.error)) {
78
+ onError(new Error((event === null || event === void 0 ? void 0 : event.error) || "Speech synthesis error"));
79
+ }
80
+ };
81
+ utteranceRef.current = utterance;
82
+ try {
38
83
  window.speechSynthesis.speak(utterance);
84
+ } catch (err) {
85
+ utteranceRef.current = null;
86
+ setIsSpeaking(false);
87
+ onError(err);
39
88
  }
40
- };
41
- return jsx("div", {
42
- children: renderButton && typeof renderButton === "function" ? renderButton({
43
- onClick: handleSpeak,
44
- isSpeaking: isSpeaking
45
- }) : jsx("button", {
46
- onClick: handleSpeak,
47
- children: isSpeaking ? "Stop" : "Speak"
48
- })
49
- });
50
- };
89
+ }, [text, rate, pitch, onSpeak, onError]);
90
+ var isDisabled = disabled || !isSupported;
91
+ var handleClick = useCallback(function () {
92
+ if (isDisabled) return;
93
+ if (isSpeaking) stop();else speak();
94
+ }, [isDisabled, isSpeaking, speak, stop]);
95
+ // Stop our own speech when unmounting so it doesn't outlive the component.
96
+ useEffect(function () {
97
+ isMountedRef.current = true;
98
+ return function () {
99
+ isMountedRef.current = false;
100
+ if (utteranceRef.current && detectSupport()) {
101
+ utteranceRef.current = null;
102
+ window.speechSynthesis.cancel();
103
+ }
104
+ };
105
+ }, []);
106
+ if (typeof renderButton === "function") {
107
+ // speak/stop only touch refs when invoked from event handlers, never while
108
+ // renderButton itself is rendering.
109
+ // eslint-disable-next-line react-hooks/refs
110
+ return renderButton({
111
+ onClick: handleClick,
112
+ isSpeaking: isSpeaking,
113
+ isSupported: isSupported,
114
+ disabled: isDisabled,
115
+ speak: speak,
116
+ stop: stop
117
+ });
118
+ }
119
+ return jsxs("button", _objectSpread2(_objectSpread2({
120
+ ref: ref,
121
+ type: "button",
122
+ onClick: handleClick,
123
+ disabled: isDisabled,
124
+ "aria-pressed": isSpeaking,
125
+ title: !isSupported ? UNSUPPORTED_MESSAGE : undefined,
126
+ className: cn("inline-flex items-center gap-2 rounded-md px-4 h-10 text-sm font-medium", "border border-gray-300 bg-transparent transition-colors motion-reduce:transition-none", "hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500", "disabled:opacity-50 disabled:cursor-not-allowed", {
127
+ "text-error-600 border-error-600": isSpeaking,
128
+ "text-gray-900": !isSpeaking
129
+ }, className)
130
+ }, rest), {}, {
131
+ children: [isSpeaking ? jsx(Square, {
132
+ className: "h-4 w-4",
133
+ "aria-hidden": "true"
134
+ }) : jsx(Volume2, {
135
+ className: "h-4 w-4",
136
+ "aria-hidden": "true"
137
+ }), isSpeaking ? "Stop" : "Speak", !isSupported && jsx("span", {
138
+ className: "sr-only",
139
+ children: " (".concat(UNSUPPORTED_MESSAGE, ")")
140
+ })]
141
+ }));
142
+ });
143
+ TextToSpeech.displayName = "TextToSpeech";
51
144
 
52
145
  export { TextToSpeech as default };
@@ -6,7 +6,7 @@ import { AlertCircle } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
7
  import { setRefs } from '../../utils/position.js';
8
8
 
9
- var _excluded = ["id", "className", "label", "error", "helperText", "required", "disabled", "showCount", "maxLength", "autoResize", "rows", "maxRows", "value", "defaultValue", "onChange"];
9
+ var _excluded = ["id", "className", "label", "error", "helperText", "required", "disabled", "showCount", "maxLength", "autoResize", "rows", "maxRows", "value", "defaultValue", "onChange", "size"];
10
10
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
11
11
  function fitToContent(el, maxRows) {
12
12
  if (!el) return;
@@ -24,6 +24,12 @@ function fitToContent(el, maxRows) {
24
24
  }
25
25
  el.style.height = "".concat(height, "px");
26
26
  }
27
+ // Padding / text per size; `md` is the original look. Rows set the height.
28
+ var SIZE_CLASSES = {
29
+ sm: "px-2.5 py-1.5 text-xs",
30
+ md: "px-3 py-2 text-sm",
31
+ lg: "px-4 py-2.5 text-base"
32
+ };
27
33
  var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
28
34
  var id = _ref.id,
29
35
  className = _ref.className,
@@ -45,6 +51,8 @@ var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
45
51
  value = _ref.value,
46
52
  defaultValue = _ref.defaultValue,
47
53
  onChange = _ref.onChange,
54
+ _ref$size = _ref.size,
55
+ size = _ref$size === void 0 ? "md" : _ref$size,
48
56
  rest = _objectWithoutProperties(_ref, _excluded);
49
57
  var reactId = useId();
50
58
  var textareaId = id || "textarea-".concat(reactId);
@@ -93,7 +101,7 @@ var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
93
101
  onChange: handleChange,
94
102
  "aria-invalid": error ? true : undefined,
95
103
  "aria-describedby": describedBy,
96
- className: cn("block w-full rounded-md border bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400", "focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500", "disabled:cursor-not-allowed disabled:opacity-50", "dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 dark:focus:ring-offset-gray-900", error ? "border-error-500 focus:ring-error-500" : "border-gray-300 dark:border-gray-600", autoResize ? "resize-none" : "resize-y", className)
104
+ className: cn("block w-full rounded-md border bg-surface text-foreground placeholder-gray-400", SIZE_CLASSES[size] || SIZE_CLASSES.md, "transition-colors duration-fast ease-standard motion-reduce:transition-none", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "disabled:cursor-not-allowed disabled:opacity-50", "dark:placeholder-gray-500 dark:focus-visible:ring-offset-gray-900", error ? "border-error-500 focus-visible:ring-error-500 dark:border-error-400" : "border-gray-300 focus-visible:ring-primary-500 dark:border-gray-600", autoResize ? "resize-none" : "resize-y", className)
97
105
  })), (error || helperText || showCount && maxLength) && jsxs("div", {
98
106
  className: "mt-1 flex items-start justify-between gap-3 text-sm",
99
107
  children: [jsxs("div", {