@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
@@ -19,6 +19,14 @@ var sizeToPx = function sizeToPx(size) {
19
19
  return 24;
20
20
  }
21
21
  };
22
+ // Keyframes live in tailwind.config.js (DTUI-48) — no per-instance <style> tag.
23
+ // `speed` reaches the animation through the --dt-loader-speed custom property.
24
+ // Under prefers-reduced-motion the spinner slows right down (it still signals
25
+ // "busy"); the bouncing dots and sliding bar stop.
26
+ var SPIN = "animate-loader-spin motion-reduce:[animation-duration:3s]";
27
+ // Default track colour, dark-safe. A `secondaryColor` prop overrides it inline.
28
+ var TRACK_STROKE = "stroke-black/[0.08] dark:stroke-white/15";
29
+ var TRACK_BG = "bg-black/[0.08] dark:bg-white/15";
22
30
  var Loader = /*#__PURE__*/forwardRef(function Loader(_ref, ref) {
23
31
  var _ref$size = _ref.size,
24
32
  size = _ref$size === void 0 ? "md" : _ref$size,
@@ -28,8 +36,7 @@ var Loader = /*#__PURE__*/forwardRef(function Loader(_ref, ref) {
28
36
  thickness = _ref$thickness === void 0 ? 2.5 : _ref$thickness,
29
37
  _ref$color = _ref.color,
30
38
  color = _ref$color === void 0 ? "currentColor" : _ref$color,
31
- _ref$secondaryColor = _ref.secondaryColor,
32
- secondaryColor = _ref$secondaryColor === void 0 ? "rgba(0,0,0,0.08)" : _ref$secondaryColor,
39
+ secondaryColor = _ref.secondaryColor,
33
40
  _ref$variant = _ref.variant,
34
41
  variant = _ref$variant === void 0 ? "spinner" : _ref$variant,
35
42
  _ref$className = _ref.className,
@@ -45,32 +52,29 @@ var Loader = /*#__PURE__*/forwardRef(function Loader(_ref, ref) {
45
52
  rest = _objectWithoutProperties(_ref, _excluded);
46
53
  var px = sizeToPx(size);
47
54
  var wrapperClass = cn(inline ? "inline-flex" : "flex", "items-center gap-2", className);
48
- // animation attributes for SVG rotate
49
- var animDur = "".concat(speed, "s");
50
- var rotateFrom = reverse ? "360 12 12" : "0 12 12";
51
- var rotateTo = reverse ? "0 12 12" : "360 12 12";
55
+ var rootStyle = _objectSpread2({
56
+ "--dt-loader-speed": "".concat(speed, "s")
57
+ }, style);
52
58
  return jsxs("div", _objectSpread2(_objectSpread2({
53
59
  ref: ref,
54
60
  role: "progressbar",
55
61
  "aria-label": ariaLabel,
56
62
  className: wrapperClass,
57
- style: style
63
+ style: rootStyle
58
64
  }, rest), {}, {
59
- children: [jsx("style", {
60
- children: "\n @keyframes loader-bounce {\n 0%, 80%, 100% { transform: translateY(0); opacity: 0.9 }\n 40% { transform: translateY(-6px); opacity: 1 }\n }\n @keyframes loader-bar {\n 0% { transform: translateX(-35%); }\n 100% { transform: translateX(135%); }\n }\n "
61
- }), variant === "spinner" && jsxs("svg", {
65
+ children: [variant === "spinner" && jsxs("svg", {
62
66
  width: px,
63
67
  height: px,
64
68
  viewBox: "0 0 24 24",
65
69
  fill: "none",
66
- style: {
67
- display: "block"
68
- },
70
+ "aria-hidden": "true",
71
+ className: cn("block", SPIN, reverse && "[animation-direction:reverse]"),
69
72
  xmlns: "http://www.w3.org/2000/svg",
70
73
  children: [jsx("circle", {
71
74
  cx: "12",
72
75
  cy: "12",
73
76
  r: 10 - thickness,
77
+ className: secondaryColor ? undefined : TRACK_STROKE,
74
78
  stroke: secondaryColor,
75
79
  strokeWidth: thickness
76
80
  }), jsx("path", {
@@ -79,60 +83,42 @@ var Loader = /*#__PURE__*/forwardRef(function Loader(_ref, ref) {
79
83
  strokeWidth: thickness,
80
84
  strokeLinecap: "round",
81
85
  strokeLinejoin: "round",
82
- strokeDasharray: "24",
83
- children: jsx("animateTransform", {
84
- attributeName: "transform",
85
- attributeType: "XML",
86
- type: "rotate",
87
- from: rotateFrom,
88
- to: rotateTo,
89
- dur: animDur,
90
- repeatCount: "indefinite"
91
- })
86
+ strokeDasharray: "24"
92
87
  })]
93
88
  }), variant === "dots" && jsx("div", {
89
+ className: "inline-flex items-center",
94
90
  style: {
95
- display: "inline-flex",
96
- alignItems: "center",
97
91
  gap: Math.max(2, Math.round(px / 8))
98
92
  },
99
93
  children: [0, 1, 2].map(function (i) {
100
94
  return jsx("span", {
101
95
  "aria-hidden": true,
96
+ className: "inline-block rounded-full animate-loader-bounce motion-reduce:animate-none",
102
97
  style: {
103
98
  width: px / 4,
104
99
  height: px / 4,
105
- borderRadius: "9999px",
106
- display: "inline-block",
107
100
  background: color,
108
- animation: "loader-bounce ".concat(speed, "s ").concat(i * (speed / 6), "s infinite ease-in-out")
101
+ animationDelay: "".concat(Math.round(i * speed * 1000 / 6) / 1000, "s")
109
102
  }
110
103
  }, i);
111
104
  })
112
105
  }), variant === "bar" && jsx("div", {
113
106
  "aria-hidden": true,
114
- style: {
107
+ className: cn("relative overflow-hidden rounded-full", !secondaryColor && TRACK_BG),
108
+ style: _objectSpread2({
115
109
  width: Math.max(72, px * 2.5),
116
- height: Math.max(6, px / 6),
117
- background: secondaryColor,
118
- overflow: "hidden",
119
- borderRadius: 9999,
120
- position: "relative"
121
- },
110
+ height: Math.max(6, px / 6)
111
+ }, secondaryColor ? {
112
+ background: secondaryColor
113
+ } : {}),
122
114
  children: jsx("div", {
115
+ className: "absolute inset-y-0 left-0 w-[35%] animate-loader-bar motion-reduce:animate-none",
123
116
  style: {
124
- position: "absolute",
125
- left: 0,
126
- top: 0,
127
- bottom: 0,
128
- width: "35%",
129
- background: "linear-gradient(90deg, ".concat(color, ", ").concat(secondaryColor, ")"),
130
- transform: "translateX(-35%)",
131
- animation: "loader-bar ".concat(speed, "s linear infinite")
117
+ background: "linear-gradient(90deg, ".concat(color, ", ").concat(secondaryColor || "transparent", ")")
132
118
  }
133
119
  })
134
120
  }), text && jsx("span", {
135
- className: "text-sm text-primary-500",
121
+ className: "text-sm text-primary-500 dark:text-primary-400",
136
122
  children: text
137
123
  })]
138
124
  }));
@@ -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 { jsxs, jsx } from 'react/jsx-runtime';
4
- import React, { forwardRef, useState, cloneElement } from 'react';
4
+ import React, { forwardRef, useState, useId, cloneElement } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { PRESS_FEEDBACK, TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
 
7
- var _excluded = ["value", "onChange", "inputProps", "icon", "iconProps", "iconPosition", "placeholder", "className"];
8
+ var _excluded = ["value", "onChange", "inputProps", "icon", "iconProps", "iconPosition", "placeholder", "fullWidth", "className"];
9
+ // User-facing copy for GeolocationPositionError codes (and "unsupported").
10
+ var LOCATION_ERROR_MESSAGES = {
11
+ unsupported: "Location isn't available in this browser.",
12
+ 1: "Location permission was denied. Allow it in your browser settings, or type the coordinates.",
13
+ 2: "Your location couldn't be determined. Try again, or type the coordinates.",
14
+ 3: "Finding your location took too long. Try again.",
15
+ "default": "Couldn't get your location. Try again, or type the coordinates."
16
+ };
8
17
  function formatCoords(coords) {
9
18
  if (!coords) return "";
10
19
  var lat = Number(coords.lat);
@@ -40,6 +49,8 @@ var LocationPicker = /*#__PURE__*/forwardRef(function LocationPicker(_ref, ref)
40
49
  iconPosition = _ref$iconPosition === void 0 ? "right" : _ref$iconPosition,
41
50
  _ref$placeholder = _ref.placeholder,
42
51
  placeholder = _ref$placeholder === void 0 ? "lat, lng" : _ref$placeholder,
52
+ _ref$fullWidth = _ref.fullWidth,
53
+ fullWidth = _ref$fullWidth === void 0 ? false : _ref$fullWidth,
43
54
  className = _ref.className,
44
55
  rest = _objectWithoutProperties(_ref, _excluded);
45
56
  var _useState = useState(function () {
@@ -48,12 +59,23 @@ var LocationPicker = /*#__PURE__*/forwardRef(function LocationPicker(_ref, ref)
48
59
  _useState2 = _slicedToArray(_useState, 2),
49
60
  text = _useState2[0],
50
61
  setText = _useState2[1];
62
+ // Geolocation request state, shown in the UI (DTUI-42).
63
+ var _useState3 = useState("idle"),
64
+ _useState4 = _slicedToArray(_useState3, 2),
65
+ status = _useState4[0],
66
+ setStatus = _useState4[1]; // "idle" | "loading" | "error"
67
+ var _useState5 = useState(""),
68
+ _useState6 = _slicedToArray(_useState5, 2),
69
+ errorMessage = _useState6[0],
70
+ setErrorMessage = _useState6[1];
71
+ var messageId = "location-picker-msg-".concat(useId());
72
+ var loading = status === "loading";
51
73
  // Re-sync the text when the external `value` changes (adjust-during-render
52
74
  // pattern, avoids a cascading effect render).
53
- var _useState3 = useState(value),
54
- _useState4 = _slicedToArray(_useState3, 2),
55
- prevValue = _useState4[0],
56
- setPrevValue = _useState4[1];
75
+ var _useState7 = useState(value),
76
+ _useState8 = _slicedToArray(_useState7, 2),
77
+ prevValue = _useState8[0],
78
+ setPrevValue = _useState8[1];
57
79
  if (value !== prevValue) {
58
80
  setPrevValue(value);
59
81
  setText(formatCoords(value));
@@ -77,15 +99,22 @@ var LocationPicker = /*#__PURE__*/forwardRef(function LocationPicker(_ref, ref)
77
99
  lng: Number(position.coords.longitude)
78
100
  };
79
101
  setText(formatCoords(coords));
102
+ setStatus("idle");
103
+ setErrorMessage("");
80
104
  if (typeof onChange === "function") onChange(coords);
81
105
  };
82
106
  var handleGeoError = function handleGeoError(err) {
83
107
  console.error("LocationPicker geolocation error:", err);
108
+ setStatus("error");
109
+ setErrorMessage(LOCATION_ERROR_MESSAGES[err === null || err === void 0 ? void 0 : err.code] || LOCATION_ERROR_MESSAGES["default"]);
84
110
  };
85
111
  var handleIconClick = function handleIconClick(e) {
86
112
  var _navigator;
113
+ if (loading) return;
87
114
  if (!((_navigator = navigator) !== null && _navigator !== void 0 && _navigator.geolocation)) {
88
115
  console.error("LocationPicker: geolocation not supported in this browser");
116
+ setStatus("error");
117
+ setErrorMessage(LOCATION_ERROR_MESSAGES.unsupported);
89
118
  if (typeof iconProps.onClick === "function") {
90
119
  try {
91
120
  iconProps.onClick(e);
@@ -95,6 +124,8 @@ var LocationPicker = /*#__PURE__*/forwardRef(function LocationPicker(_ref, ref)
95
124
  }
96
125
  return;
97
126
  }
127
+ setStatus("loading");
128
+ setErrorMessage("");
98
129
  navigator.geolocation.getCurrentPosition(handleGeoSuccess, handleGeoError, {
99
130
  enableHighAccuracy: true,
100
131
  timeout: 10000
@@ -127,33 +158,68 @@ var LocationPicker = /*#__PURE__*/forwardRef(function LocationPicker(_ref, ref)
127
158
  strokeWidth: "1.2"
128
159
  })]
129
160
  });
161
+ var Spinner = jsxs("svg", {
162
+ className: "w-4 h-4 animate-spin motion-reduce:animate-none",
163
+ viewBox: "0 0 24 24",
164
+ fill: "none",
165
+ "aria-hidden": "true",
166
+ "data-testid": "location-picker-spinner",
167
+ children: [jsx("circle", {
168
+ cx: "12",
169
+ cy: "12",
170
+ r: "9",
171
+ stroke: "currentColor",
172
+ strokeOpacity: "0.25",
173
+ strokeWidth: "2.5"
174
+ }), jsx("path", {
175
+ d: "M21 12a9 9 0 0 0-9-9",
176
+ stroke: "currentColor",
177
+ strokeWidth: "2.5",
178
+ strokeLinecap: "round"
179
+ })]
180
+ });
130
181
  var iconButton = jsx("button", _objectSpread2(_objectSpread2({
131
182
  type: "button",
132
183
  onClick: handleIconClick
133
184
  }, iconProps), {}, {
134
185
  "aria-label": iconProps["aria-label"] || "Use my location",
135
- className: "inline-flex items-center justify-center p-1 focus:outline-none ".concat(iconProps.className || ""),
136
- children: icon ? /*#__PURE__*/React.isValidElement(icon) ? /*#__PURE__*/cloneElement(icon) : icon : DefaultIcon
186
+ "aria-busy": loading || undefined,
187
+ "aria-describedby": status !== "idle" ? messageId : iconProps["aria-describedby"],
188
+ className: cn("inline-flex items-center justify-center rounded-md p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", "transition-colors duration-fast motion-reduce:transition-none", loading && "cursor-wait", TOUCH_TARGET, PRESS_FEEDBACK, "transition-[color,background-color,border-color,transform]", iconProps.className),
189
+ children: loading ? Spinner : icon ? /*#__PURE__*/React.isValidElement(icon) ? /*#__PURE__*/cloneElement(icon) : icon : DefaultIcon
137
190
  }));
138
- var inputPaddingClass = iconPosition === "right" ? "pr-10" : "pl-10";
139
- return jsx("div", _objectSpread2(_objectSpread2({
140
- className: cn("inline-flex items-center", className)
191
+ // The 44px touch target is wider than p-1, so leave room for it on coarse pointers.
192
+ var inputPaddingClass = iconPosition === "right" ? "pr-10 [@media(pointer:coarse)]:pr-12" : "pl-10 [@media(pointer:coarse)]:pl-12";
193
+ return jsxs("div", _objectSpread2(_objectSpread2({
194
+ className: cn(fullWidth ? "flex w-full flex-col" : "inline-flex flex-col", className),
195
+ "data-status": status
141
196
  }, rest), {}, {
142
- children: jsxs("div", {
143
- className: "relative",
197
+ children: [jsxs("div", {
198
+ className: cn("relative", fullWidth && "w-full"),
144
199
  children: [jsx("input", _objectSpread2(_objectSpread2({
145
200
  ref: ref,
146
201
  type: "text",
147
- placeholder: placeholder
202
+ placeholder: placeholder,
203
+ "aria-describedby": status !== "idle" ? messageId : undefined
148
204
  }, inputProps), {}, {
149
205
  value: text,
150
206
  onChange: handleInputChange,
151
- className: cn("min-w-[200px] rounded-md border border-gray-300 bg-white py-2 px-3 text-sm placeholder-gray-400 shadow-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500", inputProps.className, inputPaddingClass)
207
+ className: cn("rounded-md border border-gray-300 bg-white py-2 px-3 text-sm text-gray-900 placeholder-gray-400 shadow-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500", "dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500", "[@media(pointer:coarse)]:text-base", fullWidth ? "w-full" : "min-w-[200px]", status === "error" && "border-error-500 dark:border-error-400", inputProps.className, inputPaddingClass)
152
208
  })), jsx("div", {
153
209
  className: cn("absolute inset-y-0 flex items-center", iconPosition === "right" ? "right-2" : "left-2"),
154
210
  children: iconButton
155
211
  })]
156
- })
212
+ }), status === "loading" && jsx("p", {
213
+ id: messageId,
214
+ role: "status",
215
+ className: "mt-1 text-xs text-gray-500 dark:text-gray-400",
216
+ children: "Finding your location\u2026"
217
+ }), status === "error" && jsx("p", {
218
+ id: messageId,
219
+ role: "alert",
220
+ className: "mt-1 text-xs text-error-600 dark:text-error-400",
221
+ children: errorMessage
222
+ })]
157
223
  }));
158
224
  });
159
225
  LocationPicker.displayName = "LocationPicker";
@@ -86,7 +86,7 @@ var ProgressBar = /*#__PURE__*/forwardRef(function ProgressBar(_ref, ref) {
86
86
  var trackHex = isHexColor(bgColor) ? bgColor : null;
87
87
  var textHex = isHexColor(valueColor) ? valueColor : fillHex ? readableTextColor(fillHex, undefined) : null;
88
88
  var fillClass = fillHex ? '' : 'bg-primary-600';
89
- var trackClass = trackHex ? '' : 'bg-primary-100';
89
+ var trackClass = trackHex ? '' : 'bg-primary-100 dark:bg-primary-900/40';
90
90
  var textClass = textHex ? '' : 'text-white';
91
91
  // height class or inline style
92
92
  var heightClass = typeof size === 'number' ? '' : (_SIZE_MAP$size = SIZE_MAP[size]) !== null && _SIZE_MAP$size !== void 0 ? _SIZE_MAP$size : SIZE_MAP['md'];
@@ -101,26 +101,22 @@ var ProgressBar = /*#__PURE__*/forwardRef(function ProgressBar(_ref, ref) {
101
101
  backgroundSize: '20px 20px',
102
102
  overflow: 'hidden'
103
103
  } : {};
104
- var animatedStyle = animated ? {
105
- animation: 'stripeShift 1s linear infinite'
106
- } : {};
107
- var finalFillStyle = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
104
+ // Stripe motion is a Tailwind animation (DTUI-48) — no per-instance <style> tag —
105
+ // and stops under prefers-reduced-motion, as does the width transition.
106
+ var animatedClass = animated ? 'animate-progress-stripes motion-reduce:animate-none' : '';
107
+ var finalFillStyle = _objectSpread2(_objectSpread2(_objectSpread2({
108
108
  width: "".concat(percent, "%")
109
109
  }, fillHex ? {
110
110
  backgroundColor: fillHex
111
111
  } : {}), textHex ? {
112
112
  color: textHex
113
- } : {}), stripeStyle), animatedStyle), {}, {
114
- transition: 'width 300ms ease-out'
115
- });
113
+ } : {}), stripeStyle);
116
114
  return jsxs("div", _objectSpread2(_objectSpread2({
117
115
  ref: ref,
118
116
  className: cn("w-full", className),
119
117
  style: style
120
118
  }, rest), {}, {
121
- children: [jsx("style", {
122
- children: "@keyframes stripeShift { 0% { background-position: 0 0; } 100% { background-position: 40px 0; } }"
123
- }), label ? jsx("div", {
119
+ children: [label ? jsx("div", {
124
120
  className: "mb-2 text-sm font-medium",
125
121
  children: label
126
122
  }) : null, jsx("div", {
@@ -134,7 +130,7 @@ var ProgressBar = /*#__PURE__*/forwardRef(function ProgressBar(_ref, ref) {
134
130
  "aria-valuemax": safeMax,
135
131
  "aria-valuenow": clamped,
136
132
  children: jsx("div", {
137
- className: cn(radiusClass, heightClass, fillClass, textClass, 'flex items-center justify-center text-sm font-medium'),
133
+ className: cn(radiusClass, heightClass, fillClass, textClass, 'flex items-center justify-center text-sm font-medium', 'transition-[width] duration-300 ease-out motion-reduce:transition-none', animatedClass),
138
134
  style: finalFillStyle,
139
135
  children: showValue ? jsxs("div", {
140
136
  className: "px-2 whitespace-nowrap truncate",
@@ -3,14 +3,9 @@ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { forwardRef, useId, useState, useRef, useCallback, useEffect } from 'react';
5
5
  import PropTypes from '../../_vendor/prop-types/index.js';
6
+ import { cn } from '../../utils/cn.js';
6
7
 
7
8
  var _excluded = ["id", "name", "value", "checked", "defaultChecked", "onChange", "disabled", "required", "label", "size", "labelPosition", "radioColor", "className", "ariaLabel"];
8
- var cn = function cn() {
9
- for (var _len = arguments.length, vals = new Array(_len), _key = 0; _key < _len; _key++) {
10
- vals[_key] = arguments[_key];
11
- }
12
- return vals.filter(Boolean).join(" ");
13
- };
14
9
  var SIZE_MAP = {
15
10
  sm: {
16
11
  outer: "h-4 w-4",
@@ -108,10 +103,13 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
108
103
  var showLabelLeft = label && labelPosition === "left";
109
104
  return jsxs("label", {
110
105
  htmlFor: inputId,
111
- className: cn("relative inline-flex items-center select-none gap-2", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer", className),
106
+ className: cn(
107
+ // The invisible input covers the whole label, so the row is the hit
108
+ // area; on coarse pointers it is at least 44px tall.
109
+ "relative inline-flex items-center select-none gap-2 [@media(pointer:coarse)]:min-h-11", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer", className),
112
110
  "aria-disabled": disabled ? "true" : undefined,
113
111
  children: [showLabelLeft && jsxs("span", {
114
- className: cn(sizeClasses.label, "leading-none", disabled && "text-primary-400"),
112
+ className: cn(sizeClasses.label, "leading-none text-foreground", disabled && "text-primary-400"),
115
113
  children: [label, required ? jsx("span", {
116
114
  "aria-hidden": "true",
117
115
  children: " *"
@@ -132,7 +130,7 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
132
130
  }, props)), jsx("span", {
133
131
  "aria-hidden": true,
134
132
  "data-state": isChecked ? "checked" : "unchecked",
135
- className: cn("inline-flex items-center justify-center rounded-full transition-all motion-reduce:transition-none",
133
+ className: cn("inline-flex shrink-0 items-center justify-center rounded-full transition-shadow duration-fast motion-reduce:transition-none",
136
134
  // Visible focus indicator: the native input is opacity-0, so the
137
135
  // ring is drawn on the visual circle when the input has focus.
138
136
  "peer-focus-visible:ring-2 peer-focus-visible:ring-primary-500 peer-focus-visible:ring-offset-2 dark:peer-focus-visible:ring-offset-gray-900"),
@@ -140,10 +138,10 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
140
138
  "--radio-accent": accentColor
141
139
  },
142
140
  children: jsx("span", {
143
- className: cn("flex items-center justify-center rounded-full border transition-colors duration-150 ease-in-out motion-reduce:transition-none border-slate-300 dark:border-gray-600", sizeClasses.outer, disabled ? "bg-slate-100 dark:bg-gray-800" : "bg-white dark:bg-gray-900", isChecked ? "shadow-inner" : ""),
141
+ className: cn("flex items-center justify-center rounded-full border transition-colors duration-fast ease-standard motion-reduce:transition-none", isChecked ? "border-[color:var(--radio-accent)]" : "border-gray-300 dark:border-gray-600", sizeClasses.outer, disabled ? "bg-gray-100 dark:bg-gray-800" : "bg-surface", isChecked ? "shadow-inner" : ""),
144
142
  children: jsx("span", {
145
143
  "aria-hidden": true,
146
- className: cn("rounded-full transform transition-all duration-200 ease-out motion-reduce:transition-none", sizeClasses.dot),
144
+ className: cn("rounded-full transform transition-[opacity,transform] duration-base ease-spring-out motion-reduce:transition-none", sizeClasses.dot),
147
145
  style: {
148
146
  backgroundColor: isChecked ? accentColor : "transparent",
149
147
  opacity: isChecked ? 1 : 0,
@@ -152,7 +150,7 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
152
150
  })
153
151
  })
154
152
  }), !showLabelLeft && label && jsxs("span", {
155
- className: cn(sizeClasses.label, "leading-none", disabled && "text-primary-400"),
153
+ className: cn(sizeClasses.label, "leading-none text-foreground", disabled && "text-primary-400"),
156
154
  children: [label, required ? jsx("span", {
157
155
  "aria-hidden": "true",
158
156
  children: " *"
@@ -71,6 +71,9 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
71
71
  hoverValue = _useState4[0],
72
72
  setHoverValue = _useState4[1];
73
73
  var starRefs = useRef([]);
74
+ // Touch drag (DTUI-42): { pointerId, moved, value } while a finger is down.
75
+ var drag = useRef(null);
76
+ var suppressClick = useRef(false);
74
77
  var isControlled = value != null;
75
78
  var selectedValue = isControlled ? value : internalValue;
76
79
  // Hover preview (pointer devices only) wins over the committed value.
@@ -103,6 +106,63 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
103
106
  if (readOnly) return;
104
107
  setHoverValue(null);
105
108
  };
109
+ // Star under clientX: the last star whose left edge is at or before the finger,
110
+ // clamped to 1..count so sliding off either end keeps a valid rating.
111
+ var starAt = function starAt(clientX) {
112
+ var n = 1;
113
+ starRefs.current.slice(0, count).forEach(function (el, i) {
114
+ if (el && el.getBoundingClientRect().left <= clientX) n = i + 1;
115
+ });
116
+ return n;
117
+ };
118
+ var handleDragStart = function handleDragStart(e) {
119
+ var _e$currentTarget$setP, _e$currentTarget;
120
+ if (readOnly || e.pointerType !== "touch") return;
121
+ var start = starAt(e.clientX);
122
+ drag.current = {
123
+ pointerId: e.pointerId,
124
+ moved: false,
125
+ value: start,
126
+ startX: e.clientX
127
+ };
128
+ (_e$currentTarget$setP = (_e$currentTarget = e.currentTarget).setPointerCapture) === null || _e$currentTarget$setP === void 0 || _e$currentTarget$setP.call(_e$currentTarget, e.pointerId);
129
+ };
130
+ var handleDragMove = function handleDragMove(e) {
131
+ var d = drag.current;
132
+ if (!d || d.pointerId !== e.pointerId) return;
133
+ if (!d.moved && Math.abs(e.clientX - d.startX) < 4) return;
134
+ d.moved = true;
135
+ d.value = starAt(e.clientX);
136
+ setHoverValue(d.value);
137
+ };
138
+ var handleDragEnd = function handleDragEnd(e) {
139
+ var _e$currentTarget$rele, _e$currentTarget2, _starRefs$current;
140
+ var d = drag.current;
141
+ if (!d || d.pointerId !== e.pointerId) return;
142
+ drag.current = null;
143
+ (_e$currentTarget$rele = (_e$currentTarget2 = e.currentTarget).releasePointerCapture) === null || _e$currentTarget$rele === void 0 || _e$currentTarget$rele.call(_e$currentTarget2, e.pointerId);
144
+ setHoverValue(null);
145
+ if (!d.moved) return; // a plain tap: the star's click commits it
146
+ // The compat click that follows a drag must not re-commit the start star.
147
+ suppressClick.current = true;
148
+ setTimeout(function () {
149
+ suppressClick.current = false;
150
+ }, 400);
151
+ commit(d.value);
152
+ (_starRefs$current = starRefs.current[d.value - 1]) === null || _starRefs$current === void 0 || _starRefs$current.focus();
153
+ };
154
+ var handleDragCancel = function handleDragCancel(e) {
155
+ if (!drag.current || drag.current.pointerId !== e.pointerId) return;
156
+ drag.current = null;
157
+ setHoverValue(null);
158
+ };
159
+ var handleStarClick = function handleStarClick(n) {
160
+ if (suppressClick.current) {
161
+ suppressClick.current = false;
162
+ return;
163
+ }
164
+ commit(n);
165
+ };
106
166
  var handleKeyDown = function handleKeyDown(e, n) {
107
167
  if (readOnly) return;
108
168
  if (e.key === "Enter" || e.key === " ") {
@@ -131,9 +191,17 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
131
191
  role: "radiogroup",
132
192
  "aria-label": name || "Rating",
133
193
  "aria-readonly": readOnly ? "true" : undefined,
134
- className: "inline-flex gap-1",
135
- onPointerLeave: clearHover,
136
- onPointerCancel: clearHover,
194
+ className: cn("inline-flex gap-1", !readOnly && "touch-pan-y select-none"),
195
+ onPointerLeave: function onPointerLeave(e) {
196
+ if (!drag.current) clearHover();
197
+ },
198
+ onPointerCancel: function onPointerCancel(e) {
199
+ handleDragCancel(e);
200
+ clearHover();
201
+ },
202
+ onPointerDown: handleDragStart,
203
+ onPointerMove: handleDragMove,
204
+ onPointerUp: handleDragEnd,
137
205
  children: Array.from({
138
206
  length: count
139
207
  }, function (_, i) {
@@ -153,7 +221,7 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
153
221
  "aria-label": "".concat(starNumber, " star").concat(starNumber > 1 ? "s" : ""),
154
222
  tabIndex: !readOnly && starNumber === tabStop ? 0 : -1,
155
223
  onClick: function onClick() {
156
- return commit(starNumber);
224
+ return handleStarClick(starNumber);
157
225
  },
158
226
  onKeyDown: function onKeyDown(e) {
159
227
  return handleKeyDown(e, starNumber);
@@ -3,7 +3,7 @@ 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
+ import { TOUCH_TARGET, PRESS_FEEDBACK } from '../../utils/touchTarget.js';
7
7
 
8
8
  var _excluded = ["label", "avatarSrc", "avatarAlt", "initials", "size", "onRemove", "onClick", "className", "closeClass", "ariaLabel", "onKeyDown", "avatarPosition", "avatarClassname", "initialClassname"];
9
9
  var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
@@ -82,7 +82,7 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
82
82
  var gapClass = showAvatar ? s.gapWithAvatar : s.gapNoAvatar;
83
83
  var AvatarNode = showAvatar && jsx(Fragment, {
84
84
  children: avatarSrc ? jsx("div", {
85
- className: "flex-shrink-0 rounded-full overflow-hidden bg-gray-300 flex items-center justify-center ".concat(s.avatar, " ").concat(avatarClassname),
85
+ className: "flex-shrink-0 rounded-full overflow-hidden bg-gray-300 dark:bg-gray-600 dark:text-gray-100 flex items-center justify-center ".concat(s.avatar, " ").concat(avatarClassname),
86
86
  "aria-hidden": avatarSrc ? undefined : "true",
87
87
  children: jsx("img", {
88
88
  src: avatarSrc,
@@ -90,14 +90,14 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
90
90
  className: "w-full h-full object-cover"
91
91
  })
92
92
  }) : jsx("div", {
93
- className: "flex-shrink-0 rounded-full overflow-hidden bg-gray-300 flex items-center justify-center ".concat(s.avatar, " ").concat(initialClassname),
93
+ className: "flex-shrink-0 rounded-full overflow-hidden bg-gray-300 dark:bg-gray-600 dark:text-gray-100 flex items-center justify-center ".concat(s.avatar, " ").concat(initialClassname),
94
94
  "aria-hidden": avatarSrc ? undefined : "true",
95
95
  children: initials
96
96
  })
97
97
  });
98
98
  return jsxs("div", _objectSpread2(_objectSpread2({
99
99
  ref: ref,
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),
100
+ className: cn("inline-flex items-center rounded-full bg-gray-100 text-primary-800 dark:bg-gray-800 dark:text-gray-100", paddingClass, gapClass, s.text, clickable && ["cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-gray-900", PRESS_FEEDBACK], className),
101
101
  role: clickable ? "button" : "group",
102
102
  tabIndex: clickable ? 0 : undefined,
103
103
  "aria-label": ariaLabel,
@@ -113,7 +113,7 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
113
113
  type: "button",
114
114
  "aria-label": removeLabel,
115
115
  onClick: handleRemove,
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),
116
+ className: cn("inline-flex items-center justify-center rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-fast motion-reduce:transition-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", TOUCH_TARGET, closeClass),
117
117
  children: jsx("svg", {
118
118
  "aria-hidden": "true",
119
119
  className: "w-3 h-3",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
3
+ import { jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { forwardRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
6
 
@@ -21,6 +21,11 @@ var _excluded = ["count", "circle", "height", "width", "rounded", "animated", "a
21
21
  * - className (string) - extra wrapper classes
22
22
  * - children - optional children to render when active === false
23
23
  */
24
+ var BONE_CLASSES = "relative inline-block overflow-hidden bg-gray-200 dark:bg-gray-700";
25
+ // Shimmer (DTUI-48): a Tailwind animation, no per-instance <style> tag. The
26
+ // highlight is a soft white in light mode and a faint one in dark mode (no white
27
+ // streak); under prefers-reduced-motion the bone is static and plain.
28
+ var SHIMMER_CLASSES = ["bg-gradient-to-r from-transparent via-white/50 to-transparent dark:via-white/10", "bg-[length:200%_100%] bg-no-repeat animate-shimmer", "motion-reduce:animate-none motion-reduce:bg-none"].join(" ");
24
29
  function toPxOrUnit(value) {
25
30
  if (typeof value === "number") return "".concat(value, "px");
26
31
  if (typeof value === "string") return value;
@@ -61,8 +66,7 @@ var Skeleton = /*#__PURE__*/forwardRef(function Skeleton(_ref, ref) {
61
66
  var items = Array.from({
62
67
  length: Math.max(1, Math.floor(count))
63
68
  });
64
- var shimmerOverlay = animated ? "linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)" : "none";
65
- return jsxs("div", _objectSpread2(_objectSpread2({
69
+ return jsx("div", _objectSpread2(_objectSpread2({
66
70
  ref: ref,
67
71
  className: cn("skeleton-wrapper", className),
68
72
  role: "status",
@@ -75,24 +79,19 @@ var Skeleton = /*#__PURE__*/forwardRef(function Skeleton(_ref, ref) {
75
79
  alignItems: "center"
76
80
  }, style)
77
81
  }, rest), {}, {
78
- children: [jsx("style", {
79
- children: "\n @keyframes skeleton-shimmer {\n 0% { background-position: -150% 0; }\n 100% { background-position: 150% 0; }\n }\n .skeleton-wrapper .skeleton-item {\n display: inline-block;\n position: relative;\n overflow: hidden;\n }\n "
80
- }), items.map(function (_, i) {
82
+ children: items.map(function (_, i) {
81
83
  return jsx("div", {
82
84
  // Token classes rather than an inline hex, so brands and dark mode apply (STUDIO-59).
83
- className: "skeleton-item bg-gray-200 dark:bg-gray-700",
85
+ // Plain join, not cn(): tailwind-merge would treat the gradient / bg-size
86
+ // utilities as conflicts of the bg-gray-200 colour and drop it.
87
+ className: ["skeleton-item", BONE_CLASSES, animated && SHIMMER_CLASSES].filter(Boolean).join(" "),
84
88
  style: {
85
89
  height: resolvedHeight,
86
90
  width: resolvedWidth,
87
- borderRadius: borderRadius,
88
- backgroundImage: shimmerOverlay,
89
- backgroundSize: animated ? "200% 100%" : undefined,
90
- backgroundRepeat: "no-repeat",
91
- animation: animated ? "skeleton-shimmer 1.2s linear infinite" : undefined,
92
- willChange: animated ? "background-position" : undefined
91
+ borderRadius: borderRadius
93
92
  }
94
93
  }, i);
95
- })]
94
+ })
96
95
  }));
97
96
  });
98
97
  Skeleton.displayName = "Skeleton";