@pk-design/react-tailwind 1.9.0 → 1.10.1

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 (45) hide show
  1. package/dist/cjs/components/BottomNavigation/BottomNavigation.js +14 -5
  2. package/dist/cjs/components/BottomNavigation/BottomNavigation.js.map +1 -1
  3. package/dist/cjs/components/Drawer/Drawer.js +6 -2
  4. package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
  5. package/dist/cjs/components/MobileHeader/MobileHeader.js +23 -4
  6. package/dist/cjs/components/MobileHeader/MobileHeader.js.map +1 -1
  7. package/dist/cjs/components/PullToRefresh/PullToRefresh.js +145 -57
  8. package/dist/cjs/components/PullToRefresh/PullToRefresh.js.map +1 -1
  9. package/dist/cjs/components/StickyActionBar/StickyActionBar.js +2 -2
  10. package/dist/cjs/components/StickyActionBar/StickyActionBar.js.map +1 -1
  11. package/dist/cjs/hooks/use-breakpoint.js +48 -0
  12. package/dist/cjs/hooks/use-breakpoint.js.map +1 -0
  13. package/dist/cjs/index.js +5 -0
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/es/components/BottomNavigation/BottomNavigation.js +14 -5
  16. package/dist/es/components/BottomNavigation/BottomNavigation.js.map +1 -1
  17. package/dist/es/components/Drawer/Drawer.js +6 -2
  18. package/dist/es/components/Drawer/Drawer.js.map +1 -1
  19. package/dist/es/components/MobileHeader/MobileHeader.js +23 -4
  20. package/dist/es/components/MobileHeader/MobileHeader.js.map +1 -1
  21. package/dist/es/components/PullToRefresh/PullToRefresh.js +146 -58
  22. package/dist/es/components/PullToRefresh/PullToRefresh.js.map +1 -1
  23. package/dist/es/components/StickyActionBar/StickyActionBar.js +2 -2
  24. package/dist/es/components/StickyActionBar/StickyActionBar.js.map +1 -1
  25. package/dist/es/hooks/use-breakpoint.js +43 -0
  26. package/dist/es/hooks/use-breakpoint.js.map +1 -0
  27. package/dist/es/index.js +1 -0
  28. package/dist/es/index.js.map +1 -1
  29. package/dist/react-tailwind.cjs.js +231 -70
  30. package/dist/react-tailwind.cjs.js.map +1 -1
  31. package/dist/react-tailwind.es.js +229 -72
  32. package/dist/react-tailwind.es.js.map +1 -1
  33. package/dist/types/components/BottomNavigation/BottomNavigation.d.ts +1 -1
  34. package/dist/types/components/BottomNavigation/BottomNavigation.types.d.ts +28 -3
  35. package/dist/types/components/Drawer/Drawer.types.d.ts +8 -4
  36. package/dist/types/components/MobileHeader/MobileHeader.d.ts +1 -1
  37. package/dist/types/components/MobileHeader/MobileHeader.types.d.ts +13 -0
  38. package/dist/types/components/PullToRefresh/PullToRefresh.d.ts +1 -1
  39. package/dist/types/components/PullToRefresh/PullToRefresh.types.d.ts +45 -6
  40. package/dist/types/components/StickyActionBar/StickyActionBar.types.d.ts +12 -0
  41. package/dist/types/hooks/use-breakpoint.d.ts +27 -0
  42. package/dist/types/hooks/use-breakpoint.test.d.ts +1 -0
  43. package/dist/types/index.d.ts +1 -0
  44. package/dist/types/test-utils/mock-viewport.d.ts +7 -0
  45. package/package.json +1 -1
@@ -341,6 +341,45 @@ var SvgX = function SvgX(props) {
341
341
  })));
342
342
  };
343
343
 
344
+ const BREAKPOINT_QUERIES = {
345
+ medium: '(min-width: 768px)',
346
+ expanded: '(min-width: 1024px)',
347
+ };
348
+ // jsdom and some embedded webviews have no matchMedia; treat every query as
349
+ // unmatched there (the compact layout) rather than throwing.
350
+ const hasMatchMedia = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function';
351
+ /** Live result of a CSS media query. `false` during server rendering or without `matchMedia`. */
352
+ const useMediaQuery = (query) => {
353
+ const subscribe = React.useCallback((onChange) => {
354
+ if (!hasMatchMedia())
355
+ return () => { };
356
+ const list = window.matchMedia(query);
357
+ list.addEventListener('change', onChange);
358
+ return () => list.removeEventListener('change', onChange);
359
+ }, [query]);
360
+ return React.useSyncExternalStore(subscribe, () => hasMatchMedia() && window.matchMedia(query).matches, () => false);
361
+ };
362
+ /** Current layout range; `compact` during server rendering. */
363
+ const useBreakpoint = () => {
364
+ const isMedium = useMediaQuery(BREAKPOINT_QUERIES.medium);
365
+ const isExpanded = useMediaQuery(BREAKPOINT_QUERIES.expanded);
366
+ if (isExpanded)
367
+ return 'expanded';
368
+ return isMedium ? 'medium' : 'compact';
369
+ };
370
+ /** Resolves a `ResponsiveValue` for the current viewport. */
371
+ const useResponsiveValue = (value) => {
372
+ var _a, _b, _c;
373
+ const breakpoint = useBreakpoint();
374
+ if (typeof value !== 'object' || value === null || !('base' in value))
375
+ return value;
376
+ if (breakpoint === 'expanded')
377
+ return (_b = (_a = value.lg) !== null && _a !== void 0 ? _a : value.md) !== null && _b !== void 0 ? _b : value.base;
378
+ if (breakpoint === 'medium')
379
+ return (_c = value.md) !== null && _c !== void 0 ? _c : value.base;
380
+ return value.base;
381
+ };
382
+
344
383
  const alignmentMap = {
345
384
  center: 'items-center justify-center h-full',
346
385
  start: 'justify-start h-full',
@@ -353,7 +392,8 @@ const panelAlignmentMap = {
353
392
  start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',
354
393
  end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',
355
394
  top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',
356
- bottom: 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh]',
395
+ // Bottom sheets clear the home indicator on notched phones (0 elsewhere).
396
+ bottom: 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh] pb-[env(safe-area-inset-bottom,0px)]',
357
397
  };
358
398
  const panelSizeMap = {
359
399
  xs: 'max-w-xs',
@@ -364,7 +404,9 @@ const panelSizeMap = {
364
404
  '2xl': 'max-w-2xl',
365
405
  full: 'w-full',
366
406
  };
367
- const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align = 'end', size = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
407
+ const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align: alignProp = 'end', size: sizeProp = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
408
+ const align = useResponsiveValue(alignProp);
409
+ const size = useResponsiveValue(sizeProp);
368
410
  const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
369
411
  const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
370
412
  const [viewportHeight, setViewportHeight] = React.useState(null);
@@ -679,20 +721,23 @@ const BodyText = React.forwardRef((props, ref) => {
679
721
  });
680
722
  BodyText.displayName = 'BodyText';
681
723
 
682
- function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', }) {
724
+ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, }) {
683
725
  const handleTabClick = (item) => {
684
726
  var _a;
685
727
  if (item.disabled)
686
728
  return;
687
729
  (_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
688
- onChange(item.id);
730
+ onChange === null || onChange === void 0 ? void 0 : onChange(item.id);
689
731
  };
732
+ // Removed from the tree, not just covered, so hidden tabs can't take focus.
733
+ if (hidden)
734
+ return null;
690
735
  const themeStyles = {
691
736
  light: 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',
692
737
  dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
693
738
  primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
694
739
  }[theme];
695
- return (jsxRuntime.jsx("nav", { "aria-label": "Bottom Navigation", className: buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
740
+ return (jsxRuntime.jsx("nav", { "aria-label": ariaLabel, className: buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
696
741
  const isActive = item.id === activeId;
697
742
  const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
698
743
  const activeItemColor = {
@@ -715,11 +760,17 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
715
760
  info: 'bg-blue-500 text-white',
716
761
  success: 'bg-emerald-500 text-white',
717
762
  }[item.badgeVariant || 'danger'];
718
- return (jsxRuntime.jsxs("button", { type: "button", disabled: item.disabled, onClick: () => handleTabClick(item), className: buildClassName('flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none', isActive ? activeItemColor : inactiveItemColor, itemClass), "data-testid": `bottom-nav-item-${item.id}`, "aria-selected": isActive, children: [activeIndicatorStyle === 'line' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') })), jsxRuntime.jsxs("div", { className: buildClassName('relative inline-flex items-center justify-center transition-all duration-200', activeIndicatorStyle === 'pill'
763
+ // A disabled item stays a button: a link can't be disabled.
764
+ const isLink = Boolean(item.href) && !item.disabled;
765
+ const ItemElement = isLink ? linkAs : 'button';
766
+ const elementProps = isLink
767
+ ? { [linkHrefProp]: item.href }
768
+ : { type: 'button', disabled: item.disabled };
769
+ return (jsxRuntime.jsxs(ItemElement, { ...elementProps, onClick: () => handleTabClick(item), className: buildClassName('flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none', isActive ? activeItemColor : inactiveItemColor, itemClass), "data-testid": `bottom-nav-item-${item.id}`, "aria-current": isActive ? 'page' : undefined, "aria-label": !showLabelText && typeof item.label === 'string' ? item.label : undefined, children: [activeIndicatorStyle === 'line' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') })), jsxRuntime.jsxs("div", { className: buildClassName('relative inline-flex items-center justify-center transition-all duration-200', activeIndicatorStyle === 'pill'
719
770
  ? buildClassName('px-4 py-1 rounded-full', isActive
720
771
  ? pillActiveStyles
721
772
  : 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40')
722
- : 'px-1 py-0.5'), children: [jsxRuntime.jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsxRuntime.jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
773
+ : 'px-1 py-0.5'), children: [jsxRuntime.jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", "aria-hidden": "true", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsxRuntime.jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
723
774
  }) }));
724
775
  }
725
776
  BottomNavigation.displayName = 'BottomNavigation';
@@ -2363,77 +2414,165 @@ function PinInput({ length = 4, value: externalValue, onChange, onComplete, mask
2363
2414
  }
2364
2415
  PinInput.displayName = 'PinInput';
2365
2416
 
2366
- function PullArrowIcon({ progress }) {
2367
- const rotation = Math.min(180, progress * 180);
2368
- return (jsxRuntime.jsx("svg", { className: "size-4 text-blue-600 dark:text-blue-400 transition-transform duration-150", style: { transform: `rotate(${rotation}deg)` }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) }));
2417
+ // Resistance applied to the finger's travel, so the pull feels elastic.
2418
+ const RESISTANCE = 0.45;
2419
+ // Finger travel (px) before deciding whether a gesture is a pull or a sideways swipe.
2420
+ const AXIS_LOCK_SLOP = 8;
2421
+ // Overlay badge size and the gap it rests at below the container's top edge.
2422
+ const BADGE_SIZE = 40;
2423
+ const BADGE_TOP_GAP = 10;
2424
+ function PullArrowIcon({ progress, className }) {
2425
+ return (jsxRuntime.jsx("svg", { className: className, style: { transform: `rotate(${Math.min(180, progress * 180)}deg)` }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) }));
2369
2426
  }
2370
- function PullToRefresh({ onRefresh, pullThreshold = 70, maxPullDistance = 120, disabled = false, refreshing: externalRefreshing, pullingContent, refreshingContent, children, className = '', contentClass = '', }) {
2427
+ function PullToRefresh({ onRefresh, onError, variant = 'push', pullThreshold = 70, maxPullDistance = 120, disabled = false, refreshing: externalRefreshing, pullingContent, refreshingContent, pullLabel = 'Pull down to refresh', releaseLabel = 'Release to refresh', refreshingLabel = 'Refreshing...', refreshedLabel = 'Content refreshed', children, className = '', contentClass = '', }) {
2371
2428
  const containerRef = React.useRef(null);
2372
- const startYRef = React.useRef(0);
2373
- const isPullingRef = React.useRef(false);
2374
2429
  const [pullDistance, setPullDistance] = React.useState(0);
2430
+ const [isDragging, setIsDragging] = React.useState(false);
2375
2431
  const [internalRefreshing, setInternalRefreshing] = React.useState(false);
2432
+ const [announcement, setAnnouncement] = React.useState('');
2376
2433
  const isRefreshing = externalRefreshing !== undefined ? externalRefreshing : internalRefreshing;
2377
- const handleTouchStart = React.useCallback((e) => {
2378
- if (disabled || isRefreshing)
2379
- return;
2380
- const container = containerRef.current;
2381
- if (container && container.scrollTop === 0) {
2382
- startYRef.current = e.touches[0].clientY;
2383
- isPullingRef.current = true;
2384
- }
2385
- }, [disabled, isRefreshing]);
2386
- const handleTouchMove = React.useCallback((e) => {
2387
- if (!isPullingRef.current || disabled || isRefreshing)
2388
- return;
2434
+ // The touch listeners are attached once, natively (see below), so they read
2435
+ // the latest props and state through this ref instead of stale closures.
2436
+ const latest = React.useRef({
2437
+ disabled,
2438
+ isRefreshing,
2439
+ pullThreshold,
2440
+ maxPullDistance,
2441
+ onRefresh,
2442
+ onError,
2443
+ refreshedLabel,
2444
+ });
2445
+ React.useLayoutEffect(() => {
2446
+ latest.current = {
2447
+ disabled,
2448
+ isRefreshing,
2449
+ pullThreshold,
2450
+ maxPullDistance,
2451
+ onRefresh,
2452
+ onError,
2453
+ refreshedLabel,
2454
+ };
2455
+ });
2456
+ React.useEffect(() => {
2389
2457
  const container = containerRef.current;
2390
- if (!container || container.scrollTop > 0)
2391
- return;
2392
- const currentY = e.touches[0].clientY;
2393
- const rawDistance = currentY - startYRef.current;
2394
- if (rawDistance > 0) {
2395
- // Rubber-band resistance formula
2396
- const dampened = Math.min(maxPullDistance, rawDistance * 0.45);
2397
- setPullDistance(dampened);
2398
- if (e.cancelable) {
2399
- e.preventDefault();
2400
- }
2401
- }
2402
- else {
2403
- setPullDistance(0);
2404
- }
2405
- }, [disabled, isRefreshing, maxPullDistance]);
2406
- const handleTouchEnd = React.useCallback(async () => {
2407
- if (!isPullingRef.current || disabled || isRefreshing)
2458
+ if (!container)
2408
2459
  return;
2409
- isPullingRef.current = false;
2410
- if (pullDistance >= pullThreshold) {
2411
- setPullDistance(pullThreshold);
2460
+ let mounted = true;
2461
+ let pulling = false;
2462
+ let axis = 'pending';
2463
+ let startX = 0;
2464
+ let startY = 0;
2465
+ let distance = 0;
2466
+ const setDistance = (next) => {
2467
+ distance = next;
2468
+ setPullDistance(next);
2469
+ };
2470
+ // Ends a gesture without refreshing: a cancelled touch, a sideways swipe,
2471
+ // or content that scrolled away from the top.
2472
+ const reset = () => {
2473
+ pulling = false;
2474
+ setIsDragging(false);
2475
+ setDistance(0);
2476
+ };
2477
+ const refresh = async () => {
2478
+ const { pullThreshold: threshold, onRefresh: run, onError: report } = latest.current;
2479
+ setDistance(threshold);
2412
2480
  setInternalRefreshing(true);
2413
2481
  try {
2414
- await onRefresh();
2482
+ await run();
2483
+ if (mounted)
2484
+ setAnnouncement(latest.current.refreshedLabel);
2485
+ }
2486
+ catch (error) {
2487
+ report === null || report === void 0 ? void 0 : report(error);
2415
2488
  }
2416
2489
  finally {
2417
- setInternalRefreshing(false);
2418
- setPullDistance(0);
2490
+ if (mounted) {
2491
+ setInternalRefreshing(false);
2492
+ setDistance(0);
2493
+ }
2419
2494
  }
2420
- }
2421
- else {
2422
- setPullDistance(0);
2423
- }
2424
- }, [disabled, isRefreshing, onRefresh, pullDistance, pullThreshold]);
2495
+ };
2496
+ const handleTouchStart = (e) => {
2497
+ var _a, _b;
2498
+ const { disabled: isDisabled, isRefreshing: busy } = latest.current;
2499
+ if (isDisabled || busy || container.scrollTop > 0 || e.touches.length !== 1)
2500
+ return;
2501
+ startX = (_a = e.touches[0].clientX) !== null && _a !== void 0 ? _a : 0;
2502
+ startY = (_b = e.touches[0].clientY) !== null && _b !== void 0 ? _b : 0;
2503
+ axis = 'pending';
2504
+ pulling = true;
2505
+ setAnnouncement('');
2506
+ setIsDragging(true);
2507
+ setDistance(0);
2508
+ };
2509
+ const handleTouchMove = (e) => {
2510
+ var _a, _b, _c, _d;
2511
+ if (!pulling)
2512
+ return;
2513
+ if (latest.current.disabled || latest.current.isRefreshing || container.scrollTop > 0) {
2514
+ reset();
2515
+ return;
2516
+ }
2517
+ const dx = ((_b = (_a = e.touches[0]) === null || _a === void 0 ? void 0 : _a.clientX) !== null && _b !== void 0 ? _b : 0) - startX;
2518
+ const dy = ((_d = (_c = e.touches[0]) === null || _c === void 0 ? void 0 : _c.clientY) !== null && _d !== void 0 ? _d : 0) - startY;
2519
+ if (axis === 'pending') {
2520
+ if (Math.abs(dx) < AXIS_LOCK_SLOP && Math.abs(dy) < AXIS_LOCK_SLOP)
2521
+ return;
2522
+ axis = dy > 0 && Math.abs(dy) > Math.abs(dx) ? 'vertical' : 'horizontal';
2523
+ // A sideways swipe or an upward scroll belongs to the content, not to us.
2524
+ if (axis === 'horizontal') {
2525
+ reset();
2526
+ return;
2527
+ }
2528
+ }
2529
+ const next = Math.min(latest.current.maxPullDistance, Math.max(0, dy) * RESISTANCE);
2530
+ setDistance(next);
2531
+ // Needs the non-passive listener: stops the browser's own pull-to-refresh/overscroll.
2532
+ if (next > 0 && e.cancelable)
2533
+ e.preventDefault();
2534
+ };
2535
+ const handleTouchEnd = () => {
2536
+ if (!pulling)
2537
+ return;
2538
+ pulling = false;
2539
+ setIsDragging(false);
2540
+ if (distance >= latest.current.pullThreshold)
2541
+ void refresh();
2542
+ else
2543
+ setDistance(0);
2544
+ };
2545
+ // React registers touch listeners as passive, where preventDefault() is
2546
+ // ignored, so these are attached natively with touchmove non-passive.
2547
+ container.addEventListener('touchstart', handleTouchStart, { passive: true });
2548
+ container.addEventListener('touchmove', handleTouchMove, { passive: false });
2549
+ container.addEventListener('touchend', handleTouchEnd);
2550
+ container.addEventListener('touchcancel', reset);
2551
+ return () => {
2552
+ mounted = false;
2553
+ container.removeEventListener('touchstart', handleTouchStart);
2554
+ container.removeEventListener('touchmove', handleTouchMove);
2555
+ container.removeEventListener('touchend', handleTouchEnd);
2556
+ container.removeEventListener('touchcancel', reset);
2557
+ };
2558
+ }, []);
2425
2559
  const progress = Math.min(1, pullDistance / pullThreshold);
2426
2560
  const isReady = pullDistance >= pullThreshold;
2427
- return (jsxRuntime.jsxs("div", { ref: containerRef, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, className: buildClassName('relative overflow-y-auto overscroll-none', className), "data-testid": "pull-to-refresh-container", children: [jsxRuntime.jsx("div", { className: "left-0 right-0 top-0 flex items-center justify-center pointer-events-none transition-all duration-150 z-20", style: {
2428
- height: isRefreshing ? `${pullThreshold}px` : `${pullDistance}px`,
2429
- opacity: pullDistance > 0 || isRefreshing ? 1 : 0,
2430
- }, "data-testid": "pull-to-refresh-indicator", children: jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white dark:bg-gray-800 border border-[var(--ui-border)] shadow-md text-xs font-semibold text-gray-700 dark:text-gray-200", children: isRefreshing ? (refreshingContent || (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Loader, { size: "sm" }), jsxRuntime.jsx("span", { children: "Refreshing..." })] }))) : pullingContent ? (pullingContent) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PullArrowIcon, { progress: progress }), jsxRuntime.jsx("span", { children: isReady ? 'Release to refresh' : 'Pull down to refresh' })] })) }) }), jsxRuntime.jsx("div", { className: buildClassName('transition-transform duration-150', contentClass), style: {
2431
- transform: isRefreshing
2432
- ? `translateY(${pullThreshold}px)`
2433
- : pullDistance > 0
2434
- ? `translateY(${pullDistance}px)`
2435
- : 'translateY(0px)',
2436
- }, "data-testid": "pull-to-refresh-content", children: children })] }));
2561
+ const offset = isRefreshing ? pullThreshold : pullDistance;
2562
+ // Follow the finger exactly while dragging; animate only the settle afterwards.
2563
+ const motionClass = isDragging
2564
+ ? 'transition-none'
2565
+ : 'transition-transform duration-200 ease-out motion-reduce:transition-none';
2566
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: buildClassName('relative overflow-y-auto overscroll-none', className), "data-testid": "pull-to-refresh-container", children: [jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: isRefreshing ? refreshingLabel : announcement }), variant === 'overlay' ? (jsxRuntime.jsx("div", { className: buildClassName('pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center', motionClass), style: {
2567
+ transform: `translateY(${offset - BADGE_SIZE + BADGE_TOP_GAP}px)`,
2568
+ opacity: offset > 0 ? 1 : 0,
2569
+ }, "aria-hidden": "true", "data-testid": "pull-to-refresh-indicator", children: jsxRuntime.jsx("div", { className: "grid size-10 place-items-center rounded-full bg-[var(--ui-primary)] text-white shadow-lg", children: isRefreshing
2570
+ ? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : jsxRuntime.jsx(Loader, { size: "xs", className: "text-white" }))
2571
+ : (pullingContent !== null && pullingContent !== void 0 ? pullingContent : jsxRuntime.jsx(PullArrowIcon, { progress: progress, className: "size-4" })) }) })) : (
2572
+ // Absolutely positioned, so the content's own translate is the only shift.
2573
+ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 z-20 flex items-center justify-center overflow-hidden", style: { height: `${offset}px`, opacity: offset > 0 ? 1 : 0 }, "aria-hidden": "true", "data-testid": "pull-to-refresh-indicator", children: jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white dark:bg-gray-800 border border-[var(--ui-border)] shadow-md text-xs font-semibold text-gray-700 dark:text-gray-200", children: isRefreshing
2574
+ ? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Loader, { size: "sm" }), jsxRuntime.jsx("span", { children: refreshingLabel })] })))
2575
+ : (pullingContent !== null && pullingContent !== void 0 ? pullingContent : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PullArrowIcon, { progress: progress, className: "size-4 text-[var(--ui-primary)] dark:text-blue-400" }), jsxRuntime.jsx("span", { children: isReady ? releaseLabel : pullLabel })] }))) }) })), jsxRuntime.jsx("div", { className: buildClassName(variant === 'push' && motionClass, contentClass), style: variant === 'push' ? { transform: `translateY(${offset}px)` } : undefined, "data-testid": "pull-to-refresh-content", children: children })] }));
2437
2576
  }
2438
2577
  PullToRefresh.displayName = 'PullToRefresh';
2439
2578
 
@@ -3346,12 +3485,24 @@ function ChevronLeftIcon$1() {
3346
3485
  function SearchIcon$1() {
3347
3486
  return (jsxRuntime.jsx("svg", { className: "size-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }));
3348
3487
  }
3488
+ function MoreIcon() {
3489
+ return (jsxRuntime.jsxs("svg", { className: "size-5", fill: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "1.75" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "1.75" }), jsxRuntime.jsx("circle", { cx: "12", cy: "19", r: "1.75" })] }));
3490
+ }
3349
3491
  function ClearIcon() {
3350
3492
  return (jsxRuntime.jsx("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }));
3351
3493
  }
3352
- function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
3494
+ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, maxVisibleActions, overflowLabel = 'More actions', trailing, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
3353
3495
  const [isSearchActive, setIsSearchActive] = React.useState(false);
3354
3496
  const [internalSearchValue, setInternalSearchValue] = React.useState('');
3497
+ const [isOverflowOpen, setIsOverflowOpen] = React.useState(false);
3498
+ const hasLeadingSlot = Boolean(onBack || leading) || titleAlign === 'center';
3499
+ // The overflow button takes one of the visible slots.
3500
+ const allActions = actions !== null && actions !== void 0 ? actions : [];
3501
+ const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions;
3502
+ const visibleActions = shouldOverflow
3503
+ ? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))
3504
+ : allActions;
3505
+ const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : [];
3355
3506
  const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue;
3356
3507
  const handleSearchInput = (e) => {
3357
3508
  const val = e.target.value;
@@ -3364,16 +3515,22 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
3364
3515
  };
3365
3516
  return (jsxRuntime.jsxs("header", { className: buildClassName('w-full transition-colors select-none z-30', sticky && 'sticky top-0', transparent
3366
3517
  ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'
3367
- : 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs', 'pt-[env(safe-area-inset-top,0px)]', className), "data-testid": "mobile-header", children: [jsxRuntime.jsx("div", { className: "h-14 px-3.5 flex items-center justify-between gap-3 relative", children: searchable && isSearchActive ? (jsxRuntime.jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxRuntime.jsxs("div", { className: "relative flex-1 flex items-center", children: [jsxRuntime.jsx("span", { className: "absolute left-3 pointer-events-none", children: jsxRuntime.jsx(SearchIcon$1, {}) }), jsxRuntime.jsx("input", { type: "text", value: searchValue, onChange: handleSearchInput, placeholder: searchPlaceholder, autoFocus: true, className: "w-full pl-9 pr-8 py-1.5 text-xs bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all", "data-testid": "mobile-header-search-input" }), searchValue && (jsxRuntime.jsx("button", { type: "button", onClick: handleClearSearch, "aria-label": "Clear search", className: "absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer", children: jsxRuntime.jsx(ClearIcon, {}) }))] }), jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(false), className: "text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 z-10", children: onBack ? (jsxRuntime.jsxs("button", { type: "button", onClick: onBack, "aria-label": backLabel, className: "flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-back-btn", children: [jsxRuntime.jsx(ChevronLeftIcon$1, {}), jsxRuntime.jsx("span", { className: "text-xs font-semibold hidden sm:inline", children: backLabel })] })) : (leading) }), jsxRuntime.jsxs("div", { className: buildClassName('flex-1 min-w-0 px-2', titleAlign === 'center'
3518
+ : 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs', 'pt-[env(safe-area-inset-top,0px)]', className), "data-testid": "mobile-header", children: [jsxRuntime.jsx("div", { className: "h-14 px-4 flex items-center justify-between gap-3 relative", children: searchable && isSearchActive ? (jsxRuntime.jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxRuntime.jsxs("div", { className: "relative flex-1 flex items-center", children: [jsxRuntime.jsx("span", { className: "absolute left-3 pointer-events-none", children: jsxRuntime.jsx(SearchIcon$1, {}) }), jsxRuntime.jsx("input", { type: "text", value: searchValue, onChange: handleSearchInput, placeholder: searchPlaceholder, autoFocus: true, className: "w-full pl-9 pr-8 py-1.5 text-xs bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all", "data-testid": "mobile-header-search-input" }), searchValue && (jsxRuntime.jsx("button", { type: "button", onClick: handleClearSearch, "aria-label": "Clear search", className: "absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer", children: jsxRuntime.jsx(ClearIcon, {}) }))] }), jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(false), className: "text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasLeadingSlot && (jsxRuntime.jsx("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 z-10", "data-testid": "mobile-header-leading", children: onBack ? (jsxRuntime.jsxs("button", { type: "button", onClick: onBack, "aria-label": backLabel, className: "flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-back-btn", children: [jsxRuntime.jsx(ChevronLeftIcon$1, {}), jsxRuntime.jsx("span", { className: "text-xs font-semibold hidden sm:inline", children: backLabel })] })) : (leading) })), jsxRuntime.jsxs("div", { className: buildClassName('flex-1 min-w-0', hasLeadingSlot ? 'px-2' : 'pr-2', titleAlign === 'center'
3368
3519
  ? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'
3369
- : 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsxRuntime.jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(true), "aria-label": "Search", className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-search-toggle", children: jsxRuntime.jsx(SearchIcon$1, {}) })), actions === null || actions === void 0 ? void 0 : actions.map((action) => {
3520
+ : 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsxRuntime.jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(true), "aria-label": "Search", className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-search-toggle", children: jsxRuntime.jsx(SearchIcon$1, {}) })), visibleActions.map((action) => {
3370
3521
  const badgeColor = {
3371
3522
  danger: 'bg-red-500 text-white',
3372
3523
  info: 'bg-blue-500 text-white',
3373
3524
  success: 'bg-emerald-500 text-white',
3374
3525
  }[action.badgeVariant || 'danger'];
3375
3526
  return (jsxRuntime.jsxs("button", { type: "button", disabled: action.disabled, onClick: action.onClick, "aria-label": action.label, className: buildClassName('p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors relative cursor-pointer', action.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), "data-testid": `mobile-header-action-${action.id}`, children: [jsxRuntime.jsx("span", { className: "text-base", children: action.icon }), action.badge !== undefined && action.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute top-1 right-1 min-w-[14px] h-3.5 px-0.5 rounded-full text-[9px] font-bold flex items-center justify-center shadow-xs', badgeColor), "data-testid": `mobile-header-badge-${action.id}`, children: action.badge }))] }, action.id));
3376
- })] })] })) }), bottomSlot && (jsxRuntime.jsx("div", { className: "px-3 py-2 border-t border-[var(--ui-border-muted)]", "data-testid": "mobile-header-bottom-slot", children: bottomSlot }))] }));
3527
+ }), overflowActions.length > 0 && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsOverflowOpen(true), "aria-label": overflowLabel, "aria-haspopup": "dialog", "aria-expanded": isOverflowOpen, className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-overflow", children: jsxRuntime.jsx(MoreIcon, {}) })), trailing] })] })) }), overflowActions.length > 0 && (jsxRuntime.jsx(ActionSheet, { isOpen: isOverflowOpen, onClose: () => setIsOverflowOpen(false), actions: overflowActions.map((action) => ({
3528
+ id: action.id,
3529
+ label: action.label,
3530
+ icon: action.icon,
3531
+ disabled: action.disabled,
3532
+ onClick: action.onClick,
3533
+ })) })), bottomSlot && (jsxRuntime.jsx("div", { className: "px-3 py-2 border-t border-[var(--ui-border-muted)]", "data-testid": "mobile-header-bottom-slot", children: bottomSlot }))] }));
3377
3534
  }
3378
3535
  MobileHeader.displayName = 'MobileHeader';
3379
3536
 
@@ -4349,14 +4506,14 @@ StatusPill.displayName = 'StatusPill';
4349
4506
 
4350
4507
  const ChevronUpIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" }) }));
4351
4508
  const StickyActionBar = React.forwardRef((props, ref) => {
4352
- const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', className, ...restProps } = props;
4509
+ const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', safeArea = true, bottomOffset, className, style, ...restProps } = props;
4353
4510
  const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
4354
4511
  const positionClass = position === 'fixed'
4355
4512
  ? 'fixed bottom-0 left-0 right-0 z-30'
4356
4513
  : position === 'sticky'
4357
4514
  ? 'sticky bottom-0 z-30'
4358
4515
  : 'relative z-10';
4359
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', className), "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
4516
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', safeArea && 'pb-[calc(0.875rem+env(safe-area-inset-bottom,0px))]', className), style: bottomOffset && position !== 'relative' ? { bottom: bottomOffset, ...style } : style, "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
4360
4517
  });
4361
4518
  StickyActionBar.displayName = 'StickyActionBar';
4362
4519
 
@@ -4952,6 +5109,7 @@ const useIsTabletOrMobile = () => {
4952
5109
  exports.Accordion = Accordion;
4953
5110
  exports.ActionSheet = ActionSheet;
4954
5111
  exports.Alert = Alert;
5112
+ exports.BREAKPOINT_QUERIES = BREAKPOINT_QUERIES;
4955
5113
  exports.Badge = Badge;
4956
5114
  exports.Banner = Banner;
4957
5115
  exports.BodyText = BodyText;
@@ -5032,12 +5190,15 @@ exports.isArray = isArray;
5032
5190
  exports.isEmpty = isEmpty;
5033
5191
  exports.nextSortDirection = nextSortDirection;
5034
5192
  exports.updateSortQuery = updateSortQuery;
5193
+ exports.useBreakpoint = useBreakpoint;
5035
5194
  exports.useDebounce = useDebounce;
5036
5195
  exports.useDebounceFn = useDebounceFn;
5037
5196
  exports.useIsMobile = useIsMobile;
5038
5197
  exports.useIsTablet = useIsTablet;
5039
5198
  exports.useIsTabletOrMobile = useIsTabletOrMobile;
5199
+ exports.useMediaQuery = useMediaQuery;
5040
5200
  exports.usePagination = usePagination;
5201
+ exports.useResponsiveValue = useResponsiveValue;
5041
5202
  exports.useRowSelection = useRowSelection;
5042
5203
  exports.useToast = useToast;
5043
5204
  //# sourceMappingURL=react-tailwind.cjs.js.map