@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
@@ -4,12 +4,29 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import React from 'react';
5
5
  import { ChevronLeft, ChevronRight } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
- import { TOUCH_TARGET } from '../../utils/touchTarget.js';
7
+ import { TOUCH_TARGET, PRESS_FEEDBACK } from '../../utils/touchTarget.js';
8
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
9
+ import { useLatestRef } from '../../hooks/useLatestRef.js';
8
10
 
9
- var _excluded = ["children", "autoPlay", "interval", "showDots", "className", "itemClassName", "onMouseEnter", "onMouseLeave"];
11
+ var _excluded = ["children", "autoPlay", "interval", "showDots", "className", "itemClassName", "onMouseEnter", "onMouseLeave", "onFocus", "onBlur", "onTouchStart", "onTouchEnd"];
10
12
  // Arrows stay visible at rest (touch devices have no hover) and use the surface /
11
13
  // foreground role tokens so they read on light and dark themes alike.
12
14
  var ARROW_CLASSES = "absolute top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center p-2 rounded-full bg-surface/80 backdrop-blur-md border border-line text-foreground shadow-lg transition-colors motion-reduce:transition-none hover:bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
15
+ // The track is a native horizontal scroller with mandatory snap points, so
16
+ // touch users get momentum swipe for free. The scrollbar is hidden; the arrows,
17
+ // dots and keyboard (focus the track, then arrow keys) are the visible controls.
18
+ var TRACK_CLASSES = "flex overflow-x-auto snap-x snap-mandatory scroll-smooth motion-reduce:scroll-auto overscroll-x-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500";
19
+ function scrollTrackTo(track, slide, behavior) {
20
+ if (!track || !slide) return;
21
+ if (typeof track.scrollTo === "function") {
22
+ track.scrollTo({
23
+ left: slide.offsetLeft,
24
+ behavior: behavior
25
+ });
26
+ } else {
27
+ track.scrollLeft = slide.offsetLeft;
28
+ }
29
+ }
13
30
  var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
14
31
  var children = props.children,
15
32
  _props$autoPlay = props.autoPlay,
@@ -22,59 +39,146 @@ var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
22
39
  itemClassName = props.itemClassName,
23
40
  _onMouseEnter = props.onMouseEnter,
24
41
  _onMouseLeave = props.onMouseLeave,
42
+ _onFocus = props.onFocus,
43
+ _onBlur = props.onBlur,
44
+ _onTouchStart = props.onTouchStart,
45
+ _onTouchEnd = props.onTouchEnd,
25
46
  rest = _objectWithoutProperties(props, _excluded);
47
+ var reducedMotion = useReducedMotion();
26
48
  var _React$useState = React.useState(0),
27
49
  _React$useState2 = _slicedToArray(_React$useState, 2),
28
50
  currentIndex = _React$useState2[0],
29
51
  setCurrentIndex = _React$useState2[1];
30
52
  var _React$useState3 = React.useState(false),
31
53
  _React$useState4 = _slicedToArray(_React$useState3, 2),
32
- isPaused = _React$useState4[0],
33
- setIsPaused = _React$useState4[1];
34
- var timerRef = React.useRef(null);
54
+ hovered = _React$useState4[0],
55
+ setHovered = _React$useState4[1];
56
+ var _React$useState5 = React.useState(false),
57
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
58
+ focusWithin = _React$useState6[0],
59
+ setFocusWithin = _React$useState6[1];
60
+ var _React$useState7 = React.useState(false),
61
+ _React$useState8 = _slicedToArray(_React$useState7, 2),
62
+ touching = _React$useState8[0],
63
+ setTouching = _React$useState8[1];
64
+ var trackRef = React.useRef(null);
65
+ var slideRefs = React.useRef([]);
66
+ var indexRef = useLatestRef(currentIndex);
67
+ var childrenArray = React.Children.toArray(children);
68
+ var count = childrenArray.length;
69
+ var goTo = React.useCallback(function (index) {
70
+ if (count === 0) return;
71
+ var target = (index % count + count) % count;
72
+ indexRef.current = target;
73
+ setCurrentIndex(target);
74
+ scrollTrackTo(trackRef.current, slideRefs.current[target], reducedMotion ? "auto" : "smooth");
75
+ }, [count, reducedMotion, indexRef]);
35
76
  var next = React.useCallback(function () {
36
- setCurrentIndex(function (prevIndex) {
37
- return (prevIndex + 1) % React.Children.count(children);
38
- });
39
- }, [children]);
77
+ return goTo(indexRef.current + 1);
78
+ }, [goTo, indexRef]);
40
79
  var prev = React.useCallback(function () {
41
- var count = React.Children.count(children);
42
- setCurrentIndex(function (prevIndex) {
43
- return (prevIndex - 1 + count) % count;
80
+ return goTo(indexRef.current - 1);
81
+ }, [goTo, indexRef]);
82
+ // Swipes / native scrolling move the track without going through goTo(), so the
83
+ // active slide is whichever one is mostly in view.
84
+ React.useEffect(function () {
85
+ var track = trackRef.current;
86
+ if (!track || typeof IntersectionObserver === "undefined") return undefined;
87
+ var observer = new IntersectionObserver(function (entries) {
88
+ entries.forEach(function (entry) {
89
+ if (entry.isIntersecting && entry.intersectionRatio >= 0.6) {
90
+ var index = Number(entry.target.getAttribute("data-index"));
91
+ if (!Number.isNaN(index)) setCurrentIndex(index);
92
+ }
93
+ });
94
+ }, {
95
+ root: track,
96
+ threshold: [0.6]
44
97
  });
45
- }, [children]);
98
+ slideRefs.current.slice(0, count).forEach(function (slide) {
99
+ return slide && observer.observe(slide);
100
+ });
101
+ return function () {
102
+ return observer.disconnect();
103
+ };
104
+ }, [count]);
105
+ var isPaused = hovered || focusWithin || touching;
106
+ var isAutoPlaying = autoPlay && !reducedMotion && !isPaused && count > 1;
46
107
  React.useEffect(function () {
47
- if (autoPlay && !isPaused) {
48
- timerRef.current = setInterval(next, interval);
49
- }
108
+ if (!isAutoPlaying) return undefined;
109
+ var timer = setInterval(next, interval);
50
110
  return function () {
51
- if (timerRef.current) clearInterval(timerRef.current);
111
+ return clearInterval(timer);
52
112
  };
53
- }, [autoPlay, isPaused, next, interval]);
54
- var childrenArray = React.Children.toArray(children);
113
+ }, [isAutoPlaying, next, interval]);
55
114
  return jsxs("div", _objectSpread2(_objectSpread2({
56
115
  ref: ref,
116
+ role: "region",
117
+ "aria-roledescription": "carousel",
118
+ "aria-label": "Carousel",
57
119
  className: cn("relative overflow-hidden rounded-xl bg-surface border border-line shadow-sm", className),
58
120
  onMouseEnter: function onMouseEnter(e) {
59
- setIsPaused(true);
121
+ setHovered(true);
60
122
  _onMouseEnter === null || _onMouseEnter === void 0 || _onMouseEnter(e);
61
123
  },
62
124
  onMouseLeave: function onMouseLeave(e) {
63
- setIsPaused(false);
125
+ setHovered(false);
64
126
  _onMouseLeave === null || _onMouseLeave === void 0 || _onMouseLeave(e);
127
+ },
128
+ onFocus: function onFocus(e) {
129
+ setFocusWithin(true);
130
+ _onFocus === null || _onFocus === void 0 || _onFocus(e);
131
+ },
132
+ onBlur: function onBlur(e) {
133
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocusWithin(false);
134
+ _onBlur === null || _onBlur === void 0 || _onBlur(e);
135
+ },
136
+ onTouchStart: function onTouchStart(e) {
137
+ setTouching(true);
138
+ _onTouchStart === null || _onTouchStart === void 0 || _onTouchStart(e);
139
+ },
140
+ onTouchEnd: function onTouchEnd(e) {
141
+ setTouching(false);
142
+ _onTouchEnd === null || _onTouchEnd === void 0 || _onTouchEnd(e);
143
+ },
144
+ onTouchCancel: function onTouchCancel() {
145
+ return setTouching(false);
65
146
  }
66
147
  }, rest), {}, {
67
148
  children: [jsx("div", {
68
- className: "flex transition-transform duration-500 ease-out motion-reduce:transition-none",
69
- style: {
70
- transform: "translateX(-".concat(currentIndex * 100, "%)")
149
+ ref: trackRef,
150
+ className: TRACK_CLASSES,
151
+ tabIndex: 0,
152
+ "aria-label": "Slides",
153
+ "data-carousel-track": "",
154
+ onKeyDown: function onKeyDown(e) {
155
+ if (e.key === "ArrowRight") {
156
+ e.preventDefault();
157
+ next();
158
+ } else if (e.key === "ArrowLeft") {
159
+ e.preventDefault();
160
+ prev();
161
+ }
71
162
  },
72
163
  children: childrenArray.map(function (child, index) {
73
164
  return jsx("div", {
74
- className: cn("w-full flex-shrink-0 min-w-full", itemClassName),
165
+ ref: function ref(node) {
166
+ slideRefs.current[index] = node;
167
+ },
168
+ "data-index": index,
169
+ role: "group",
170
+ "aria-roledescription": "slide",
171
+ "aria-label": "".concat(index + 1, " of ").concat(count),
172
+ className: cn("w-full flex-shrink-0 min-w-full snap-start snap-always", itemClassName),
75
173
  children: child
76
174
  }, index);
77
175
  })
176
+ }), jsx("div", {
177
+ className: "sr-only",
178
+ "aria-live": isAutoPlaying ? "off" : "polite",
179
+ "aria-atomic": "true",
180
+ "data-carousel-status": "",
181
+ children: count > 0 ? "Slide ".concat(currentIndex + 1, " of ").concat(count) : ""
78
182
  }), jsx("button", {
79
183
  type: "button",
80
184
  onClick: prev,
@@ -94,16 +198,22 @@ var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
94
198
  "aria-hidden": "true"
95
199
  })
96
200
  }), showDots && jsx("div", {
97
- className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10",
201
+ className: "absolute bottom-1 left-1/2 -translate-x-1/2 flex z-10",
98
202
  children: childrenArray.map(function (_, index) {
203
+ var active = currentIndex === index;
99
204
  return jsx("button", {
100
205
  type: "button",
101
206
  onClick: function onClick() {
102
- return setCurrentIndex(index);
207
+ return goTo(index);
103
208
  },
104
- className: cn("h-2 rounded-full transition-all duration-300 motion-reduce:transition-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2", currentIndex === index ? "bg-primary-600 w-6" : "w-2 bg-muted/40 hover:bg-muted/70"),
209
+ className: cn("group inline-flex h-11 min-w-11 items-center justify-center rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-inset", PRESS_FEEDBACK),
105
210
  "aria-label": "Go to slide ".concat(index + 1),
106
- "aria-current": currentIndex === index ? "true" : undefined
211
+ "aria-current": active ? "true" : undefined,
212
+ children: jsx("span", {
213
+ "aria-hidden": "true",
214
+ "data-carousel-dot": "",
215
+ className: cn("block h-2 rounded-full transition-all duration-base ease-standard motion-reduce:transition-none", active ? "bg-primary-600 w-6" : "w-2 bg-muted/40 group-hover:bg-muted/70")
216
+ })
107
217
  }, index);
108
218
  })
109
219
  })]
@@ -1,11 +1,17 @@
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, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import React, { useState, useRef, useCallback } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
- import { Upload, X, Image, FileText, File } from 'lucide-react';
6
+ import { Upload, Camera, X, Image, FileText, File } from 'lucide-react';
7
+ import { useMediaQuery } from '../../hooks/useMediaQuery.js';
8
+ import { PRESS_FEEDBACK, TOUCH_TARGET } from '../../utils/touchTarget.js';
7
9
 
8
- var _excluded = ["accept", "multiple", "maxSize", "onFileSelect", "onFileRemove", "files", "disabled", "className"];
10
+ var _excluded = ["accept", "multiple", "maxSize", "onFileSelect", "onFileRemove", "files", "disabled", "capture", "className"];
11
+ var COARSE_POINTER_QUERY = '(pointer: coarse)';
12
+ // PRESS_FEEDBACK sets transition-transform; this keeps the colour transition as well.
13
+ var TRANSITION_WITH_PRESS = 'transition-[color,background-color,border-color,transform]';
14
+ var PICK_BUTTON_CLASSES = 'inline-flex min-h-11 items-center justify-center gap-2 rounded-control border border-line bg-surface px-4 text-sm font-medium text-foreground shadow-xs transition-colors duration-fast hover:bg-background disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900';
9
15
  function formatFileSize(bytes) {
10
16
  if (bytes === 0) return '0 Bytes';
11
17
  var k = 1024;
@@ -24,6 +30,7 @@ var FileUpload = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
24
30
  files = _ref$files === void 0 ? [] : _ref$files,
25
31
  _ref$disabled = _ref.disabled,
26
32
  disabled = _ref$disabled === void 0 ? false : _ref$disabled,
33
+ capture = _ref.capture,
27
34
  className = _ref.className,
28
35
  props = _objectWithoutProperties(_ref, _excluded);
29
36
  var _useState = useState(false),
@@ -35,6 +42,9 @@ var FileUpload = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
35
42
  error = _useState4[0],
36
43
  setError = _useState4[1];
37
44
  var fileInputRef = useRef(null);
45
+ var cameraInputRef = useRef(null);
46
+ // Touch screens have no drag and drop; show explicit pick buttons instead.
47
+ var isCoarse = useMediaQuery(COARSE_POINTER_QUERY);
38
48
  var validateFile = useCallback(function (file) {
39
49
  if (maxSize && file.size > maxSize) {
40
50
  setError("File size must be less than ".concat(formatFileSize(maxSize)));
@@ -97,20 +107,42 @@ var FileUpload = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
97
107
  (_fileInputRef$current = fileInputRef.current) === null || _fileInputRef$current === void 0 || _fileInputRef$current.click();
98
108
  }
99
109
  };
110
+ var openCamera = function openCamera() {
111
+ if (!disabled) {
112
+ var _cameraInputRef$curre;
113
+ (_cameraInputRef$curre = cameraInputRef.current) === null || _cameraInputRef$curre === void 0 || _cameraInputRef$curre.click();
114
+ }
115
+ };
116
+ var handleZoneKeyDown = function handleZoneKeyDown(e) {
117
+ if (e.key === 'Enter' || e.key === ' ') {
118
+ e.preventDefault();
119
+ openFileDialog();
120
+ }
121
+ };
122
+ var hint = jsxs("p", {
123
+ className: "text-xs text-primary-500 dark:text-primary-300 mt-1",
124
+ children: [accept && "Accepted formats: ".concat(accept), maxSize && " \u2022 Max size: ".concat(formatFileSize(maxSize))]
125
+ });
100
126
  return jsxs("div", _objectSpread2(_objectSpread2({
101
127
  ref: ref,
102
128
  className: cn('w-full', className)
103
129
  }, props), {}, {
104
130
  children: [jsxs("div", {
105
- className: cn('relative border-2 border-dashed rounded-lg p-6 text-center transition-colors cursor-pointer', {
106
- 'border-primary-500 bg-primary-50': isDragOver && !disabled,
107
- 'border-gray-300 hover:border-gray-400': !isDragOver && !disabled,
108
- 'border-gray-200 bg-gray-50 cursor-not-allowed': disabled
131
+ className: cn('relative border-2 border-dashed rounded-lg p-6 text-center transition-colors duration-base motion-reduce:transition-none', {
132
+ 'cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900': !isCoarse && !disabled,
133
+ 'border-primary-500 bg-primary-50 dark:bg-primary-900/20': isDragOver && !disabled,
134
+ 'border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500': !isDragOver && !disabled,
135
+ 'border-gray-200 bg-gray-50 cursor-not-allowed dark:border-gray-700 dark:bg-gray-800/50': disabled
109
136
  }),
110
137
  onDrop: handleDrop,
111
138
  onDragOver: handleDragOver,
112
139
  onDragLeave: handleDragLeave,
113
- onClick: openFileDialog,
140
+ onClick: isCoarse ? undefined : openFileDialog,
141
+ onKeyDown: isCoarse ? undefined : handleZoneKeyDown,
142
+ role: isCoarse ? undefined : 'button',
143
+ tabIndex: isCoarse || disabled ? undefined : 0,
144
+ "aria-disabled": !isCoarse && disabled ? true : undefined,
145
+ "data-coarse": isCoarse || undefined,
114
146
  children: [jsx("input", {
115
147
  ref: fileInputRef,
116
148
  type: "file",
@@ -118,53 +150,96 @@ var FileUpload = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
118
150
  multiple: multiple,
119
151
  onChange: handleFileInputChange,
120
152
  className: "hidden",
121
- disabled: disabled
153
+ disabled: disabled,
154
+ tabIndex: -1
155
+ }), capture && jsx("input", {
156
+ ref: cameraInputRef,
157
+ type: "file",
158
+ accept: accept || 'image/*',
159
+ capture: capture,
160
+ onChange: handleFileInputChange,
161
+ className: "hidden",
162
+ disabled: disabled,
163
+ tabIndex: -1,
164
+ "data-capture-input": ""
122
165
  }), jsx(Upload, {
123
166
  className: cn('mx-auto h-12 w-12', {
124
167
  'text-primary-500': !disabled,
125
168
  'text-primary-400': disabled
126
- })
127
- }), jsxs("div", {
169
+ }),
170
+ "aria-hidden": "true"
171
+ }), jsx("div", {
128
172
  className: "mt-4",
129
- children: [jsx("p", {
130
- className: cn('text-sm font-medium', {
131
- 'text-primary-900': !disabled,
132
- 'text-primary-500': disabled
133
- }),
134
- children: isDragOver ? 'Drop files here' : 'Click to upload or drag and drop'
135
- }), jsxs("p", {
136
- className: "text-xs text-primary-500 mt-1",
137
- children: [accept && "Accepted formats: ".concat(accept), maxSize && " \u2022 Max size: ".concat(formatFileSize(maxSize))]
138
- })]
173
+ children: isCoarse ? jsxs(Fragment, {
174
+ children: [jsxs("div", {
175
+ className: "flex flex-col items-stretch gap-2 sm:flex-row sm:justify-center",
176
+ children: [jsxs("button", {
177
+ type: "button",
178
+ className: cn(PICK_BUTTON_CLASSES, PRESS_FEEDBACK, TRANSITION_WITH_PRESS),
179
+ onClick: openFileDialog,
180
+ disabled: disabled,
181
+ children: [jsx(Upload, {
182
+ className: "h-4 w-4",
183
+ "aria-hidden": "true"
184
+ }), multiple ? 'Choose files' : 'Choose file']
185
+ }), capture && jsxs("button", {
186
+ type: "button",
187
+ className: cn(PICK_BUTTON_CLASSES, PRESS_FEEDBACK, TRANSITION_WITH_PRESS),
188
+ onClick: openCamera,
189
+ disabled: disabled,
190
+ children: [jsx(Camera, {
191
+ className: "h-4 w-4",
192
+ "aria-hidden": "true"
193
+ }), "Take photo"]
194
+ })]
195
+ }), hint]
196
+ }) : jsxs(Fragment, {
197
+ children: [jsx("p", {
198
+ className: cn('text-sm font-medium', {
199
+ 'text-primary-900 dark:text-primary-100': !disabled,
200
+ 'text-primary-500': disabled
201
+ }),
202
+ children: isDragOver ? 'Drop files here' : 'Click to upload or drag and drop'
203
+ }), hint]
204
+ })
139
205
  })]
140
206
  }), error && jsx("p", {
141
- className: "mt-2 text-sm text-error-500",
207
+ role: "alert",
208
+ className: "mt-2 text-sm text-error-600 dark:text-error-400",
142
209
  children: error
143
- }), files.length > 0 && jsx("div", {
210
+ }), files.length > 0 && jsx("ul", {
144
211
  className: "mt-4 space-y-2",
212
+ "aria-label": "Selected files",
145
213
  children: files.map(function (file, index) {
146
- return jsxs("div", {
147
- className: "flex items-center justify-between rounded-md border border-gray-200 bg-white p-3",
214
+ return jsxs("li", {
215
+ className: "flex items-center justify-between gap-3 rounded-md border border-line bg-surface p-3",
148
216
  children: [jsxs("div", {
149
- className: "flex items-center space-x-3",
150
- children: [getFileIcon(file), jsxs("div", {
217
+ className: "flex min-w-0 items-center space-x-3",
218
+ children: [jsx("span", {
219
+ className: "shrink-0",
220
+ "aria-hidden": "true",
221
+ children: getFileIcon(file)
222
+ }), jsxs("div", {
223
+ className: "min-w-0",
151
224
  children: [jsx("p", {
152
- className: "text-sm font-medium text-primary-900",
225
+ className: "truncate text-sm font-medium text-foreground",
153
226
  children: file.name
154
227
  }), jsx("p", {
155
- className: "text-xs text-primary-500",
228
+ className: "text-xs text-muted",
156
229
  children: formatFileSize(file.size)
157
230
  })]
158
231
  })]
159
232
  }), jsx("button", {
160
233
  type: "button",
161
- className: "rounded-md p-1 text-primary-400 hover:text-primary-600 hover:bg-gray-100",
234
+ "aria-label": "Remove ".concat(file.name),
235
+ className: cn('inline-flex shrink-0 items-center justify-center rounded-md p-1 text-muted transition-colors duration-fast hover:text-foreground hover:bg-background disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', TOUCH_TARGET, PRESS_FEEDBACK, TRANSITION_WITH_PRESS),
162
236
  onClick: function onClick() {
163
237
  return handleRemoveFile(index);
164
238
  },
165
239
  disabled: disabled,
166
240
  children: jsx(X, {
167
- className: "h-4 w-4"
241
+ className: "h-4 w-4",
242
+ "aria-hidden": "true"
168
243
  })
169
244
  })]
170
245
  }, index);
@@ -3,9 +3,15 @@ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import React from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
  import { ChevronLeft, ChevronRight } from 'lucide-react';
7
8
 
8
9
  var _excluded = ["currentPage", "totalPages", "onPageChange", "showFirstLast", "showPrevNext", "maxVisiblePages", "className"];
10
+ // Shared button chrome: dark-safe surface, focus-visible ring, 44px target on coarse pointers.
11
+ var BUTTON_BASE = cn('inline-flex items-center justify-center rounded-md border px-3 py-2 text-sm font-medium', 'transition-colors duration-fast motion-reduce:transition-none', 'outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900', TOUCH_TARGET);
12
+ var BUTTON_IDLE = 'border-gray-300 bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:hover:bg-gray-800';
13
+ var BUTTON_ACTIVE = 'border-primary-500 bg-primary-50 text-primary-600 dark:bg-primary-950 dark:text-primary-300';
14
+ var DISABLED = 'disabled:opacity-50 disabled:cursor-not-allowed';
9
15
  var Pagination = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
10
16
  var _ref$currentPage = _ref.currentPage,
11
17
  currentPage = _ref$currentPage === void 0 ? 1 : _ref$currentPage,
@@ -45,37 +51,46 @@ var Pagination = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
45
51
  }
46
52
  };
47
53
  if (totalPages <= 1) return null;
54
+ // Below `sm` the trail collapses to "‹ Page 3 of 10 ›" — only when there are
55
+ // prev/next buttons to drive it; otherwise the full layout shows everywhere.
56
+ var compact = showPrevNext;
57
+ var fullOnly = compact ? 'hidden sm:inline-flex' : '';
48
58
  return jsxs("nav", _objectSpread2(_objectSpread2({
49
59
  ref: ref,
50
- className: cn('flex items-center justify-center space-x-1', className),
60
+ className: cn('flex flex-wrap items-center justify-center gap-1', className),
51
61
  "aria-label": "Pagination"
52
62
  }, props), {}, {
53
63
  children: [showFirstLast && visiblePages[0] > 1 && jsx("button", {
54
- className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2",
64
+ type: "button",
65
+ className: cn(BUTTON_BASE, BUTTON_IDLE, fullOnly),
55
66
  onClick: function onClick() {
56
67
  return handlePageClick(1);
57
68
  },
58
69
  "aria-label": "Go to first page",
59
70
  children: "1"
60
71
  }), showPrevNext && jsx("button", {
61
- className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
72
+ type: "button",
73
+ className: cn(BUTTON_BASE, BUTTON_IDLE, DISABLED),
62
74
  onClick: function onClick() {
63
75
  return handlePageClick(currentPage - 1);
64
76
  },
65
77
  disabled: currentPage === 1,
66
78
  "aria-label": "Go to previous page",
67
79
  children: jsx(ChevronLeft, {
68
- className: "h-4 w-4"
80
+ className: "h-4 w-4",
81
+ "aria-hidden": "true"
69
82
  })
83
+ }), compact && jsxs("span", {
84
+ className: "px-2 text-sm font-medium tabular-nums text-foreground sm:hidden",
85
+ "data-pagination-compact": "",
86
+ children: ["Page ", currentPage, " of ", totalPages]
70
87
  }), showFirstLast && visiblePages[0] > 2 && jsx("span", {
71
- className: "px-3 py-2 text-sm text-primary-500",
88
+ className: cn('px-3 py-2 text-sm text-primary-500 dark:text-primary-400', fullOnly),
72
89
  children: "..."
73
90
  }), visiblePages.map(function (page) {
74
91
  return jsx("button", {
75
- className: cn('rounded-md border px-3 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2', {
76
- 'border-primary-500 bg-primary-50 text-primary-600': page === currentPage,
77
- 'border-gray-300 bg-white hover:bg-gray-50': page !== currentPage
78
- }),
92
+ type: "button",
93
+ className: cn(BUTTON_BASE, page === currentPage ? BUTTON_ACTIVE : BUTTON_IDLE, fullOnly),
79
94
  onClick: function onClick() {
80
95
  return handlePageClick(page);
81
96
  },
@@ -84,20 +99,23 @@ var Pagination = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
84
99
  children: page
85
100
  }, page);
86
101
  }), showFirstLast && visiblePages[visiblePages.length - 1] < totalPages - 1 && jsx("span", {
87
- className: "px-3 py-2 text-sm text-primary-500",
102
+ className: cn('px-3 py-2 text-sm text-primary-500 dark:text-primary-400', fullOnly),
88
103
  children: "..."
89
104
  }), showPrevNext && jsx("button", {
90
- className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
105
+ type: "button",
106
+ className: cn(BUTTON_BASE, BUTTON_IDLE, DISABLED),
91
107
  onClick: function onClick() {
92
108
  return handlePageClick(currentPage + 1);
93
109
  },
94
110
  disabled: currentPage === totalPages,
95
111
  "aria-label": "Go to next page",
96
112
  children: jsx(ChevronRight, {
97
- className: "h-4 w-4"
113
+ className: "h-4 w-4",
114
+ "aria-hidden": "true"
98
115
  })
99
116
  }), showFirstLast && visiblePages[visiblePages.length - 1] < totalPages && jsx("button", {
100
- className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2",
117
+ type: "button",
118
+ className: cn(BUTTON_BASE, BUTTON_IDLE, fullOnly),
101
119
  onClick: function onClick() {
102
120
  return handlePageClick(totalPages);
103
121
  },
@@ -137,6 +137,10 @@ var Sortable = /*#__PURE__*/forwardRef(function (_ref4, ref) {
137
137
  var isGrabbed = keyboard.grabbed === key;
138
138
  var isDragging = pointer.dragging === key;
139
139
  var startDrag = disabled ? undefined : pointer.startFor(key);
140
+ // Whole-item drags wait for a long-press on touch so the page still scrolls.
141
+ var startItemDrag = disabled || handle ? undefined : pointer.startFor(key, {
142
+ longPress: true
143
+ });
140
144
  return jsx(ItemTag, {
141
145
  ref: function ref(node) {
142
146
  if (node) nodes.current.set(key, node);else nodes.current["delete"](key);
@@ -151,8 +155,8 @@ var Sortable = /*#__PURE__*/forwardRef(function (_ref4, ref) {
151
155
  onBlur: function onBlur(e) {
152
156
  if (isGrabbed && e.relatedTarget && !e.currentTarget.contains(e.relatedTarget)) keyboard.cancel();
153
157
  },
154
- onPointerDown: handle ? undefined : startDrag,
155
- className: cn("rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-offset-gray-900", !disabled && !handle && "cursor-grab touch-none select-none", (isGrabbed || isDragging) && "relative z-10 shadow-lg ring-2 ring-primary-500 opacity-90", itemClassName),
158
+ onPointerDown: startItemDrag,
159
+ className: cn("rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-offset-gray-900", !disabled && !handle && "cursor-grab select-none [-webkit-touch-callout:none]", (isGrabbed || isDragging) && "relative z-10 shadow-lg ring-2 ring-primary-500 opacity-90", itemClassName),
156
160
  children: renderItem ? renderItem(item, {
157
161
  index: index,
158
162
  originalIndex: originalIndex,