@dreamtree-org/twreact-ui 1.3.10 → 2.0.0

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 (230) hide show
  1. package/README.md +70 -6
  2. package/ai-skills/dreamtree-ui.md +58 -15
  3. package/codemods/v2.cjs +198 -0
  4. package/dist/ai-schema.json +745 -53
  5. package/dist/charts/index.js +195 -66
  6. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  7. package/dist/components/blocks/BottomActionBar.d.ts +6 -0
  8. package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
  9. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
  10. package/dist/components/blocks/PageHeader.d.ts.map +1 -1
  11. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  12. package/dist/components/charts/ChartFrame.d.ts.map +1 -1
  13. package/dist/components/charts/useChartLayout.d.ts +25 -0
  14. package/dist/components/charts/useChartLayout.d.ts.map +1 -0
  15. package/dist/components/core/Button.d.ts.map +1 -1
  16. package/dist/components/core/Checkbox.d.ts.map +1 -1
  17. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  18. package/dist/components/core/DatePicker.d.ts.map +1 -1
  19. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  20. package/dist/components/core/Form.d.ts.map +1 -1
  21. package/dist/components/core/Input.d.ts.map +1 -1
  22. package/dist/components/core/Loader.d.ts.map +1 -1
  23. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  24. package/dist/components/core/Popover.d.ts.map +1 -1
  25. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  26. package/dist/components/core/Radio.d.ts.map +1 -1
  27. package/dist/components/core/Rate.d.ts.map +1 -1
  28. package/dist/components/core/RoundedTag.d.ts.map +1 -1
  29. package/dist/components/core/SearchField.d.ts +9 -0
  30. package/dist/components/core/SearchField.d.ts.map +1 -0
  31. package/dist/components/core/SegmentedControl.d.ts +8 -0
  32. package/dist/components/core/SegmentedControl.d.ts.map +1 -0
  33. package/dist/components/core/Select.d.ts.map +1 -1
  34. package/dist/components/core/Skeleton.d.ts.map +1 -1
  35. package/dist/components/core/Slider.d.ts.map +1 -1
  36. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  37. package/dist/components/core/SwipeableListItem.d.ts +4 -0
  38. package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
  39. package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
  40. package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
  41. package/dist/components/core/Switch.d.ts.map +1 -1
  42. package/dist/components/core/Table/ActionMenu.d.ts +10 -3
  43. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  44. package/dist/components/core/Table/MobileCard.d.ts +10 -1
  45. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  46. package/dist/components/core/Table/MobileFilters.d.ts +8 -2
  47. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  48. package/dist/components/core/Table/Table.d.ts.map +1 -1
  49. package/dist/components/core/Table/TableRow.d.ts +2 -1
  50. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  51. package/dist/components/core/Tabs.d.ts +5 -8
  52. package/dist/components/core/Tabs.d.ts.map +1 -1
  53. package/dist/components/core/Textarea.d.ts.map +1 -1
  54. package/dist/components/core/ThreeDotPopover.d.ts +10 -22
  55. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  56. package/dist/components/core/Tooltip.d.ts +2 -1
  57. package/dist/components/core/Tooltip.d.ts.map +1 -1
  58. package/dist/components/core/internal/Calendar.d.ts +77 -0
  59. package/dist/components/core/internal/Calendar.d.ts.map +1 -0
  60. package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
  61. package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
  62. package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
  63. package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
  64. package/dist/components/core/menu/MenuPopover.d.ts +21 -0
  65. package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
  66. package/dist/components/core/menu/menuItems.d.ts +21 -0
  67. package/dist/components/core/menu/menuItems.d.ts.map +1 -0
  68. package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
  69. package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
  70. package/dist/components/feedback/ActionSheet.d.ts +4 -0
  71. package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
  72. package/dist/components/feedback/Alert.d.ts.map +1 -1
  73. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  74. package/dist/components/feedback/Drawer.d.ts.map +1 -1
  75. package/dist/components/feedback/Sheet.d.ts +4 -0
  76. package/dist/components/feedback/Sheet.d.ts.map +1 -0
  77. package/dist/components/feedback/Toast.d.ts +4 -3
  78. package/dist/components/feedback/Toast.d.ts.map +1 -1
  79. package/dist/components/feedback/ToastItem.d.ts +47 -0
  80. package/dist/components/feedback/ToastItem.d.ts.map +1 -0
  81. package/dist/components/feedback/ToastStack.d.ts +20 -0
  82. package/dist/components/feedback/ToastStack.d.ts.map +1 -0
  83. package/dist/components/feedback/ToastSwipe.d.ts +22 -0
  84. package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
  85. package/dist/components/feedback/Tour.d.ts.map +1 -1
  86. package/dist/components/feedback/sheetCore.d.ts +30 -0
  87. package/dist/components/feedback/sheetCore.d.ts.map +1 -0
  88. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  89. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  90. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  91. package/dist/components/navigation/Sidebar.d.ts +6 -0
  92. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  93. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
  94. package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
  95. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
  96. package/dist/components/utility/Avatar.d.ts +8 -0
  97. package/dist/components/utility/Avatar.d.ts.map +1 -1
  98. package/dist/components/utility/AvatarGroup.d.ts +4 -0
  99. package/dist/components/utility/AvatarGroup.d.ts.map +1 -0
  100. package/dist/components/utility/Badge.d.ts.map +1 -1
  101. package/dist/components/utility/Card.d.ts.map +1 -1
  102. package/dist/components/utility/Carousel.d.ts.map +1 -1
  103. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  104. package/dist/components/utility/Pagination.d.ts.map +1 -1
  105. package/dist/components/utility/PullToRefresh.d.ts +5 -0
  106. package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
  107. package/dist/components/utility/Sortable.d.ts.map +1 -1
  108. package/dist/components/utility/Stepper.d.ts.map +1 -1
  109. package/dist/esm/components/blocks/AppShell.js +97 -90
  110. package/dist/esm/components/blocks/BottomActionBar.js +132 -0
  111. package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
  112. package/dist/esm/components/blocks/EmptyState.js +1 -1
  113. package/dist/esm/components/blocks/ErrorState.js +1 -1
  114. package/dist/esm/components/blocks/LoadingState.js +4 -4
  115. package/dist/esm/components/blocks/PageHeader.js +16 -3
  116. package/dist/esm/components/blocks/StatCard.js +6 -6
  117. package/dist/esm/components/blocks/StatusPill.js +5 -8
  118. package/dist/esm/components/charts/CartesianChart.js +85 -62
  119. package/dist/esm/components/charts/ChartFrame.js +12 -5
  120. package/dist/esm/components/charts/useChartLayout.js +105 -0
  121. package/dist/esm/components/core/Button.js +49 -25
  122. package/dist/esm/components/core/Checkbox.js +72 -57
  123. package/dist/esm/components/core/ColorPicker.js +31 -42
  124. package/dist/esm/components/core/DatePicker.js +117 -304
  125. package/dist/esm/components/core/DateRangePicker.js +260 -308
  126. package/dist/esm/components/core/Form.js +11 -2
  127. package/dist/esm/components/core/Input.js +100 -38
  128. package/dist/esm/components/core/Loader.js +30 -44
  129. package/dist/esm/components/core/LocationPicker.js +82 -16
  130. package/dist/esm/components/core/Popover.js +59 -48
  131. package/dist/esm/components/core/ProgressBar.js +40 -72
  132. package/dist/esm/components/core/Radio.js +10 -12
  133. package/dist/esm/components/core/Rate.js +77 -14
  134. package/dist/esm/components/core/RoundedTag.js +5 -5
  135. package/dist/esm/components/core/SearchField.js +311 -0
  136. package/dist/esm/components/core/SegmentedControl.js +189 -0
  137. package/dist/esm/components/core/Select.js +185 -190
  138. package/dist/esm/components/core/Skeleton.js +13 -14
  139. package/dist/esm/components/core/Slider.js +24 -6
  140. package/dist/esm/components/core/SpeechToText.js +68 -16
  141. package/dist/esm/components/core/SwipeableListItem.js +286 -0
  142. package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
  143. package/dist/esm/components/core/Switch.js +98 -28
  144. package/dist/esm/components/core/Table/ActionMenu.js +45 -231
  145. package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
  146. package/dist/esm/components/core/Table/MobileCard.js +66 -16
  147. package/dist/esm/components/core/Table/MobileFilters.js +181 -10
  148. package/dist/esm/components/core/Table/Table.js +149 -102
  149. package/dist/esm/components/core/Table/TableRow.js +10 -2
  150. package/dist/esm/components/core/Tabs.js +186 -39
  151. package/dist/esm/components/core/Textarea.js +10 -2
  152. package/dist/esm/components/core/ThreeDotPopover.js +93 -99
  153. package/dist/esm/components/core/Tooltip.js +138 -145
  154. package/dist/esm/components/core/internal/Calendar.js +381 -0
  155. package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
  156. package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
  157. package/dist/esm/components/core/menu/MenuPopover.js +177 -0
  158. package/dist/esm/components/core/menu/menuItems.js +61 -0
  159. package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
  160. package/dist/esm/components/feedback/ActionSheet.js +55 -0
  161. package/dist/esm/components/feedback/Alert.js +63 -53
  162. package/dist/esm/components/feedback/Dialog.js +141 -53
  163. package/dist/esm/components/feedback/Drawer.js +130 -132
  164. package/dist/esm/components/feedback/Sheet.js +125 -0
  165. package/dist/esm/components/feedback/Toast.js +21 -291
  166. package/dist/esm/components/feedback/ToastItem.js +348 -0
  167. package/dist/esm/components/feedback/ToastStack.js +130 -0
  168. package/dist/esm/components/feedback/ToastSwipe.js +52 -0
  169. package/dist/esm/components/feedback/Tour.js +107 -30
  170. package/dist/esm/components/feedback/sheetCore.js +494 -0
  171. package/dist/esm/components/navigation/Breadcrumbs.js +97 -35
  172. package/dist/esm/components/navigation/Dropdown.js +1 -1
  173. package/dist/esm/components/navigation/FootNav.js +102 -13
  174. package/dist/esm/components/navigation/Navbar.js +172 -37
  175. package/dist/esm/components/navigation/Sidebar.js +263 -111
  176. package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
  177. package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
  178. package/dist/esm/components/utility/Avatar.js +14 -12
  179. package/dist/esm/components/utility/AvatarGroup.js +56 -0
  180. package/dist/esm/components/utility/Badge.js +43 -23
  181. package/dist/esm/components/utility/Card.js +32 -8
  182. package/dist/esm/components/utility/Carousel.js +139 -29
  183. package/dist/esm/components/utility/FileUpload.js +106 -31
  184. package/dist/esm/components/utility/Pagination.js +31 -13
  185. package/dist/esm/components/utility/Portal.js +31 -0
  186. package/dist/esm/components/utility/PullToRefresh.js +229 -0
  187. package/dist/esm/components/utility/Sortable.js +6 -2
  188. package/dist/esm/components/utility/Stepper.js +110 -47
  189. package/dist/esm/hooks/useFloatingPosition.js +82 -0
  190. package/dist/esm/hooks/useHaptics.js +65 -0
  191. package/dist/esm/hooks/usePresence.js +58 -0
  192. package/dist/esm/hooks/usePresentation.js +21 -0
  193. package/dist/esm/hooks/useSheetDrag.js +220 -0
  194. package/dist/esm/hooks/useSliderValues.js +8 -2
  195. package/dist/esm/hooks/useSortable.js +107 -5
  196. package/dist/esm/index.js +14 -2
  197. package/dist/esm/utils/position.js +10 -63
  198. package/dist/esm/utils/touchTarget.js +4 -1
  199. package/dist/hooks/useFloatingPosition.d.ts +8 -4
  200. package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
  201. package/dist/hooks/useHaptics.d.ts +19 -0
  202. package/dist/hooks/useHaptics.d.ts.map +1 -0
  203. package/dist/hooks/useSheetDrag.d.ts +31 -0
  204. package/dist/hooks/useSheetDrag.d.ts.map +1 -0
  205. package/dist/hooks/useSliderValues.d.ts +6 -2
  206. package/dist/hooks/useSliderValues.d.ts.map +1 -1
  207. package/dist/hooks/useSortable.d.ts +11 -1
  208. package/dist/hooks/useSortable.d.ts.map +1 -1
  209. package/dist/index.css +1 -1
  210. package/dist/index.d.ts +11 -2
  211. package/dist/index.esm.css +1 -1
  212. package/dist/index.esm.js +10645 -3842
  213. package/dist/index.js +10657 -3845
  214. package/dist/rich-text/index.js +41 -11
  215. package/dist/types/blocks.d.ts +32 -2
  216. package/dist/types/blocks.d.ts.map +1 -1
  217. package/dist/types/charts.d.ts +3 -1
  218. package/dist/types/charts.d.ts.map +1 -1
  219. package/dist/types/index.d.ts +409 -26
  220. package/dist/types/index.d.ts.map +1 -1
  221. package/dist/types/rich-text.d.ts +5 -0
  222. package/dist/types/rich-text.d.ts.map +1 -1
  223. package/dist/utils/position.d.ts +0 -15
  224. package/dist/utils/position.d.ts.map +1 -1
  225. package/doc/README.md +16 -0
  226. package/mcp/catalog.snapshot.json +1540 -407
  227. package/package.json +7 -2
  228. package/dist/esm/hooks/useSwipeToClose.js +0 -66
  229. package/dist/hooks/useSwipeToClose.d.ts +0 -24
  230. package/dist/hooks/useSwipeToClose.d.ts.map +0 -1
@@ -142,13 +142,22 @@ var Form = /*#__PURE__*/forwardRef(function Form(_ref2, ref) {
142
142
  onChange = _ref3$field.onChange,
143
143
  value = _ref3$field.value,
144
144
  ref = _ref3$field.ref;
145
- return jsx(Input, _objectSpread2({
145
+ return jsx(Input, _objectSpread2(_objectSpread2({
146
146
  type: type,
147
147
  value: value || "",
148
148
  onChange: onChange,
149
149
  ref: ref,
150
150
  helperText: helperText
151
- }, commonProps));
151
+ }, commonProps), {}, {
152
+ // Input's clear no longer fires a synthetic onChange (2.0), so a
153
+ // clearable field resets the form value here, then calls the
154
+ // field's own onClear.
155
+ onClear: function onClear() {
156
+ var _commonProps$onClear;
157
+ onChange("");
158
+ (_commonProps$onClear = commonProps.onClear) === null || _commonProps$onClear === void 0 || _commonProps$onClear.call(commonProps);
159
+ }
160
+ }));
152
161
  }
153
162
  }, name);
154
163
  case "select":
@@ -1,11 +1,52 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
4
  import { forwardRef, useId, useRef, useCallback, useState } from 'react';
5
5
  import { Search, EyeOff, Eye, X } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
7
8
 
8
- var _excluded = ["id", "className", "type", "label", "placeholder", "error", "success", "helperText", "disabled", "required", "clearable", "icon", "iconPosition", "onClear", "showCount", "maxLength", "readOnly", "onChange", "value", "defaultValue"];
9
+ var _excluded = ["id", "className", "type", "label", "placeholder", "error", "success", "helperText", "disabled", "required", "clearable", "leftIcon", "rightIcon", "onClear", "showCount", "maxLength", "readOnly", "onChange", "value", "defaultValue", "size", "floatingLabel", "otp"];
10
+ // Field heights. `md` keeps its 40px desktop look and grows to the 44px touch
11
+ // target on coarse pointers (same rule as Select); `lg` is 44px everywhere.
12
+ var SIZE_CLASSES = {
13
+ sm: "h-8 text-xs",
14
+ md: "h-10 text-sm leading-10 [@media(pointer:coarse)]:h-11",
15
+ lg: "h-11 text-base"
16
+ };
17
+ // Taller fields for `floatingLabel`, with top padding so the value sits below
18
+ // the floated label.
19
+ var FLOATING_SIZE_CLASSES = {
20
+ sm: "h-10 pt-3 text-sm",
21
+ md: "h-12 pt-4 text-sm",
22
+ lg: "h-14 pt-5 text-base"
23
+ };
24
+ // Mobile keyboard / autofill hints for native types. Applied only when the
25
+ // caller did not pass the attribute themselves.
26
+ var TYPE_HINTS = {
27
+ tel: {
28
+ inputMode: "tel",
29
+ autoComplete: "tel"
30
+ },
31
+ email: {
32
+ inputMode: "email",
33
+ autoComplete: "email"
34
+ },
35
+ url: {
36
+ inputMode: "url",
37
+ autoComplete: "url"
38
+ },
39
+ search: {
40
+ inputMode: "search",
41
+ enterKeyHint: "search"
42
+ }
43
+ };
44
+ var OTP_HINTS = {
45
+ inputMode: "numeric",
46
+ autoComplete: "one-time-code"
47
+ };
48
+ // Password toggle / clear: real buttons that reach 44×44 on coarse pointers.
49
+ var ADORNMENT_BUTTON = cn("absolute right-1 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-md", "text-primary-500 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300", "transition-colors duration-fast ease-standard motion-reduce:transition-none", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", "disabled:cursor-not-allowed disabled:opacity-50", TOUCH_TARGET);
9
50
  var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
10
51
  var id = props.id,
11
52
  className = props.className,
@@ -22,9 +63,8 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
22
63
  required = _props$required === void 0 ? false : _props$required,
23
64
  _props$clearable = props.clearable,
24
65
  clearable = _props$clearable === void 0 ? false : _props$clearable,
25
- icon = props.icon,
26
- _props$iconPosition = props.iconPosition,
27
- iconPosition = _props$iconPosition === void 0 ? "left" : _props$iconPosition,
66
+ leftIcon = props.leftIcon,
67
+ rightIcon = props.rightIcon,
28
68
  onClear = props.onClear,
29
69
  _props$showCount = props.showCount,
30
70
  showCount = _props$showCount === void 0 ? false : _props$showCount,
@@ -34,7 +74,25 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
34
74
  onChange = props.onChange,
35
75
  value = props.value,
36
76
  defaultValue = props.defaultValue,
77
+ _props$size = props.size,
78
+ sizeProp = _props$size === void 0 ? "md" : _props$size,
79
+ _props$floatingLabel = props.floatingLabel,
80
+ floatingLabel = _props$floatingLabel === void 0 ? false : _props$floatingLabel,
81
+ _props$otp = props.otp,
82
+ otp = _props$otp === void 0 ? false : _props$otp,
37
83
  rest = _objectWithoutProperties(props, _excluded);
84
+ // `size` is the field size (sm|md|lg). A numeric value is the native
85
+ // `<input size>` attribute and is passed through unchanged.
86
+ var size = SIZE_CLASSES[sizeProp] ? sizeProp : "md";
87
+ var nativeSize = SIZE_CLASSES[sizeProp] ? undefined : sizeProp;
88
+ var hints = otp ? OTP_HINTS : TYPE_HINTS[type];
89
+ var hintProps = {};
90
+ if (hints) {
91
+ Object.keys(hints).forEach(function (key) {
92
+ if (!(key in rest)) hintProps[key] = hints[key];
93
+ });
94
+ }
95
+ var isFloating = Boolean(floatingLabel && label);
38
96
  var internalId = useId();
39
97
  var inputId = id || "input-".concat(internalId);
40
98
  var isSearch = type === "search";
@@ -66,43 +124,40 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
66
124
  if (!isControlled) setUncontrolledLength(val.length);
67
125
  onChange === null || onChange === void 0 || onChange(e);
68
126
  };
127
+ // Clear (2.0, DTUI-65): no synthetic `onChange` event any more. Controlled
128
+ // fields clear their own `value` in `onClear`; uncontrolled fields clear the
129
+ // DOM value themselves.
69
130
  var handleClear = function handleClear(e) {
70
131
  var _localRef$current, _localRef$current$foc;
71
132
  e === null || e === void 0 || e.preventDefault();
72
- if (isControlled) {
73
- // controlled — notify parent
74
- onChange === null || onChange === void 0 || onChange({
75
- target: {
76
- value: ""
77
- }
78
- });
79
- } else if (localRef.current) {
133
+ if (!isControlled && localRef.current) {
80
134
  localRef.current.value = "";
81
- // update local state
82
135
  setUncontrolledLength(0);
83
136
  }
84
137
  (_localRef$current = localRef.current) === null || _localRef$current === void 0 || (_localRef$current$foc = _localRef$current.focus) === null || _localRef$current$foc === void 0 || _localRef$current$foc.call(_localRef$current);
85
138
  onClear === null || onClear === void 0 || onClear();
86
139
  };
87
140
  // padding logic: left and right padding depend on icons/buttons visible
88
- var leftHasIcon = Boolean(icon && iconPosition === "left") || isSearch;
89
- var rightHasIcon = Boolean(icon && iconPosition === "right") || isPassword || clearable && currentLength > 0;
141
+ var leftHasIcon = Boolean(leftIcon) || isSearch;
142
+ var showRightIcon = Boolean(rightIcon) && !isPassword && !(clearable && currentLength > 0);
143
+ var rightHasIcon = showRightIcon || isPassword || clearable && currentLength > 0;
90
144
  var paddingClass = cn(leftHasIcon ? "pl-10" : "pl-3", rightHasIcon ? "pr-12" : "pr-3");
91
- var baseClasses = cn("w-full rounded-md border bg-white text-sm placeholder-gray-400 disabled:opacity-50", "h-10 leading-10",
92
- // consistent height
93
- paddingClass, error ? "border-red-500 focus:ring-red-500" : "border-gray-300", success ? "border-green-500 focus:ring-green-500" : "",
94
- // Neutral state needs a visible keyboard focus indicator too.
95
- !error && !success && "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500", className);
145
+ var baseClasses = cn("w-full rounded-md border bg-surface text-foreground placeholder-gray-400 dark:placeholder-gray-500", "transition-colors duration-fast ease-standard motion-reduce:transition-none", "disabled:cursor-not-allowed disabled:opacity-50", isFloating ? FLOATING_SIZE_CLASSES[size] : SIZE_CLASSES[size],
146
+ // The floated label replaces the placeholder until the field is focused.
147
+ isFloating && "peer placeholder-transparent focus:placeholder-gray-400 dark:focus:placeholder-gray-500", paddingClass, "focus:outline-none focus-visible:ring-2", error ? "border-error-500 focus-visible:ring-error-500 dark:border-error-400" : success ? "border-success-500 focus-visible:ring-success-500 dark:border-success-400" : "border-gray-300 focus-visible:ring-primary-500 dark:border-gray-600", className);
148
+ var labelContent = jsxs(Fragment, {
149
+ children: [label, required && jsx("span", {
150
+ className: "text-error-500 ml-1",
151
+ children: "*"
152
+ })]
153
+ });
96
154
  var inputType = isPassword && showPassword ? "text" : type;
97
155
  return jsxs("div", {
98
156
  className: "w-full",
99
- children: [label && jsxs("label", {
157
+ children: [label && !isFloating && jsx("label", {
100
158
  htmlFor: inputId,
101
- className: "block text-sm font-medium mb-1",
102
- children: [label, required && jsx("span", {
103
- className: "text-error-500 ml-1",
104
- children: "*"
105
- })]
159
+ className: "block text-sm font-medium mb-1 text-foreground",
160
+ children: labelContent
106
161
  }), jsxs("div", {
107
162
  className: "relative",
108
163
  children: [leftHasIcon && jsx("div", {
@@ -111,14 +166,15 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
111
166
  className: "h-4 w-4 text-primary-400"
112
167
  }) : jsx("span", {
113
168
  className: "text-primary-400",
114
- children: icon
169
+ children: leftIcon
115
170
  })
116
- }), jsx("input", _objectSpread2({
171
+ }), jsx("input", _objectSpread2(_objectSpread2({
117
172
  id: inputId,
118
173
  ref: setRefs,
119
174
  type: inputType,
120
175
  className: baseClasses,
121
- placeholder: placeholder,
176
+ // :placeholder-shown drives the floating label, so it needs a placeholder.
177
+ placeholder: isFloating ? placeholder || " " : placeholder,
122
178
  disabled: disabled,
123
179
  readOnly: readOnly,
124
180
  "aria-invalid": !!error,
@@ -126,13 +182,19 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
126
182
  maxLength: maxLength,
127
183
  onChange: handleChange,
128
184
  value: value,
129
- defaultValue: defaultValue
130
- }, rest)), icon && iconPosition === "right" && !isPassword && !(clearable && currentLength > 0) && jsx("div", {
185
+ defaultValue: defaultValue,
186
+ size: nativeSize
187
+ }, hintProps), rest)), isFloating && jsx("label", {
188
+ htmlFor: inputId,
189
+ "data-floating-label": "",
190
+ className: cn("pointer-events-none absolute top-1 origin-left text-xs font-medium text-muted", leftHasIcon ? "left-10" : "left-3", "transition-[top,transform,font-size,color] duration-fast ease-standard motion-reduce:transition-none", "peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:text-sm", "peer-focus:top-1 peer-focus:translate-y-0 peer-focus:text-xs peer-focus:text-primary-600 dark:peer-focus:text-primary-400"),
191
+ children: labelContent
192
+ }), showRightIcon && jsx("div", {
131
193
  className: "absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none text-primary-400",
132
- children: icon
194
+ children: rightIcon
133
195
  }), isPassword && jsx("button", {
134
196
  type: "button",
135
- className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-primary-500",
197
+ className: ADORNMENT_BUTTON,
136
198
  onClick: function onClick() {
137
199
  return setShowPassword(function (s) {
138
200
  return !s;
@@ -147,7 +209,7 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
147
209
  })
148
210
  }), clearable && currentLength > 0 && !isPassword && jsx("button", {
149
211
  type: "button",
150
- className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-primary-500",
212
+ className: ADORNMENT_BUTTON,
151
213
  onClick: handleClear,
152
214
  "aria-label": "Clear input",
153
215
  disabled: disabled,
@@ -157,12 +219,12 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
157
219
  })]
158
220
  }), error && jsx("p", {
159
221
  id: "".concat(inputId, "-error"),
160
- className: "mt-1 text-sm text-error-600",
222
+ className: "mt-1 text-sm text-error-600 dark:text-error-400",
161
223
  "aria-live": "polite",
162
224
  children: error
163
225
  }), !error && success && jsx("p", {
164
226
  id: "".concat(inputId, "-success"),
165
- className: "mt-1 text-sm text-success-600",
227
+ className: "mt-1 text-sm text-success-600 dark:text-success-400",
166
228
  "aria-live": "polite",
167
229
  children: success
168
230
  }), !error && !success && helperText && jsx("p", {
@@ -170,7 +232,7 @@ var Input = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
170
232
  className: "mt-1 text-sm text-muted",
171
233
  children: helperText
172
234
  }), showCount && maxLength && jsxs("div", {
173
- className: "mt-1 text-right text-xs text-primary-500",
235
+ className: "mt-1 text-right text-xs text-primary-500 dark:text-primary-400",
174
236
  "aria-hidden": true,
175
237
  children: [currentLength, "/", maxLength]
176
238
  })]
@@ -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";