@papernote/ui 1.7.7 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/components/Badge.d.ts +3 -1
  2. package/dist/components/Badge.d.ts.map +1 -1
  3. package/dist/components/BottomSheet.d.ts +72 -8
  4. package/dist/components/BottomSheet.d.ts.map +1 -1
  5. package/dist/components/CompactStat.d.ts +52 -0
  6. package/dist/components/CompactStat.d.ts.map +1 -0
  7. package/dist/components/HorizontalScroll.d.ts +43 -0
  8. package/dist/components/HorizontalScroll.d.ts.map +1 -0
  9. package/dist/components/NotificationBanner.d.ts +53 -0
  10. package/dist/components/NotificationBanner.d.ts.map +1 -0
  11. package/dist/components/Progress.d.ts +2 -2
  12. package/dist/components/Progress.d.ts.map +1 -1
  13. package/dist/components/PullToRefresh.d.ts +23 -71
  14. package/dist/components/PullToRefresh.d.ts.map +1 -1
  15. package/dist/components/Stack.d.ts +2 -1
  16. package/dist/components/Stack.d.ts.map +1 -1
  17. package/dist/components/SwipeableCard.d.ts +65 -0
  18. package/dist/components/SwipeableCard.d.ts.map +1 -0
  19. package/dist/components/Text.d.ts +9 -2
  20. package/dist/components/Text.d.ts.map +1 -1
  21. package/dist/components/index.d.ts +11 -3
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/index.d.ts +317 -86
  24. package/dist/index.esm.js +932 -253
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +937 -252
  27. package/dist/index.js.map +1 -1
  28. package/dist/styles.css +178 -8
  29. package/package.json +1 -1
  30. package/src/components/Badge.tsx +13 -2
  31. package/src/components/BottomSheet.tsx +227 -98
  32. package/src/components/Card.tsx +1 -1
  33. package/src/components/CompactStat.tsx +150 -0
  34. package/src/components/HorizontalScroll.tsx +275 -0
  35. package/src/components/NotificationBanner.tsx +238 -0
  36. package/src/components/Progress.tsx +6 -3
  37. package/src/components/PullToRefresh.tsx +158 -196
  38. package/src/components/Stack.tsx +4 -1
  39. package/src/components/SwipeableCard.tsx +347 -0
  40. package/src/components/Text.tsx +45 -3
  41. package/src/components/index.ts +16 -3
  42. package/src/styles/index.css +32 -0
package/dist/index.js CHANGED
@@ -2527,7 +2527,7 @@ const Card = React.forwardRef(({ children, variant = 'default', width = 'auto',
2527
2527
  const baseStyles = 'bg-white bg-subtle-grain border-2 border-paper-300 transition-shadow duration-200';
2528
2528
  const variantStyles = {
2529
2529
  default: 'rounded-xl shadow-lg p-8',
2530
- compact: 'rounded-lg shadow-md p-5',
2530
+ compact: 'rounded-lg shadow-md p-3', // 12px padding for mobile-density layouts
2531
2531
  flat: 'rounded-lg p-5',
2532
2532
  };
2533
2533
  const widthStyles = {
@@ -2643,6 +2643,7 @@ function Separator({ orientation = 'horizontal', className = '', spacing = 'md',
2643
2643
  *
2644
2644
  * Spacing scale (use either `spacing` or `gap` prop - they're aliases):
2645
2645
  * - none: 0
2646
+ * - tight: 0.25rem (1) - for mobile-density layouts
2646
2647
  * - xs: 0.5rem (2)
2647
2648
  * - sm: 0.75rem (3)
2648
2649
  * - md: 1.5rem (6)
@@ -2670,6 +2671,7 @@ const Stack = React.forwardRef(({ children, direction = 'vertical', spacing, gap
2670
2671
  const spacingClasses = {
2671
2672
  vertical: {
2672
2673
  none: '',
2674
+ tight: 'space-y-1', // 4px - for mobile-density layouts
2673
2675
  xs: 'space-y-2',
2674
2676
  sm: 'space-y-3',
2675
2677
  md: 'space-y-6',
@@ -2678,6 +2680,7 @@ const Stack = React.forwardRef(({ children, direction = 'vertical', spacing, gap
2678
2680
  },
2679
2681
  horizontal: {
2680
2682
  none: '',
2683
+ tight: 'space-x-1', // 4px - for mobile-density layouts
2681
2684
  xs: 'space-x-2',
2682
2685
  sm: 'space-x-3',
2683
2686
  md: 'space-x-6',
@@ -2975,7 +2978,7 @@ const GridItem = ({ colSpan, rowSpan, children, className = '', ...boxProps }) =
2975
2978
  * <Text ref={textRef}>Measurable text</Text>
2976
2979
  * ```
2977
2980
  */
2978
- const Text = React.forwardRef(({ children, as: Component = 'p', size = 'base', weight = 'normal', color = 'primary', align = 'left', truncate = false, lineClamp, transform, className = '', ...htmlProps }, ref) => {
2981
+ const Text = React.forwardRef(({ children, as: Component = 'p', size = 'base', smSize, mdSize, lgSize, weight = 'normal', color = 'primary', align = 'left', truncate = false, lineClamp, transform, className = '', ...htmlProps }, ref) => {
2979
2982
  const sizeClasses = {
2980
2983
  xs: 'text-xs',
2981
2984
  sm: 'text-sm',
@@ -2984,6 +2987,31 @@ const Text = React.forwardRef(({ children, as: Component = 'p', size = 'base', w
2984
2987
  xl: 'text-xl',
2985
2988
  '2xl': 'text-2xl',
2986
2989
  };
2990
+ // Responsive size classes
2991
+ const smSizeClasses = {
2992
+ xs: 'sm:text-xs',
2993
+ sm: 'sm:text-sm',
2994
+ base: 'sm:text-base',
2995
+ lg: 'sm:text-lg',
2996
+ xl: 'sm:text-xl',
2997
+ '2xl': 'sm:text-2xl',
2998
+ };
2999
+ const mdSizeClasses = {
3000
+ xs: 'md:text-xs',
3001
+ sm: 'md:text-sm',
3002
+ base: 'md:text-base',
3003
+ lg: 'md:text-lg',
3004
+ xl: 'md:text-xl',
3005
+ '2xl': 'md:text-2xl',
3006
+ };
3007
+ const lgSizeClasses = {
3008
+ xs: 'lg:text-xs',
3009
+ sm: 'lg:text-sm',
3010
+ base: 'lg:text-base',
3011
+ lg: 'lg:text-lg',
3012
+ xl: 'lg:text-xl',
3013
+ '2xl': 'lg:text-2xl',
3014
+ };
2987
3015
  const weightClasses = {
2988
3016
  normal: 'font-normal',
2989
3017
  medium: 'font-medium',
@@ -3021,6 +3049,9 @@ const Text = React.forwardRef(({ children, as: Component = 'p', size = 'base', w
3021
3049
  // Build class list
3022
3050
  const classes = [
3023
3051
  sizeClasses[size],
3052
+ smSize ? smSizeClasses[smSize] : '',
3053
+ mdSize ? mdSizeClasses[mdSize] : '',
3054
+ lgSize ? lgSizeClasses[lgSize] : '',
3024
3055
  weightClasses[weight],
3025
3056
  colorClasses[color],
3026
3057
  alignClasses[align],
@@ -3127,24 +3158,48 @@ function Alert({ variant = 'info', title, children, onClose, className = '', act
3127
3158
  return (jsxRuntime.jsx("div", { className: `rounded-lg border p-4 ${styles.container} ${className}`, role: "alert", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [jsxRuntime.jsx("div", { className: "flex-shrink-0 mt-0.5", children: styles.icon }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [title && jsxRuntime.jsx("h4", { className: "text-sm font-medium mb-1", children: title }), jsxRuntime.jsx("div", { className: "text-sm", children: children }), actions.length > 0 && (jsxRuntime.jsx("div", { className: "flex gap-2 mt-3", children: actions.map((action, index) => (jsxRuntime.jsx("button", { onClick: action.onClick, className: getButtonStyles(action.variant), children: action.label }, index))) }))] }), onClose && (jsxRuntime.jsx("button", { onClick: onClose, className: "flex-shrink-0 text-current opacity-70 hover:opacity-100 transition-opacity", "aria-label": "Close alert", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) }))] }) }));
3128
3159
  }
3129
3160
 
3130
- const heightPresets = {
3131
- sm: '33vh',
3132
- md: '50vh',
3133
- lg: '75vh',
3134
- full: '90vh',
3135
- };
3136
- function BottomSheet({ isOpen, onClose, children, title, height = 'md', showHandle = true, showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, className = '', }) {
3137
- const titleId = React.useId();
3161
+ /**
3162
+ * BottomSheet - Mobile-friendly modal that slides up from the bottom
3163
+ *
3164
+ * Designed for mobile contexts with touch-friendly interactions:
3165
+ * - Drag handle for swipe-to-dismiss
3166
+ * - Snap points for partial expansion
3167
+ * - Sticky action area at thumb zone
3168
+ *
3169
+ * @example
3170
+ * ```tsx
3171
+ * <BottomSheet open={isOpen} onClose={() => setIsOpen(false)}>
3172
+ * <BottomSheetHeader>
3173
+ * <Text weight="bold">Transaction Details</Text>
3174
+ * </BottomSheetHeader>
3175
+ * <BottomSheetContent>
3176
+ * {content}
3177
+ * </BottomSheetContent>
3178
+ * <BottomSheetActions>
3179
+ * <Button fullWidth>Approve</Button>
3180
+ * </BottomSheetActions>
3181
+ * </BottomSheet>
3182
+ * ```
3183
+ */
3184
+ function BottomSheet({ open, isOpen, onClose, children, title, height = 'auto', maxHeight = '90vh', snapPoints, closeOnOverlayClick = true, closeOnEscape = true, showHandle = true, showCloseButton = true, preventScroll = true, className = '', }) {
3185
+ // Support both 'open' and 'isOpen' props for flexibility
3186
+ const isSheetOpen = open ?? isOpen ?? false;
3187
+ // Height presets for convenience
3188
+ const heightPresets = {
3189
+ sm: '40vh',
3190
+ md: '60vh',
3191
+ lg: '80vh',
3192
+ full: '100vh',
3193
+ };
3194
+ const sheetRef = React.useRef(null);
3138
3195
  const [isDragging, setIsDragging] = React.useState(false);
3139
3196
  const [dragOffset, setDragOffset] = React.useState(0);
3140
- const [currentHeight] = React.useState(typeof height === 'string' && height in heightPresets
3141
- ? heightPresets[height]
3142
- : height);
3143
- const sheetRef = React.useRef(null);
3144
- const startYRef = React.useRef(0);
3145
- // Close on Escape
3197
+ const [currentSnapIndex, setCurrentSnapIndex] = React.useState(snapPoints?.length ? snapPoints.length - 1 : 0);
3198
+ const startY = React.useRef(0);
3199
+ const startOffset = React.useRef(0);
3200
+ // Handle escape key
3146
3201
  React.useEffect(() => {
3147
- if (!isOpen || !closeOnEscape)
3202
+ if (!isSheetOpen || !closeOnEscape)
3148
3203
  return;
3149
3204
  const handleEscape = (e) => {
3150
3205
  if (e.key === 'Escape') {
@@ -3153,77 +3208,132 @@ function BottomSheet({ isOpen, onClose, children, title, height = 'md', showHand
3153
3208
  };
3154
3209
  document.addEventListener('keydown', handleEscape);
3155
3210
  return () => document.removeEventListener('keydown', handleEscape);
3156
- }, [isOpen, closeOnEscape, onClose]);
3157
- // Prevent body scroll when open
3211
+ }, [open, closeOnEscape, onClose]);
3212
+ // Prevent body scroll
3158
3213
  React.useEffect(() => {
3159
- if (isOpen) {
3160
- document.body.style.overflow = 'hidden';
3161
- }
3162
- else {
3163
- document.body.style.overflow = '';
3164
- }
3214
+ if (!isSheetOpen || !preventScroll)
3215
+ return;
3216
+ const originalOverflow = document.body.style.overflow;
3217
+ document.body.style.overflow = 'hidden';
3165
3218
  return () => {
3166
- document.body.style.overflow = '';
3219
+ document.body.style.overflow = originalOverflow;
3167
3220
  };
3168
- }, [isOpen]);
3169
- const handleOverlayClick = (e) => {
3170
- if (closeOnOverlayClick && e.target === e.currentTarget) {
3171
- onClose();
3172
- }
3173
- };
3174
- const handleDragStart = (e) => {
3221
+ }, [open, preventScroll]);
3222
+ // Handle drag start
3223
+ const handleDragStart = React.useCallback((clientY) => {
3175
3224
  setIsDragging(true);
3176
- const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
3177
- startYRef.current = clientY;
3178
- };
3179
- const handleDragMove = (e) => {
3225
+ startY.current = clientY;
3226
+ startOffset.current = dragOffset;
3227
+ }, [dragOffset]);
3228
+ // Handle drag move
3229
+ const handleDragMove = React.useCallback((clientY) => {
3230
+ if (!isDragging)
3231
+ return;
3232
+ const delta = clientY - startY.current;
3233
+ const newOffset = Math.max(0, startOffset.current + delta);
3234
+ setDragOffset(newOffset);
3235
+ }, [isDragging]);
3236
+ // Handle drag end
3237
+ const handleDragEnd = React.useCallback(() => {
3180
3238
  if (!isDragging)
3181
3239
  return;
3182
- const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
3183
- const offset = clientY - startYRef.current;
3184
- // Only allow dragging down
3185
- if (offset > 0) {
3186
- setDragOffset(offset);
3187
- }
3188
- };
3189
- const handleDragEnd = () => {
3190
3240
  setIsDragging(false);
3191
- // Close if dragged down more than 150px
3192
- if (dragOffset > 150) {
3193
- onClose();
3241
+ const threshold = 100; // pixels to trigger close
3242
+ if (dragOffset > threshold) {
3243
+ // If we have snap points, snap to next lower point or close
3244
+ if (snapPoints && currentSnapIndex > 0) {
3245
+ setCurrentSnapIndex(currentSnapIndex - 1);
3246
+ setDragOffset(0);
3247
+ }
3248
+ else {
3249
+ onClose();
3250
+ setDragOffset(0);
3251
+ }
3252
+ }
3253
+ else {
3254
+ // Snap back
3255
+ setDragOffset(0);
3194
3256
  }
3195
- setDragOffset(0);
3257
+ }, [isDragging, dragOffset, snapPoints, currentSnapIndex, onClose]);
3258
+ // Touch event handlers
3259
+ const handleTouchStart = (e) => {
3260
+ handleDragStart(e.touches[0].clientY);
3261
+ };
3262
+ const handleTouchMove = (e) => {
3263
+ handleDragMove(e.touches[0].clientY);
3264
+ };
3265
+ const handleTouchEnd = () => {
3266
+ handleDragEnd();
3267
+ };
3268
+ // Mouse event handlers (for desktop testing)
3269
+ const handleMouseDown = (e) => {
3270
+ handleDragStart(e.clientY);
3196
3271
  };
3197
3272
  React.useEffect(() => {
3198
3273
  if (!isDragging)
3199
3274
  return;
3200
- const handleMove = (e) => handleDragMove(e);
3201
- const handleEnd = () => handleDragEnd();
3202
- document.addEventListener('touchmove', handleMove);
3203
- document.addEventListener('mousemove', handleMove);
3204
- document.addEventListener('touchend', handleEnd);
3205
- document.addEventListener('mouseup', handleEnd);
3275
+ const handleMouseMove = (e) => {
3276
+ handleDragMove(e.clientY);
3277
+ };
3278
+ const handleMouseUp = () => {
3279
+ handleDragEnd();
3280
+ };
3281
+ document.addEventListener('mousemove', handleMouseMove);
3282
+ document.addEventListener('mouseup', handleMouseUp);
3206
3283
  return () => {
3207
- document.removeEventListener('touchmove', handleMove);
3208
- document.removeEventListener('mousemove', handleMove);
3209
- document.removeEventListener('touchend', handleEnd);
3210
- document.removeEventListener('mouseup', handleEnd);
3284
+ document.removeEventListener('mousemove', handleMouseMove);
3285
+ document.removeEventListener('mouseup', handleMouseUp);
3211
3286
  };
3212
- }, [isDragging, dragOffset]);
3213
- if (!isOpen)
3287
+ }, [isDragging, handleDragMove, handleDragEnd]);
3288
+ // Calculate height based on snap points or presets
3289
+ const getSheetHeight = () => {
3290
+ if (snapPoints && snapPoints[currentSnapIndex]) {
3291
+ return snapPoints[currentSnapIndex];
3292
+ }
3293
+ if (typeof height === 'number') {
3294
+ return `${height}px`;
3295
+ }
3296
+ // Check for preset heights
3297
+ if (typeof height === 'string' && heightPresets[height]) {
3298
+ return heightPresets[height];
3299
+ }
3300
+ return height;
3301
+ };
3302
+ if (!isSheetOpen)
3214
3303
  return null;
3215
- return (jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-end", onClick: handleOverlayClick, children: [jsxRuntime.jsx("div", { className: `
3304
+ const sheetContent = (jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50", children: [jsxRuntime.jsx("div", { className: `
3216
3305
  absolute inset-0 bg-black/50 transition-opacity duration-300
3217
- ${isOpen ? 'opacity-100' : 'opacity-0'}
3218
- ` }), jsxRuntime.jsxs("div", { ref: sheetRef, className: `
3219
- relative w-full bg-white rounded-t-2xl shadow-2xl
3306
+ ${isSheetOpen ? 'opacity-100' : 'opacity-0'}
3307
+ `, onClick: closeOnOverlayClick ? onClose : undefined, "aria-hidden": "true" }), jsxRuntime.jsxs("div", { ref: sheetRef, className: `
3308
+ absolute bottom-0 left-0 right-0
3309
+ bg-white rounded-t-2xl shadow-2xl
3220
3310
  transition-transform duration-300 ease-out
3221
- ${isOpen ? 'translate-y-0' : 'translate-y-full'}
3311
+ ${isDragging ? 'transition-none' : ''}
3222
3312
  ${className}
3223
3313
  `, style: {
3224
- height: currentHeight,
3314
+ height: getSheetHeight(),
3315
+ maxHeight,
3225
3316
  transform: `translateY(${dragOffset}px)`,
3226
- }, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined, children: [showHandle && (jsxRuntime.jsx("div", { className: "py-3 cursor-grab active:cursor-grabbing", onTouchStart: handleDragStart, onMouseDown: handleDragStart, children: jsxRuntime.jsx("div", { className: "w-12 h-1.5 bg-ink-300 rounded-full mx-auto" }) })), (title || showCloseButton) && (jsxRuntime.jsxs("div", { className: "px-6 py-4 border-b border-ink-200 flex items-center justify-between", children: [title && (jsxRuntime.jsx("h2", { id: titleId, className: "text-lg font-semibold text-ink-900", children: title })), showCloseButton && (jsxRuntime.jsx("button", { onClick: onClose, className: "text-ink-400 hover:text-ink-600 transition-colors ml-auto", "aria-label": "Close", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) }))] })), jsxRuntime.jsx("div", { className: "overflow-y-auto flex-1 p-6", children: children })] })] }));
3317
+ }, role: "dialog", "aria-modal": "true", children: [showHandle && (jsxRuntime.jsx("div", { className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, children: jsxRuntime.jsx("div", { className: "w-10 h-1 bg-paper-300 rounded-full" }) })), title && (jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-paper-200", children: [jsxRuntime.jsx("h2", { className: "text-lg font-medium text-ink-900", children: title }), showCloseButton && (jsxRuntime.jsx("button", { onClick: onClose, className: "p-1 rounded-full text-ink-500 hover:text-ink-700 hover:bg-paper-100 transition-colors", "aria-label": "Close", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) }))] })), jsxRuntime.jsx("div", { className: "flex flex-col h-full overflow-hidden", children: children })] })] }));
3318
+ return reactDom.createPortal(sheetContent, document.body);
3319
+ }
3320
+ /**
3321
+ * BottomSheetHeader - Header section with title and optional close button
3322
+ */
3323
+ function BottomSheetHeader({ children, className = '' }) {
3324
+ return (jsxRuntime.jsx("div", { className: `flex items-center justify-between px-4 py-3 border-b border-paper-200 ${className}`, children: children }));
3325
+ }
3326
+ /**
3327
+ * BottomSheetContent - Scrollable content area
3328
+ */
3329
+ function BottomSheetContent({ children, className = '' }) {
3330
+ return (jsxRuntime.jsx("div", { className: `flex-1 overflow-y-auto px-4 py-4 ${className}`, children: children }));
3331
+ }
3332
+ /**
3333
+ * BottomSheetActions - Sticky footer for action buttons (thumb zone)
3334
+ */
3335
+ function BottomSheetActions({ children, className = '' }) {
3336
+ return (jsxRuntime.jsx("div", { className: `flex flex-col gap-2 px-4 py-4 border-t border-paper-200 bg-white ${className}`, children: children }));
3227
3337
  }
3228
3338
 
3229
3339
  const sizeClasses$8 = {
@@ -7358,6 +7468,598 @@ function Hide({ children, above, below, only, className = '' }) {
7358
7468
  return (jsxRuntime.jsx("div", { className: `${visibilityClasses} ${className}`, children: children }));
7359
7469
  }
7360
7470
 
7471
+ /**
7472
+ * HorizontalScroll - Horizontally scrollable container with peek indicators
7473
+ *
7474
+ * Designed for mobile carousels of cards with:
7475
+ * - Touch-friendly momentum scrolling
7476
+ * - Peek hint showing more items exist
7477
+ * - Optional snap scrolling
7478
+ * - Navigation arrows for desktop
7479
+ *
7480
+ * @example
7481
+ * ```tsx
7482
+ * <HorizontalScroll gap="md" peekAmount={24} showIndicators>
7483
+ * <Card>Bill 1</Card>
7484
+ * <Card>Bill 2</Card>
7485
+ * <Card>Bill 3</Card>
7486
+ * </HorizontalScroll>
7487
+ * ```
7488
+ */
7489
+ function HorizontalScroll({ children, gap = 'md', peekAmount = 24, showIndicators = false, snapToItem = true, showArrows = 'hover', scrollBehavior = 'smooth', className = '', scrollClassName = '', }) {
7490
+ const scrollRef = React.useRef(null);
7491
+ const [canScrollLeft, setCanScrollLeft] = React.useState(false);
7492
+ const [canScrollRight, setCanScrollRight] = React.useState(false);
7493
+ const [activeIndex, setActiveIndex] = React.useState(0);
7494
+ const [itemCount, setItemCount] = React.useState(0);
7495
+ const [isHovered, setIsHovered] = React.useState(false);
7496
+ // Gap classes
7497
+ const gapClasses = {
7498
+ none: 'gap-0',
7499
+ sm: 'gap-2',
7500
+ md: 'gap-4',
7501
+ lg: 'gap-6',
7502
+ };
7503
+ const gapStyle = typeof gap === 'number' ? { gap: `${gap}px` } : {};
7504
+ const gapClass = typeof gap === 'string' ? gapClasses[gap] : '';
7505
+ // Check scroll position and update state
7506
+ const checkScrollPosition = React.useCallback(() => {
7507
+ const container = scrollRef.current;
7508
+ if (!container)
7509
+ return;
7510
+ const { scrollLeft, scrollWidth, clientWidth } = container;
7511
+ setCanScrollLeft(scrollLeft > 0);
7512
+ setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1);
7513
+ // Calculate active index based on scroll position
7514
+ if (showIndicators && container.children.length > 0) {
7515
+ const children = Array.from(container.children);
7516
+ const containerRect = container.getBoundingClientRect();
7517
+ const containerCenter = containerRect.left + containerRect.width / 2;
7518
+ let closestIndex = 0;
7519
+ let closestDistance = Infinity;
7520
+ children.forEach((child, index) => {
7521
+ const childRect = child.getBoundingClientRect();
7522
+ const childCenter = childRect.left + childRect.width / 2;
7523
+ const distance = Math.abs(childCenter - containerCenter);
7524
+ if (distance < closestDistance) {
7525
+ closestDistance = distance;
7526
+ closestIndex = index;
7527
+ }
7528
+ });
7529
+ setActiveIndex(closestIndex);
7530
+ }
7531
+ }, [showIndicators]);
7532
+ // Initialize and handle resize
7533
+ React.useEffect(() => {
7534
+ const container = scrollRef.current;
7535
+ if (!container)
7536
+ return;
7537
+ setItemCount(React.Children.count(children));
7538
+ checkScrollPosition();
7539
+ const resizeObserver = new ResizeObserver(() => {
7540
+ checkScrollPosition();
7541
+ });
7542
+ resizeObserver.observe(container);
7543
+ return () => {
7544
+ resizeObserver.disconnect();
7545
+ };
7546
+ }, [children, checkScrollPosition]);
7547
+ // Handle scroll event
7548
+ React.useEffect(() => {
7549
+ const container = scrollRef.current;
7550
+ if (!container)
7551
+ return;
7552
+ const handleScroll = () => {
7553
+ checkScrollPosition();
7554
+ };
7555
+ container.addEventListener('scroll', handleScroll, { passive: true });
7556
+ return () => {
7557
+ container.removeEventListener('scroll', handleScroll);
7558
+ };
7559
+ }, [checkScrollPosition]);
7560
+ // Scroll by one item
7561
+ const scrollByItem = (direction) => {
7562
+ const container = scrollRef.current;
7563
+ if (!container)
7564
+ return;
7565
+ const children = Array.from(container.children);
7566
+ if (children.length === 0)
7567
+ return;
7568
+ const firstChild = children[0];
7569
+ const itemWidth = firstChild.offsetWidth;
7570
+ const gapValue = typeof gap === 'number' ? gap :
7571
+ gap === 'sm' ? 8 : gap === 'md' ? 16 : gap === 'lg' ? 24 : 0;
7572
+ const scrollAmount = itemWidth + gapValue;
7573
+ container.scrollBy({
7574
+ left: direction === 'left' ? -scrollAmount : scrollAmount,
7575
+ behavior: scrollBehavior,
7576
+ });
7577
+ };
7578
+ // Scroll to specific index
7579
+ const scrollToIndex = (index) => {
7580
+ const container = scrollRef.current;
7581
+ if (!container)
7582
+ return;
7583
+ const children = Array.from(container.children);
7584
+ if (index < 0 || index >= children.length)
7585
+ return;
7586
+ const child = children[index];
7587
+ child.scrollIntoView({
7588
+ behavior: scrollBehavior,
7589
+ block: 'nearest',
7590
+ inline: 'center',
7591
+ });
7592
+ };
7593
+ const showLeftArrow = showArrows === 'always' || (showArrows === 'hover' && isHovered);
7594
+ const showRightArrow = showArrows === 'always' || (showArrows === 'hover' && isHovered);
7595
+ return (jsxRuntime.jsxs("div", { className: `relative ${className}`, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [showLeftArrow && canScrollLeft && (jsxRuntime.jsx("button", { onClick: () => scrollByItem('left'), className: "\n absolute left-0 top-1/2 -translate-y-1/2 z-10\n w-10 h-10 flex items-center justify-center\n bg-white/90 backdrop-blur-sm rounded-full shadow-lg\n text-ink-600 hover:text-ink-900 hover:bg-white\n transition-all duration-200\n -ml-2\n ", "aria-label": "Scroll left", children: jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" }) })), jsxRuntime.jsx("div", { ref: scrollRef, className: `
7596
+ flex overflow-x-auto scrollbar-hide
7597
+ ${gapClass}
7598
+ ${snapToItem ? 'snap-x snap-mandatory' : ''}
7599
+ ${scrollClassName}
7600
+ `, style: {
7601
+ ...gapStyle,
7602
+ paddingRight: peekAmount > 0 ? `${peekAmount}px` : undefined,
7603
+ scrollPaddingLeft: '0px',
7604
+ scrollPaddingRight: `${peekAmount}px`,
7605
+ }, children: React.Children.map(children, (child, index) => (jsxRuntime.jsx("div", { className: `flex-shrink-0 ${snapToItem ? 'snap-start' : ''}`, children: child }, index))) }), showRightArrow && canScrollRight && (jsxRuntime.jsx("button", { onClick: () => scrollByItem('right'), className: "\n absolute right-0 top-1/2 -translate-y-1/2 z-10\n w-10 h-10 flex items-center justify-center\n bg-white/90 backdrop-blur-sm rounded-full shadow-lg\n text-ink-600 hover:text-ink-900 hover:bg-white\n transition-all duration-200\n -mr-2\n ", "aria-label": "Scroll right", children: jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" }) })), showIndicators && itemCount > 1 && (jsxRuntime.jsx("div", { className: "flex justify-center gap-1.5 mt-3", children: Array.from({ length: itemCount }).map((_, index) => (jsxRuntime.jsx("button", { onClick: () => scrollToIndex(index), className: `
7606
+ w-2 h-2 rounded-full transition-all duration-200
7607
+ ${index === activeIndex
7608
+ ? 'bg-accent-500 w-4'
7609
+ : 'bg-paper-300 hover:bg-paper-400'}
7610
+ `, "aria-label": `Go to item ${index + 1}`, "aria-current": index === activeIndex ? 'true' : 'false' }, index))) }))] }));
7611
+ }
7612
+
7613
+ /**
7614
+ * SwipeableCard - Card component with swipe-to-action functionality
7615
+ *
7616
+ * Designed for mobile approval workflows:
7617
+ * - Swipe right to approve/confirm
7618
+ * - Swipe left to see options/alternatives
7619
+ * - Visual feedback showing action being revealed
7620
+ * - Haptic feedback on mobile devices
7621
+ *
7622
+ * @example
7623
+ * ```tsx
7624
+ * <SwipeableCard
7625
+ * onSwipeRight={() => handleApprove()}
7626
+ * onSwipeLeft={() => handleShowOptions()}
7627
+ * rightAction={{
7628
+ * icon: <Check />,
7629
+ * color: 'success',
7630
+ * label: 'Approve'
7631
+ * }}
7632
+ * leftAction={{
7633
+ * icon: <MoreHorizontal />,
7634
+ * color: 'neutral',
7635
+ * label: 'Options'
7636
+ * }}
7637
+ * >
7638
+ * <TransactionContent />
7639
+ * </SwipeableCard>
7640
+ * ```
7641
+ */
7642
+ function SwipeableCard({ children, onSwipeRight, onSwipeLeft, rightAction = {
7643
+ icon: jsxRuntime.jsx(lucideReact.Check, { className: "h-6 w-6" }),
7644
+ color: 'success',
7645
+ label: 'Approve',
7646
+ }, leftAction = {
7647
+ icon: jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-6 w-6" }),
7648
+ color: 'neutral',
7649
+ label: 'Options',
7650
+ }, swipeThreshold = 100, hapticFeedback = true, disabled = false, onSwipeStart, onSwipeEnd, className = '', }) {
7651
+ const cardRef = React.useRef(null);
7652
+ const [isDragging, setIsDragging] = React.useState(false);
7653
+ const [offsetX, setOffsetX] = React.useState(0);
7654
+ const [isTriggered, setIsTriggered] = React.useState(null);
7655
+ const startX = React.useRef(0);
7656
+ const startY = React.useRef(0);
7657
+ const isHorizontalSwipe = React.useRef(null);
7658
+ // Color classes for action backgrounds
7659
+ const colorClasses = {
7660
+ success: 'bg-success-500',
7661
+ error: 'bg-error-500',
7662
+ warning: 'bg-warning-500',
7663
+ neutral: 'bg-paper-400',
7664
+ primary: 'bg-accent-500',
7665
+ };
7666
+ // Trigger haptic feedback
7667
+ const triggerHaptic = React.useCallback((style = 'medium') => {
7668
+ if (!hapticFeedback)
7669
+ return;
7670
+ // Use Vibration API if available
7671
+ if ('vibrate' in navigator) {
7672
+ const patterns = {
7673
+ light: 10,
7674
+ medium: 25,
7675
+ heavy: [50, 30, 50],
7676
+ };
7677
+ navigator.vibrate(patterns[style]);
7678
+ }
7679
+ }, [hapticFeedback]);
7680
+ // Handle drag start
7681
+ const handleDragStart = React.useCallback((clientX, clientY) => {
7682
+ if (disabled)
7683
+ return;
7684
+ setIsDragging(true);
7685
+ startX.current = clientX;
7686
+ startY.current = clientY;
7687
+ isHorizontalSwipe.current = null;
7688
+ onSwipeStart?.();
7689
+ }, [disabled, onSwipeStart]);
7690
+ // Handle drag move
7691
+ const handleDragMove = React.useCallback((clientX, clientY) => {
7692
+ if (!isDragging || disabled)
7693
+ return;
7694
+ const deltaX = clientX - startX.current;
7695
+ const deltaY = clientY - startY.current;
7696
+ // Determine if this is a horizontal swipe on first significant movement
7697
+ if (isHorizontalSwipe.current === null) {
7698
+ const absDeltaX = Math.abs(deltaX);
7699
+ const absDeltaY = Math.abs(deltaY);
7700
+ if (absDeltaX > 10 || absDeltaY > 10) {
7701
+ isHorizontalSwipe.current = absDeltaX > absDeltaY;
7702
+ }
7703
+ }
7704
+ // Only process horizontal swipes
7705
+ if (isHorizontalSwipe.current !== true)
7706
+ return;
7707
+ // Check if we should allow this direction
7708
+ const canSwipeRight = onSwipeRight !== undefined;
7709
+ const canSwipeLeft = onSwipeLeft !== undefined;
7710
+ let newOffset = deltaX;
7711
+ // Limit swipe direction based on available actions
7712
+ if (!canSwipeRight && deltaX > 0)
7713
+ newOffset = 0;
7714
+ if (!canSwipeLeft && deltaX < 0)
7715
+ newOffset = 0;
7716
+ // Add resistance when exceeding threshold
7717
+ const maxSwipe = swipeThreshold * 1.5;
7718
+ if (Math.abs(newOffset) > swipeThreshold) {
7719
+ const overflow = Math.abs(newOffset) - swipeThreshold;
7720
+ const resistance = overflow * 0.3;
7721
+ newOffset = newOffset > 0
7722
+ ? swipeThreshold + resistance
7723
+ : -(swipeThreshold + resistance);
7724
+ newOffset = Math.max(-maxSwipe, Math.min(maxSwipe, newOffset));
7725
+ }
7726
+ setOffsetX(newOffset);
7727
+ // Check for threshold crossing and trigger haptic
7728
+ const newTriggered = Math.abs(newOffset) >= swipeThreshold
7729
+ ? (newOffset > 0 ? 'right' : 'left')
7730
+ : null;
7731
+ if (newTriggered !== isTriggered) {
7732
+ if (newTriggered) {
7733
+ triggerHaptic('medium');
7734
+ }
7735
+ setIsTriggered(newTriggered);
7736
+ }
7737
+ }, [isDragging, disabled, onSwipeRight, onSwipeLeft, swipeThreshold, isTriggered, triggerHaptic]);
7738
+ // Handle drag end
7739
+ const handleDragEnd = React.useCallback(() => {
7740
+ if (!isDragging)
7741
+ return;
7742
+ setIsDragging(false);
7743
+ onSwipeEnd?.();
7744
+ // Check if action should be triggered
7745
+ if (Math.abs(offsetX) >= swipeThreshold) {
7746
+ if (offsetX > 0 && onSwipeRight) {
7747
+ triggerHaptic('heavy');
7748
+ // Animate card away then call handler
7749
+ setOffsetX(window.innerWidth);
7750
+ setTimeout(() => {
7751
+ onSwipeRight();
7752
+ setOffsetX(0);
7753
+ setIsTriggered(null);
7754
+ }, 200);
7755
+ return;
7756
+ }
7757
+ else if (offsetX < 0 && onSwipeLeft) {
7758
+ triggerHaptic('heavy');
7759
+ setOffsetX(-window.innerWidth);
7760
+ setTimeout(() => {
7761
+ onSwipeLeft();
7762
+ setOffsetX(0);
7763
+ setIsTriggered(null);
7764
+ }, 200);
7765
+ return;
7766
+ }
7767
+ }
7768
+ // Snap back
7769
+ setOffsetX(0);
7770
+ setIsTriggered(null);
7771
+ }, [isDragging, offsetX, swipeThreshold, onSwipeRight, onSwipeLeft, onSwipeEnd, triggerHaptic]);
7772
+ // Touch event handlers
7773
+ const handleTouchStart = (e) => {
7774
+ handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
7775
+ };
7776
+ const handleTouchMove = (e) => {
7777
+ handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
7778
+ // Prevent vertical scroll if horizontal swipe
7779
+ if (isHorizontalSwipe.current === true) {
7780
+ e.preventDefault();
7781
+ }
7782
+ };
7783
+ const handleTouchEnd = () => {
7784
+ handleDragEnd();
7785
+ };
7786
+ // Mouse event handlers (for desktop testing)
7787
+ const handleMouseDown = (e) => {
7788
+ handleDragStart(e.clientX, e.clientY);
7789
+ };
7790
+ React.useEffect(() => {
7791
+ if (!isDragging)
7792
+ return;
7793
+ const handleMouseMove = (e) => {
7794
+ handleDragMove(e.clientX, e.clientY);
7795
+ };
7796
+ const handleMouseUp = () => {
7797
+ handleDragEnd();
7798
+ };
7799
+ document.addEventListener('mousemove', handleMouseMove);
7800
+ document.addEventListener('mouseup', handleMouseUp);
7801
+ return () => {
7802
+ document.removeEventListener('mousemove', handleMouseMove);
7803
+ document.removeEventListener('mouseup', handleMouseUp);
7804
+ };
7805
+ }, [isDragging, handleDragMove, handleDragEnd]);
7806
+ // Calculate action opacity based on swipe distance
7807
+ const rightActionOpacity = offsetX > 0 ? Math.min(1, offsetX / swipeThreshold) : 0;
7808
+ const leftActionOpacity = offsetX < 0 ? Math.min(1, Math.abs(offsetX) / swipeThreshold) : 0;
7809
+ return (jsxRuntime.jsxs("div", { className: `relative overflow-hidden rounded-lg ${className}`, children: [onSwipeRight && (jsxRuntime.jsx("div", { className: `
7810
+ absolute inset-y-0 left-0 flex items-center justify-start pl-6
7811
+ ${colorClasses[rightAction.color]}
7812
+ transition-opacity duration-100
7813
+ `, style: {
7814
+ opacity: rightActionOpacity,
7815
+ width: Math.abs(offsetX) + 20,
7816
+ }, "aria-hidden": "true", children: jsxRuntime.jsx("div", { className: `
7817
+ text-white transform transition-transform duration-200
7818
+ ${isTriggered === 'right' ? 'scale-125' : 'scale-100'}
7819
+ `, children: rightAction.icon }) })), onSwipeLeft && (jsxRuntime.jsx("div", { className: `
7820
+ absolute inset-y-0 right-0 flex items-center justify-end pr-6
7821
+ ${colorClasses[leftAction.color]}
7822
+ transition-opacity duration-100
7823
+ `, style: {
7824
+ opacity: leftActionOpacity,
7825
+ width: Math.abs(offsetX) + 20,
7826
+ }, "aria-hidden": "true", children: jsxRuntime.jsx("div", { className: `
7827
+ text-white transform transition-transform duration-200
7828
+ ${isTriggered === 'left' ? 'scale-125' : 'scale-100'}
7829
+ `, children: leftAction.icon }) })), jsxRuntime.jsx("div", { ref: cardRef, className: `
7830
+ relative bg-white
7831
+ ${isDragging ? '' : 'transition-transform duration-200 ease-out'}
7832
+ ${disabled ? 'opacity-50 pointer-events-none' : ''}
7833
+ `, style: {
7834
+ transform: `translateX(${offsetX}px)`,
7835
+ }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, role: "button", "aria-label": `Swipeable card. ${onSwipeRight ? `Swipe right to ${rightAction.label}.` : ''} ${onSwipeLeft ? `Swipe left to ${leftAction.label}.` : ''}`, tabIndex: disabled ? -1 : 0, children: children })] }));
7836
+ }
7837
+
7838
+ /**
7839
+ * NotificationBanner - Dismissible banner for important alerts
7840
+ *
7841
+ * Displays at top of screen for alerts that need attention but aren't blocking:
7842
+ * - Money Found alerts
7843
+ * - System messages
7844
+ * - Promotional info
7845
+ *
7846
+ * @example
7847
+ * ```tsx
7848
+ * <NotificationBanner
7849
+ * variant="warning"
7850
+ * icon={<DollarSign />}
7851
+ * title="Found $33.98 in potential savings"
7852
+ * description="Tap to review"
7853
+ * action={{
7854
+ * label: "Review",
7855
+ * onClick: handleReview
7856
+ * }}
7857
+ * onDismiss={() => setShowBanner(false)}
7858
+ * />
7859
+ * ```
7860
+ */
7861
+ function NotificationBanner({ variant = 'info', icon, title, description, action, onDismiss, dismissible = true, sticky = false, className = '', }) {
7862
+ const bannerRef = React.useRef(null);
7863
+ const [isDragging, setIsDragging] = React.useState(false);
7864
+ const [offsetX, setOffsetX] = React.useState(0);
7865
+ const [isDismissed, setIsDismissed] = React.useState(false);
7866
+ const startX = React.useRef(0);
7867
+ // Default icons based on variant
7868
+ const defaultIcons = {
7869
+ info: jsxRuntime.jsx(lucideReact.Info, { className: "h-5 w-5" }),
7870
+ success: jsxRuntime.jsx(lucideReact.CheckCircle, { className: "h-5 w-5" }),
7871
+ warning: jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5" }),
7872
+ error: jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-5 w-5" }),
7873
+ };
7874
+ // Color classes
7875
+ const variantClasses = {
7876
+ info: 'bg-gradient-to-r from-primary-50 to-primary-100 border-primary-200 text-primary-900',
7877
+ success: 'bg-gradient-to-r from-success-50 to-success-100 border-success-200 text-success-900',
7878
+ warning: 'bg-gradient-to-r from-warning-50 to-warning-100 border-warning-200 text-warning-900',
7879
+ error: 'bg-gradient-to-r from-error-50 to-error-100 border-error-200 text-error-900',
7880
+ };
7881
+ const iconColorClasses = {
7882
+ info: 'text-primary-600',
7883
+ success: 'text-success-600',
7884
+ warning: 'text-warning-600',
7885
+ error: 'text-error-600',
7886
+ };
7887
+ const buttonClasses = {
7888
+ info: 'bg-primary-600 hover:bg-primary-700 text-white',
7889
+ success: 'bg-success-600 hover:bg-success-700 text-white',
7890
+ warning: 'bg-warning-600 hover:bg-warning-700 text-white',
7891
+ error: 'bg-error-600 hover:bg-error-700 text-white',
7892
+ };
7893
+ // Handle swipe dismiss
7894
+ const handleDragStart = React.useCallback((clientX) => {
7895
+ if (!dismissible)
7896
+ return;
7897
+ setIsDragging(true);
7898
+ startX.current = clientX;
7899
+ }, [dismissible]);
7900
+ const handleDragMove = React.useCallback((clientX) => {
7901
+ if (!isDragging)
7902
+ return;
7903
+ const delta = clientX - startX.current;
7904
+ setOffsetX(delta);
7905
+ }, [isDragging]);
7906
+ const handleDragEnd = React.useCallback(() => {
7907
+ if (!isDragging)
7908
+ return;
7909
+ setIsDragging(false);
7910
+ const threshold = 100;
7911
+ if (Math.abs(offsetX) > threshold) {
7912
+ // Animate out
7913
+ setOffsetX(offsetX > 0 ? window.innerWidth : -window.innerWidth);
7914
+ setIsDismissed(true);
7915
+ setTimeout(() => {
7916
+ onDismiss?.();
7917
+ }, 200);
7918
+ }
7919
+ else {
7920
+ // Snap back
7921
+ setOffsetX(0);
7922
+ }
7923
+ }, [isDragging, offsetX, onDismiss]);
7924
+ // Touch handlers
7925
+ const handleTouchStart = (e) => {
7926
+ handleDragStart(e.touches[0].clientX);
7927
+ };
7928
+ const handleTouchMove = (e) => {
7929
+ handleDragMove(e.touches[0].clientX);
7930
+ };
7931
+ const handleTouchEnd = () => {
7932
+ handleDragEnd();
7933
+ };
7934
+ // Mouse handlers for desktop testing
7935
+ const handleMouseDown = (e) => {
7936
+ if (dismissible) {
7937
+ handleDragStart(e.clientX);
7938
+ }
7939
+ };
7940
+ React.useEffect(() => {
7941
+ if (!isDragging)
7942
+ return;
7943
+ const handleMouseMove = (e) => {
7944
+ handleDragMove(e.clientX);
7945
+ };
7946
+ const handleMouseUp = () => {
7947
+ handleDragEnd();
7948
+ };
7949
+ document.addEventListener('mousemove', handleMouseMove);
7950
+ document.addEventListener('mouseup', handleMouseUp);
7951
+ return () => {
7952
+ document.removeEventListener('mousemove', handleMouseMove);
7953
+ document.removeEventListener('mouseup', handleMouseUp);
7954
+ };
7955
+ }, [isDragging, handleDragMove, handleDragEnd]);
7956
+ if (isDismissed)
7957
+ return null;
7958
+ return (jsxRuntime.jsx("div", { ref: bannerRef, className: `
7959
+ w-full border-b
7960
+ ${variantClasses[variant]}
7961
+ ${sticky ? 'sticky top-0 z-40' : ''}
7962
+ ${isDragging ? '' : 'transition-transform duration-200 ease-out'}
7963
+ ${className}
7964
+ `, style: {
7965
+ transform: `translateX(${offsetX}px)`,
7966
+ opacity: Math.max(0, 1 - Math.abs(offsetX) / 200),
7967
+ }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, role: "alert", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [jsxRuntime.jsx("div", { className: `flex-shrink-0 ${iconColorClasses[variant]}`, children: icon || defaultIcons[variant] }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: title }), description && (jsxRuntime.jsx("p", { className: "text-xs opacity-80 truncate", children: description }))] }), action && (jsxRuntime.jsx("button", { onClick: action.onClick, className: `
7968
+ flex-shrink-0 px-3 py-1.5 text-xs font-medium rounded-md
7969
+ transition-colors duration-200
7970
+ ${buttonClasses[variant]}
7971
+ `, children: action.label })), onDismiss && (jsxRuntime.jsx("button", { onClick: onDismiss, className: "flex-shrink-0 p-1 rounded-full hover:bg-black/10 transition-colors duration-200", "aria-label": "Dismiss notification", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) }))] }) }));
7972
+ }
7973
+
7974
+ /**
7975
+ * CompactStat - Single stat display optimized for mobile
7976
+ *
7977
+ * Designed for dashboard stats in 2-column mobile layouts:
7978
+ * - Compact presentation with value, label, and optional trend
7979
+ * - Responsive sizing
7980
+ * - Trend indicators with color coding
7981
+ *
7982
+ * @example
7983
+ * ```tsx
7984
+ * <Grid columns={2} gap="sm">
7985
+ * <CompactStat
7986
+ * value="$62,329"
7987
+ * label="Net Worth"
7988
+ * trend={{
7989
+ * direction: 'up',
7990
+ * value: '+$1,247',
7991
+ * color: 'success'
7992
+ * }}
7993
+ * />
7994
+ * <CompactStat
7995
+ * value="$4,521"
7996
+ * label="Monthly Income"
7997
+ * />
7998
+ * </Grid>
7999
+ * ```
8000
+ */
8001
+ function CompactStat({ value, label, trend, size = 'md', align = 'left', className = '', }) {
8002
+ // Size classes
8003
+ const sizeClasses = {
8004
+ sm: {
8005
+ value: 'text-lg font-semibold',
8006
+ label: 'text-xs',
8007
+ trend: 'text-xs',
8008
+ icon: 'h-3 w-3',
8009
+ },
8010
+ md: {
8011
+ value: 'text-xl font-semibold',
8012
+ label: 'text-sm',
8013
+ trend: 'text-xs',
8014
+ icon: 'h-3.5 w-3.5',
8015
+ },
8016
+ lg: {
8017
+ value: 'text-2xl font-bold',
8018
+ label: 'text-sm',
8019
+ trend: 'text-sm',
8020
+ icon: 'h-4 w-4',
8021
+ },
8022
+ };
8023
+ // Alignment classes
8024
+ const alignClasses = {
8025
+ left: 'text-left',
8026
+ center: 'text-center',
8027
+ right: 'text-right',
8028
+ };
8029
+ // Trend color classes
8030
+ const getTrendColor = (trend) => {
8031
+ if (trend.color) {
8032
+ const colorMap = {
8033
+ success: 'text-success-600',
8034
+ error: 'text-error-600',
8035
+ warning: 'text-warning-600',
8036
+ neutral: 'text-ink-500',
8037
+ };
8038
+ return colorMap[trend.color];
8039
+ }
8040
+ // Default colors based on direction
8041
+ const directionColors = {
8042
+ up: 'text-success-600',
8043
+ down: 'text-error-600',
8044
+ neutral: 'text-ink-500',
8045
+ };
8046
+ return directionColors[trend.direction];
8047
+ };
8048
+ // Trend icons
8049
+ const TrendIcon = trend ? {
8050
+ up: lucideReact.TrendingUp,
8051
+ down: lucideReact.TrendingDown,
8052
+ neutral: lucideReact.Minus,
8053
+ }[trend.direction] : null;
8054
+ const sizes = sizeClasses[size];
8055
+ return (jsxRuntime.jsxs("div", { className: `${alignClasses[align]} ${className}`, children: [jsxRuntime.jsx("div", { className: `${sizes.value} text-ink-900 tracking-tight`, children: value }), jsxRuntime.jsx("div", { className: `${sizes.label} text-ink-500 mt-0.5`, children: label }), trend && (jsxRuntime.jsxs("div", { className: `
8056
+ flex items-center gap-1 mt-1
8057
+ ${align === 'center' ? 'justify-center' : ''}
8058
+ ${align === 'right' ? 'justify-end' : ''}
8059
+ ${sizes.trend} ${getTrendColor(trend)}
8060
+ `, children: [TrendIcon && jsxRuntime.jsx(TrendIcon, { className: sizes.icon }), jsxRuntime.jsx("span", { children: trend.value })] }))] }));
8061
+ }
8062
+
7361
8063
  /**
7362
8064
  * Hook to detect breadcrumb navigation and trigger callbacks.
7363
8065
  * Use this in host components to reset state when a breadcrumb is clicked.
@@ -7641,7 +8343,7 @@ function StepIndicator({ steps, currentStep, variant = 'horizontal', onStepClick
7641
8343
  }) }) }));
7642
8344
  }
7643
8345
 
7644
- function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, }) {
8346
+ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, pill = false, }) {
7645
8347
  const variantStyles = {
7646
8348
  success: 'bg-success-50 text-success-700 border-success-200',
7647
8349
  warning: 'bg-warning-50 text-warning-700 border-warning-200',
@@ -7661,6 +8363,12 @@ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, cla
7661
8363
  md: 'px-3 py-1 text-xs gap-1.5',
7662
8364
  lg: 'px-3 py-1.5 text-sm gap-2',
7663
8365
  };
8366
+ // Pill variant has tighter horizontal padding and fully rounded ends
8367
+ const pillSizeStyles = {
8368
+ sm: 'px-1.5 py-0.5 text-xs gap-1',
8369
+ md: 'px-2 py-0.5 text-xs gap-1',
8370
+ lg: 'px-2.5 py-1 text-sm gap-1.5',
8371
+ };
7664
8372
  const dotSizeStyles = {
7665
8373
  sm: 'h-1.5 w-1.5',
7666
8374
  md: 'h-2 w-2',
@@ -7682,9 +8390,10 @@ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, cla
7682
8390
  }
7683
8391
  // Regular badge
7684
8392
  return (jsxRuntime.jsxs("span", { className: `
7685
- inline-flex items-center rounded-full border font-medium
8393
+ inline-flex items-center border font-medium
8394
+ ${pill ? 'rounded-full' : 'rounded-full'}
7686
8395
  ${variantStyles[variant]}
7687
- ${sizeStyles[size]}
8396
+ ${pill ? pillSizeStyles[size] : sizeStyles[size]}
7688
8397
  ${className}
7689
8398
  `, children: [icon && jsxRuntime.jsx("span", { className: iconSize[size], children: icon }), jsxRuntime.jsx("span", { children: children }), onRemove && (jsxRuntime.jsx("button", { onClick: onRemove, className: "ml-1 hover:opacity-70 transition-opacity", "aria-label": "Remove badge", children: jsxRuntime.jsx(lucideReact.X, { className: iconSize[size] }) }))] }));
7690
8399
  }
@@ -7792,8 +8501,10 @@ function Progress({ value, variant = 'linear', size = 'md', color = 'primary', s
7792
8501
  warning: 'bg-warning-100',
7793
8502
  error: 'bg-error-100',
7794
8503
  };
8504
+ // Normalize 'ring' to 'circular'
8505
+ const normalizedVariant = variant === 'ring' ? 'circular' : variant;
7795
8506
  // Linear progress
7796
- if (variant === 'linear') {
8507
+ if (normalizedVariant === 'linear') {
7797
8508
  const heightClasses = {
7798
8509
  sm: 'h-1',
7799
8510
  md: 'h-2',
@@ -8725,94 +9436,112 @@ function useFABScroll(threshold = 10) {
8725
9436
  }
8726
9437
 
8727
9438
  /**
8728
- * PullToRefresh - Mobile pull-to-refresh gesture handler
9439
+ * PullToRefresh - Pull-down refresh indicator and handler for mobile lists
8729
9440
  *
8730
- * Wraps content and provides native-feeling pull-to-refresh functionality.
8731
- * Only activates when scrolled to top of content.
8732
- *
8733
- * @example Basic usage
8734
- * ```tsx
8735
- * <PullToRefresh onRefresh={async () => {
8736
- * await fetchLatestData();
8737
- * }}>
8738
- * <div className="min-h-screen">
8739
- * {content}
8740
- * </div>
8741
- * </PullToRefresh>
8742
- * ```
9441
+ * Wraps content to enable pull-to-refresh behavior on mobile:
9442
+ * - Pull down to trigger refresh
9443
+ * - Visual feedback showing progress
9444
+ * - Custom content for each state
8743
9445
  *
8744
- * @example With custom threshold
9446
+ * @example
8745
9447
  * ```tsx
8746
- * <PullToRefresh
8747
- * onRefresh={handleRefresh}
8748
- * pullThreshold={100}
8749
- * maxPull={150}
8750
- * >
8751
- * {content}
9448
+ * <PullToRefresh onRefresh={async () => { await syncData(); }}>
9449
+ * <TransactionList transactions={transactions} />
8752
9450
  * </PullToRefresh>
8753
9451
  * ```
8754
9452
  */
8755
- function PullToRefresh({ children, onRefresh, disabled = false, pullThreshold = 80, maxPull = 120, loadingIndicator, pullIndicator, className = '', }) {
9453
+ function PullToRefresh({ children, onRefresh, threshold = 80, disabled = false, pullingContent, releaseContent, refreshingContent, completeContent, className = '', }) {
9454
+ const containerRef = React.useRef(null);
8756
9455
  const [state, setState] = React.useState('idle');
8757
9456
  const [pullDistance, setPullDistance] = React.useState(0);
8758
- const containerRef = React.useRef(null);
8759
9457
  const startY = React.useRef(0);
8760
9458
  const currentY = React.useRef(0);
8761
- // Check if at top of scroll container
9459
+ const isDragging = React.useRef(false);
9460
+ // Check if content is at top (can pull to refresh)
8762
9461
  const isAtTop = React.useCallback(() => {
8763
9462
  const container = containerRef.current;
8764
9463
  if (!container)
8765
9464
  return false;
8766
- return container.scrollTop <= 0;
9465
+ // Check if the scrollable content is at the top
9466
+ const scrollableParent = container.querySelector('[data-ptr-scrollable]') || container;
9467
+ return scrollableParent.scrollTop <= 0;
8767
9468
  }, []);
8768
- // Handle touch start
9469
+ // Handle pull start
8769
9470
  const handleTouchStart = React.useCallback((e) => {
8770
- if (disabled || state === 'refreshing' || !isAtTop())
9471
+ if (disabled || state === 'refreshing')
9472
+ return;
9473
+ if (!isAtTop())
8771
9474
  return;
9475
+ isDragging.current = true;
8772
9476
  startY.current = e.touches[0].clientY;
8773
- currentY.current = startY.current;
9477
+ currentY.current = e.touches[0].clientY;
8774
9478
  }, [disabled, state, isAtTop]);
8775
- // Handle touch move
9479
+ // Handle pull move
8776
9480
  const handleTouchMove = React.useCallback((e) => {
8777
- if (disabled || state === 'refreshing')
8778
- return;
8779
- if (startY.current === 0)
9481
+ if (!isDragging.current || disabled || state === 'refreshing')
8780
9482
  return;
8781
9483
  currentY.current = e.touches[0].clientY;
8782
- const diff = currentY.current - startY.current;
8783
- // Only allow pulling down when at top
8784
- if (diff > 0 && isAtTop()) {
8785
- // Apply resistance - pull slows down as distance increases
8786
- const resistance = 0.5;
8787
- const adjustedPull = Math.min(diff * resistance, maxPull);
8788
- setPullDistance(adjustedPull);
8789
- setState(adjustedPull >= pullThreshold ? 'ready' : 'pulling');
8790
- // Prevent default scroll when pulling
8791
- if (adjustedPull > 0) {
8792
- e.preventDefault();
8793
- }
9484
+ const delta = currentY.current - startY.current;
9485
+ // Only activate pull-to-refresh when pulling down
9486
+ if (delta < 0) {
9487
+ isDragging.current = false;
9488
+ setPullDistance(0);
9489
+ setState('idle');
9490
+ return;
9491
+ }
9492
+ // Check if we're at the top before allowing pull
9493
+ if (!isAtTop()) {
9494
+ isDragging.current = false;
9495
+ return;
9496
+ }
9497
+ // Apply resistance to pull
9498
+ const resistance = 0.5;
9499
+ const resistedDelta = delta * resistance;
9500
+ const maxPull = threshold * 2;
9501
+ const clampedDelta = Math.min(resistedDelta, maxPull);
9502
+ setPullDistance(clampedDelta);
9503
+ // Update state based on pull distance
9504
+ if (clampedDelta >= threshold) {
9505
+ setState('ready');
9506
+ }
9507
+ else if (clampedDelta > 0) {
9508
+ setState('pulling');
9509
+ }
9510
+ // Prevent default scroll when pulling
9511
+ if (delta > 0 && isAtTop()) {
9512
+ e.preventDefault();
8794
9513
  }
8795
- }, [disabled, state, isAtTop, maxPull, pullThreshold]);
8796
- // Handle touch end
9514
+ }, [disabled, state, threshold, isAtTop]);
9515
+ // Handle pull end
8797
9516
  const handleTouchEnd = React.useCallback(async () => {
8798
- if (disabled || state === 'refreshing')
9517
+ if (!isDragging.current)
8799
9518
  return;
8800
- if (state === 'ready') {
9519
+ isDragging.current = false;
9520
+ if (state === 'ready' && pullDistance >= threshold) {
8801
9521
  setState('refreshing');
8802
- setPullDistance(pullThreshold); // Hold at threshold while refreshing
9522
+ setPullDistance(threshold * 0.6); // Settle at a smaller height while refreshing
8803
9523
  try {
8804
9524
  await onRefresh();
9525
+ setState('complete');
9526
+ // Show complete state briefly
9527
+ setTimeout(() => {
9528
+ setState('idle');
9529
+ setPullDistance(0);
9530
+ }, 500);
8805
9531
  }
8806
9532
  catch (error) {
8807
9533
  console.error('Refresh failed:', error);
9534
+ setState('idle');
9535
+ setPullDistance(0);
8808
9536
  }
9537
+ }
9538
+ else {
9539
+ // Snap back
8809
9540
  setState('idle');
9541
+ setPullDistance(0);
8810
9542
  }
8811
- setPullDistance(0);
8812
- startY.current = 0;
8813
- currentY.current = 0;
8814
- }, [disabled, state, pullThreshold, onRefresh]);
8815
- // Attach touch listeners
9543
+ }, [state, pullDistance, threshold, onRefresh]);
9544
+ // Attach touch event listeners
8816
9545
  React.useEffect(() => {
8817
9546
  const container = containerRef.current;
8818
9547
  if (!container)
@@ -8826,99 +9555,41 @@ function PullToRefresh({ children, onRefresh, disabled = false, pullThreshold =
8826
9555
  container.removeEventListener('touchend', handleTouchEnd);
8827
9556
  };
8828
9557
  }, [handleTouchStart, handleTouchMove, handleTouchEnd]);
8829
- // Calculate indicator opacity and rotation
8830
- const progress = Math.min(pullDistance / pullThreshold, 1);
8831
- const rotation = progress * 180;
8832
- // Default loading indicator
8833
- const defaultLoadingIndicator = (jsxRuntime.jsx(lucideReact.Loader2, { className: "h-6 w-6 text-accent-600 animate-spin" }));
8834
- // Default pull indicator
8835
- const defaultPullIndicator = (jsxRuntime.jsx("div", { className: `
8836
- transition-transform duration-200
8837
- ${state === 'ready' ? 'text-accent-600' : 'text-ink-400'}
8838
- `, style: { transform: `rotate(${rotation}deg)` }, children: jsxRuntime.jsx(lucideReact.ArrowDown, { className: "h-6 w-6" }) }));
8839
- return (jsxRuntime.jsxs("div", { ref: containerRef, className: `relative overflow-auto ${className}`, style: { touchAction: pullDistance > 0 ? 'none' : 'auto' }, children: [jsxRuntime.jsx("div", { className: `
8840
- absolute left-0 right-0 flex items-center justify-center
8841
- transition-all duration-200 overflow-hidden
8842
- ${state === 'idle' && pullDistance === 0 ? 'opacity-0' : 'opacity-100'}
9558
+ // Calculate progress percentage
9559
+ const progress = Math.min(1, pullDistance / threshold);
9560
+ // Default content for each state
9561
+ const defaultPullingContent = (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsxRuntime.jsx(lucideReact.ArrowDown, { className: "h-5 w-5 text-ink-400 transition-transform duration-200", style: { transform: `rotate(${progress * 180}deg)` } }), jsxRuntime.jsx("span", { className: "text-xs text-ink-500", children: "Pull to refresh" })] }));
9562
+ const defaultReleaseContent = (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsxRuntime.jsx(lucideReact.ArrowDown, { className: "h-5 w-5 text-accent-500 rotate-180" }), jsxRuntime.jsx("span", { className: "text-xs text-accent-600 font-medium", children: "Release to refresh" })] }));
9563
+ const defaultRefreshingContent = (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 text-accent-500 animate-spin" }), jsxRuntime.jsx("span", { className: "text-xs text-ink-500", children: "Refreshing..." })] }));
9564
+ const defaultCompleteContent = (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsxRuntime.jsx(lucideReact.Check, { className: "h-5 w-5 text-success-500" }), jsxRuntime.jsx("span", { className: "text-xs text-success-600", children: "Done!" })] }));
9565
+ // Get content based on current state
9566
+ const getIndicatorContent = () => {
9567
+ switch (state) {
9568
+ case 'pulling':
9569
+ return pullingContent || defaultPullingContent;
9570
+ case 'ready':
9571
+ return releaseContent || defaultReleaseContent;
9572
+ case 'refreshing':
9573
+ return refreshingContent || defaultRefreshingContent;
9574
+ case 'complete':
9575
+ return completeContent || defaultCompleteContent;
9576
+ default:
9577
+ return null;
9578
+ }
9579
+ };
9580
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: `relative overflow-hidden ${className}`, children: [jsxRuntime.jsx("div", { className: `
9581
+ absolute top-0 left-0 right-0
9582
+ flex items-center justify-center
9583
+ bg-paper-50
9584
+ transition-all duration-200 ease-out
9585
+ ${state === 'idle' ? 'opacity-0' : 'opacity-100'}
8843
9586
  `, style: {
8844
- height: `${pullDistance}px`,
8845
- top: 0,
8846
- zIndex: 10,
8847
- }, children: jsxRuntime.jsx("div", { className: `
8848
- w-10 h-10 rounded-full bg-white shadow-md
8849
- flex items-center justify-center
8850
- transition-transform duration-200
8851
- ${state === 'refreshing' ? 'scale-100' : progress < 0.3 ? 'scale-75' : 'scale-100'}
8852
- `, children: state === 'refreshing'
8853
- ? (loadingIndicator || defaultLoadingIndicator)
8854
- : (pullIndicator || defaultPullIndicator) }) }), jsxRuntime.jsx("div", { className: "transition-transform duration-200", style: {
9587
+ height: pullDistance,
9588
+ transform: state === 'idle' ? 'translateY(-100%)' : 'translateY(0)',
9589
+ }, children: getIndicatorContent() }), jsxRuntime.jsx("div", { className: "transition-transform duration-200 ease-out", style: {
8855
9590
  transform: `translateY(${pullDistance}px)`,
8856
9591
  }, children: children })] }));
8857
9592
  }
8858
- /**
8859
- * usePullToRefresh - Hook for custom pull-to-refresh implementations
8860
- *
8861
- * @example
8862
- * ```tsx
8863
- * const { pullDistance, isRefreshing, bind } = usePullToRefresh({
8864
- * onRefresh: async () => {
8865
- * await fetchData();
8866
- * }
8867
- * });
8868
- *
8869
- * return (
8870
- * <div {...bind}>
8871
- * {isRefreshing && <Spinner />}
8872
- * {content}
8873
- * </div>
8874
- * );
8875
- * ```
8876
- */
8877
- function usePullToRefresh({ onRefresh, pullThreshold = 80, maxPull = 120, disabled = false, }) {
8878
- const [pullDistance, setPullDistance] = React.useState(0);
8879
- const [isRefreshing, setIsRefreshing] = React.useState(false);
8880
- const startY = React.useRef(0);
8881
- const handleTouchStart = React.useCallback((e) => {
8882
- if (disabled || isRefreshing)
8883
- return;
8884
- startY.current = e.touches[0].clientY;
8885
- }, [disabled, isRefreshing]);
8886
- const handleTouchMove = React.useCallback((e) => {
8887
- if (disabled || isRefreshing || startY.current === 0)
8888
- return;
8889
- const diff = e.touches[0].clientY - startY.current;
8890
- if (diff > 0) {
8891
- const adjustedPull = Math.min(diff * 0.5, maxPull);
8892
- setPullDistance(adjustedPull);
8893
- }
8894
- }, [disabled, isRefreshing, maxPull]);
8895
- const handleTouchEnd = React.useCallback(async () => {
8896
- if (disabled || isRefreshing)
8897
- return;
8898
- if (pullDistance >= pullThreshold) {
8899
- setIsRefreshing(true);
8900
- try {
8901
- await onRefresh();
8902
- }
8903
- finally {
8904
- setIsRefreshing(false);
8905
- }
8906
- }
8907
- setPullDistance(0);
8908
- startY.current = 0;
8909
- }, [disabled, isRefreshing, pullDistance, pullThreshold, onRefresh]);
8910
- return {
8911
- pullDistance,
8912
- isRefreshing,
8913
- isReady: pullDistance >= pullThreshold,
8914
- progress: Math.min(pullDistance / pullThreshold, 1),
8915
- bind: {
8916
- onTouchStart: handleTouchStart,
8917
- onTouchMove: handleTouchMove,
8918
- onTouchEnd: handleTouchEnd,
8919
- },
8920
- };
8921
- }
8922
9593
 
8923
9594
  function Logo({ size = 'md', showText = true, text = 'Commora', className = '', }) {
8924
9595
  const sizes = {
@@ -10421,44 +11092,52 @@ function getAugmentedNamespace(n) {
10421
11092
  * (A1, A1:C5, ...)
10422
11093
  */
10423
11094
 
10424
- let Collection$3 = class Collection {
11095
+ var collection;
11096
+ var hasRequiredCollection;
11097
+
11098
+ function requireCollection () {
11099
+ if (hasRequiredCollection) return collection;
11100
+ hasRequiredCollection = 1;
11101
+ class Collection {
10425
11102
 
10426
- constructor(data, refs) {
10427
- if (data == null && refs == null) {
10428
- this._data = [];
10429
- this._refs = [];
10430
- } else {
10431
- if (data.length !== refs.length)
10432
- throw Error('Collection: data length should match references length.');
10433
- this._data = data;
10434
- this._refs = refs;
10435
- }
10436
- }
11103
+ constructor(data, refs) {
11104
+ if (data == null && refs == null) {
11105
+ this._data = [];
11106
+ this._refs = [];
11107
+ } else {
11108
+ if (data.length !== refs.length)
11109
+ throw Error('Collection: data length should match references length.');
11110
+ this._data = data;
11111
+ this._refs = refs;
11112
+ }
11113
+ }
10437
11114
 
10438
- get data() {
10439
- return this._data;
10440
- }
11115
+ get data() {
11116
+ return this._data;
11117
+ }
10441
11118
 
10442
- get refs() {
10443
- return this._refs;
10444
- }
11119
+ get refs() {
11120
+ return this._refs;
11121
+ }
10445
11122
 
10446
- get length() {
10447
- return this._data.length;
10448
- }
11123
+ get length() {
11124
+ return this._data.length;
11125
+ }
10449
11126
 
10450
- /**
10451
- * Add data and references to this collection.
10452
- * @param {{}} obj - data
10453
- * @param {{}} ref - reference
10454
- */
10455
- add(obj, ref) {
10456
- this._data.push(obj);
10457
- this._refs.push(ref);
10458
- }
10459
- };
11127
+ /**
11128
+ * Add data and references to this collection.
11129
+ * @param {{}} obj - data
11130
+ * @param {{}} ref - reference
11131
+ */
11132
+ add(obj, ref) {
11133
+ this._data.push(obj);
11134
+ this._refs.push(ref);
11135
+ }
11136
+ }
10460
11137
 
10461
- var collection = Collection$3;
11138
+ collection = Collection;
11139
+ return collection;
11140
+ }
10462
11141
 
10463
11142
  var helpers;
10464
11143
  var hasRequiredHelpers;
@@ -10467,7 +11146,7 @@ function requireHelpers () {
10467
11146
  if (hasRequiredHelpers) return helpers;
10468
11147
  hasRequiredHelpers = 1;
10469
11148
  const FormulaError = requireError();
10470
- const Collection = collection;
11149
+ const Collection = requireCollection();
10471
11150
 
10472
11151
  const Types = {
10473
11152
  NUMBER: 0,
@@ -20121,7 +20800,7 @@ var engineering = EngineeringFunctions;
20121
20800
 
20122
20801
  const FormulaError$b = requireError();
20123
20802
  const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
20124
- const Collection$2 = collection;
20803
+ const Collection$2 = requireCollection();
20125
20804
  const H$5 = FormulaHelpers$8;
20126
20805
 
20127
20806
  const ReferenceFunctions$1 = {
@@ -31749,7 +32428,7 @@ var parsing = {
31749
32428
  const FormulaError$4 = requireError();
31750
32429
  const {Address: Address$1} = requireHelpers();
31751
32430
  const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
31752
- const Collection$1 = collection;
32431
+ const Collection$1 = requireCollection();
31753
32432
  const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
31754
32433
  const {NotAllInputParsedException} = require$$4;
31755
32434
 
@@ -32511,7 +33190,7 @@ var hooks$1 = {
32511
33190
  const FormulaError$2 = requireError();
32512
33191
  const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
32513
33192
  const {Prefix, Postfix, Infix, Operators} = operators;
32514
- const Collection = collection;
33193
+ const Collection = requireCollection();
32515
33194
  const MAX_ROW = 1048576, MAX_COLUMN = 16384;
32516
33195
 
32517
33196
  let Utils$1 = class Utils {
@@ -57018,6 +57697,9 @@ exports.Badge = Badge;
57018
57697
  exports.BottomNavigation = BottomNavigation;
57019
57698
  exports.BottomNavigationSpacer = BottomNavigationSpacer;
57020
57699
  exports.BottomSheet = BottomSheet;
57700
+ exports.BottomSheetActions = BottomSheetActions;
57701
+ exports.BottomSheetContent = BottomSheetContent;
57702
+ exports.BottomSheetHeader = BottomSheetHeader;
57021
57703
  exports.Box = Box;
57022
57704
  exports.Breadcrumbs = Breadcrumbs;
57023
57705
  exports.Button = Button;
@@ -57040,6 +57722,7 @@ exports.ColorPicker = ColorPicker;
57040
57722
  exports.Combobox = Combobox;
57041
57723
  exports.ComingSoon = ComingSoon;
57042
57724
  exports.CommandPalette = CommandPalette;
57725
+ exports.CompactStat = CompactStat;
57043
57726
  exports.ConfirmDialog = ConfirmDialog;
57044
57727
  exports.ContextMenu = ContextMenu;
57045
57728
  exports.ControlBar = ControlBar;
@@ -57086,6 +57769,7 @@ exports.FormWizard = FormWizard;
57086
57769
  exports.Grid = Grid;
57087
57770
  exports.GridItem = GridItem;
57088
57771
  exports.Hide = Hide;
57772
+ exports.HorizontalScroll = HorizontalScroll;
57089
57773
  exports.HoverCard = HoverCard;
57090
57774
  exports.InfiniteScroll = InfiniteScroll;
57091
57775
  exports.Input = Input;
@@ -57106,6 +57790,7 @@ exports.MobileProvider = MobileProvider;
57106
57790
  exports.Modal = Modal;
57107
57791
  exports.ModalFooter = ModalFooter;
57108
57792
  exports.MultiSelect = MultiSelect;
57793
+ exports.NotificationBanner = NotificationBanner;
57109
57794
  exports.NotificationBar = NotificationBar;
57110
57795
  exports.NotificationIndicator = NotificationIndicator;
57111
57796
  exports.NumberInput = NumberInput;
@@ -57146,6 +57831,7 @@ exports.StatusBar = StatusBar;
57146
57831
  exports.StepIndicator = StepIndicator;
57147
57832
  exports.Stepper = Stepper;
57148
57833
  exports.SwipeActions = SwipeActions;
57834
+ exports.SwipeableCard = SwipeableCard;
57149
57835
  exports.Switch = Switch;
57150
57836
  exports.Tabs = Tabs;
57151
57837
  exports.Text = Text;
@@ -57200,7 +57886,6 @@ exports.useMediaQuery = useMediaQuery;
57200
57886
  exports.useMobileContext = useMobileContext;
57201
57887
  exports.useOrientation = useOrientation;
57202
57888
  exports.usePrefersMobile = usePrefersMobile;
57203
- exports.usePullToRefresh = usePullToRefresh;
57204
57889
  exports.useResponsiveCallback = useResponsiveCallback;
57205
57890
  exports.useSafeAreaInsets = useSafeAreaInsets;
57206
57891
  exports.useViewportSize = useViewportSize;