@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
@@ -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 { jsx, jsxs } from 'react/jsx-runtime';
4
- import React, { forwardRef, useState, useRef, useId, useEffect, createContext, useContext } from 'react';
4
+ import React, { forwardRef, useState, useRef, useId, useEffect, createContext, useContext, useLayoutEffect } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
6
 
7
- var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation", "size", "variant", "animated", "className"];
7
+ var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation", "size", "variant", "animated", "swipeable", "className"],
8
+ _excluded2 = ["children", "className"];
9
+ // useLayoutEffect warns during SSR; fall back to useEffect on the server.
10
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
11
+ // Minimum horizontal travel (px) for a swipe to change tabs, and how much more
12
+ // horizontal than vertical it must be, so vertical page scrolls never switch tabs.
13
+ var SWIPE_THRESHOLD = 50;
14
+ var SWIPE_DOMINANCE = 1.5;
15
+ // Variants whose active state is drawn by the sliding indicator (horizontal only).
16
+ var INDICATOR_VARIANTS = ["line", "segmented"];
8
17
  /*
9
18
  Tabs.jsx
10
19
  - Default export: Tabs (provider wrapper)
@@ -14,7 +23,10 @@ var _excluded = ["children", "defaultIndex", "index", "onChange", "orientation",
14
23
  - Controlled / uncontrolled usage
15
24
  - Keyboard navigation (Arrow keys, Home, End)
16
25
  - ARIA attributes for accessibility
17
- - Variants: "line" | "pills" | "unstyled"
26
+ - Variants: "line" | "pills" | "segmented" | "unstyled"
27
+ - Horizontal lists scroll with snap; the active tab is kept in view
28
+ - Animated active indicator for "line" / "segmented" (off under reduced motion)
29
+ - Optional `swipeable` panels (touch / pen pointer swipes)
18
30
  - Sizes: "sm" | "md" | "lg"
19
31
  - Simple fade animation
20
32
  - Tailwind CSS only
@@ -60,6 +72,8 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
60
72
  variant = _ref$variant === void 0 ? "line" : _ref$variant,
61
73
  _ref$animated = _ref.animated,
62
74
  animated = _ref$animated === void 0 ? true : _ref$animated,
75
+ _ref$swipeable = _ref.swipeable,
76
+ swipeable = _ref$swipeable === void 0 ? false : _ref$swipeable,
63
77
  _ref$className = _ref.className,
64
78
  className = _ref$className === void 0 ? "" : _ref$className,
65
79
  rest = _objectWithoutProperties(_ref, _excluded);
@@ -71,6 +85,12 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
71
85
  var activeIndex = isControlled ? controlledIndex : internalIndex;
72
86
  var tabsRef = useRef([]); // DOM refs for keyboard navigation
73
87
  var baseId = useId();
88
+ // Set by TabList once the sliding indicator has been measured; until then
89
+ // (SSR, first paint) tabs draw their own active styling.
90
+ var _useState3 = useState(false),
91
+ _useState4 = _slicedToArray(_useState3, 2),
92
+ indicatorReady = _useState4[0],
93
+ setIndicatorReady = _useState4[1];
74
94
  var changeTab = function changeTab(idx) {
75
95
  if (!isControlled) setActiveIndex(idx);
76
96
  onChange === null || onChange === void 0 || onChange(idx);
@@ -87,7 +107,10 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
87
107
  orientation: orientation,
88
108
  size: size,
89
109
  variant: variant,
90
- animated: animated
110
+ animated: animated,
111
+ swipeable: swipeable,
112
+ indicatorReady: indicatorReady,
113
+ setIndicatorReady: setIndicatorReady
91
114
  };
92
115
  return jsx(TabsContext.Provider, {
93
116
  value: value,
@@ -101,25 +124,96 @@ var Tabs = /*#__PURE__*/forwardRef(function Tabs(_ref, ref) {
101
124
  });
102
125
  Tabs.displayName = "Tabs";
103
126
  /* ------------------------- TabList ------------------------- */
104
- function TabList(_ref2) {
127
+ var TabList = /*#__PURE__*/forwardRef(function TabList(_ref2, ref) {
105
128
  var children = _ref2.children,
106
129
  _ref2$className = _ref2.className,
107
- className = _ref2$className === void 0 ? "" : _ref2$className;
130
+ className = _ref2$className === void 0 ? "" : _ref2$className,
131
+ rest = _objectWithoutProperties(_ref2, _excluded2);
108
132
  var _useTabsContext = useTabsContext("Tabs.List"),
109
- orientation = _useTabsContext.orientation;
110
- return jsx("div", {
133
+ orientation = _useTabsContext.orientation,
134
+ variant = _useTabsContext.variant,
135
+ activeIndex = _useTabsContext.activeIndex,
136
+ tabsRef = _useTabsContext.tabsRef,
137
+ indicatorReady = _useTabsContext.indicatorReady,
138
+ setIndicatorReady = _useTabsContext.setIndicatorReady;
139
+ var listRef = useRef(null);
140
+ var _useState5 = useState(null),
141
+ _useState6 = _slicedToArray(_useState5, 2),
142
+ indicator = _useState6[0],
143
+ setIndicator = _useState6[1];
144
+ var horizontal = orientation !== "vertical";
145
+ var hasIndicator = horizontal && INDICATOR_VARIANTS.includes(variant);
146
+ var childCount = React.Children.count(children);
147
+ var setRefs = function setRefs(node) {
148
+ listRef.current = node;
149
+ if (typeof ref === "function") ref(node);else if (ref) ref.current = node;
150
+ };
151
+ // Measure the active tab: position the indicator and scroll the tab into view.
152
+ useIsomorphicLayoutEffect(function () {
153
+ var list = listRef.current;
154
+ var tab = tabsRef.current[activeIndex];
155
+ if (!list || !tab) return undefined;
156
+ var measure = function measure() {
157
+ if (horizontal) {
158
+ var left = tab.offsetLeft;
159
+ var right = left + tab.offsetWidth;
160
+ if (left < list.scrollLeft) list.scrollLeft = left;else if (right > list.scrollLeft + list.clientWidth) list.scrollLeft = right - list.clientWidth;
161
+ }
162
+ // jsdom / display:none report 0; keep the tabs' own active styling then.
163
+ if (!hasIndicator || !tab.offsetWidth) return;
164
+ setIndicator({
165
+ left: tab.offsetLeft,
166
+ top: tab.offsetTop,
167
+ width: tab.offsetWidth,
168
+ height: tab.offsetHeight
169
+ });
170
+ setIndicatorReady(true);
171
+ };
172
+ measure();
173
+ if (typeof ResizeObserver === "undefined") return undefined;
174
+ var ro = new ResizeObserver(measure);
175
+ ro.observe(list);
176
+ ro.observe(tab);
177
+ return function () {
178
+ return ro.disconnect();
179
+ };
180
+ }, [activeIndex, hasIndicator, horizontal, childCount]); // eslint-disable-line react-hooks/exhaustive-deps
181
+ // Leaving an indicator variant hands the active styling back to the tabs.
182
+ useEffect(function () {
183
+ if (!hasIndicator && indicatorReady) setIndicatorReady(false);
184
+ }, [hasIndicator, indicatorReady, setIndicatorReady]);
185
+ var showIndicator = hasIndicator && indicatorReady && indicator;
186
+ return jsxs("div", _objectSpread2(_objectSpread2({
187
+ ref: setRefs,
111
188
  role: "tablist",
112
189
  "aria-orientation": orientation,
113
- className: "flex ".concat(orientation === "vertical" ? "flex-col" : "flex-row", " gap-1 ").concat(className),
114
- children: React.Children.map(children, function (child, i) {
190
+ className: cn("relative flex gap-1", horizontal ?
191
+ // Scroll with snap, scrollbar hidden; native smooth scrolling unless reduced motion.
192
+ "flex-row overflow-x-auto snap-x snap-mandatory scroll-smooth motion-reduce:scroll-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" : "flex-col", variant === "segmented" && "rounded-lg bg-gray-100 p-1 dark:bg-gray-800", className)
193
+ }, rest), {}, {
194
+ children: [showIndicator && jsx("span", {
195
+ "aria-hidden": "true",
196
+ "data-tabs-indicator": "",
197
+ className: cn("pointer-events-none absolute left-0 top-0", "transition-[transform,width,height] duration-base ease-standard motion-reduce:transition-none", variant === "line" ? "bg-primary-500" : "rounded-md bg-surface shadow-sm"),
198
+ style: variant === "line" ? {
199
+ width: indicator.width,
200
+ height: 2,
201
+ transform: "translate(".concat(indicator.left, "px, ").concat(indicator.top + indicator.height - 2, "px)")
202
+ } : {
203
+ width: indicator.width,
204
+ height: indicator.height,
205
+ transform: "translate(".concat(indicator.left, "px, ").concat(indicator.top, "px)")
206
+ }
207
+ }), React.Children.map(children, function (child, i) {
115
208
  var _child$props$index;
116
209
  if (! /*#__PURE__*/React.isValidElement(child)) return child;
117
210
  return /*#__PURE__*/React.cloneElement(child, {
118
211
  index: (_child$props$index = child.props.index) !== null && _child$props$index !== void 0 ? _child$props$index : i
119
212
  });
120
- })
121
- });
122
- }
213
+ })]
214
+ }));
215
+ });
216
+ TabList.displayName = "Tabs.List";
123
217
  /* ------------------------- Tab ------------------------- */
124
218
  function Tab(_ref3) {
125
219
  var children = _ref3.children,
@@ -139,32 +233,45 @@ function Tab(_ref3) {
139
233
  tabsRef = _useTabsContext2.tabsRef,
140
234
  baseId = _useTabsContext2.baseId,
141
235
  size = _useTabsContext2.size,
142
- variant = _useTabsContext2.variant;
236
+ variant = _useTabsContext2.variant,
237
+ orientation = _useTabsContext2.orientation,
238
+ indicatorReady = _useTabsContext2.indicatorReady;
143
239
  var internalRef = useRef(null);
144
240
  // If the user didn't provide `index` we derive from DOM position by registering nulls until mount.
145
241
  // However it's recommended to always keep Tabs.Tab order consistent with Panels.
146
- useEffect(function () {
242
+ // Registered from a ref callback (runs at commit, before any layout effect) so
243
+ // Tabs.List can measure the active tab on mount without a flash.
244
+ var setTabRef = function setTabRef(el) {
245
+ internalRef.current = el;
246
+ if (!el) return;
147
247
  // find index in tabsRef if index not provided
148
- var idx = typeof index === "number" ? index : tabsRef.current.findIndex(function (el) {
149
- return el === internalRef.current;
248
+ var slot = typeof index === "number" ? index : tabsRef.current.findIndex(function (node) {
249
+ return node === el;
150
250
  });
151
- if (idx === -1) idx = tabsRef.current.length; // fallback
152
- registerTab(internalRef.current, idx);
153
- // ensure the tab has the correct array slot
154
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
251
+ if (slot === -1) slot = tabsRef.current.length; // fallback
252
+ registerTab(el, slot);
253
+ };
155
254
  // TabList always injects `index`; a Tab rendered outside it is never selected.
156
255
  var idx = typeof index === "number" ? index : -1;
157
256
  var selected = idx === activeIndex;
158
257
  var id = tabId(baseId, idx);
159
- var base = "inline-flex items-center gap-2 outline-none select-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1";
258
+ var horizontal = orientation !== "vertical";
259
+ // Once the sliding indicator is measured it draws the active state; until then
260
+ // (SSR / first paint) the tab draws it itself so nothing flashes.
261
+ var indicatorDraws = indicatorReady && horizontal && INDICATOR_VARIANTS.includes(variant);
262
+ var base = cn("relative inline-flex items-center gap-2 whitespace-nowrap outline-none select-none", "focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1", "transition-colors duration-fast motion-reduce:transition-none", "disabled:cursor-not-allowed disabled:opacity-50",
263
+ // The scrolling list clips outside rings, so horizontal tabs draw theirs inset.
264
+ horizontal && "shrink-0 snap-start focus-visible:ring-inset focus-visible:ring-offset-0");
160
265
  var sizeClasses = {
161
266
  sm: "px-2 py-1 text-sm",
162
267
  md: "px-3 py-2 text-sm",
163
268
  lg: "px-4 py-3 text-base"
164
269
  };
165
270
  var variantClasses = {
166
- line: selected ? "border-b-2 border-primary-500" : "border-b-2 border-transparent hover:border-gray-200",
167
- pills: selected ? "bg-primary-600 text-white rounded-full shadow-sm" : "bg-transparent rounded-full hover:bg-gray-100",
271
+ line: selected ? cn("border-b-2", indicatorDraws ? "border-transparent" : "border-primary-500") : "border-b-2 border-transparent hover:border-gray-200 dark:hover:border-gray-700",
272
+ pills: selected ? "bg-primary-600 text-white rounded-full shadow-sm" : "bg-transparent rounded-full hover:bg-gray-100 dark:hover:bg-gray-800",
273
+ // iOS-style segmented control: equal-width segments on a tinted track.
274
+ segmented: cn("z-10 flex-1 justify-center rounded-md font-medium", selected ? cn("text-foreground", !indicatorDraws && "bg-surface shadow-sm") : "text-muted hover:text-foreground"),
168
275
  unstyled: ""
169
276
  };
170
277
  var computedClass = cn(base, sizeClasses[size] || sizeClasses.md, variantClasses[variant], tabClassName, className);
@@ -208,7 +315,7 @@ function Tab(_ref3) {
208
315
  }
209
316
  };
210
317
  return jsxs("button", {
211
- ref: internalRef,
318
+ ref: setTabRef,
212
319
  id: id,
213
320
  role: "tab",
214
321
  "aria-selected": selected,
@@ -237,9 +344,49 @@ function Panels(_ref4) {
237
344
  _ref4$className = _ref4.className,
238
345
  className = _ref4$className === void 0 ? "" : _ref4$className;
239
346
  var _useTabsContext3 = useTabsContext("Tabs.Panels"),
240
- animated = _useTabsContext3.animated;
241
- return jsx("div", {
242
- className: "relative w-full ".concat(className),
347
+ animated = _useTabsContext3.animated,
348
+ swipeable = _useTabsContext3.swipeable,
349
+ activeIndex = _useTabsContext3.activeIndex,
350
+ changeTab = _useTabsContext3.changeTab,
351
+ tabsRef = _useTabsContext3.tabsRef;
352
+ var start = useRef(null);
353
+ // Next enabled tab in `dir`, or -1 at either end (no wrap-around on swipe).
354
+ var neighbour = function neighbour(dir) {
355
+ var tabs = tabsRef.current;
356
+ for (var i = activeIndex + dir; i >= 0 && i < tabs.length; i += dir) {
357
+ if (tabs[i] && !tabs[i].disabled) return i;
358
+ }
359
+ return -1;
360
+ };
361
+ var swipeHandlers = swipeable ? {
362
+ onPointerDown: function onPointerDown(e) {
363
+ // Touch and pen only: a mouse drag selects text instead.
364
+ if (e.pointerType === "mouse" || e.isPrimary === false) return;
365
+ start.current = {
366
+ x: e.clientX,
367
+ y: e.clientY
368
+ };
369
+ },
370
+ onPointerUp: function onPointerUp(e) {
371
+ var from = start.current;
372
+ start.current = null;
373
+ if (!from) return;
374
+ var dx = e.clientX - from.x;
375
+ var dy = e.clientY - from.y;
376
+ if (!Number.isFinite(dx) || !Number.isFinite(dy)) return;
377
+ if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) < Math.abs(dy) * SWIPE_DOMINANCE) return;
378
+ // Swipe left -> next tab, swipe right -> previous tab.
379
+ var next = neighbour(dx < 0 ? 1 : -1);
380
+ if (next !== -1) changeTab(next);
381
+ },
382
+ onPointerCancel: function onPointerCancel() {
383
+ start.current = null;
384
+ }
385
+ } : {};
386
+ return jsx("div", _objectSpread2(_objectSpread2({
387
+ // touch-pan-y: the browser keeps vertical scrolling; horizontal moves reach us.
388
+ className: cn("relative w-full", swipeable && "touch-pan-y", className)
389
+ }, swipeHandlers), {}, {
243
390
  children: React.Children.map(children, function (child, i) {
244
391
  var _child$props$index2;
245
392
  // inject index if missing
@@ -249,7 +396,7 @@ function Panels(_ref4) {
249
396
  animated: animated
250
397
  });
251
398
  })
252
- });
399
+ }));
253
400
  }
254
401
  function Panel(_ref5) {
255
402
  var children = _ref5.children,
@@ -262,10 +409,10 @@ function Panel(_ref5) {
262
409
  activeIndex = _useTabsContext4.activeIndex,
263
410
  baseId = _useTabsContext4.baseId;
264
411
  var isActive = index === activeIndex;
265
- var _useState3 = useState(isActive),
266
- _useState4 = _slicedToArray(_useState3, 2),
267
- wasActive = _useState4[0],
268
- setWasActive = _useState4[1];
412
+ var _useState7 = useState(isActive),
413
+ _useState8 = _slicedToArray(_useState7, 2),
414
+ wasActive = _useState8[0],
415
+ setWasActive = _useState8[1];
269
416
  // Remember that the panel has been shown once (adjust-during-render pattern).
270
417
  if (isActive && !wasActive) setWasActive(true);
271
418
  if (!wasActive && !isActive) return null;
@@ -6,7 +6,7 @@ import { AlertCircle } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
7
  import { setRefs } from '../../utils/position.js';
8
8
 
9
- var _excluded = ["id", "className", "label", "error", "helperText", "required", "disabled", "showCount", "maxLength", "autoResize", "rows", "maxRows", "value", "defaultValue", "onChange"];
9
+ var _excluded = ["id", "className", "label", "error", "helperText", "required", "disabled", "showCount", "maxLength", "autoResize", "rows", "maxRows", "value", "defaultValue", "onChange", "size"];
10
10
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
11
11
  function fitToContent(el, maxRows) {
12
12
  if (!el) return;
@@ -24,6 +24,12 @@ function fitToContent(el, maxRows) {
24
24
  }
25
25
  el.style.height = "".concat(height, "px");
26
26
  }
27
+ // Padding / text per size; `md` is the original look. Rows set the height.
28
+ var SIZE_CLASSES = {
29
+ sm: "px-2.5 py-1.5 text-xs",
30
+ md: "px-3 py-2 text-sm",
31
+ lg: "px-4 py-2.5 text-base"
32
+ };
27
33
  var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
28
34
  var id = _ref.id,
29
35
  className = _ref.className,
@@ -45,6 +51,8 @@ var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
45
51
  value = _ref.value,
46
52
  defaultValue = _ref.defaultValue,
47
53
  onChange = _ref.onChange,
54
+ _ref$size = _ref.size,
55
+ size = _ref$size === void 0 ? "md" : _ref$size,
48
56
  rest = _objectWithoutProperties(_ref, _excluded);
49
57
  var reactId = useId();
50
58
  var textareaId = id || "textarea-".concat(reactId);
@@ -93,7 +101,7 @@ var Textarea = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
93
101
  onChange: handleChange,
94
102
  "aria-invalid": error ? true : undefined,
95
103
  "aria-describedby": describedBy,
96
- className: cn("block w-full rounded-md border bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400", "focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500", "disabled:cursor-not-allowed disabled:opacity-50", "dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 dark:focus:ring-offset-gray-900", error ? "border-error-500 focus:ring-error-500" : "border-gray-300 dark:border-gray-600", autoResize ? "resize-none" : "resize-y", className)
104
+ className: cn("block w-full rounded-md border bg-surface text-foreground placeholder-gray-400", SIZE_CLASSES[size] || SIZE_CLASSES.md, "transition-colors duration-fast ease-standard motion-reduce:transition-none", "focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "disabled:cursor-not-allowed disabled:opacity-50", "dark:placeholder-gray-500 dark:focus-visible:ring-offset-gray-900", error ? "border-error-500 focus-visible:ring-error-500 dark:border-error-400" : "border-gray-300 focus-visible:ring-primary-500 dark:border-gray-600", autoResize ? "resize-none" : "resize-y", className)
97
105
  })), (error || helperText || showCount && maxLength) && jsxs("div", {
98
106
  className: "mt-1 flex items-start justify-between gap-3 text-sm",
99
107
  children: [jsxs("div", {
@@ -1,32 +1,60 @@
1
1
  'use client';
2
- import { objectWithoutProperties as _objectWithoutProperties, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsx, jsxs } from 'react/jsx-runtime';
4
- import React from 'react';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import React, { useState, useRef, useEffect } from 'react';
5
5
  import { cn } from '../../utils/cn.js';
6
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
6
7
  import { ChevronRight, Home } from 'lucide-react';
7
8
 
8
- var _excluded = ["items", "separator", "showHome", "homeIcon", "onHomeClick", "className"];
9
+ var _excluded = ["items", "separator", "showHome", "homeIcon", "onHomeClick", "maxItems", "className"];
9
10
  // Items without an `id` still need a stable, unique React key.
10
11
  var itemKey = function itemKey(item, index) {
11
12
  var _ref, _item$href;
12
13
  return item.id != null ? "id:".concat(item.id) : "idx:".concat(index, ":").concat((_ref = (_item$href = item.href) !== null && _item$href !== void 0 ? _item$href : item.label) !== null && _ref !== void 0 ? _ref : '');
13
14
  };
14
- var Breadcrumbs = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
15
- var _ref2$items = _ref2.items,
16
- items = _ref2$items === void 0 ? [] : _ref2$items,
17
- _ref2$separator = _ref2.separator,
18
- separator = _ref2$separator === void 0 ? jsx(ChevronRight, {
15
+ // Long labels truncate with an ellipsis; the full text stays available as a tooltip.
16
+ var LABEL_CLASSES = 'min-w-0 max-w-[10rem] truncate sm:max-w-[16rem]';
17
+ var FOCUS_RING = 'rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-gray-900';
18
+ var labelTitle = function labelTitle(label) {
19
+ return typeof label === 'string' || typeof label === 'number' ? String(label) : undefined;
20
+ };
21
+ function ItemContent(_ref2) {
22
+ var item = _ref2.item;
23
+ return jsxs(Fragment, {
24
+ children: [item.icon && jsx("span", {
25
+ className: "mr-1 shrink-0",
26
+ children: item.icon
27
+ }), jsx("span", {
28
+ className: LABEL_CLASSES,
29
+ title: labelTitle(item.label),
30
+ children: item.label
31
+ })]
32
+ });
33
+ }
34
+ var Breadcrumbs = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
35
+ var _ref3$items = _ref3.items,
36
+ items = _ref3$items === void 0 ? [] : _ref3$items,
37
+ _ref3$separator = _ref3.separator,
38
+ separator = _ref3$separator === void 0 ? jsx(ChevronRight, {
19
39
  className: "h-4 w-4"
20
- }) : _ref2$separator,
21
- _ref2$showHome = _ref2.showHome,
22
- showHome = _ref2$showHome === void 0 ? true : _ref2$showHome,
23
- _ref2$homeIcon = _ref2.homeIcon,
24
- homeIcon = _ref2$homeIcon === void 0 ? jsx(Home, {
40
+ }) : _ref3$separator,
41
+ _ref3$showHome = _ref3.showHome,
42
+ showHome = _ref3$showHome === void 0 ? true : _ref3$showHome,
43
+ _ref3$homeIcon = _ref3.homeIcon,
44
+ homeIcon = _ref3$homeIcon === void 0 ? jsx(Home, {
25
45
  className: "h-4 w-4"
26
- }) : _ref2$homeIcon,
27
- onHomeClick = _ref2.onHomeClick,
28
- className = _ref2.className,
29
- props = _objectWithoutProperties(_ref2, _excluded);
46
+ }) : _ref3$homeIcon,
47
+ onHomeClick = _ref3.onHomeClick,
48
+ maxItems = _ref3.maxItems,
49
+ className = _ref3.className,
50
+ props = _objectWithoutProperties(_ref3, _excluded);
51
+ var _useState = useState(false),
52
+ _useState2 = _slicedToArray(_useState, 2),
53
+ expanded = _useState2[0],
54
+ setExpanded = _useState2[1];
55
+ // After expanding, focus moves to the first revealed crumb instead of being lost.
56
+ var focusRevealed = useRef(false);
57
+ var firstHiddenRef = useRef(null);
30
58
  var allItems = showHome ? [{
31
59
  id: 'home',
32
60
  label: 'Home',
@@ -34,37 +62,71 @@ var Breadcrumbs = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
34
62
  icon: homeIcon,
35
63
  onClick: onHomeClick
36
64
  }].concat(_toConsumableArray(items)) : items;
65
+ // Collapse keeps the first crumb and the last (maxItems - 1); needs at least 2 to make sense.
66
+ var limit = Number.isFinite(maxItems) ? Math.max(2, Math.floor(maxItems)) : Infinity;
67
+ var collapsed = !expanded && allItems.length > limit;
68
+ var hiddenFrom = 1;
69
+ var hiddenTo = allItems.length - (limit - 1); // exclusive
70
+ var hiddenCount = collapsed ? hiddenTo - hiddenFrom : 0;
71
+ useEffect(function () {
72
+ if (expanded && focusRevealed.current) {
73
+ var _firstHiddenRef$curre;
74
+ focusRevealed.current = false;
75
+ (_firstHiddenRef$curre = firstHiddenRef.current) === null || _firstHiddenRef$curre === void 0 || _firstHiddenRef$curre.focus();
76
+ }
77
+ }, [expanded]);
78
+ var renderSeparator = function renderSeparator() {
79
+ return jsx("span", {
80
+ className: "shrink-0 text-primary-400 dark:text-primary-500",
81
+ "aria-hidden": "true",
82
+ children: separator
83
+ });
84
+ };
37
85
  return jsx("nav", _objectSpread2(_objectSpread2({
38
86
  ref: ref,
39
- className: cn('flex items-center space-x-1 text-sm', className),
87
+ className: cn('flex min-w-0 items-center space-x-1 text-sm', className),
40
88
  "aria-label": "Breadcrumb"
41
89
  }, props), {}, {
42
90
  children: allItems.map(function (item, index) {
91
+ if (collapsed && index >= hiddenFrom && index < hiddenTo) {
92
+ if (index !== hiddenFrom) return null;
93
+ return jsxs(React.Fragment, {
94
+ children: [renderSeparator(), jsx("button", {
95
+ type: "button",
96
+ className: cn('inline-flex items-center justify-center px-1 text-primary-500 transition-colors duration-fast motion-reduce:transition-none', 'hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300', FOCUS_RING, TOUCH_TARGET),
97
+ "aria-label": "Show ".concat(hiddenCount, " more breadcrumb").concat(hiddenCount === 1 ? '' : 's'),
98
+ onClick: function onClick() {
99
+ focusRevealed.current = true;
100
+ setExpanded(true);
101
+ },
102
+ children: jsx("span", {
103
+ "aria-hidden": "true",
104
+ children: "\u2026"
105
+ })
106
+ })]
107
+ }, "breadcrumbs-collapsed");
108
+ }
109
+ var isLast = index === allItems.length - 1;
43
110
  return jsxs(React.Fragment, {
44
- children: [index > 0 && jsx("span", {
45
- className: "text-primary-400",
46
- "aria-hidden": "true",
47
- children: separator
48
- }), index === allItems.length - 1 ? jsxs("span", {
49
- className: "font-medium text-primary-900",
111
+ children: [index > 0 && renderSeparator(), isLast ? jsx("span", {
112
+ className: "flex min-w-0 items-center font-medium text-primary-900 dark:text-primary-100",
50
113
  "aria-current": "page",
51
- children: [item.icon && jsx("span", {
52
- className: "mr-1",
53
- children: item.icon
54
- }), item.label]
55
- }) : jsxs("a", {
114
+ children: jsx(ItemContent, {
115
+ item: item
116
+ })
117
+ }) : jsx("a", {
118
+ ref: expanded && index === hiddenFrom ? firstHiddenRef : undefined,
56
119
  href: item.href,
57
- className: "flex items-center text-primary-500 transition-colors",
120
+ className: cn('flex min-w-0 items-center text-primary-500 underline-offset-4 transition-colors duration-fast motion-reduce:transition-none', 'hover:text-primary-700 hover:underline dark:text-primary-400 dark:hover:text-primary-300', FOCUS_RING),
58
121
  onClick: function onClick(e) {
59
122
  if (item.onClick) {
60
123
  e.preventDefault();
61
124
  item.onClick(item);
62
125
  }
63
126
  },
64
- children: [item.icon && jsx("span", {
65
- className: "mr-1",
66
- children: item.icon
67
- }), item.label]
127
+ children: jsx(ItemContent, {
128
+ item: item
129
+ })
68
130
  })]
69
131
  }, itemKey(item, index));
70
132
  })