@dreamtree-org/twreact-ui 1.3.10 → 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 (93) hide show
  1. package/README.md +30 -0
  2. package/ai-skills/dreamtree-ui.md +12 -7
  3. package/dist/ai-schema.json +137 -21
  4. package/dist/charts/index.js +195 -66
  5. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  6. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  7. package/dist/components/charts/useChartLayout.d.ts +25 -0
  8. package/dist/components/charts/useChartLayout.d.ts.map +1 -0
  9. package/dist/components/core/Button.d.ts.map +1 -1
  10. package/dist/components/core/Checkbox.d.ts.map +1 -1
  11. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  12. package/dist/components/core/Input.d.ts.map +1 -1
  13. package/dist/components/core/Loader.d.ts.map +1 -1
  14. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  15. package/dist/components/core/ProgressBar.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/Skeleton.d.ts.map +1 -1
  20. package/dist/components/core/Slider.d.ts.map +1 -1
  21. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  22. package/dist/components/core/Switch.d.ts.map +1 -1
  23. package/dist/components/core/Tabs.d.ts +2 -5
  24. package/dist/components/core/Tabs.d.ts.map +1 -1
  25. package/dist/components/core/Textarea.d.ts.map +1 -1
  26. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  27. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  28. package/dist/components/utility/Avatar.d.ts +8 -0
  29. package/dist/components/utility/Avatar.d.ts.map +1 -1
  30. package/dist/components/utility/AvatarGroup.d.ts +4 -0
  31. package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
  32. package/dist/components/utility/Badge.d.ts.map +1 -1
  33. package/dist/components/utility/Card.d.ts.map +1 -1
  34. package/dist/components/utility/Carousel.d.ts.map +1 -1
  35. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  36. package/dist/components/utility/Pagination.d.ts.map +1 -1
  37. package/dist/components/utility/Sortable.d.ts.map +1 -1
  38. package/dist/components/utility/Stepper.d.ts.map +1 -1
  39. package/dist/esm/components/blocks/EmptyState.js +1 -1
  40. package/dist/esm/components/blocks/ErrorState.js +1 -1
  41. package/dist/esm/components/blocks/LoadingState.js +4 -4
  42. package/dist/esm/components/blocks/StatCard.js +6 -6
  43. package/dist/esm/components/blocks/StatusPill.js +5 -8
  44. package/dist/esm/components/charts/CartesianChart.js +85 -62
  45. package/dist/esm/components/charts/ChartFrame.js +12 -5
  46. package/dist/esm/components/charts/useChartLayout.js +105 -0
  47. package/dist/esm/components/core/Button.js +49 -25
  48. package/dist/esm/components/core/Checkbox.js +71 -45
  49. package/dist/esm/components/core/ColorPicker.js +31 -42
  50. package/dist/esm/components/core/Input.js +89 -22
  51. package/dist/esm/components/core/Loader.js +30 -44
  52. package/dist/esm/components/core/LocationPicker.js +82 -16
  53. package/dist/esm/components/core/ProgressBar.js +8 -12
  54. package/dist/esm/components/core/Radio.js +10 -12
  55. package/dist/esm/components/core/Rate.js +72 -4
  56. package/dist/esm/components/core/RoundedTag.js +5 -5
  57. package/dist/esm/components/core/Skeleton.js +13 -14
  58. package/dist/esm/components/core/Slider.js +24 -6
  59. package/dist/esm/components/core/SpeechToText.js +68 -16
  60. package/dist/esm/components/core/Switch.js +88 -16
  61. package/dist/esm/components/core/Tabs.js +180 -33
  62. package/dist/esm/components/core/Textarea.js +10 -2
  63. package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
  64. package/dist/esm/components/navigation/FootNav.js +102 -13
  65. package/dist/esm/components/utility/Avatar.js +14 -12
  66. package/dist/esm/components/utility/AvatarGroup.js +56 -0
  67. package/dist/esm/components/utility/Badge.js +43 -23
  68. package/dist/esm/components/utility/Card.js +32 -8
  69. package/dist/esm/components/utility/Carousel.js +139 -29
  70. package/dist/esm/components/utility/FileUpload.js +106 -31
  71. package/dist/esm/components/utility/Pagination.js +31 -13
  72. package/dist/esm/components/utility/Sortable.js +6 -2
  73. package/dist/esm/components/utility/Stepper.js +110 -47
  74. package/dist/esm/hooks/useSliderValues.js +8 -2
  75. package/dist/esm/hooks/useSortable.js +107 -5
  76. package/dist/esm/index.js +1 -0
  77. package/dist/esm/utils/touchTarget.js +4 -1
  78. package/dist/hooks/useSliderValues.d.ts +6 -2
  79. package/dist/hooks/useSliderValues.d.ts.map +1 -1
  80. package/dist/hooks/useSortable.d.ts +11 -1
  81. package/dist/hooks/useSortable.d.ts.map +1 -1
  82. package/dist/index.css +1 -1
  83. package/dist/index.d.ts +3 -2
  84. package/dist/index.esm.css +1 -1
  85. package/dist/index.esm.js +2341 -1191
  86. package/dist/index.js +2341 -1190
  87. package/dist/types/charts.d.ts +3 -1
  88. package/dist/types/charts.d.ts.map +1 -1
  89. package/dist/types/index.d.ts +56 -7
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/doc/README.md +1 -0
  92. package/mcp/catalog.snapshot.json +279 -80
  93. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  'use client';
2
- import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
- import { forwardRef, useId, useRef, useMemo } from 'react';
4
+ import { forwardRef, useId, useRef, useMemo, useState } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
6
  import { useSliderValues, useSliderPointer, toPercent, keyToValue } from '../../hooks/useSliderValues.js';
7
7
 
8
- var _excluded = ["value", "defaultValue", "onChange", "onChangeEnd", "min", "max", "step", "largeStep", "disabled", "label", "showValue", "formatValue", "thumbLabels", "name", "className", "aria-label"];
8
+ var _excluded = ["value", "defaultValue", "onChange", "onChangeEnd", "min", "max", "step", "largeStep", "disabled", "label", "showValue", "showBubble", "formatValue", "thumbLabels", "name", "className", "aria-label"];
9
9
  var DEFAULT_THUMB_LABELS = ["Minimum", "Maximum"];
10
10
  var Slider = /*#__PURE__*/forwardRef(function (_ref, ref) {
11
11
  var value = _ref.value,
@@ -24,6 +24,8 @@ var Slider = /*#__PURE__*/forwardRef(function (_ref, ref) {
24
24
  label = _ref.label,
25
25
  _ref$showValue = _ref.showValue,
26
26
  showValue = _ref$showValue === void 0 ? false : _ref$showValue,
27
+ _ref$showBubble = _ref.showBubble,
28
+ showBubble = _ref$showBubble === void 0 ? true : _ref$showBubble,
27
29
  _ref$formatValue = _ref.formatValue,
28
30
  formatValue = _ref$formatValue === void 0 ? String : _ref$formatValue,
29
31
  _ref$thumbLabels = _ref.thumbLabels,
@@ -52,12 +54,18 @@ var Slider = /*#__PURE__*/forwardRef(function (_ref, ref) {
52
54
  values = _useSliderValues.values,
53
55
  isRange = _useSliderValues.isRange,
54
56
  setThumb = _useSliderValues.setThumb;
57
+ // Index of the thumb being dragged (drives the value bubble), else null.
58
+ var _useState = useState(null),
59
+ _useState2 = _slicedToArray(_useState, 2),
60
+ dragging = _useState2[0],
61
+ setDragging = _useState2[1];
55
62
  var pointer = useSliderPointer({
56
63
  trackRef: trackRef,
57
64
  values: values,
58
65
  setThumb: setThumb,
59
66
  bounds: bounds,
60
- disabled: disabled
67
+ disabled: disabled,
68
+ onActiveChange: setDragging
61
69
  });
62
70
  var handleKeyDown = function handleKeyDown(index) {
63
71
  return function (e) {
@@ -90,7 +98,9 @@ var Slider = /*#__PURE__*/forwardRef(function (_ref, ref) {
90
98
  })]
91
99
  }), jsxs("div", _objectSpread2(_objectSpread2({
92
100
  ref: trackRef,
93
- className: cn("relative flex h-5 touch-none select-none items-center", disabled ? "cursor-not-allowed" : "cursor-pointer")
101
+ className: cn(
102
+ // 44px tall row on coarse pointers so the whole strip is easy to grab.
103
+ "relative flex h-5 touch-none select-none items-center [@media(pointer:coarse)]:h-11", disabled ? "cursor-not-allowed" : "cursor-pointer")
94
104
  }, pointer), {}, {
95
105
  children: [jsx("div", {
96
106
  className: "h-1.5 w-full rounded-full bg-gray-200 dark:bg-gray-700"
@@ -117,7 +127,15 @@ var Slider = /*#__PURE__*/forwardRef(function (_ref, ref) {
117
127
  style: {
118
128
  left: "".concat(toPercent(v, min, max), "%")
119
129
  },
120
- className: cn("absolute h-4 w-4 -translate-x-1/2 rounded-full border-2 border-primary-600 bg-white shadow", "transition-shadow motion-reduce:transition-none", "focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500", "dark:border-primary-500 dark:bg-gray-900 dark:focus:ring-offset-gray-900")
130
+ className: cn("absolute h-4 w-4 -translate-x-1/2 rounded-full border-2 border-primary-600 bg-surface shadow", "transition-shadow duration-fast motion-reduce:transition-none", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500", "dark:border-primary-500 dark:focus-visible:ring-offset-gray-900",
131
+ // Grow the hit area to 44px on touch without changing the visual size.
132
+ "before:absolute before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2", "[@media(pointer:coarse)]:before:h-11 [@media(pointer:coarse)]:before:w-11 before:content-['']"),
133
+ children: showBubble && dragging === i && jsx("span", {
134
+ "aria-hidden": "true",
135
+ "data-slider-bubble": "",
136
+ className: cn("pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-md border border-line", "bg-surface px-2 py-1 text-xs tabular-nums text-foreground shadow-sm", "animate-scale-in motion-reduce:animate-none"),
137
+ children: formatValue(v)
138
+ })
121
139
  }, i);
122
140
  })]
123
141
  })), name && values.map(function (v, i) {
@@ -4,8 +4,21 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle } from 'react';
5
5
  import { MicOff, Mic } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
+ import { PRESS_FEEDBACK } from '../../utils/touchTarget.js';
7
8
 
8
9
  var _excluded = ["lang", "continuous", "interimResults", "onSpeechComplete", "onSpeaking", "onError", "onStart", "onStop", "renderButton", "autoStart", "disabled", "resetOnStart", "className"];
10
+ // Level bars (DTUI-42). There is no audio-level API without getUserMedia, so the
11
+ // bars show *whether* speech is being heard (speechstart / results), not volume.
12
+ var LEVEL_BARS = [{
13
+ height: "h-2",
14
+ delay: "[animation-delay:0ms]"
15
+ }, {
16
+ height: "h-3.5",
17
+ delay: "[animation-delay:150ms]"
18
+ }, {
19
+ height: "h-2.5",
20
+ delay: "[animation-delay:300ms]"
21
+ }];
9
22
  var isProbablyMobile = function isProbablyMobile() {
10
23
  if (typeof navigator === "undefined") return false;
11
24
  return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
@@ -48,14 +61,19 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
48
61
  _useState2 = _slicedToArray(_useState, 2),
49
62
  _isListening = _useState2[0],
50
63
  setIsListening = _useState2[1];
51
- var _useState3 = useState(true),
64
+ // True while the engine reports speech (speechstart → speechend, or results).
65
+ var _useState3 = useState(false),
52
66
  _useState4 = _slicedToArray(_useState3, 2),
53
- _isSupported = _useState4[0],
54
- setIsSupported = _useState4[1];
55
- var _useState5 = useState(null),
67
+ isHearing = _useState4[0],
68
+ setIsHearing = _useState4[1];
69
+ var _useState5 = useState(true),
56
70
  _useState6 = _slicedToArray(_useState5, 2),
57
- error = _useState6[0],
58
- setError = _useState6[1];
71
+ _isSupported = _useState6[0],
72
+ setIsSupported = _useState6[1];
73
+ var _useState7 = useState(null),
74
+ _useState8 = _slicedToArray(_useState7, 2),
75
+ error = _useState8[0],
76
+ setError = _useState8[1];
59
77
  useEffect(function () {
60
78
  var hasSpeechRecognition = typeof window !== "undefined" && ("SpeechRecognition" in window || "webkitSpeechRecognition" in window);
61
79
  setIsSupported(hasSpeechRecognition);
@@ -76,6 +94,14 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
76
94
  recognition.continuous = isProbablyMobile() ? false : continuous;
77
95
  recognition.interimResults = interimResults;
78
96
  recognition.lang = lang;
97
+ recognition.onspeechstart = function () {
98
+ if (!isMountedRef.current) return;
99
+ setIsHearing(true);
100
+ };
101
+ recognition.onspeechend = function () {
102
+ if (!isMountedRef.current) return;
103
+ setIsHearing(false);
104
+ };
79
105
  recognition.onstart = function () {
80
106
  if (!isMountedRef.current) return;
81
107
  setIsListening(true);
@@ -97,6 +123,7 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
97
123
  interim += text;
98
124
  }
99
125
  }
126
+ if (interim || finalChunk) setIsHearing(true);
100
127
  if (interim) onSpeaking(interim.trim());
101
128
  if (finalChunk) onSpeechComplete(finalChunk.trim());
102
129
  };
@@ -107,11 +134,13 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
107
134
  var err = new Error((event === null || event === void 0 ? void 0 : event.error) || "Speech recognition error");
108
135
  setError(err);
109
136
  setIsListening(false);
137
+ setIsHearing(false);
110
138
  onError(err);
111
139
  };
112
140
  recognition.onend = function () {
113
141
  if (!isMountedRef.current) return;
114
142
  setIsListening(false);
143
+ setIsHearing(false);
115
144
  onStop();
116
145
  };
117
146
  recognitionRef.current = recognition;
@@ -192,6 +221,7 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
192
221
  // eslint-disable-next-line react-hooks/refs
193
222
  return renderButton({
194
223
  isListening: _isListening,
224
+ isHearing: isHearing,
195
225
  isSupported: _isSupported,
196
226
  error: error,
197
227
  start: startListening,
@@ -207,18 +237,40 @@ var SpeechToText = /*#__PURE__*/forwardRef(function (_ref, ref) {
207
237
  "aria-label": _isListening ? "Stop listening" : "Start listening",
208
238
  onClick: toggleListening,
209
239
  disabled: isDisabled,
210
- 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", "hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500", "disabled:opacity-50 disabled:pointer-events-none", {
211
- "text-error-600 border-error-600": _isListening,
212
- "text-gray-900": !_isListening
240
+ "data-listening": _isListening || undefined,
241
+ "data-hearing": _isListening && isHearing || undefined,
242
+ className: cn("inline-flex items-center gap-2 rounded-md px-4 h-10 text-sm font-medium", "[@media(pointer:coarse)]:min-h-11", "border border-gray-300 bg-transparent transition-colors duration-fast motion-reduce:transition-none", "hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500 dark:focus-visible:ring-offset-gray-900", "disabled:opacity-50 disabled:pointer-events-none", PRESS_FEEDBACK,
243
+ // PRESS_FEEDBACK sets transition-transform; keep the colour transition too.
244
+ "transition-[color,background-color,border-color,transform]", {
245
+ "text-error-600 border-error-600 dark:text-error-400 dark:border-error-400": _isListening,
246
+ "text-gray-900 dark:text-gray-100": !_isListening
213
247
  }, className)
214
248
  }, rest), {}, {
215
- children: [_isListening ? jsx(MicOff, {
216
- className: "h-4 w-4",
217
- "aria-hidden": "true"
218
- }) : jsx(Mic, {
219
- className: "h-4 w-4",
220
- "aria-hidden": "true"
221
- }), _isListening ? "Stop" : "Start"]
249
+ children: [jsxs("span", {
250
+ className: "relative inline-flex h-4 w-4 items-center justify-center",
251
+ "aria-hidden": "true",
252
+ children: [_isListening &&
253
+ // Listening pulse; a static ring replaces it under reduced motion.
254
+ jsx("span", {
255
+ "data-testid": "stt-pulse",
256
+ className: "absolute -inset-1.5 rounded-full bg-error-500/30 animate-ping motion-reduce:animate-none motion-reduce:bg-transparent motion-reduce:ring-2 motion-reduce:ring-error-500/60"
257
+ }), _isListening ? jsx(MicOff, {
258
+ className: "relative h-4 w-4",
259
+ "aria-hidden": "true"
260
+ }) : jsx(Mic, {
261
+ className: "relative h-4 w-4",
262
+ "aria-hidden": "true"
263
+ })]
264
+ }), _isListening ? "Stop" : "Start", _isListening && jsx("span", {
265
+ className: "inline-flex h-3.5 items-end gap-0.5",
266
+ "aria-hidden": "true",
267
+ "data-testid": "stt-level",
268
+ children: LEVEL_BARS.map(function (bar, i) {
269
+ return jsx("span", {
270
+ className: cn("w-0.5 rounded-full transition-colors duration-base motion-reduce:transition-none", isHearing ? cn("bg-current animate-pulse motion-reduce:animate-none", bar.height, bar.delay) : "h-1 bg-current opacity-40")
271
+ }, i);
272
+ })
273
+ })]
222
274
  }));
223
275
  });
224
276
  SpeechToText.displayName = "SpeechToText";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
- import { forwardRef, useState, useId, useEffect } from 'react';
4
+ import { forwardRef, useState, useId, useEffect, useRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
6
 
7
- var _excluded = ["id", "name", "checked", "defaultChecked", "onChange", "disabled", "size", "color", "trackClass", "thumbClass", "className", "topLabel", "bottomLabel", "rightLabel", "leftLabel", "ariaLabel"];
7
+ var _excluded = ["id", "name", "checked", "defaultChecked", "onChange", "disabled", "size", "color", "trackClass", "thumbClass", "className", "topLabel", "bottomLabel", "rightLabel", "leftLabel", "ariaLabel", "onPointerDown", "onPointerMove", "onPointerUp", "onPointerCancel"];
8
8
  /**
9
9
  * Switch.jsx
10
10
  * - Plain JSX (no TypeScript)
@@ -17,23 +17,29 @@ var _excluded = ["id", "name", "checked", "defaultChecked", "onChange", "disable
17
17
  * New props:
18
18
  * - topLabel, bottomLabel, rightLabel, leftLabel (renders label in those positions)
19
19
  */
20
+ // `md` takes the iOS 51×31 size on coarse pointers (27px knob, 2px inset).
20
21
  var SIZE_MAP = {
21
22
  sm: {
22
23
  track: "w-9 h-5",
23
24
  knob: "w-4 h-4",
24
- knobTranslate: "translate-x-4"
25
+ knobOn: "translate-x-4",
26
+ knobOff: "translate-x-1"
25
27
  },
26
28
  md: {
27
- track: "w-11 h-6",
28
- knob: "w-5 h-5",
29
- knobTranslate: "translate-x-5"
29
+ track: "w-11 h-6 [@media(pointer:coarse)]:h-[31px] [@media(pointer:coarse)]:w-[51px]",
30
+ knob: "w-5 h-5 [@media(pointer:coarse)]:h-[27px] [@media(pointer:coarse)]:w-[27px]",
31
+ knobOn: "translate-x-5 [@media(pointer:coarse)]:translate-x-[22px]",
32
+ knobOff: "translate-x-1 [@media(pointer:coarse)]:translate-x-0.5"
30
33
  },
31
34
  lg: {
32
35
  track: "w-14 h-8",
33
36
  knob: "w-6 h-6",
34
- knobTranslate: "translate-x-6"
37
+ knobOn: "translate-x-6",
38
+ knobOff: "translate-x-1"
35
39
  }
36
40
  };
41
+ // Horizontal travel (px) before a press becomes a drag.
42
+ var DRAG_THRESHOLD = 4;
37
43
  var COLOR_PRESETS = {
38
44
  primary: {
39
45
  bg: "bg-primary-600",
@@ -102,6 +108,10 @@ var Switch = /*#__PURE__*/forwardRef(function Switch(props, ref) {
102
108
  rightLabel = props.rightLabel,
103
109
  leftLabel = props.leftLabel,
104
110
  ariaLabel = props.ariaLabel,
111
+ onPointerDown = props.onPointerDown,
112
+ onPointerMove = props.onPointerMove,
113
+ onPointerUp = props.onPointerUp,
114
+ onPointerCancel = props.onPointerCancel,
105
115
  rest = _objectWithoutProperties(props, _excluded);
106
116
  var isControlled = typeof checked === "boolean";
107
117
  var _useState = useState(defaultChecked),
@@ -115,31 +125,89 @@ var Switch = /*#__PURE__*/forwardRef(function Switch(props, ref) {
115
125
  if (!isControlled) setInternalChecked(defaultChecked);
116
126
  // eslint-disable-next-line react-hooks/exhaustive-deps
117
127
  }, [defaultChecked]);
118
- var toggle = function toggle() {
119
- if (disabled) return;
120
- var next = !isOn;
128
+ // Drag-to-toggle: slide the knob (iOS style). A tap still toggles via click.
129
+ var drag = useRef(null);
130
+ var suppressClick = useRef(false);
131
+ var _useState3 = useState(null),
132
+ _useState4 = _slicedToArray(_useState3, 2),
133
+ dragPreview = _useState4[0],
134
+ setDragPreview = _useState4[1];
135
+ var setOn = function setOn(next) {
136
+ if (disabled || next === isOn) return;
121
137
  if (!isControlled) setInternalChecked(next);
122
138
  onChange && onChange(next);
123
139
  };
140
+ var toggle = function toggle() {
141
+ if (disabled) return;
142
+ if (suppressClick.current) {
143
+ // The click that follows a drag's pointerup; the drag already decided.
144
+ suppressClick.current = false;
145
+ return;
146
+ }
147
+ setOn(!isOn);
148
+ };
149
+ var dragTarget = function dragTarget(dx, start) {
150
+ return dx > DRAG_THRESHOLD ? true : dx < -DRAG_THRESHOLD ? false : start;
151
+ };
152
+ var handlePointerDown = function handlePointerDown(e) {
153
+ var _e$currentTarget$setP, _e$currentTarget;
154
+ onPointerDown && onPointerDown(e);
155
+ if (disabled || e.button > 0) return;
156
+ suppressClick.current = false;
157
+ drag.current = {
158
+ x: e.clientX,
159
+ start: isOn,
160
+ moved: false
161
+ };
162
+ (_e$currentTarget$setP = (_e$currentTarget = e.currentTarget).setPointerCapture) === null || _e$currentTarget$setP === void 0 || _e$currentTarget$setP.call(_e$currentTarget, e.pointerId);
163
+ };
164
+ var handlePointerMove = function handlePointerMove(e) {
165
+ onPointerMove && onPointerMove(e);
166
+ if (!drag.current) return;
167
+ var dx = e.clientX - drag.current.x;
168
+ if (Math.abs(dx) > DRAG_THRESHOLD) drag.current.moved = true;
169
+ if (drag.current.moved) setDragPreview(dragTarget(dx, drag.current.start));
170
+ };
171
+ var endDrag = function endDrag(e, commit) {
172
+ var d = drag.current;
173
+ drag.current = null;
174
+ setDragPreview(null);
175
+ if (!d || !d.moved) return;
176
+ suppressClick.current = true;
177
+ if (commit) setOn(dragTarget(e.clientX - d.x, d.start));
178
+ };
179
+ var handlePointerUp = function handlePointerUp(e) {
180
+ onPointerUp && onPointerUp(e);
181
+ endDrag(e, true);
182
+ };
183
+ var handlePointerCancel = function handlePointerCancel(e) {
184
+ onPointerCancel && onPointerCancel(e);
185
+ endDrag(e, false);
186
+ };
124
187
  var handleKeyDown = function handleKeyDown(e) {
125
188
  if (disabled) return;
126
189
  if (e.key === " " || e.key === "Spacebar" || e.key === "Enter") {
127
190
  e.preventDefault();
128
- toggle();
191
+ setOn(!isOn);
129
192
  }
130
193
  };
131
194
  var sizeStuff = SIZE_MAP[size] || SIZE_MAP.md;
132
195
  var isPreset = COLOR_PRESETS[color];
133
196
  // Classes for the track (we use inline style for arbitrary trackClass when ON)
134
- var trackClasses = cn("relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out", sizeStuff.track, isOn ? trackClass ? "" : isPreset ? isPreset.bg : "" : "bg-gray-100", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer", trackClass // Accepting trackClass as a class name
197
+ // While dragging, the knob and track follow the drag's preview state.
198
+ var shownOn = dragPreview !== null && dragPreview !== void 0 ? dragPreview : isOn;
199
+ var trackClasses = cn("relative inline-flex shrink-0 items-center rounded-full touch-pan-y select-none", "transition-colors duration-base ease-standard motion-reduce:transition-none", sizeStuff.track,
200
+ // Off track: a visible grey on white (light) and on dark surfaces.
201
+ shownOn ? trackClass ? "" : isPreset ? isPreset.bg : "" : "bg-gray-300 dark:bg-gray-600", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer", trackClass // Accepting trackClass as a class name
135
202
  );
136
203
  // Knob classes (thumb)
137
- var knobBase = cn("inline-block rounded-full bg-white shadow transform ring-0 transition-transform duration-200 ease-in-out", sizeStuff.knob, thumbClass // Accepting thumbClass as a class name
204
+ // The knob stays white in both themes (it sits on a coloured or grey track).
205
+ var knobBase = cn("pointer-events-none inline-block rounded-full bg-white shadow-sm transform ring-0", "transition-transform duration-base ease-spring-out motion-reduce:transition-none", sizeStuff.knob, thumbClass // Accepting thumbClass as a class name
138
206
  );
139
- var knobTransform = isOn ? sizeStuff.knobTranslate : "translate-x-1";
207
+ var knobTransform = shownOn ? sizeStuff.knobOn : sizeStuff.knobOff;
140
208
  var ringClass = isPreset ? isPreset.ring : "focus-visible:ring-primary-300";
141
- var focusClasses = disabled ? "" : "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ".concat(ringClass);
142
- var trackStyle = isOn && !trackClass && !isPreset ? {
209
+ var focusClasses = disabled ? "" : "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900 ".concat(ringClass);
210
+ var trackStyle = shownOn && !trackClass && !isPreset ? {
143
211
  backgroundColor: color
144
212
  } : undefined; // Fallback to default trackClass if trackClass is not a class
145
213
  var accessibleLabel = ariaLabel || rightLabel || leftLabel || topLabel || bottomLabel || "Toggle";
@@ -171,6 +239,10 @@ var Switch = /*#__PURE__*/forwardRef(function Switch(props, ref) {
171
239
  className: cn(trackClasses, focusClasses),
172
240
  onClick: toggle,
173
241
  onKeyDown: handleKeyDown,
242
+ onPointerDown: handlePointerDown,
243
+ onPointerMove: handlePointerMove,
244
+ onPointerUp: handlePointerUp,
245
+ onPointerCancel: handlePointerCancel,
174
246
  style: trackStyle
175
247
  }, rest), {}, {
176
248
  children: [jsx("span", {