@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.
- package/README.md +30 -0
- package/ai-skills/dreamtree-ui.md +12 -7
- package/dist/ai-schema.json +137 -21
- package/dist/charts/index.js +195 -66
- package/dist/components/charts/CartesianChart.d.ts.map +1 -1
- package/dist/components/charts/ChartFrame.d.ts.map +1 -1
- package/dist/components/charts/useChartLayout.d.ts +25 -0
- package/dist/components/charts/useChartLayout.d.ts.map +1 -0
- package/dist/components/core/Button.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/ColorPicker.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/Loader.d.ts.map +1 -1
- package/dist/components/core/LocationPicker.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Radio.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/RoundedTag.d.ts.map +1 -1
- package/dist/components/core/Skeleton.d.ts.map +1 -1
- package/dist/components/core/Slider.d.ts.map +1 -1
- package/dist/components/core/SpeechToText.d.ts.map +1 -1
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts +2 -5
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/Textarea.d.ts.map +1 -1
- package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/navigation/FootNav.d.ts.map +1 -1
- package/dist/components/utility/Avatar.d.ts +8 -0
- package/dist/components/utility/Avatar.d.ts.map +1 -1
- package/dist/components/utility/AvatarGroup.d.ts +4 -0
- package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
- package/dist/components/utility/Badge.d.ts.map +1 -1
- package/dist/components/utility/Card.d.ts.map +1 -1
- package/dist/components/utility/Carousel.d.ts.map +1 -1
- package/dist/components/utility/FileUpload.d.ts.map +1 -1
- package/dist/components/utility/Pagination.d.ts.map +1 -1
- package/dist/components/utility/Sortable.d.ts.map +1 -1
- package/dist/components/utility/Stepper.d.ts.map +1 -1
- package/dist/esm/components/blocks/EmptyState.js +1 -1
- package/dist/esm/components/blocks/ErrorState.js +1 -1
- package/dist/esm/components/blocks/LoadingState.js +4 -4
- package/dist/esm/components/blocks/StatCard.js +6 -6
- package/dist/esm/components/blocks/StatusPill.js +5 -8
- package/dist/esm/components/charts/CartesianChart.js +85 -62
- package/dist/esm/components/charts/ChartFrame.js +12 -5
- package/dist/esm/components/charts/useChartLayout.js +105 -0
- package/dist/esm/components/core/Button.js +49 -25
- package/dist/esm/components/core/Checkbox.js +71 -45
- package/dist/esm/components/core/ColorPicker.js +31 -42
- package/dist/esm/components/core/Input.js +89 -22
- package/dist/esm/components/core/Loader.js +30 -44
- package/dist/esm/components/core/LocationPicker.js +82 -16
- package/dist/esm/components/core/ProgressBar.js +8 -12
- package/dist/esm/components/core/Radio.js +10 -12
- package/dist/esm/components/core/Rate.js +72 -4
- package/dist/esm/components/core/RoundedTag.js +5 -5
- package/dist/esm/components/core/Skeleton.js +13 -14
- package/dist/esm/components/core/Slider.js +24 -6
- package/dist/esm/components/core/SpeechToText.js +68 -16
- package/dist/esm/components/core/Switch.js +88 -16
- package/dist/esm/components/core/Tabs.js +180 -33
- package/dist/esm/components/core/Textarea.js +10 -2
- package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
- package/dist/esm/components/navigation/FootNav.js +102 -13
- package/dist/esm/components/utility/Avatar.js +14 -12
- package/dist/esm/components/utility/AvatarGroup.js +56 -0
- package/dist/esm/components/utility/Badge.js +43 -23
- package/dist/esm/components/utility/Card.js +32 -8
- package/dist/esm/components/utility/Carousel.js +139 -29
- package/dist/esm/components/utility/FileUpload.js +106 -31
- package/dist/esm/components/utility/Pagination.js +31 -13
- package/dist/esm/components/utility/Sortable.js +6 -2
- package/dist/esm/components/utility/Stepper.js +110 -47
- package/dist/esm/hooks/useSliderValues.js +8 -2
- package/dist/esm/hooks/useSortable.js +107 -5
- package/dist/esm/index.js +1 -0
- package/dist/esm/utils/touchTarget.js +4 -1
- package/dist/hooks/useSliderValues.d.ts +6 -2
- package/dist/hooks/useSliderValues.d.ts.map +1 -1
- package/dist/hooks/useSortable.d.ts +11 -1
- package/dist/hooks/useSortable.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +2341 -1191
- package/dist/index.js +2341 -1190
- package/dist/types/charts.d.ts +3 -1
- package/dist/types/charts.d.ts.map +1 -1
- package/dist/types/index.d.ts +56 -7
- package/dist/types/index.d.ts.map +1 -1
- package/doc/README.md +1 -0
- package/mcp/catalog.snapshot.json +279 -80
- 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(
|
|
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-
|
|
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
|
-
|
|
64
|
+
// True while the engine reports speech (speechstart → speechend, or results).
|
|
65
|
+
var _useState3 = useState(false),
|
|
52
66
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
var _useState5 = useState(
|
|
67
|
+
isHearing = _useState4[0],
|
|
68
|
+
setIsHearing = _useState4[1];
|
|
69
|
+
var _useState5 = useState(true),
|
|
56
70
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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: [
|
|
216
|
-
className: "h-4 w-4",
|
|
217
|
-
"aria-hidden": "true"
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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", {
|