@dreamtree-org/twreact-ui 1.3.9 → 1.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +32 -0
  2. package/ai-skills/dreamtree-ui.md +12 -7
  3. package/dist/ai-schema.json +156 -29
  4. package/dist/charts/index.js +195 -66
  5. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  6. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  7. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  8. package/dist/components/charts/useChartLayout.d.ts +25 -0
  9. package/dist/components/charts/useChartLayout.d.ts.map +1 -0
  10. package/dist/components/core/Accordion.d.ts.map +1 -1
  11. package/dist/components/core/Button.d.ts.map +1 -1
  12. package/dist/components/core/Checkbox.d.ts.map +1 -1
  13. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  14. package/dist/components/core/DatePicker.d.ts.map +1 -1
  15. package/dist/components/core/Form.d.ts.map +1 -1
  16. package/dist/components/core/Input.d.ts.map +1 -1
  17. package/dist/components/core/Loader.d.ts.map +1 -1
  18. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  19. package/dist/components/core/PriceRangePicker.d.ts +0 -29
  20. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  21. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  22. package/dist/components/core/Radio.d.ts.map +1 -1
  23. package/dist/components/core/Rate.d.ts.map +1 -1
  24. package/dist/components/core/RoundedTag.d.ts.map +1 -1
  25. package/dist/components/core/Skeleton.d.ts.map +1 -1
  26. package/dist/components/core/Slider.d.ts.map +1 -1
  27. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  28. package/dist/components/core/Switch.d.ts.map +1 -1
  29. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  30. package/dist/components/core/Table/Pagination.d.ts.map +1 -1
  31. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  32. package/dist/components/core/Table/helpers.d.ts +2 -0
  33. package/dist/components/core/Table/helpers.d.ts.map +1 -1
  34. package/dist/components/core/Tabs.d.ts +2 -5
  35. package/dist/components/core/Tabs.d.ts.map +1 -1
  36. package/dist/components/core/TextToSpeech.d.ts +2 -7
  37. package/dist/components/core/TextToSpeech.d.ts.map +1 -1
  38. package/dist/components/core/Textarea.d.ts.map +1 -1
  39. package/dist/components/core/Tooltip.d.ts.map +1 -1
  40. package/dist/components/feedback/Alert.d.ts.map +1 -1
  41. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  42. package/dist/components/feedback/Toast.d.ts.map +1 -1
  43. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  44. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  45. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  46. package/dist/components/utility/Avatar.d.ts +8 -0
  47. package/dist/components/utility/Avatar.d.ts.map +1 -1
  48. package/dist/components/utility/AvatarGroup.d.ts +4 -0
  49. package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
  50. package/dist/components/utility/Badge.d.ts.map +1 -1
  51. package/dist/components/utility/Card.d.ts.map +1 -1
  52. package/dist/components/utility/Carousel.d.ts.map +1 -1
  53. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  54. package/dist/components/utility/Pagination.d.ts.map +1 -1
  55. package/dist/components/utility/Sortable.d.ts.map +1 -1
  56. package/dist/components/utility/Stepper.d.ts.map +1 -1
  57. package/dist/esm/components/blocks/AppShell.js +24 -18
  58. package/dist/esm/components/blocks/EmptyState.js +1 -1
  59. package/dist/esm/components/blocks/ErrorState.js +1 -1
  60. package/dist/esm/components/blocks/LoadingState.js +4 -4
  61. package/dist/esm/components/blocks/StatCard.js +6 -6
  62. package/dist/esm/components/blocks/StatusPill.js +5 -8
  63. package/dist/esm/components/charts/CartesianChart.js +85 -62
  64. package/dist/esm/components/charts/ChartFrame.js +12 -5
  65. package/dist/esm/components/charts/useChartLayout.js +105 -0
  66. package/dist/esm/components/core/Accordion.js +40 -9
  67. package/dist/esm/components/core/Button.js +49 -25
  68. package/dist/esm/components/core/Checkbox.js +71 -45
  69. package/dist/esm/components/core/ColorPicker.js +49 -48
  70. package/dist/esm/components/core/DatePicker.js +97 -58
  71. package/dist/esm/components/core/Form.js +35 -8
  72. package/dist/esm/components/core/Input.js +89 -20
  73. package/dist/esm/components/core/Loader.js +30 -44
  74. package/dist/esm/components/core/LocationPicker.js +82 -16
  75. package/dist/esm/components/core/PriceRangePicker.js +205 -218
  76. package/dist/esm/components/core/ProgressBar.js +8 -12
  77. package/dist/esm/components/core/Radio.js +43 -15
  78. package/dist/esm/components/core/Rate.js +109 -50
  79. package/dist/esm/components/core/RoundedTag.js +27 -10
  80. package/dist/esm/components/core/Skeleton.js +13 -14
  81. package/dist/esm/components/core/Slider.js +24 -6
  82. package/dist/esm/components/core/SpeechToText.js +68 -16
  83. package/dist/esm/components/core/Switch.js +88 -16
  84. package/dist/esm/components/core/Table/ColumnMenu.js +2 -4
  85. package/dist/esm/components/core/Table/Pagination.js +15 -18
  86. package/dist/esm/components/core/Table/TableRow.js +2 -2
  87. package/dist/esm/components/core/Table/helpers.js +33 -1
  88. package/dist/esm/components/core/Tabs.js +185 -37
  89. package/dist/esm/components/core/TextToSpeech.js +121 -28
  90. package/dist/esm/components/core/Textarea.js +10 -2
  91. package/dist/esm/components/core/Tooltip.js +100 -28
  92. package/dist/esm/components/feedback/Alert.js +21 -11
  93. package/dist/esm/components/feedback/Dialog.js +44 -100
  94. package/dist/esm/components/feedback/Toast.js +24 -5
  95. package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
  96. package/dist/esm/components/navigation/FootNav.js +102 -13
  97. package/dist/esm/components/navigation/Sidebar.js +13 -61
  98. package/dist/esm/components/utility/Avatar.js +37 -22
  99. package/dist/esm/components/utility/AvatarGroup.js +56 -0
  100. package/dist/esm/components/utility/Badge.js +43 -23
  101. package/dist/esm/components/utility/Card.js +32 -8
  102. package/dist/esm/components/utility/Carousel.js +165 -38
  103. package/dist/esm/components/utility/FileUpload.js +106 -31
  104. package/dist/esm/components/utility/Pagination.js +33 -15
  105. package/dist/esm/components/utility/Sortable.js +6 -2
  106. package/dist/esm/components/utility/Stepper.js +110 -47
  107. package/dist/esm/hooks/useFocusTrap.js +10 -2
  108. package/dist/esm/hooks/useSliderValues.js +8 -2
  109. package/dist/esm/hooks/useSortable.js +107 -5
  110. package/dist/esm/index.js +1 -0
  111. package/dist/esm/utils/touchTarget.js +4 -1
  112. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  113. package/dist/hooks/useSliderValues.d.ts +6 -2
  114. package/dist/hooks/useSliderValues.d.ts.map +1 -1
  115. package/dist/hooks/useSortable.d.ts +11 -1
  116. package/dist/hooks/useSortable.d.ts.map +1 -1
  117. package/dist/index.css +1 -1
  118. package/dist/index.d.ts +3 -2
  119. package/dist/index.esm.css +1 -1
  120. package/dist/index.esm.js +3734 -2174
  121. package/dist/index.js +3732 -2171
  122. package/dist/types/charts.d.ts +3 -1
  123. package/dist/types/charts.d.ts.map +1 -1
  124. package/dist/types/index.d.ts +64 -9
  125. package/dist/types/index.d.ts.map +1 -1
  126. package/doc/README.md +1 -0
  127. package/mcp/catalog.snapshot.json +350 -121
  128. package/package.json +1 -1
@@ -1,16 +1,11 @@
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, useId, useState } from 'react';
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",
@@ -74,6 +69,32 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
74
69
  if (checked === undefined) setInternalChecked(!!defaultChecked);
75
70
  }
76
71
  var isChecked = checked !== undefined ? checked : internalChecked;
72
+ // Merge the forwarded ref with our own so we can read the native state.
73
+ var inputRef = useRef(null);
74
+ var setRefs = useCallback(function (node) {
75
+ inputRef.current = node;
76
+ if (typeof ref === "function") ref(node);else if (ref) ref.current = node;
77
+ }, [ref]);
78
+ // DTUI-17: browsers never fire `change` on the radio being *unchecked*
79
+ // when a sibling in the same group is selected. In uncontrolled mode,
80
+ // listen for changes on any same-name radio and re-sync our visual state
81
+ // from the native input, so only one dot shows at a time.
82
+ useEffect(function () {
83
+ if (checked !== undefined || !name) return undefined;
84
+ var doc = inputRef.current && inputRef.current.ownerDocument;
85
+ if (!doc) return undefined;
86
+ var sync = function sync(e) {
87
+ var t = e.target;
88
+ var el = inputRef.current;
89
+ if (!el || t === el || !t || t.type !== "radio" || t.name !== name) return;
90
+ if (t.form !== el.form) return;
91
+ setInternalChecked(el.checked);
92
+ };
93
+ doc.addEventListener("change", sync, true);
94
+ return function () {
95
+ return doc.removeEventListener("change", sync, true);
96
+ };
97
+ }, [checked, name]);
77
98
  function handleChange(e) {
78
99
  if (checked === undefined) setInternalChecked(e.target.checked);
79
100
  if (typeof onChange === "function") onChange(e);
@@ -82,16 +103,19 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
82
103
  var showLabelLeft = label && labelPosition === "left";
83
104
  return jsxs("label", {
84
105
  htmlFor: inputId,
85
- 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),
86
110
  "aria-disabled": disabled ? "true" : undefined,
87
111
  children: [showLabelLeft && jsxs("span", {
88
- className: cn(sizeClasses.label, "leading-none", disabled && "text-primary-400"),
112
+ className: cn(sizeClasses.label, "leading-none text-foreground", disabled && "text-primary-400"),
89
113
  children: [label, required ? jsx("span", {
90
114
  "aria-hidden": "true",
91
115
  children: " *"
92
116
  }) : null]
93
117
  }), jsx("input", _objectSpread2({
94
- ref: ref,
118
+ ref: setRefs,
95
119
  id: inputId,
96
120
  type: "radio",
97
121
  name: name,
@@ -102,18 +126,22 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
102
126
  disabled: disabled,
103
127
  required: required,
104
128
  "aria-label": ariaLabel,
105
- className: "absolute inset-0 w-full h-full opacity-0 appearance-none m-0 z-10 cursor-pointer"
129
+ className: "peer absolute inset-0 w-full h-full opacity-0 appearance-none m-0 z-10 cursor-pointer disabled:cursor-not-allowed"
106
130
  }, props)), jsx("span", {
107
131
  "aria-hidden": true,
108
- className: cn("inline-flex items-center justify-center rounded-full transition-all"),
132
+ "data-state": isChecked ? "checked" : "unchecked",
133
+ className: cn("inline-flex shrink-0 items-center justify-center rounded-full transition-shadow duration-fast motion-reduce:transition-none",
134
+ // Visible focus indicator: the native input is opacity-0, so the
135
+ // ring is drawn on the visual circle when the input has focus.
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"),
109
137
  style: {
110
138
  "--radio-accent": accentColor
111
139
  },
112
140
  children: jsx("span", {
113
- className: cn("flex items-center justify-center rounded-full border transition-colors duration-150 ease-in-out 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" : ""),
114
142
  children: jsx("span", {
115
143
  "aria-hidden": true,
116
- className: cn("rounded-full transform transition-all duration-200 ease-out", sizeClasses.dot),
144
+ className: cn("rounded-full transform transition-[opacity,transform] duration-base ease-spring-out motion-reduce:transition-none", sizeClasses.dot),
117
145
  style: {
118
146
  backgroundColor: isChecked ? accentColor : "transparent",
119
147
  opacity: isChecked ? 1 : 0,
@@ -122,7 +150,7 @@ var Radio = /*#__PURE__*/forwardRef(function Radio(_ref, ref) {
122
150
  })
123
151
  })
124
152
  }), !showLabelLeft && label && jsxs("span", {
125
- className: cn(sizeClasses.label, "leading-none", disabled && "text-primary-400"),
153
+ className: cn(sizeClasses.label, "leading-none text-foreground", disabled && "text-primary-400"),
126
154
  children: [label, required ? jsx("span", {
127
155
  "aria-hidden": "true",
128
156
  children: " *"
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
- import { forwardRef, useState } from 'react';
4
+ import { forwardRef, useState, useRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
 
7
8
  var _excluded = ["count", "value", "defaultValue", "readOnly", "icon", "toggledIcon", "activeColor", "inactiveColor", "size", "onChange", "onClick", "text", "className", "id", "name"];
8
9
  function StarFilled(_ref) {
@@ -69,44 +70,98 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
69
70
  _useState4 = _slicedToArray(_useState3, 2),
70
71
  hoverValue = _useState4[0],
71
72
  setHoverValue = _useState4[1];
72
- var _useState5 = useState(null),
73
- _useState6 = _slicedToArray(_useState5, 2),
74
- focusValue = _useState6[0],
75
- setFocusValue = _useState6[1];
73
+ var starRefs = useRef([]);
74
+ // Touch drag (DTUI-42): { pointerId, moved, value } while a finger is down.
75
+ var drag = useRef(null);
76
+ var suppressClick = useRef(false);
76
77
  var isControlled = value != null;
77
78
  var selectedValue = isControlled ? value : internalValue;
78
- var displayValue = hoverValue != null ? hoverValue : focusValue != null ? focusValue : selectedValue;
79
+ // Hover preview (pointer devices only) wins over the committed value.
80
+ var displayValue = hoverValue != null ? hoverValue : selectedValue;
81
+ // Roving tabindex (DTUI-23): the group is a single tab stop — the star
82
+ // equal to the value, or the first star when nothing is selected.
83
+ var tabStop = selectedValue >= 1 ? Math.min(count, selectedValue) : 1;
79
84
  var InactiveIcon = icon !== null && icon !== void 0 ? icon : jsx(StarOutline, {
80
85
  size: size
81
86
  });
82
87
  var ActiveIcon = toggledIcon !== null && toggledIcon !== void 0 ? toggledIcon : jsx(StarFilled, {
83
88
  size: size
84
89
  });
85
- var commit = function commit(n) {
86
- if (readOnly) return;
90
+ var setValue = function setValue(n) {
87
91
  if (!isControlled) setInternalValue(n);
88
92
  if (typeof onChange === "function") onChange(n);
89
- if (typeof onClick === "function") onClick(n);
90
93
  };
91
- var handleMouseEnter = function handleMouseEnter(n) {
94
+ var commit = function commit(n) {
92
95
  if (readOnly) return;
93
- setHoverValue(n);
96
+ setValue(n);
97
+ if (typeof onClick === "function") onClick(n);
94
98
  };
95
- var handleMouseMove = function handleMouseMove(n) {
96
- if (readOnly) return;
99
+ // Touch taps emit pointer/compat-mouse events with no matching "leave",
100
+ // which used to leave the preview stuck. Only real hover devices preview.
101
+ var handlePointerHover = function handlePointerHover(e, n) {
102
+ if (readOnly || e.pointerType === "touch") return;
97
103
  setHoverValue(n);
98
104
  };
99
- var handleMouseLeave = function handleMouseLeave() {
105
+ var clearHover = function clearHover() {
100
106
  if (readOnly) return;
101
107
  setHoverValue(null);
102
108
  };
103
- var handleFocus = function handleFocus(n) {
104
- if (readOnly) return;
105
- setFocusValue(n);
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;
106
117
  };
107
- var handleBlur = function handleBlur() {
108
- if (readOnly) return;
109
- setFocusValue(null);
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);
110
165
  };
111
166
  var handleKeyDown = function handleKeyDown(e, n) {
112
167
  if (readOnly) return;
@@ -115,22 +170,16 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
115
170
  commit(n);
116
171
  return;
117
172
  }
118
- if (e.key === "ArrowLeft") {
119
- e.preventDefault();
120
- setFocusValue(function (prev) {
121
- var base = prev != null ? prev : selectedValue || 0;
122
- return Math.max(0, base - 1);
123
- });
124
- return;
125
- }
126
- if (e.key === "ArrowRight") {
127
- e.preventDefault();
128
- setFocusValue(function (prev) {
129
- var base = prev != null ? prev : selectedValue || 0;
130
- return Math.min(count, base + 1);
131
- });
132
- return;
133
- }
173
+ var next = null;
174
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") next = n + 1;else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = n - 1;else if (e.key === "Home") next = 1;else if (e.key === "End") next = count;
175
+ if (next == null) return;
176
+ e.preventDefault();
177
+ next = Math.max(1, Math.min(count, next));
178
+ // Radio-group semantics: arrows move real focus AND the selection.
179
+ setHoverValue(null);
180
+ var target = starRefs.current[next - 1];
181
+ if (target) target.focus();
182
+ if (next !== selectedValue) setValue(next);
134
183
  };
135
184
  return jsxs("div", _objectSpread2(_objectSpread2({
136
185
  ref: ref,
@@ -141,41 +190,51 @@ var Rate = /*#__PURE__*/forwardRef(function Rate(_ref3, ref) {
141
190
  children: [jsx("div", {
142
191
  role: "radiogroup",
143
192
  "aria-label": name || "Rating",
144
- className: "inline-flex gap-1",
145
- onMouseLeave: handleMouseLeave,
193
+ "aria-readonly": readOnly ? "true" : undefined,
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,
146
205
  children: Array.from({
147
206
  length: count
148
207
  }, function (_, i) {
149
208
  var starNumber = i + 1;
150
209
  var filled = starNumber <= (displayValue || 0);
210
+ var checked = starNumber === selectedValue;
151
211
  var color = filled ? activeColor : inactiveColor;
152
212
  // Token classes unless a colour override is passed, so brands and dark mode apply (STUDIO-59).
153
213
  var colorClass = color ? "" : filled ? " text-warning-400" : " text-gray-200";
154
214
  return jsx("button", {
215
+ ref: function ref(el) {
216
+ starRefs.current[i] = el;
217
+ },
155
218
  type: "button",
156
219
  role: "radio",
157
- "aria-checked": filled,
220
+ "aria-checked": checked,
158
221
  "aria-label": "".concat(starNumber, " star").concat(starNumber > 1 ? "s" : ""),
159
- tabIndex: readOnly ? -1 : 0,
222
+ tabIndex: !readOnly && starNumber === tabStop ? 0 : -1,
160
223
  onClick: function onClick() {
161
- return commit(starNumber);
224
+ return handleStarClick(starNumber);
162
225
  },
163
226
  onKeyDown: function onKeyDown(e) {
164
227
  return handleKeyDown(e, starNumber);
165
228
  },
166
- onMouseEnter: function onMouseEnter() {
167
- return handleMouseEnter(starNumber);
168
- },
169
- onMouseMove: function onMouseMove() {
170
- return handleMouseMove(starNumber);
229
+ onPointerEnter: function onPointerEnter(e) {
230
+ return handlePointerHover(e, starNumber);
171
231
  },
172
- onFocus: function onFocus() {
173
- return handleFocus(starNumber);
232
+ onPointerMove: function onPointerMove(e) {
233
+ return handlePointerHover(e, starNumber);
174
234
  },
175
- onBlur: handleBlur,
176
235
  disabled: readOnly,
177
236
  title: "".concat(starNumber, " of ").concat(count),
178
- className: "p-0 m-0 bg-transparent border-0 inline-flex items-center justify-center leading-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-slate-300 rounded".concat(colorClass),
237
+ className: cn("p-0 m-0 bg-transparent border-0 inline-flex items-center justify-center leading-0 rounded", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500", "transition-colors motion-reduce:transition-none", TOUCH_TARGET, colorClass.trim()),
179
238
  style: color ? {
180
239
  color: color
181
240
  } : undefined,
@@ -3,8 +3,9 @@ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import { forwardRef } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET, PRESS_FEEDBACK } from '../../utils/touchTarget.js';
6
7
 
7
- var _excluded = ["label", "avatarSrc", "avatarAlt", "initials", "size", "onRemove", "onClick", "className", "closeClass", "ariaLabel", "avatarPosition", "avatarClassname", "initialClassname"];
8
+ var _excluded = ["label", "avatarSrc", "avatarAlt", "initials", "size", "onRemove", "onClick", "className", "closeClass", "ariaLabel", "onKeyDown", "avatarPosition", "avatarClassname", "initialClassname"];
8
9
  var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
9
10
  var label = _ref.label,
10
11
  _ref$avatarSrc = _ref.avatarSrc,
@@ -23,8 +24,8 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
23
24
  className = _ref$className === void 0 ? "" : _ref$className,
24
25
  _ref$closeClass = _ref.closeClass,
25
26
  closeClass = _ref$closeClass === void 0 ? "" : _ref$closeClass,
26
- _ref$ariaLabel = _ref.ariaLabel,
27
- ariaLabel = _ref$ariaLabel === void 0 ? "tag" : _ref$ariaLabel,
27
+ ariaLabel = _ref.ariaLabel,
28
+ onKeyDown = _ref.onKeyDown,
28
29
  _ref$avatarPosition = _ref.avatarPosition,
29
30
  avatarPosition = _ref$avatarPosition === void 0 ? "left" : _ref$avatarPosition,
30
31
  _ref$avatarClassname = _ref.avatarClassname,
@@ -63,12 +64,25 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
63
64
  e && e.stopPropagation();
64
65
  onRemove && onRemove({});
65
66
  }
67
+ var clickable = typeof _onClick === "function";
68
+ // Enter / Space activate a clickable tag like a native button. Keys from
69
+ // nested controls (the remove button) are left alone.
70
+ function handleKeyDown(e) {
71
+ onKeyDown && onKeyDown(e);
72
+ if (!clickable || e.defaultPrevented || e.target !== e.currentTarget) return;
73
+ if (e.key === "Enter" || e.key === " ") {
74
+ e.preventDefault();
75
+ _onClick(e);
76
+ }
77
+ }
78
+ var labelText = typeof label === "string" || typeof label === "number" ? String(label) : "";
79
+ var removeLabel = labelText ? "Remove ".concat(labelText) : "Remove";
66
80
  var s = sizes[size] || sizes.md;
67
81
  var paddingClass = showAvatar ? s.paddingWithAvatar : s.paddingNoAvatar;
68
82
  var gapClass = showAvatar ? s.gapWithAvatar : s.gapNoAvatar;
69
83
  var AvatarNode = showAvatar && jsx(Fragment, {
70
84
  children: avatarSrc ? jsx("div", {
71
- 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),
72
86
  "aria-hidden": avatarSrc ? undefined : "true",
73
87
  children: jsx("img", {
74
88
  src: avatarSrc,
@@ -76,29 +90,32 @@ var RoundedTag = /*#__PURE__*/forwardRef(function RoundedTag(_ref, ref) {
76
90
  className: "w-full h-full object-cover"
77
91
  })
78
92
  }) : jsx("div", {
79
- 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),
80
94
  "aria-hidden": avatarSrc ? undefined : "true",
81
95
  children: initials
82
96
  })
83
97
  });
84
98
  return jsxs("div", _objectSpread2(_objectSpread2({
85
99
  ref: ref,
86
- className: cn("inline-flex items-center rounded-full bg-gray-100 text-primary-800", paddingClass, gapClass, s.text, className),
87
- role: "group",
100
+ className: cn("inline-flex items-center rounded-full bg-gray-100 text-primary-800 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
+ role: clickable ? "button" : "group",
102
+ tabIndex: clickable ? 0 : undefined,
88
103
  "aria-label": ariaLabel,
89
104
  onClick: function onClick(e) {
90
105
  return _onClick && _onClick(e);
91
- }
106
+ },
107
+ onKeyDown: handleKeyDown
92
108
  }, rest), {}, {
93
109
  children: [avatarPosition === "left" && AvatarNode, jsx("div", {
94
110
  className: "truncate max-w-[10rem]",
95
111
  children: label
96
112
  }), avatarPosition === "right" && AvatarNode, onRemove && jsx("button", {
97
113
  type: "button",
98
- "aria-label": "Remove",
114
+ "aria-label": removeLabel,
99
115
  onClick: handleRemove,
100
- className: "inline-flex items-center justify-center rounded-full p-1 hover:bg-gray-200 focus:outline-none ".concat(closeClass),
116
+ className: cn("inline-flex items-center justify-center rounded-full p-1 hover:bg-gray-200 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),
101
117
  children: jsx("svg", {
118
+ "aria-hidden": "true",
102
119
  className: "w-3 h-3",
103
120
  viewBox: "0 0 24 24",
104
121
  fill: "none",
@@ -1,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";
@@ -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) {