@papernote/ui 1.7.6 → 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.
- package/dist/components/Autocomplete.d.ts +4 -0
- package/dist/components/Autocomplete.d.ts.map +1 -1
- package/dist/components/Badge.d.ts +3 -1
- package/dist/components/Badge.d.ts.map +1 -1
- package/dist/components/BottomSheet.d.ts +72 -8
- package/dist/components/BottomSheet.d.ts.map +1 -1
- package/dist/components/CompactStat.d.ts +52 -0
- package/dist/components/CompactStat.d.ts.map +1 -0
- package/dist/components/HorizontalScroll.d.ts +43 -0
- package/dist/components/HorizontalScroll.d.ts.map +1 -0
- package/dist/components/NotificationBanner.d.ts +53 -0
- package/dist/components/NotificationBanner.d.ts.map +1 -0
- package/dist/components/Progress.d.ts +2 -2
- package/dist/components/Progress.d.ts.map +1 -1
- package/dist/components/PullToRefresh.d.ts +23 -71
- package/dist/components/PullToRefresh.d.ts.map +1 -1
- package/dist/components/Stack.d.ts +2 -1
- package/dist/components/Stack.d.ts.map +1 -1
- package/dist/components/SwipeableCard.d.ts +65 -0
- package/dist/components/SwipeableCard.d.ts.map +1 -0
- package/dist/components/Text.d.ts +9 -2
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/components/index.d.ts +11 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +321 -86
- package/dist/index.esm.js +999 -267
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1004 -266
- package/dist/index.js.map +1 -1
- package/dist/styles.css +191 -8
- package/package.json +1 -1
- package/src/components/Autocomplete.tsx +95 -32
- package/src/components/Badge.tsx +13 -2
- package/src/components/BottomSheet.tsx +227 -98
- package/src/components/Card.tsx +1 -1
- package/src/components/CompactStat.tsx +150 -0
- package/src/components/HorizontalScroll.tsx +275 -0
- package/src/components/NotificationBanner.tsx +238 -0
- package/src/components/Progress.tsx +6 -3
- package/src/components/PullToRefresh.tsx +158 -196
- package/src/components/Stack.tsx +4 -1
- package/src/components/SwipeableCard.tsx +347 -0
- package/src/components/Text.tsx +45 -3
- package/src/components/index.ts +16 -3
- package/src/styles/index.css +32 -0
package/dist/index.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default, { forwardRef, useState, useEffect, useCallback, useRef, useId, useImperativeHandle, useMemo, Children, isValidElement, cloneElement, Component, createContext as createContext$1, useLayoutEffect, createElement, useContext, useReducer } from 'react';
|
|
4
|
-
import { Loader2, X, EyeOff, Eye, AlertTriangle, CheckCircle, AlertCircle, ChevronDown, Search, Check, Minus, Star, Calendar as Calendar$1, ChevronLeft, ChevronRight, Clock, ChevronUp, Plus, TrendingUp, TrendingDown, Info, Trash2, ChevronsLeft, ChevronsRight, Circle, MoreVertical, GripVertical, Upload, Bold, Italic, Underline, List, ListOrdered, Code, Link, Home, FileText, Image, File as File$1, Menu as Menu$1, ArrowDown, User, Settings, LogOut, Moon, Sun, Bell, Edit, Trash, Pin, PinOff, Download, Save, ArrowUpDown, Filter, XCircle, BarChart3, MessageSquare } from 'lucide-react';
|
|
4
|
+
import { Loader2, X, EyeOff, Eye, AlertTriangle, CheckCircle, AlertCircle, ChevronDown, Search, Check, Minus, Star, Calendar as Calendar$1, ChevronLeft, ChevronRight, Clock, ChevronUp, Plus, TrendingUp, TrendingDown, Info, Trash2, ChevronsLeft, ChevronsRight, Circle, MoreVertical, GripVertical, Upload, Bold, Italic, Underline, List, ListOrdered, Code, Link, MoreHorizontal, Home, FileText, Image, File as File$1, Menu as Menu$1, ArrowDown, User, Settings, LogOut, Moon, Sun, Bell, Edit, Trash, Pin, PinOff, Download, Save, ArrowUpDown, Filter, XCircle, BarChart3, MessageSquare } from 'lucide-react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { useInRouterContext, useNavigate, useLocation, Link as Link$1 } from 'react-router-dom';
|
|
7
7
|
|
|
@@ -2507,7 +2507,7 @@ const Card = forwardRef(({ children, variant = 'default', width = 'auto', classN
|
|
|
2507
2507
|
const baseStyles = 'bg-white bg-subtle-grain border-2 border-paper-300 transition-shadow duration-200';
|
|
2508
2508
|
const variantStyles = {
|
|
2509
2509
|
default: 'rounded-xl shadow-lg p-8',
|
|
2510
|
-
compact: 'rounded-lg shadow-md p-
|
|
2510
|
+
compact: 'rounded-lg shadow-md p-3', // 12px padding for mobile-density layouts
|
|
2511
2511
|
flat: 'rounded-lg p-5',
|
|
2512
2512
|
};
|
|
2513
2513
|
const widthStyles = {
|
|
@@ -2623,6 +2623,7 @@ function Separator({ orientation = 'horizontal', className = '', spacing = 'md',
|
|
|
2623
2623
|
*
|
|
2624
2624
|
* Spacing scale (use either `spacing` or `gap` prop - they're aliases):
|
|
2625
2625
|
* - none: 0
|
|
2626
|
+
* - tight: 0.25rem (1) - for mobile-density layouts
|
|
2626
2627
|
* - xs: 0.5rem (2)
|
|
2627
2628
|
* - sm: 0.75rem (3)
|
|
2628
2629
|
* - md: 1.5rem (6)
|
|
@@ -2650,6 +2651,7 @@ const Stack = forwardRef(({ children, direction = 'vertical', spacing, gap, alig
|
|
|
2650
2651
|
const spacingClasses = {
|
|
2651
2652
|
vertical: {
|
|
2652
2653
|
none: '',
|
|
2654
|
+
tight: 'space-y-1', // 4px - for mobile-density layouts
|
|
2653
2655
|
xs: 'space-y-2',
|
|
2654
2656
|
sm: 'space-y-3',
|
|
2655
2657
|
md: 'space-y-6',
|
|
@@ -2658,6 +2660,7 @@ const Stack = forwardRef(({ children, direction = 'vertical', spacing, gap, alig
|
|
|
2658
2660
|
},
|
|
2659
2661
|
horizontal: {
|
|
2660
2662
|
none: '',
|
|
2663
|
+
tight: 'space-x-1', // 4px - for mobile-density layouts
|
|
2661
2664
|
xs: 'space-x-2',
|
|
2662
2665
|
sm: 'space-x-3',
|
|
2663
2666
|
md: 'space-x-6',
|
|
@@ -2955,7 +2958,7 @@ const GridItem = ({ colSpan, rowSpan, children, className = '', ...boxProps }) =
|
|
|
2955
2958
|
* <Text ref={textRef}>Measurable text</Text>
|
|
2956
2959
|
* ```
|
|
2957
2960
|
*/
|
|
2958
|
-
const Text = forwardRef(({ children, as: Component = 'p', size = 'base', weight = 'normal', color = 'primary', align = 'left', truncate = false, lineClamp, transform, className = '', ...htmlProps }, ref) => {
|
|
2961
|
+
const Text = forwardRef(({ children, as: Component = 'p', size = 'base', smSize, mdSize, lgSize, weight = 'normal', color = 'primary', align = 'left', truncate = false, lineClamp, transform, className = '', ...htmlProps }, ref) => {
|
|
2959
2962
|
const sizeClasses = {
|
|
2960
2963
|
xs: 'text-xs',
|
|
2961
2964
|
sm: 'text-sm',
|
|
@@ -2964,6 +2967,31 @@ const Text = forwardRef(({ children, as: Component = 'p', size = 'base', weight
|
|
|
2964
2967
|
xl: 'text-xl',
|
|
2965
2968
|
'2xl': 'text-2xl',
|
|
2966
2969
|
};
|
|
2970
|
+
// Responsive size classes
|
|
2971
|
+
const smSizeClasses = {
|
|
2972
|
+
xs: 'sm:text-xs',
|
|
2973
|
+
sm: 'sm:text-sm',
|
|
2974
|
+
base: 'sm:text-base',
|
|
2975
|
+
lg: 'sm:text-lg',
|
|
2976
|
+
xl: 'sm:text-xl',
|
|
2977
|
+
'2xl': 'sm:text-2xl',
|
|
2978
|
+
};
|
|
2979
|
+
const mdSizeClasses = {
|
|
2980
|
+
xs: 'md:text-xs',
|
|
2981
|
+
sm: 'md:text-sm',
|
|
2982
|
+
base: 'md:text-base',
|
|
2983
|
+
lg: 'md:text-lg',
|
|
2984
|
+
xl: 'md:text-xl',
|
|
2985
|
+
'2xl': 'md:text-2xl',
|
|
2986
|
+
};
|
|
2987
|
+
const lgSizeClasses = {
|
|
2988
|
+
xs: 'lg:text-xs',
|
|
2989
|
+
sm: 'lg:text-sm',
|
|
2990
|
+
base: 'lg:text-base',
|
|
2991
|
+
lg: 'lg:text-lg',
|
|
2992
|
+
xl: 'lg:text-xl',
|
|
2993
|
+
'2xl': 'lg:text-2xl',
|
|
2994
|
+
};
|
|
2967
2995
|
const weightClasses = {
|
|
2968
2996
|
normal: 'font-normal',
|
|
2969
2997
|
medium: 'font-medium',
|
|
@@ -3001,6 +3029,9 @@ const Text = forwardRef(({ children, as: Component = 'p', size = 'base', weight
|
|
|
3001
3029
|
// Build class list
|
|
3002
3030
|
const classes = [
|
|
3003
3031
|
sizeClasses[size],
|
|
3032
|
+
smSize ? smSizeClasses[smSize] : '',
|
|
3033
|
+
mdSize ? mdSizeClasses[mdSize] : '',
|
|
3034
|
+
lgSize ? lgSizeClasses[lgSize] : '',
|
|
3004
3035
|
weightClasses[weight],
|
|
3005
3036
|
colorClasses[color],
|
|
3006
3037
|
alignClasses[align],
|
|
@@ -3107,24 +3138,48 @@ function Alert({ variant = 'info', title, children, onClose, className = '', act
|
|
|
3107
3138
|
return (jsx("div", { className: `rounded-lg border p-4 ${styles.container} ${className}`, role: "alert", children: jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex-shrink-0 mt-0.5", children: styles.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [title && jsx("h4", { className: "text-sm font-medium mb-1", children: title }), jsx("div", { className: "text-sm", children: children }), actions.length > 0 && (jsx("div", { className: "flex gap-2 mt-3", children: actions.map((action, index) => (jsx("button", { onClick: action.onClick, className: getButtonStyles(action.variant), children: action.label }, index))) }))] }), onClose && (jsx("button", { onClick: onClose, className: "flex-shrink-0 text-current opacity-70 hover:opacity-100 transition-opacity", "aria-label": "Close alert", children: jsx(X, { className: "h-4 w-4" }) }))] }) }));
|
|
3108
3139
|
}
|
|
3109
3140
|
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3141
|
+
/**
|
|
3142
|
+
* BottomSheet - Mobile-friendly modal that slides up from the bottom
|
|
3143
|
+
*
|
|
3144
|
+
* Designed for mobile contexts with touch-friendly interactions:
|
|
3145
|
+
* - Drag handle for swipe-to-dismiss
|
|
3146
|
+
* - Snap points for partial expansion
|
|
3147
|
+
* - Sticky action area at thumb zone
|
|
3148
|
+
*
|
|
3149
|
+
* @example
|
|
3150
|
+
* ```tsx
|
|
3151
|
+
* <BottomSheet open={isOpen} onClose={() => setIsOpen(false)}>
|
|
3152
|
+
* <BottomSheetHeader>
|
|
3153
|
+
* <Text weight="bold">Transaction Details</Text>
|
|
3154
|
+
* </BottomSheetHeader>
|
|
3155
|
+
* <BottomSheetContent>
|
|
3156
|
+
* {content}
|
|
3157
|
+
* </BottomSheetContent>
|
|
3158
|
+
* <BottomSheetActions>
|
|
3159
|
+
* <Button fullWidth>Approve</Button>
|
|
3160
|
+
* </BottomSheetActions>
|
|
3161
|
+
* </BottomSheet>
|
|
3162
|
+
* ```
|
|
3163
|
+
*/
|
|
3164
|
+
function BottomSheet({ open, isOpen, onClose, children, title, height = 'auto', maxHeight = '90vh', snapPoints, closeOnOverlayClick = true, closeOnEscape = true, showHandle = true, showCloseButton = true, preventScroll = true, className = '', }) {
|
|
3165
|
+
// Support both 'open' and 'isOpen' props for flexibility
|
|
3166
|
+
const isSheetOpen = open ?? isOpen ?? false;
|
|
3167
|
+
// Height presets for convenience
|
|
3168
|
+
const heightPresets = {
|
|
3169
|
+
sm: '40vh',
|
|
3170
|
+
md: '60vh',
|
|
3171
|
+
lg: '80vh',
|
|
3172
|
+
full: '100vh',
|
|
3173
|
+
};
|
|
3174
|
+
const sheetRef = useRef(null);
|
|
3118
3175
|
const [isDragging, setIsDragging] = useState(false);
|
|
3119
3176
|
const [dragOffset, setDragOffset] = useState(0);
|
|
3120
|
-
const [
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
const startYRef = useRef(0);
|
|
3125
|
-
// Close on Escape
|
|
3177
|
+
const [currentSnapIndex, setCurrentSnapIndex] = useState(snapPoints?.length ? snapPoints.length - 1 : 0);
|
|
3178
|
+
const startY = useRef(0);
|
|
3179
|
+
const startOffset = useRef(0);
|
|
3180
|
+
// Handle escape key
|
|
3126
3181
|
useEffect(() => {
|
|
3127
|
-
if (!
|
|
3182
|
+
if (!isSheetOpen || !closeOnEscape)
|
|
3128
3183
|
return;
|
|
3129
3184
|
const handleEscape = (e) => {
|
|
3130
3185
|
if (e.key === 'Escape') {
|
|
@@ -3133,77 +3188,132 @@ function BottomSheet({ isOpen, onClose, children, title, height = 'md', showHand
|
|
|
3133
3188
|
};
|
|
3134
3189
|
document.addEventListener('keydown', handleEscape);
|
|
3135
3190
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
3136
|
-
}, [
|
|
3137
|
-
// Prevent body scroll
|
|
3191
|
+
}, [open, closeOnEscape, onClose]);
|
|
3192
|
+
// Prevent body scroll
|
|
3138
3193
|
useEffect(() => {
|
|
3139
|
-
if (
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
document.body.style.overflow = '';
|
|
3144
|
-
}
|
|
3194
|
+
if (!isSheetOpen || !preventScroll)
|
|
3195
|
+
return;
|
|
3196
|
+
const originalOverflow = document.body.style.overflow;
|
|
3197
|
+
document.body.style.overflow = 'hidden';
|
|
3145
3198
|
return () => {
|
|
3146
|
-
document.body.style.overflow =
|
|
3199
|
+
document.body.style.overflow = originalOverflow;
|
|
3147
3200
|
};
|
|
3148
|
-
}, [
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
onClose();
|
|
3152
|
-
}
|
|
3153
|
-
};
|
|
3154
|
-
const handleDragStart = (e) => {
|
|
3201
|
+
}, [open, preventScroll]);
|
|
3202
|
+
// Handle drag start
|
|
3203
|
+
const handleDragStart = useCallback((clientY) => {
|
|
3155
3204
|
setIsDragging(true);
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
};
|
|
3159
|
-
|
|
3205
|
+
startY.current = clientY;
|
|
3206
|
+
startOffset.current = dragOffset;
|
|
3207
|
+
}, [dragOffset]);
|
|
3208
|
+
// Handle drag move
|
|
3209
|
+
const handleDragMove = useCallback((clientY) => {
|
|
3210
|
+
if (!isDragging)
|
|
3211
|
+
return;
|
|
3212
|
+
const delta = clientY - startY.current;
|
|
3213
|
+
const newOffset = Math.max(0, startOffset.current + delta);
|
|
3214
|
+
setDragOffset(newOffset);
|
|
3215
|
+
}, [isDragging]);
|
|
3216
|
+
// Handle drag end
|
|
3217
|
+
const handleDragEnd = useCallback(() => {
|
|
3160
3218
|
if (!isDragging)
|
|
3161
3219
|
return;
|
|
3162
|
-
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
3163
|
-
const offset = clientY - startYRef.current;
|
|
3164
|
-
// Only allow dragging down
|
|
3165
|
-
if (offset > 0) {
|
|
3166
|
-
setDragOffset(offset);
|
|
3167
|
-
}
|
|
3168
|
-
};
|
|
3169
|
-
const handleDragEnd = () => {
|
|
3170
3220
|
setIsDragging(false);
|
|
3171
|
-
|
|
3172
|
-
if (dragOffset >
|
|
3173
|
-
|
|
3221
|
+
const threshold = 100; // pixels to trigger close
|
|
3222
|
+
if (dragOffset > threshold) {
|
|
3223
|
+
// If we have snap points, snap to next lower point or close
|
|
3224
|
+
if (snapPoints && currentSnapIndex > 0) {
|
|
3225
|
+
setCurrentSnapIndex(currentSnapIndex - 1);
|
|
3226
|
+
setDragOffset(0);
|
|
3227
|
+
}
|
|
3228
|
+
else {
|
|
3229
|
+
onClose();
|
|
3230
|
+
setDragOffset(0);
|
|
3231
|
+
}
|
|
3174
3232
|
}
|
|
3175
|
-
|
|
3233
|
+
else {
|
|
3234
|
+
// Snap back
|
|
3235
|
+
setDragOffset(0);
|
|
3236
|
+
}
|
|
3237
|
+
}, [isDragging, dragOffset, snapPoints, currentSnapIndex, onClose]);
|
|
3238
|
+
// Touch event handlers
|
|
3239
|
+
const handleTouchStart = (e) => {
|
|
3240
|
+
handleDragStart(e.touches[0].clientY);
|
|
3241
|
+
};
|
|
3242
|
+
const handleTouchMove = (e) => {
|
|
3243
|
+
handleDragMove(e.touches[0].clientY);
|
|
3244
|
+
};
|
|
3245
|
+
const handleTouchEnd = () => {
|
|
3246
|
+
handleDragEnd();
|
|
3247
|
+
};
|
|
3248
|
+
// Mouse event handlers (for desktop testing)
|
|
3249
|
+
const handleMouseDown = (e) => {
|
|
3250
|
+
handleDragStart(e.clientY);
|
|
3176
3251
|
};
|
|
3177
3252
|
useEffect(() => {
|
|
3178
3253
|
if (!isDragging)
|
|
3179
3254
|
return;
|
|
3180
|
-
const
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3255
|
+
const handleMouseMove = (e) => {
|
|
3256
|
+
handleDragMove(e.clientY);
|
|
3257
|
+
};
|
|
3258
|
+
const handleMouseUp = () => {
|
|
3259
|
+
handleDragEnd();
|
|
3260
|
+
};
|
|
3261
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
3262
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
3186
3263
|
return () => {
|
|
3187
|
-
document.removeEventListener('
|
|
3188
|
-
document.removeEventListener('
|
|
3189
|
-
document.removeEventListener('touchend', handleEnd);
|
|
3190
|
-
document.removeEventListener('mouseup', handleEnd);
|
|
3264
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
3265
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
3191
3266
|
};
|
|
3192
|
-
}, [isDragging,
|
|
3193
|
-
|
|
3267
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
3268
|
+
// Calculate height based on snap points or presets
|
|
3269
|
+
const getSheetHeight = () => {
|
|
3270
|
+
if (snapPoints && snapPoints[currentSnapIndex]) {
|
|
3271
|
+
return snapPoints[currentSnapIndex];
|
|
3272
|
+
}
|
|
3273
|
+
if (typeof height === 'number') {
|
|
3274
|
+
return `${height}px`;
|
|
3275
|
+
}
|
|
3276
|
+
// Check for preset heights
|
|
3277
|
+
if (typeof height === 'string' && heightPresets[height]) {
|
|
3278
|
+
return heightPresets[height];
|
|
3279
|
+
}
|
|
3280
|
+
return height;
|
|
3281
|
+
};
|
|
3282
|
+
if (!isSheetOpen)
|
|
3194
3283
|
return null;
|
|
3195
|
-
|
|
3284
|
+
const sheetContent = (jsxs("div", { className: "fixed inset-0 z-50", children: [jsx("div", { className: `
|
|
3196
3285
|
absolute inset-0 bg-black/50 transition-opacity duration-300
|
|
3197
|
-
${
|
|
3198
|
-
|
|
3199
|
-
|
|
3286
|
+
${isSheetOpen ? 'opacity-100' : 'opacity-0'}
|
|
3287
|
+
`, onClick: closeOnOverlayClick ? onClose : undefined, "aria-hidden": "true" }), jsxs("div", { ref: sheetRef, className: `
|
|
3288
|
+
absolute bottom-0 left-0 right-0
|
|
3289
|
+
bg-white rounded-t-2xl shadow-2xl
|
|
3200
3290
|
transition-transform duration-300 ease-out
|
|
3201
|
-
${
|
|
3291
|
+
${isDragging ? 'transition-none' : ''}
|
|
3202
3292
|
${className}
|
|
3203
3293
|
`, style: {
|
|
3204
|
-
height:
|
|
3294
|
+
height: getSheetHeight(),
|
|
3295
|
+
maxHeight,
|
|
3205
3296
|
transform: `translateY(${dragOffset}px)`,
|
|
3206
|
-
}, role: "dialog", "aria-modal": "true",
|
|
3297
|
+
}, role: "dialog", "aria-modal": "true", children: [showHandle && (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: jsx("div", { className: "w-10 h-1 bg-paper-300 rounded-full" }) })), title && (jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-paper-200", children: [jsx("h2", { className: "text-lg font-medium text-ink-900", children: title }), showCloseButton && (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: jsx(X, { className: "h-5 w-5" }) }))] })), jsx("div", { className: "flex flex-col h-full overflow-hidden", children: children })] })] }));
|
|
3298
|
+
return createPortal(sheetContent, document.body);
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* BottomSheetHeader - Header section with title and optional close button
|
|
3302
|
+
*/
|
|
3303
|
+
function BottomSheetHeader({ children, className = '' }) {
|
|
3304
|
+
return (jsx("div", { className: `flex items-center justify-between px-4 py-3 border-b border-paper-200 ${className}`, children: children }));
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* BottomSheetContent - Scrollable content area
|
|
3308
|
+
*/
|
|
3309
|
+
function BottomSheetContent({ children, className = '' }) {
|
|
3310
|
+
return (jsx("div", { className: `flex-1 overflow-y-auto px-4 py-4 ${className}`, children: children }));
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* BottomSheetActions - Sticky footer for action buttons (thumb zone)
|
|
3314
|
+
*/
|
|
3315
|
+
function BottomSheetActions({ children, className = '' }) {
|
|
3316
|
+
return (jsx("div", { className: `flex flex-col gap-2 px-4 py-4 border-t border-paper-200 bg-white ${className}`, children: children }));
|
|
3207
3317
|
}
|
|
3208
3318
|
|
|
3209
3319
|
const sizeClasses$8 = {
|
|
@@ -5007,7 +5117,7 @@ const MaskedInput = forwardRef(({ value, onChange, maskType = 'phone', customMas
|
|
|
5007
5117
|
});
|
|
5008
5118
|
MaskedInput.displayName = 'MaskedInput';
|
|
5009
5119
|
|
|
5010
|
-
const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, label, placeholder = 'Search...', required = false, disabled = false, error, helperText, minChars = 1, debounceMs = 300, maxResults = 10, clearable = true, className = '', }, ref) => {
|
|
5120
|
+
const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, label, placeholder = 'Search...', required = false, disabled = false, error, helperText, minChars = 1, debounceMs = 300, maxResults = 10, clearable = true, className = '', showOptionsOnFocus = true, }, ref) => {
|
|
5011
5121
|
const [isOpen, setIsOpen] = useState(false);
|
|
5012
5122
|
const [filteredOptions, setFilteredOptions] = useState([]);
|
|
5013
5123
|
const [loading, setLoading] = useState(false);
|
|
@@ -5019,6 +5129,30 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5019
5129
|
const labelId = useId();
|
|
5020
5130
|
const listboxId = useId();
|
|
5021
5131
|
const errorId = useId();
|
|
5132
|
+
// Helper to find next selectable (non-header) index
|
|
5133
|
+
const findNextSelectableIndex = (currentIndex, optionsList) => {
|
|
5134
|
+
for (let i = currentIndex + 1; i < optionsList.length; i++) {
|
|
5135
|
+
if (!optionsList[i].isHeader)
|
|
5136
|
+
return i;
|
|
5137
|
+
}
|
|
5138
|
+
return currentIndex; // Stay at current if no next selectable
|
|
5139
|
+
};
|
|
5140
|
+
// Helper to find previous selectable (non-header) index
|
|
5141
|
+
const findPrevSelectableIndex = (currentIndex, optionsList) => {
|
|
5142
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
5143
|
+
if (!optionsList[i].isHeader)
|
|
5144
|
+
return i;
|
|
5145
|
+
}
|
|
5146
|
+
return -1; // Go to -1 if no previous selectable
|
|
5147
|
+
};
|
|
5148
|
+
// Helper to find first selectable (non-header) index
|
|
5149
|
+
const findFirstSelectableIndex = (optionsList) => {
|
|
5150
|
+
for (let i = 0; i < optionsList.length; i++) {
|
|
5151
|
+
if (!optionsList[i].isHeader)
|
|
5152
|
+
return i;
|
|
5153
|
+
}
|
|
5154
|
+
return -1;
|
|
5155
|
+
};
|
|
5022
5156
|
// Expose methods via ref
|
|
5023
5157
|
useImperativeHandle(ref, () => ({
|
|
5024
5158
|
focus: () => inputRef.current?.focus(),
|
|
@@ -5048,8 +5182,8 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5048
5182
|
const results = await onSearch(query);
|
|
5049
5183
|
setFilteredOptions(results.slice(0, maxResults));
|
|
5050
5184
|
setIsOpen(results.length > 0);
|
|
5051
|
-
// Auto-highlight first
|
|
5052
|
-
setHighlightedIndex(results.
|
|
5185
|
+
// Auto-highlight first selectable (non-header) result
|
|
5186
|
+
setHighlightedIndex(findFirstSelectableIndex(results.slice(0, maxResults)));
|
|
5053
5187
|
}
|
|
5054
5188
|
catch (err) {
|
|
5055
5189
|
console.error('Autocomplete search error:', err);
|
|
@@ -5066,8 +5200,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5066
5200
|
const filtered = filterOptions(query);
|
|
5067
5201
|
setFilteredOptions(filtered);
|
|
5068
5202
|
setIsOpen(filtered.length > 0);
|
|
5069
|
-
// Auto-highlight first
|
|
5070
|
-
setHighlightedIndex(filtered
|
|
5203
|
+
// Auto-highlight first selectable (non-header) result
|
|
5204
|
+
setHighlightedIndex(findFirstSelectableIndex(filtered));
|
|
5205
|
+
}
|
|
5206
|
+
};
|
|
5207
|
+
// Show static options (for focus/arrow down when input is empty)
|
|
5208
|
+
const showStaticOptions = () => {
|
|
5209
|
+
if (options.length > 0) {
|
|
5210
|
+
setFilteredOptions(options.slice(0, maxResults));
|
|
5211
|
+
setIsOpen(true);
|
|
5212
|
+
setHighlightedIndex(findFirstSelectableIndex(options.slice(0, maxResults)));
|
|
5071
5213
|
}
|
|
5072
5214
|
};
|
|
5073
5215
|
// Debounced search
|
|
@@ -5108,7 +5250,11 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5108
5250
|
// If we have cached results from a previous search, show them
|
|
5109
5251
|
if (filteredOptions.length > 0) {
|
|
5110
5252
|
setIsOpen(true);
|
|
5111
|
-
setHighlightedIndex(
|
|
5253
|
+
setHighlightedIndex(findFirstSelectableIndex(filteredOptions));
|
|
5254
|
+
}
|
|
5255
|
+
else if (value.length < minChars && options.length > 0) {
|
|
5256
|
+
// Show static options when input is empty/below minChars
|
|
5257
|
+
showStaticOptions();
|
|
5112
5258
|
}
|
|
5113
5259
|
else if (value.length >= minChars) {
|
|
5114
5260
|
// Otherwise trigger a new search
|
|
@@ -5120,16 +5266,20 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5120
5266
|
switch (e.key) {
|
|
5121
5267
|
case 'ArrowDown':
|
|
5122
5268
|
e.preventDefault();
|
|
5123
|
-
setHighlightedIndex((prev) => prev
|
|
5269
|
+
setHighlightedIndex((prev) => findNextSelectableIndex(prev, filteredOptions));
|
|
5124
5270
|
break;
|
|
5125
5271
|
case 'ArrowUp':
|
|
5126
5272
|
e.preventDefault();
|
|
5127
|
-
setHighlightedIndex((prev) => (prev
|
|
5273
|
+
setHighlightedIndex((prev) => findPrevSelectableIndex(prev, filteredOptions));
|
|
5128
5274
|
break;
|
|
5129
5275
|
case 'Enter':
|
|
5130
5276
|
e.preventDefault();
|
|
5131
5277
|
if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
|
|
5132
|
-
|
|
5278
|
+
const option = filteredOptions[highlightedIndex];
|
|
5279
|
+
// Don't select headers
|
|
5280
|
+
if (!option.isHeader) {
|
|
5281
|
+
handleSelect(option);
|
|
5282
|
+
}
|
|
5133
5283
|
}
|
|
5134
5284
|
break;
|
|
5135
5285
|
case 'Escape':
|
|
@@ -5159,7 +5309,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5159
5309
|
}
|
|
5160
5310
|
};
|
|
5161
5311
|
}, []);
|
|
5162
|
-
return (jsxs("div", { className: `relative ${className}`, children: [label && (jsxs("label", { id: labelId, className: "block text-sm font-medium text-ink-900 mb-1.5", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2", children: loading ? (jsx(Loader2, { className: "h-4 w-4 text-ink-400 animate-spin" })) : (jsx(Search, { className: "h-4 w-4 text-ink-400" })) }), jsx("input", { ref: inputRef, type: "text", value: value, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: () =>
|
|
5312
|
+
return (jsxs("div", { className: `relative ${className}`, children: [label && (jsxs("label", { id: labelId, className: "block text-sm font-medium text-ink-900 mb-1.5", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2", children: loading ? (jsx(Loader2, { className: "h-4 w-4 text-ink-400 animate-spin" })) : (jsx(Search, { className: "h-4 w-4 text-ink-400" })) }), jsx("input", { ref: inputRef, type: "text", value: value, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: () => {
|
|
5313
|
+
if (showOptionsOnFocus && value.length < minChars && options.length > 0) {
|
|
5314
|
+
// Show static options when input is empty/below minChars
|
|
5315
|
+
showStaticOptions();
|
|
5316
|
+
}
|
|
5317
|
+
else if (value.length >= minChars) {
|
|
5318
|
+
// Trigger search if we have enough chars
|
|
5319
|
+
handleSearch(value);
|
|
5320
|
+
}
|
|
5321
|
+
}, placeholder: placeholder, disabled: disabled, className: `
|
|
5163
5322
|
w-full pl-9 pr-9 py-2
|
|
5164
5323
|
text-sm text-ink-900 placeholder-ink-400
|
|
5165
5324
|
bg-white border rounded-lg
|
|
@@ -5169,12 +5328,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
|
|
|
5169
5328
|
${error
|
|
5170
5329
|
? 'border-error-500 focus:ring-error-400 focus:border-error-400'
|
|
5171
5330
|
: 'border-paper-300'}
|
|
5172
|
-
`, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-busy": loading }), clearable && value && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsx(X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (
|
|
5173
|
-
|
|
5174
|
-
|
|
5331
|
+
`, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-busy": loading }), clearable && value && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsx(X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (option.isHeader ? (
|
|
5332
|
+
// Render section header (non-selectable)
|
|
5333
|
+
jsx("div", { className: "px-3 py-2 text-xs font-semibold text-ink-500 uppercase tracking-wide bg-paper-50 border-t border-paper-200 first:border-t-0 first:rounded-t-lg cursor-default", role: "presentation", children: option.label }, `header-${option.value}`)) : (
|
|
5334
|
+
// Render selectable option
|
|
5335
|
+
jsxs("button", { id: `autocomplete-option-${index}`, type: "button", onClick: () => handleSelect(option), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": highlightedIndex === index, className: `
|
|
5336
|
+
w-full text-left px-3 py-2 transition-colors
|
|
5337
|
+
${highlightedIndex === index
|
|
5175
5338
|
? 'bg-accent-50'
|
|
5176
5339
|
: 'hover:bg-paper-50'}
|
|
5177
|
-
|
|
5340
|
+
`, children: [jsx("div", { className: "text-sm font-medium text-ink-900", children: option.label }), option.description && (jsx("div", { className: "text-xs text-ink-600 mt-0.5", children: option.description }))] }, option.value)))) })), isOpen && !loading && filteredOptions.length === 0 && value.length >= minChars && (jsx("div", { className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg p-3", role: "status", "aria-live": "polite", children: jsx("p", { className: "text-sm text-ink-500 text-center", children: "No results found" }) })), error && (jsx("p", { id: errorId, className: "mt-1.5 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsx("p", { className: "mt-1.5 text-xs text-ink-600", children: helperText }))] }));
|
|
5178
5341
|
});
|
|
5179
5342
|
Autocomplete.displayName = 'Autocomplete';
|
|
5180
5343
|
|
|
@@ -7285,6 +7448,598 @@ function Hide({ children, above, below, only, className = '' }) {
|
|
|
7285
7448
|
return (jsx("div", { className: `${visibilityClasses} ${className}`, children: children }));
|
|
7286
7449
|
}
|
|
7287
7450
|
|
|
7451
|
+
/**
|
|
7452
|
+
* HorizontalScroll - Horizontally scrollable container with peek indicators
|
|
7453
|
+
*
|
|
7454
|
+
* Designed for mobile carousels of cards with:
|
|
7455
|
+
* - Touch-friendly momentum scrolling
|
|
7456
|
+
* - Peek hint showing more items exist
|
|
7457
|
+
* - Optional snap scrolling
|
|
7458
|
+
* - Navigation arrows for desktop
|
|
7459
|
+
*
|
|
7460
|
+
* @example
|
|
7461
|
+
* ```tsx
|
|
7462
|
+
* <HorizontalScroll gap="md" peekAmount={24} showIndicators>
|
|
7463
|
+
* <Card>Bill 1</Card>
|
|
7464
|
+
* <Card>Bill 2</Card>
|
|
7465
|
+
* <Card>Bill 3</Card>
|
|
7466
|
+
* </HorizontalScroll>
|
|
7467
|
+
* ```
|
|
7468
|
+
*/
|
|
7469
|
+
function HorizontalScroll({ children, gap = 'md', peekAmount = 24, showIndicators = false, snapToItem = true, showArrows = 'hover', scrollBehavior = 'smooth', className = '', scrollClassName = '', }) {
|
|
7470
|
+
const scrollRef = useRef(null);
|
|
7471
|
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
7472
|
+
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
7473
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
7474
|
+
const [itemCount, setItemCount] = useState(0);
|
|
7475
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
7476
|
+
// Gap classes
|
|
7477
|
+
const gapClasses = {
|
|
7478
|
+
none: 'gap-0',
|
|
7479
|
+
sm: 'gap-2',
|
|
7480
|
+
md: 'gap-4',
|
|
7481
|
+
lg: 'gap-6',
|
|
7482
|
+
};
|
|
7483
|
+
const gapStyle = typeof gap === 'number' ? { gap: `${gap}px` } : {};
|
|
7484
|
+
const gapClass = typeof gap === 'string' ? gapClasses[gap] : '';
|
|
7485
|
+
// Check scroll position and update state
|
|
7486
|
+
const checkScrollPosition = useCallback(() => {
|
|
7487
|
+
const container = scrollRef.current;
|
|
7488
|
+
if (!container)
|
|
7489
|
+
return;
|
|
7490
|
+
const { scrollLeft, scrollWidth, clientWidth } = container;
|
|
7491
|
+
setCanScrollLeft(scrollLeft > 0);
|
|
7492
|
+
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1);
|
|
7493
|
+
// Calculate active index based on scroll position
|
|
7494
|
+
if (showIndicators && container.children.length > 0) {
|
|
7495
|
+
const children = Array.from(container.children);
|
|
7496
|
+
const containerRect = container.getBoundingClientRect();
|
|
7497
|
+
const containerCenter = containerRect.left + containerRect.width / 2;
|
|
7498
|
+
let closestIndex = 0;
|
|
7499
|
+
let closestDistance = Infinity;
|
|
7500
|
+
children.forEach((child, index) => {
|
|
7501
|
+
const childRect = child.getBoundingClientRect();
|
|
7502
|
+
const childCenter = childRect.left + childRect.width / 2;
|
|
7503
|
+
const distance = Math.abs(childCenter - containerCenter);
|
|
7504
|
+
if (distance < closestDistance) {
|
|
7505
|
+
closestDistance = distance;
|
|
7506
|
+
closestIndex = index;
|
|
7507
|
+
}
|
|
7508
|
+
});
|
|
7509
|
+
setActiveIndex(closestIndex);
|
|
7510
|
+
}
|
|
7511
|
+
}, [showIndicators]);
|
|
7512
|
+
// Initialize and handle resize
|
|
7513
|
+
useEffect(() => {
|
|
7514
|
+
const container = scrollRef.current;
|
|
7515
|
+
if (!container)
|
|
7516
|
+
return;
|
|
7517
|
+
setItemCount(React__default.Children.count(children));
|
|
7518
|
+
checkScrollPosition();
|
|
7519
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
7520
|
+
checkScrollPosition();
|
|
7521
|
+
});
|
|
7522
|
+
resizeObserver.observe(container);
|
|
7523
|
+
return () => {
|
|
7524
|
+
resizeObserver.disconnect();
|
|
7525
|
+
};
|
|
7526
|
+
}, [children, checkScrollPosition]);
|
|
7527
|
+
// Handle scroll event
|
|
7528
|
+
useEffect(() => {
|
|
7529
|
+
const container = scrollRef.current;
|
|
7530
|
+
if (!container)
|
|
7531
|
+
return;
|
|
7532
|
+
const handleScroll = () => {
|
|
7533
|
+
checkScrollPosition();
|
|
7534
|
+
};
|
|
7535
|
+
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
7536
|
+
return () => {
|
|
7537
|
+
container.removeEventListener('scroll', handleScroll);
|
|
7538
|
+
};
|
|
7539
|
+
}, [checkScrollPosition]);
|
|
7540
|
+
// Scroll by one item
|
|
7541
|
+
const scrollByItem = (direction) => {
|
|
7542
|
+
const container = scrollRef.current;
|
|
7543
|
+
if (!container)
|
|
7544
|
+
return;
|
|
7545
|
+
const children = Array.from(container.children);
|
|
7546
|
+
if (children.length === 0)
|
|
7547
|
+
return;
|
|
7548
|
+
const firstChild = children[0];
|
|
7549
|
+
const itemWidth = firstChild.offsetWidth;
|
|
7550
|
+
const gapValue = typeof gap === 'number' ? gap :
|
|
7551
|
+
gap === 'sm' ? 8 : gap === 'md' ? 16 : gap === 'lg' ? 24 : 0;
|
|
7552
|
+
const scrollAmount = itemWidth + gapValue;
|
|
7553
|
+
container.scrollBy({
|
|
7554
|
+
left: direction === 'left' ? -scrollAmount : scrollAmount,
|
|
7555
|
+
behavior: scrollBehavior,
|
|
7556
|
+
});
|
|
7557
|
+
};
|
|
7558
|
+
// Scroll to specific index
|
|
7559
|
+
const scrollToIndex = (index) => {
|
|
7560
|
+
const container = scrollRef.current;
|
|
7561
|
+
if (!container)
|
|
7562
|
+
return;
|
|
7563
|
+
const children = Array.from(container.children);
|
|
7564
|
+
if (index < 0 || index >= children.length)
|
|
7565
|
+
return;
|
|
7566
|
+
const child = children[index];
|
|
7567
|
+
child.scrollIntoView({
|
|
7568
|
+
behavior: scrollBehavior,
|
|
7569
|
+
block: 'nearest',
|
|
7570
|
+
inline: 'center',
|
|
7571
|
+
});
|
|
7572
|
+
};
|
|
7573
|
+
const showLeftArrow = showArrows === 'always' || (showArrows === 'hover' && isHovered);
|
|
7574
|
+
const showRightArrow = showArrows === 'always' || (showArrows === 'hover' && isHovered);
|
|
7575
|
+
return (jsxs("div", { className: `relative ${className}`, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [showLeftArrow && canScrollLeft && (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: jsx(ChevronLeft, { className: "h-5 w-5" }) })), jsx("div", { ref: scrollRef, className: `
|
|
7576
|
+
flex overflow-x-auto scrollbar-hide
|
|
7577
|
+
${gapClass}
|
|
7578
|
+
${snapToItem ? 'snap-x snap-mandatory' : ''}
|
|
7579
|
+
${scrollClassName}
|
|
7580
|
+
`, style: {
|
|
7581
|
+
...gapStyle,
|
|
7582
|
+
paddingRight: peekAmount > 0 ? `${peekAmount}px` : undefined,
|
|
7583
|
+
scrollPaddingLeft: '0px',
|
|
7584
|
+
scrollPaddingRight: `${peekAmount}px`,
|
|
7585
|
+
}, children: React__default.Children.map(children, (child, index) => (jsx("div", { className: `flex-shrink-0 ${snapToItem ? 'snap-start' : ''}`, children: child }, index))) }), showRightArrow && canScrollRight && (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: jsx(ChevronRight, { className: "h-5 w-5" }) })), showIndicators && itemCount > 1 && (jsx("div", { className: "flex justify-center gap-1.5 mt-3", children: Array.from({ length: itemCount }).map((_, index) => (jsx("button", { onClick: () => scrollToIndex(index), className: `
|
|
7586
|
+
w-2 h-2 rounded-full transition-all duration-200
|
|
7587
|
+
${index === activeIndex
|
|
7588
|
+
? 'bg-accent-500 w-4'
|
|
7589
|
+
: 'bg-paper-300 hover:bg-paper-400'}
|
|
7590
|
+
`, "aria-label": `Go to item ${index + 1}`, "aria-current": index === activeIndex ? 'true' : 'false' }, index))) }))] }));
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
/**
|
|
7594
|
+
* SwipeableCard - Card component with swipe-to-action functionality
|
|
7595
|
+
*
|
|
7596
|
+
* Designed for mobile approval workflows:
|
|
7597
|
+
* - Swipe right to approve/confirm
|
|
7598
|
+
* - Swipe left to see options/alternatives
|
|
7599
|
+
* - Visual feedback showing action being revealed
|
|
7600
|
+
* - Haptic feedback on mobile devices
|
|
7601
|
+
*
|
|
7602
|
+
* @example
|
|
7603
|
+
* ```tsx
|
|
7604
|
+
* <SwipeableCard
|
|
7605
|
+
* onSwipeRight={() => handleApprove()}
|
|
7606
|
+
* onSwipeLeft={() => handleShowOptions()}
|
|
7607
|
+
* rightAction={{
|
|
7608
|
+
* icon: <Check />,
|
|
7609
|
+
* color: 'success',
|
|
7610
|
+
* label: 'Approve'
|
|
7611
|
+
* }}
|
|
7612
|
+
* leftAction={{
|
|
7613
|
+
* icon: <MoreHorizontal />,
|
|
7614
|
+
* color: 'neutral',
|
|
7615
|
+
* label: 'Options'
|
|
7616
|
+
* }}
|
|
7617
|
+
* >
|
|
7618
|
+
* <TransactionContent />
|
|
7619
|
+
* </SwipeableCard>
|
|
7620
|
+
* ```
|
|
7621
|
+
*/
|
|
7622
|
+
function SwipeableCard({ children, onSwipeRight, onSwipeLeft, rightAction = {
|
|
7623
|
+
icon: jsx(Check, { className: "h-6 w-6" }),
|
|
7624
|
+
color: 'success',
|
|
7625
|
+
label: 'Approve',
|
|
7626
|
+
}, leftAction = {
|
|
7627
|
+
icon: jsx(MoreHorizontal, { className: "h-6 w-6" }),
|
|
7628
|
+
color: 'neutral',
|
|
7629
|
+
label: 'Options',
|
|
7630
|
+
}, swipeThreshold = 100, hapticFeedback = true, disabled = false, onSwipeStart, onSwipeEnd, className = '', }) {
|
|
7631
|
+
const cardRef = useRef(null);
|
|
7632
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
7633
|
+
const [offsetX, setOffsetX] = useState(0);
|
|
7634
|
+
const [isTriggered, setIsTriggered] = useState(null);
|
|
7635
|
+
const startX = useRef(0);
|
|
7636
|
+
const startY = useRef(0);
|
|
7637
|
+
const isHorizontalSwipe = useRef(null);
|
|
7638
|
+
// Color classes for action backgrounds
|
|
7639
|
+
const colorClasses = {
|
|
7640
|
+
success: 'bg-success-500',
|
|
7641
|
+
error: 'bg-error-500',
|
|
7642
|
+
warning: 'bg-warning-500',
|
|
7643
|
+
neutral: 'bg-paper-400',
|
|
7644
|
+
primary: 'bg-accent-500',
|
|
7645
|
+
};
|
|
7646
|
+
// Trigger haptic feedback
|
|
7647
|
+
const triggerHaptic = useCallback((style = 'medium') => {
|
|
7648
|
+
if (!hapticFeedback)
|
|
7649
|
+
return;
|
|
7650
|
+
// Use Vibration API if available
|
|
7651
|
+
if ('vibrate' in navigator) {
|
|
7652
|
+
const patterns = {
|
|
7653
|
+
light: 10,
|
|
7654
|
+
medium: 25,
|
|
7655
|
+
heavy: [50, 30, 50],
|
|
7656
|
+
};
|
|
7657
|
+
navigator.vibrate(patterns[style]);
|
|
7658
|
+
}
|
|
7659
|
+
}, [hapticFeedback]);
|
|
7660
|
+
// Handle drag start
|
|
7661
|
+
const handleDragStart = useCallback((clientX, clientY) => {
|
|
7662
|
+
if (disabled)
|
|
7663
|
+
return;
|
|
7664
|
+
setIsDragging(true);
|
|
7665
|
+
startX.current = clientX;
|
|
7666
|
+
startY.current = clientY;
|
|
7667
|
+
isHorizontalSwipe.current = null;
|
|
7668
|
+
onSwipeStart?.();
|
|
7669
|
+
}, [disabled, onSwipeStart]);
|
|
7670
|
+
// Handle drag move
|
|
7671
|
+
const handleDragMove = useCallback((clientX, clientY) => {
|
|
7672
|
+
if (!isDragging || disabled)
|
|
7673
|
+
return;
|
|
7674
|
+
const deltaX = clientX - startX.current;
|
|
7675
|
+
const deltaY = clientY - startY.current;
|
|
7676
|
+
// Determine if this is a horizontal swipe on first significant movement
|
|
7677
|
+
if (isHorizontalSwipe.current === null) {
|
|
7678
|
+
const absDeltaX = Math.abs(deltaX);
|
|
7679
|
+
const absDeltaY = Math.abs(deltaY);
|
|
7680
|
+
if (absDeltaX > 10 || absDeltaY > 10) {
|
|
7681
|
+
isHorizontalSwipe.current = absDeltaX > absDeltaY;
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
// Only process horizontal swipes
|
|
7685
|
+
if (isHorizontalSwipe.current !== true)
|
|
7686
|
+
return;
|
|
7687
|
+
// Check if we should allow this direction
|
|
7688
|
+
const canSwipeRight = onSwipeRight !== undefined;
|
|
7689
|
+
const canSwipeLeft = onSwipeLeft !== undefined;
|
|
7690
|
+
let newOffset = deltaX;
|
|
7691
|
+
// Limit swipe direction based on available actions
|
|
7692
|
+
if (!canSwipeRight && deltaX > 0)
|
|
7693
|
+
newOffset = 0;
|
|
7694
|
+
if (!canSwipeLeft && deltaX < 0)
|
|
7695
|
+
newOffset = 0;
|
|
7696
|
+
// Add resistance when exceeding threshold
|
|
7697
|
+
const maxSwipe = swipeThreshold * 1.5;
|
|
7698
|
+
if (Math.abs(newOffset) > swipeThreshold) {
|
|
7699
|
+
const overflow = Math.abs(newOffset) - swipeThreshold;
|
|
7700
|
+
const resistance = overflow * 0.3;
|
|
7701
|
+
newOffset = newOffset > 0
|
|
7702
|
+
? swipeThreshold + resistance
|
|
7703
|
+
: -(swipeThreshold + resistance);
|
|
7704
|
+
newOffset = Math.max(-maxSwipe, Math.min(maxSwipe, newOffset));
|
|
7705
|
+
}
|
|
7706
|
+
setOffsetX(newOffset);
|
|
7707
|
+
// Check for threshold crossing and trigger haptic
|
|
7708
|
+
const newTriggered = Math.abs(newOffset) >= swipeThreshold
|
|
7709
|
+
? (newOffset > 0 ? 'right' : 'left')
|
|
7710
|
+
: null;
|
|
7711
|
+
if (newTriggered !== isTriggered) {
|
|
7712
|
+
if (newTriggered) {
|
|
7713
|
+
triggerHaptic('medium');
|
|
7714
|
+
}
|
|
7715
|
+
setIsTriggered(newTriggered);
|
|
7716
|
+
}
|
|
7717
|
+
}, [isDragging, disabled, onSwipeRight, onSwipeLeft, swipeThreshold, isTriggered, triggerHaptic]);
|
|
7718
|
+
// Handle drag end
|
|
7719
|
+
const handleDragEnd = useCallback(() => {
|
|
7720
|
+
if (!isDragging)
|
|
7721
|
+
return;
|
|
7722
|
+
setIsDragging(false);
|
|
7723
|
+
onSwipeEnd?.();
|
|
7724
|
+
// Check if action should be triggered
|
|
7725
|
+
if (Math.abs(offsetX) >= swipeThreshold) {
|
|
7726
|
+
if (offsetX > 0 && onSwipeRight) {
|
|
7727
|
+
triggerHaptic('heavy');
|
|
7728
|
+
// Animate card away then call handler
|
|
7729
|
+
setOffsetX(window.innerWidth);
|
|
7730
|
+
setTimeout(() => {
|
|
7731
|
+
onSwipeRight();
|
|
7732
|
+
setOffsetX(0);
|
|
7733
|
+
setIsTriggered(null);
|
|
7734
|
+
}, 200);
|
|
7735
|
+
return;
|
|
7736
|
+
}
|
|
7737
|
+
else if (offsetX < 0 && onSwipeLeft) {
|
|
7738
|
+
triggerHaptic('heavy');
|
|
7739
|
+
setOffsetX(-window.innerWidth);
|
|
7740
|
+
setTimeout(() => {
|
|
7741
|
+
onSwipeLeft();
|
|
7742
|
+
setOffsetX(0);
|
|
7743
|
+
setIsTriggered(null);
|
|
7744
|
+
}, 200);
|
|
7745
|
+
return;
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
// Snap back
|
|
7749
|
+
setOffsetX(0);
|
|
7750
|
+
setIsTriggered(null);
|
|
7751
|
+
}, [isDragging, offsetX, swipeThreshold, onSwipeRight, onSwipeLeft, onSwipeEnd, triggerHaptic]);
|
|
7752
|
+
// Touch event handlers
|
|
7753
|
+
const handleTouchStart = (e) => {
|
|
7754
|
+
handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
|
|
7755
|
+
};
|
|
7756
|
+
const handleTouchMove = (e) => {
|
|
7757
|
+
handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
7758
|
+
// Prevent vertical scroll if horizontal swipe
|
|
7759
|
+
if (isHorizontalSwipe.current === true) {
|
|
7760
|
+
e.preventDefault();
|
|
7761
|
+
}
|
|
7762
|
+
};
|
|
7763
|
+
const handleTouchEnd = () => {
|
|
7764
|
+
handleDragEnd();
|
|
7765
|
+
};
|
|
7766
|
+
// Mouse event handlers (for desktop testing)
|
|
7767
|
+
const handleMouseDown = (e) => {
|
|
7768
|
+
handleDragStart(e.clientX, e.clientY);
|
|
7769
|
+
};
|
|
7770
|
+
useEffect(() => {
|
|
7771
|
+
if (!isDragging)
|
|
7772
|
+
return;
|
|
7773
|
+
const handleMouseMove = (e) => {
|
|
7774
|
+
handleDragMove(e.clientX, e.clientY);
|
|
7775
|
+
};
|
|
7776
|
+
const handleMouseUp = () => {
|
|
7777
|
+
handleDragEnd();
|
|
7778
|
+
};
|
|
7779
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
7780
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
7781
|
+
return () => {
|
|
7782
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
7783
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
7784
|
+
};
|
|
7785
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
7786
|
+
// Calculate action opacity based on swipe distance
|
|
7787
|
+
const rightActionOpacity = offsetX > 0 ? Math.min(1, offsetX / swipeThreshold) : 0;
|
|
7788
|
+
const leftActionOpacity = offsetX < 0 ? Math.min(1, Math.abs(offsetX) / swipeThreshold) : 0;
|
|
7789
|
+
return (jsxs("div", { className: `relative overflow-hidden rounded-lg ${className}`, children: [onSwipeRight && (jsx("div", { className: `
|
|
7790
|
+
absolute inset-y-0 left-0 flex items-center justify-start pl-6
|
|
7791
|
+
${colorClasses[rightAction.color]}
|
|
7792
|
+
transition-opacity duration-100
|
|
7793
|
+
`, style: {
|
|
7794
|
+
opacity: rightActionOpacity,
|
|
7795
|
+
width: Math.abs(offsetX) + 20,
|
|
7796
|
+
}, "aria-hidden": "true", children: jsx("div", { className: `
|
|
7797
|
+
text-white transform transition-transform duration-200
|
|
7798
|
+
${isTriggered === 'right' ? 'scale-125' : 'scale-100'}
|
|
7799
|
+
`, children: rightAction.icon }) })), onSwipeLeft && (jsx("div", { className: `
|
|
7800
|
+
absolute inset-y-0 right-0 flex items-center justify-end pr-6
|
|
7801
|
+
${colorClasses[leftAction.color]}
|
|
7802
|
+
transition-opacity duration-100
|
|
7803
|
+
`, style: {
|
|
7804
|
+
opacity: leftActionOpacity,
|
|
7805
|
+
width: Math.abs(offsetX) + 20,
|
|
7806
|
+
}, "aria-hidden": "true", children: jsx("div", { className: `
|
|
7807
|
+
text-white transform transition-transform duration-200
|
|
7808
|
+
${isTriggered === 'left' ? 'scale-125' : 'scale-100'}
|
|
7809
|
+
`, children: leftAction.icon }) })), jsx("div", { ref: cardRef, className: `
|
|
7810
|
+
relative bg-white
|
|
7811
|
+
${isDragging ? '' : 'transition-transform duration-200 ease-out'}
|
|
7812
|
+
${disabled ? 'opacity-50 pointer-events-none' : ''}
|
|
7813
|
+
`, style: {
|
|
7814
|
+
transform: `translateX(${offsetX}px)`,
|
|
7815
|
+
}, 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 })] }));
|
|
7816
|
+
}
|
|
7817
|
+
|
|
7818
|
+
/**
|
|
7819
|
+
* NotificationBanner - Dismissible banner for important alerts
|
|
7820
|
+
*
|
|
7821
|
+
* Displays at top of screen for alerts that need attention but aren't blocking:
|
|
7822
|
+
* - Money Found alerts
|
|
7823
|
+
* - System messages
|
|
7824
|
+
* - Promotional info
|
|
7825
|
+
*
|
|
7826
|
+
* @example
|
|
7827
|
+
* ```tsx
|
|
7828
|
+
* <NotificationBanner
|
|
7829
|
+
* variant="warning"
|
|
7830
|
+
* icon={<DollarSign />}
|
|
7831
|
+
* title="Found $33.98 in potential savings"
|
|
7832
|
+
* description="Tap to review"
|
|
7833
|
+
* action={{
|
|
7834
|
+
* label: "Review",
|
|
7835
|
+
* onClick: handleReview
|
|
7836
|
+
* }}
|
|
7837
|
+
* onDismiss={() => setShowBanner(false)}
|
|
7838
|
+
* />
|
|
7839
|
+
* ```
|
|
7840
|
+
*/
|
|
7841
|
+
function NotificationBanner({ variant = 'info', icon, title, description, action, onDismiss, dismissible = true, sticky = false, className = '', }) {
|
|
7842
|
+
const bannerRef = useRef(null);
|
|
7843
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
7844
|
+
const [offsetX, setOffsetX] = useState(0);
|
|
7845
|
+
const [isDismissed, setIsDismissed] = useState(false);
|
|
7846
|
+
const startX = useRef(0);
|
|
7847
|
+
// Default icons based on variant
|
|
7848
|
+
const defaultIcons = {
|
|
7849
|
+
info: jsx(Info, { className: "h-5 w-5" }),
|
|
7850
|
+
success: jsx(CheckCircle, { className: "h-5 w-5" }),
|
|
7851
|
+
warning: jsx(AlertTriangle, { className: "h-5 w-5" }),
|
|
7852
|
+
error: jsx(AlertCircle, { className: "h-5 w-5" }),
|
|
7853
|
+
};
|
|
7854
|
+
// Color classes
|
|
7855
|
+
const variantClasses = {
|
|
7856
|
+
info: 'bg-gradient-to-r from-primary-50 to-primary-100 border-primary-200 text-primary-900',
|
|
7857
|
+
success: 'bg-gradient-to-r from-success-50 to-success-100 border-success-200 text-success-900',
|
|
7858
|
+
warning: 'bg-gradient-to-r from-warning-50 to-warning-100 border-warning-200 text-warning-900',
|
|
7859
|
+
error: 'bg-gradient-to-r from-error-50 to-error-100 border-error-200 text-error-900',
|
|
7860
|
+
};
|
|
7861
|
+
const iconColorClasses = {
|
|
7862
|
+
info: 'text-primary-600',
|
|
7863
|
+
success: 'text-success-600',
|
|
7864
|
+
warning: 'text-warning-600',
|
|
7865
|
+
error: 'text-error-600',
|
|
7866
|
+
};
|
|
7867
|
+
const buttonClasses = {
|
|
7868
|
+
info: 'bg-primary-600 hover:bg-primary-700 text-white',
|
|
7869
|
+
success: 'bg-success-600 hover:bg-success-700 text-white',
|
|
7870
|
+
warning: 'bg-warning-600 hover:bg-warning-700 text-white',
|
|
7871
|
+
error: 'bg-error-600 hover:bg-error-700 text-white',
|
|
7872
|
+
};
|
|
7873
|
+
// Handle swipe dismiss
|
|
7874
|
+
const handleDragStart = useCallback((clientX) => {
|
|
7875
|
+
if (!dismissible)
|
|
7876
|
+
return;
|
|
7877
|
+
setIsDragging(true);
|
|
7878
|
+
startX.current = clientX;
|
|
7879
|
+
}, [dismissible]);
|
|
7880
|
+
const handleDragMove = useCallback((clientX) => {
|
|
7881
|
+
if (!isDragging)
|
|
7882
|
+
return;
|
|
7883
|
+
const delta = clientX - startX.current;
|
|
7884
|
+
setOffsetX(delta);
|
|
7885
|
+
}, [isDragging]);
|
|
7886
|
+
const handleDragEnd = useCallback(() => {
|
|
7887
|
+
if (!isDragging)
|
|
7888
|
+
return;
|
|
7889
|
+
setIsDragging(false);
|
|
7890
|
+
const threshold = 100;
|
|
7891
|
+
if (Math.abs(offsetX) > threshold) {
|
|
7892
|
+
// Animate out
|
|
7893
|
+
setOffsetX(offsetX > 0 ? window.innerWidth : -window.innerWidth);
|
|
7894
|
+
setIsDismissed(true);
|
|
7895
|
+
setTimeout(() => {
|
|
7896
|
+
onDismiss?.();
|
|
7897
|
+
}, 200);
|
|
7898
|
+
}
|
|
7899
|
+
else {
|
|
7900
|
+
// Snap back
|
|
7901
|
+
setOffsetX(0);
|
|
7902
|
+
}
|
|
7903
|
+
}, [isDragging, offsetX, onDismiss]);
|
|
7904
|
+
// Touch handlers
|
|
7905
|
+
const handleTouchStart = (e) => {
|
|
7906
|
+
handleDragStart(e.touches[0].clientX);
|
|
7907
|
+
};
|
|
7908
|
+
const handleTouchMove = (e) => {
|
|
7909
|
+
handleDragMove(e.touches[0].clientX);
|
|
7910
|
+
};
|
|
7911
|
+
const handleTouchEnd = () => {
|
|
7912
|
+
handleDragEnd();
|
|
7913
|
+
};
|
|
7914
|
+
// Mouse handlers for desktop testing
|
|
7915
|
+
const handleMouseDown = (e) => {
|
|
7916
|
+
if (dismissible) {
|
|
7917
|
+
handleDragStart(e.clientX);
|
|
7918
|
+
}
|
|
7919
|
+
};
|
|
7920
|
+
useEffect(() => {
|
|
7921
|
+
if (!isDragging)
|
|
7922
|
+
return;
|
|
7923
|
+
const handleMouseMove = (e) => {
|
|
7924
|
+
handleDragMove(e.clientX);
|
|
7925
|
+
};
|
|
7926
|
+
const handleMouseUp = () => {
|
|
7927
|
+
handleDragEnd();
|
|
7928
|
+
};
|
|
7929
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
7930
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
7931
|
+
return () => {
|
|
7932
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
7933
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
7934
|
+
};
|
|
7935
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
7936
|
+
if (isDismissed)
|
|
7937
|
+
return null;
|
|
7938
|
+
return (jsx("div", { ref: bannerRef, className: `
|
|
7939
|
+
w-full border-b
|
|
7940
|
+
${variantClasses[variant]}
|
|
7941
|
+
${sticky ? 'sticky top-0 z-40' : ''}
|
|
7942
|
+
${isDragging ? '' : 'transition-transform duration-200 ease-out'}
|
|
7943
|
+
${className}
|
|
7944
|
+
`, style: {
|
|
7945
|
+
transform: `translateX(${offsetX}px)`,
|
|
7946
|
+
opacity: Math.max(0, 1 - Math.abs(offsetX) / 200),
|
|
7947
|
+
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, role: "alert", children: jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [jsx("div", { className: `flex-shrink-0 ${iconColorClasses[variant]}`, children: icon || defaultIcons[variant] }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("p", { className: "text-sm font-medium truncate", children: title }), description && (jsx("p", { className: "text-xs opacity-80 truncate", children: description }))] }), action && (jsx("button", { onClick: action.onClick, className: `
|
|
7948
|
+
flex-shrink-0 px-3 py-1.5 text-xs font-medium rounded-md
|
|
7949
|
+
transition-colors duration-200
|
|
7950
|
+
${buttonClasses[variant]}
|
|
7951
|
+
`, children: action.label })), onDismiss && (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: jsx(X, { className: "h-4 w-4" }) }))] }) }));
|
|
7952
|
+
}
|
|
7953
|
+
|
|
7954
|
+
/**
|
|
7955
|
+
* CompactStat - Single stat display optimized for mobile
|
|
7956
|
+
*
|
|
7957
|
+
* Designed for dashboard stats in 2-column mobile layouts:
|
|
7958
|
+
* - Compact presentation with value, label, and optional trend
|
|
7959
|
+
* - Responsive sizing
|
|
7960
|
+
* - Trend indicators with color coding
|
|
7961
|
+
*
|
|
7962
|
+
* @example
|
|
7963
|
+
* ```tsx
|
|
7964
|
+
* <Grid columns={2} gap="sm">
|
|
7965
|
+
* <CompactStat
|
|
7966
|
+
* value="$62,329"
|
|
7967
|
+
* label="Net Worth"
|
|
7968
|
+
* trend={{
|
|
7969
|
+
* direction: 'up',
|
|
7970
|
+
* value: '+$1,247',
|
|
7971
|
+
* color: 'success'
|
|
7972
|
+
* }}
|
|
7973
|
+
* />
|
|
7974
|
+
* <CompactStat
|
|
7975
|
+
* value="$4,521"
|
|
7976
|
+
* label="Monthly Income"
|
|
7977
|
+
* />
|
|
7978
|
+
* </Grid>
|
|
7979
|
+
* ```
|
|
7980
|
+
*/
|
|
7981
|
+
function CompactStat({ value, label, trend, size = 'md', align = 'left', className = '', }) {
|
|
7982
|
+
// Size classes
|
|
7983
|
+
const sizeClasses = {
|
|
7984
|
+
sm: {
|
|
7985
|
+
value: 'text-lg font-semibold',
|
|
7986
|
+
label: 'text-xs',
|
|
7987
|
+
trend: 'text-xs',
|
|
7988
|
+
icon: 'h-3 w-3',
|
|
7989
|
+
},
|
|
7990
|
+
md: {
|
|
7991
|
+
value: 'text-xl font-semibold',
|
|
7992
|
+
label: 'text-sm',
|
|
7993
|
+
trend: 'text-xs',
|
|
7994
|
+
icon: 'h-3.5 w-3.5',
|
|
7995
|
+
},
|
|
7996
|
+
lg: {
|
|
7997
|
+
value: 'text-2xl font-bold',
|
|
7998
|
+
label: 'text-sm',
|
|
7999
|
+
trend: 'text-sm',
|
|
8000
|
+
icon: 'h-4 w-4',
|
|
8001
|
+
},
|
|
8002
|
+
};
|
|
8003
|
+
// Alignment classes
|
|
8004
|
+
const alignClasses = {
|
|
8005
|
+
left: 'text-left',
|
|
8006
|
+
center: 'text-center',
|
|
8007
|
+
right: 'text-right',
|
|
8008
|
+
};
|
|
8009
|
+
// Trend color classes
|
|
8010
|
+
const getTrendColor = (trend) => {
|
|
8011
|
+
if (trend.color) {
|
|
8012
|
+
const colorMap = {
|
|
8013
|
+
success: 'text-success-600',
|
|
8014
|
+
error: 'text-error-600',
|
|
8015
|
+
warning: 'text-warning-600',
|
|
8016
|
+
neutral: 'text-ink-500',
|
|
8017
|
+
};
|
|
8018
|
+
return colorMap[trend.color];
|
|
8019
|
+
}
|
|
8020
|
+
// Default colors based on direction
|
|
8021
|
+
const directionColors = {
|
|
8022
|
+
up: 'text-success-600',
|
|
8023
|
+
down: 'text-error-600',
|
|
8024
|
+
neutral: 'text-ink-500',
|
|
8025
|
+
};
|
|
8026
|
+
return directionColors[trend.direction];
|
|
8027
|
+
};
|
|
8028
|
+
// Trend icons
|
|
8029
|
+
const TrendIcon = trend ? {
|
|
8030
|
+
up: TrendingUp,
|
|
8031
|
+
down: TrendingDown,
|
|
8032
|
+
neutral: Minus,
|
|
8033
|
+
}[trend.direction] : null;
|
|
8034
|
+
const sizes = sizeClasses[size];
|
|
8035
|
+
return (jsxs("div", { className: `${alignClasses[align]} ${className}`, children: [jsx("div", { className: `${sizes.value} text-ink-900 tracking-tight`, children: value }), jsx("div", { className: `${sizes.label} text-ink-500 mt-0.5`, children: label }), trend && (jsxs("div", { className: `
|
|
8036
|
+
flex items-center gap-1 mt-1
|
|
8037
|
+
${align === 'center' ? 'justify-center' : ''}
|
|
8038
|
+
${align === 'right' ? 'justify-end' : ''}
|
|
8039
|
+
${sizes.trend} ${getTrendColor(trend)}
|
|
8040
|
+
`, children: [TrendIcon && jsx(TrendIcon, { className: sizes.icon }), jsx("span", { children: trend.value })] }))] }));
|
|
8041
|
+
}
|
|
8042
|
+
|
|
7288
8043
|
/**
|
|
7289
8044
|
* Hook to detect breadcrumb navigation and trigger callbacks.
|
|
7290
8045
|
* Use this in host components to reset state when a breadcrumb is clicked.
|
|
@@ -7568,7 +8323,7 @@ function StepIndicator({ steps, currentStep, variant = 'horizontal', onStepClick
|
|
|
7568
8323
|
}) }) }));
|
|
7569
8324
|
}
|
|
7570
8325
|
|
|
7571
|
-
function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, }) {
|
|
8326
|
+
function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, pill = false, }) {
|
|
7572
8327
|
const variantStyles = {
|
|
7573
8328
|
success: 'bg-success-50 text-success-700 border-success-200',
|
|
7574
8329
|
warning: 'bg-warning-50 text-warning-700 border-warning-200',
|
|
@@ -7588,6 +8343,12 @@ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, cla
|
|
|
7588
8343
|
md: 'px-3 py-1 text-xs gap-1.5',
|
|
7589
8344
|
lg: 'px-3 py-1.5 text-sm gap-2',
|
|
7590
8345
|
};
|
|
8346
|
+
// Pill variant has tighter horizontal padding and fully rounded ends
|
|
8347
|
+
const pillSizeStyles = {
|
|
8348
|
+
sm: 'px-1.5 py-0.5 text-xs gap-1',
|
|
8349
|
+
md: 'px-2 py-0.5 text-xs gap-1',
|
|
8350
|
+
lg: 'px-2.5 py-1 text-sm gap-1.5',
|
|
8351
|
+
};
|
|
7591
8352
|
const dotSizeStyles = {
|
|
7592
8353
|
sm: 'h-1.5 w-1.5',
|
|
7593
8354
|
md: 'h-2 w-2',
|
|
@@ -7609,9 +8370,10 @@ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, cla
|
|
|
7609
8370
|
}
|
|
7610
8371
|
// Regular badge
|
|
7611
8372
|
return (jsxs("span", { className: `
|
|
7612
|
-
inline-flex items-center
|
|
8373
|
+
inline-flex items-center border font-medium
|
|
8374
|
+
${pill ? 'rounded-full' : 'rounded-full'}
|
|
7613
8375
|
${variantStyles[variant]}
|
|
7614
|
-
${sizeStyles[size]}
|
|
8376
|
+
${pill ? pillSizeStyles[size] : sizeStyles[size]}
|
|
7615
8377
|
${className}
|
|
7616
8378
|
`, children: [icon && jsx("span", { className: iconSize[size], children: icon }), jsx("span", { children: children }), onRemove && (jsx("button", { onClick: onRemove, className: "ml-1 hover:opacity-70 transition-opacity", "aria-label": "Remove badge", children: jsx(X, { className: iconSize[size] }) }))] }));
|
|
7617
8379
|
}
|
|
@@ -7719,8 +8481,10 @@ function Progress({ value, variant = 'linear', size = 'md', color = 'primary', s
|
|
|
7719
8481
|
warning: 'bg-warning-100',
|
|
7720
8482
|
error: 'bg-error-100',
|
|
7721
8483
|
};
|
|
8484
|
+
// Normalize 'ring' to 'circular'
|
|
8485
|
+
const normalizedVariant = variant === 'ring' ? 'circular' : variant;
|
|
7722
8486
|
// Linear progress
|
|
7723
|
-
if (
|
|
8487
|
+
if (normalizedVariant === 'linear') {
|
|
7724
8488
|
const heightClasses = {
|
|
7725
8489
|
sm: 'h-1',
|
|
7726
8490
|
md: 'h-2',
|
|
@@ -8652,94 +9416,112 @@ function useFABScroll(threshold = 10) {
|
|
|
8652
9416
|
}
|
|
8653
9417
|
|
|
8654
9418
|
/**
|
|
8655
|
-
* PullToRefresh -
|
|
9419
|
+
* PullToRefresh - Pull-down refresh indicator and handler for mobile lists
|
|
8656
9420
|
*
|
|
8657
|
-
* Wraps content
|
|
8658
|
-
*
|
|
9421
|
+
* Wraps content to enable pull-to-refresh behavior on mobile:
|
|
9422
|
+
* - Pull down to trigger refresh
|
|
9423
|
+
* - Visual feedback showing progress
|
|
9424
|
+
* - Custom content for each state
|
|
8659
9425
|
*
|
|
8660
|
-
* @example
|
|
8661
|
-
* ```tsx
|
|
8662
|
-
* <PullToRefresh onRefresh={async () => {
|
|
8663
|
-
* await fetchLatestData();
|
|
8664
|
-
* }}>
|
|
8665
|
-
* <div className="min-h-screen">
|
|
8666
|
-
* {content}
|
|
8667
|
-
* </div>
|
|
8668
|
-
* </PullToRefresh>
|
|
8669
|
-
* ```
|
|
8670
|
-
*
|
|
8671
|
-
* @example With custom threshold
|
|
9426
|
+
* @example
|
|
8672
9427
|
* ```tsx
|
|
8673
|
-
* <PullToRefresh
|
|
8674
|
-
*
|
|
8675
|
-
* pullThreshold={100}
|
|
8676
|
-
* maxPull={150}
|
|
8677
|
-
* >
|
|
8678
|
-
* {content}
|
|
9428
|
+
* <PullToRefresh onRefresh={async () => { await syncData(); }}>
|
|
9429
|
+
* <TransactionList transactions={transactions} />
|
|
8679
9430
|
* </PullToRefresh>
|
|
8680
9431
|
* ```
|
|
8681
9432
|
*/
|
|
8682
|
-
function PullToRefresh({ children, onRefresh,
|
|
9433
|
+
function PullToRefresh({ children, onRefresh, threshold = 80, disabled = false, pullingContent, releaseContent, refreshingContent, completeContent, className = '', }) {
|
|
9434
|
+
const containerRef = useRef(null);
|
|
8683
9435
|
const [state, setState] = useState('idle');
|
|
8684
9436
|
const [pullDistance, setPullDistance] = useState(0);
|
|
8685
|
-
const containerRef = useRef(null);
|
|
8686
9437
|
const startY = useRef(0);
|
|
8687
9438
|
const currentY = useRef(0);
|
|
8688
|
-
|
|
9439
|
+
const isDragging = useRef(false);
|
|
9440
|
+
// Check if content is at top (can pull to refresh)
|
|
8689
9441
|
const isAtTop = useCallback(() => {
|
|
8690
9442
|
const container = containerRef.current;
|
|
8691
9443
|
if (!container)
|
|
8692
9444
|
return false;
|
|
8693
|
-
|
|
9445
|
+
// Check if the scrollable content is at the top
|
|
9446
|
+
const scrollableParent = container.querySelector('[data-ptr-scrollable]') || container;
|
|
9447
|
+
return scrollableParent.scrollTop <= 0;
|
|
8694
9448
|
}, []);
|
|
8695
|
-
// Handle
|
|
9449
|
+
// Handle pull start
|
|
8696
9450
|
const handleTouchStart = useCallback((e) => {
|
|
8697
|
-
if (disabled || state === 'refreshing'
|
|
9451
|
+
if (disabled || state === 'refreshing')
|
|
8698
9452
|
return;
|
|
9453
|
+
if (!isAtTop())
|
|
9454
|
+
return;
|
|
9455
|
+
isDragging.current = true;
|
|
8699
9456
|
startY.current = e.touches[0].clientY;
|
|
8700
|
-
currentY.current =
|
|
9457
|
+
currentY.current = e.touches[0].clientY;
|
|
8701
9458
|
}, [disabled, state, isAtTop]);
|
|
8702
|
-
// Handle
|
|
9459
|
+
// Handle pull move
|
|
8703
9460
|
const handleTouchMove = useCallback((e) => {
|
|
8704
|
-
if (disabled || state === 'refreshing')
|
|
8705
|
-
return;
|
|
8706
|
-
if (startY.current === 0)
|
|
9461
|
+
if (!isDragging.current || disabled || state === 'refreshing')
|
|
8707
9462
|
return;
|
|
8708
9463
|
currentY.current = e.touches[0].clientY;
|
|
8709
|
-
const
|
|
8710
|
-
// Only
|
|
8711
|
-
if (
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
9464
|
+
const delta = currentY.current - startY.current;
|
|
9465
|
+
// Only activate pull-to-refresh when pulling down
|
|
9466
|
+
if (delta < 0) {
|
|
9467
|
+
isDragging.current = false;
|
|
9468
|
+
setPullDistance(0);
|
|
9469
|
+
setState('idle');
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
// Check if we're at the top before allowing pull
|
|
9473
|
+
if (!isAtTop()) {
|
|
9474
|
+
isDragging.current = false;
|
|
9475
|
+
return;
|
|
9476
|
+
}
|
|
9477
|
+
// Apply resistance to pull
|
|
9478
|
+
const resistance = 0.5;
|
|
9479
|
+
const resistedDelta = delta * resistance;
|
|
9480
|
+
const maxPull = threshold * 2;
|
|
9481
|
+
const clampedDelta = Math.min(resistedDelta, maxPull);
|
|
9482
|
+
setPullDistance(clampedDelta);
|
|
9483
|
+
// Update state based on pull distance
|
|
9484
|
+
if (clampedDelta >= threshold) {
|
|
9485
|
+
setState('ready');
|
|
9486
|
+
}
|
|
9487
|
+
else if (clampedDelta > 0) {
|
|
9488
|
+
setState('pulling');
|
|
9489
|
+
}
|
|
9490
|
+
// Prevent default scroll when pulling
|
|
9491
|
+
if (delta > 0 && isAtTop()) {
|
|
9492
|
+
e.preventDefault();
|
|
8721
9493
|
}
|
|
8722
|
-
}, [disabled, state,
|
|
8723
|
-
// Handle
|
|
9494
|
+
}, [disabled, state, threshold, isAtTop]);
|
|
9495
|
+
// Handle pull end
|
|
8724
9496
|
const handleTouchEnd = useCallback(async () => {
|
|
8725
|
-
if (
|
|
9497
|
+
if (!isDragging.current)
|
|
8726
9498
|
return;
|
|
8727
|
-
|
|
9499
|
+
isDragging.current = false;
|
|
9500
|
+
if (state === 'ready' && pullDistance >= threshold) {
|
|
8728
9501
|
setState('refreshing');
|
|
8729
|
-
setPullDistance(
|
|
9502
|
+
setPullDistance(threshold * 0.6); // Settle at a smaller height while refreshing
|
|
8730
9503
|
try {
|
|
8731
9504
|
await onRefresh();
|
|
9505
|
+
setState('complete');
|
|
9506
|
+
// Show complete state briefly
|
|
9507
|
+
setTimeout(() => {
|
|
9508
|
+
setState('idle');
|
|
9509
|
+
setPullDistance(0);
|
|
9510
|
+
}, 500);
|
|
8732
9511
|
}
|
|
8733
9512
|
catch (error) {
|
|
8734
9513
|
console.error('Refresh failed:', error);
|
|
9514
|
+
setState('idle');
|
|
9515
|
+
setPullDistance(0);
|
|
8735
9516
|
}
|
|
9517
|
+
}
|
|
9518
|
+
else {
|
|
9519
|
+
// Snap back
|
|
8736
9520
|
setState('idle');
|
|
9521
|
+
setPullDistance(0);
|
|
8737
9522
|
}
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
currentY.current = 0;
|
|
8741
|
-
}, [disabled, state, pullThreshold, onRefresh]);
|
|
8742
|
-
// Attach touch listeners
|
|
9523
|
+
}, [state, pullDistance, threshold, onRefresh]);
|
|
9524
|
+
// Attach touch event listeners
|
|
8743
9525
|
useEffect(() => {
|
|
8744
9526
|
const container = containerRef.current;
|
|
8745
9527
|
if (!container)
|
|
@@ -8753,99 +9535,41 @@ function PullToRefresh({ children, onRefresh, disabled = false, pullThreshold =
|
|
|
8753
9535
|
container.removeEventListener('touchend', handleTouchEnd);
|
|
8754
9536
|
};
|
|
8755
9537
|
}, [handleTouchStart, handleTouchMove, handleTouchEnd]);
|
|
8756
|
-
// Calculate
|
|
8757
|
-
const progress = Math.min(pullDistance /
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
const
|
|
8761
|
-
|
|
8762
|
-
const
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
9538
|
+
// Calculate progress percentage
|
|
9539
|
+
const progress = Math.min(1, pullDistance / threshold);
|
|
9540
|
+
// Default content for each state
|
|
9541
|
+
const defaultPullingContent = (jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsx(ArrowDown, { className: "h-5 w-5 text-ink-400 transition-transform duration-200", style: { transform: `rotate(${progress * 180}deg)` } }), jsx("span", { className: "text-xs text-ink-500", children: "Pull to refresh" })] }));
|
|
9542
|
+
const defaultReleaseContent = (jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsx(ArrowDown, { className: "h-5 w-5 text-accent-500 rotate-180" }), jsx("span", { className: "text-xs text-accent-600 font-medium", children: "Release to refresh" })] }));
|
|
9543
|
+
const defaultRefreshingContent = (jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsx(Loader2, { className: "h-5 w-5 text-accent-500 animate-spin" }), jsx("span", { className: "text-xs text-ink-500", children: "Refreshing..." })] }));
|
|
9544
|
+
const defaultCompleteContent = (jsxs("div", { className: "flex flex-col items-center gap-1", children: [jsx(Check, { className: "h-5 w-5 text-success-500" }), jsx("span", { className: "text-xs text-success-600", children: "Done!" })] }));
|
|
9545
|
+
// Get content based on current state
|
|
9546
|
+
const getIndicatorContent = () => {
|
|
9547
|
+
switch (state) {
|
|
9548
|
+
case 'pulling':
|
|
9549
|
+
return pullingContent || defaultPullingContent;
|
|
9550
|
+
case 'ready':
|
|
9551
|
+
return releaseContent || defaultReleaseContent;
|
|
9552
|
+
case 'refreshing':
|
|
9553
|
+
return refreshingContent || defaultRefreshingContent;
|
|
9554
|
+
case 'complete':
|
|
9555
|
+
return completeContent || defaultCompleteContent;
|
|
9556
|
+
default:
|
|
9557
|
+
return null;
|
|
9558
|
+
}
|
|
9559
|
+
};
|
|
9560
|
+
return (jsxs("div", { ref: containerRef, className: `relative overflow-hidden ${className}`, children: [jsx("div", { className: `
|
|
9561
|
+
absolute top-0 left-0 right-0
|
|
9562
|
+
flex items-center justify-center
|
|
9563
|
+
bg-paper-50
|
|
9564
|
+
transition-all duration-200 ease-out
|
|
9565
|
+
${state === 'idle' ? 'opacity-0' : 'opacity-100'}
|
|
8770
9566
|
`, style: {
|
|
8771
|
-
height:
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
}, children: jsx("div", { className: `
|
|
8775
|
-
w-10 h-10 rounded-full bg-white shadow-md
|
|
8776
|
-
flex items-center justify-center
|
|
8777
|
-
transition-transform duration-200
|
|
8778
|
-
${state === 'refreshing' ? 'scale-100' : progress < 0.3 ? 'scale-75' : 'scale-100'}
|
|
8779
|
-
`, children: state === 'refreshing'
|
|
8780
|
-
? (loadingIndicator || defaultLoadingIndicator)
|
|
8781
|
-
: (pullIndicator || defaultPullIndicator) }) }), jsx("div", { className: "transition-transform duration-200", style: {
|
|
9567
|
+
height: pullDistance,
|
|
9568
|
+
transform: state === 'idle' ? 'translateY(-100%)' : 'translateY(0)',
|
|
9569
|
+
}, children: getIndicatorContent() }), jsx("div", { className: "transition-transform duration-200 ease-out", style: {
|
|
8782
9570
|
transform: `translateY(${pullDistance}px)`,
|
|
8783
9571
|
}, children: children })] }));
|
|
8784
9572
|
}
|
|
8785
|
-
/**
|
|
8786
|
-
* usePullToRefresh - Hook for custom pull-to-refresh implementations
|
|
8787
|
-
*
|
|
8788
|
-
* @example
|
|
8789
|
-
* ```tsx
|
|
8790
|
-
* const { pullDistance, isRefreshing, bind } = usePullToRefresh({
|
|
8791
|
-
* onRefresh: async () => {
|
|
8792
|
-
* await fetchData();
|
|
8793
|
-
* }
|
|
8794
|
-
* });
|
|
8795
|
-
*
|
|
8796
|
-
* return (
|
|
8797
|
-
* <div {...bind}>
|
|
8798
|
-
* {isRefreshing && <Spinner />}
|
|
8799
|
-
* {content}
|
|
8800
|
-
* </div>
|
|
8801
|
-
* );
|
|
8802
|
-
* ```
|
|
8803
|
-
*/
|
|
8804
|
-
function usePullToRefresh({ onRefresh, pullThreshold = 80, maxPull = 120, disabled = false, }) {
|
|
8805
|
-
const [pullDistance, setPullDistance] = useState(0);
|
|
8806
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
8807
|
-
const startY = useRef(0);
|
|
8808
|
-
const handleTouchStart = useCallback((e) => {
|
|
8809
|
-
if (disabled || isRefreshing)
|
|
8810
|
-
return;
|
|
8811
|
-
startY.current = e.touches[0].clientY;
|
|
8812
|
-
}, [disabled, isRefreshing]);
|
|
8813
|
-
const handleTouchMove = useCallback((e) => {
|
|
8814
|
-
if (disabled || isRefreshing || startY.current === 0)
|
|
8815
|
-
return;
|
|
8816
|
-
const diff = e.touches[0].clientY - startY.current;
|
|
8817
|
-
if (diff > 0) {
|
|
8818
|
-
const adjustedPull = Math.min(diff * 0.5, maxPull);
|
|
8819
|
-
setPullDistance(adjustedPull);
|
|
8820
|
-
}
|
|
8821
|
-
}, [disabled, isRefreshing, maxPull]);
|
|
8822
|
-
const handleTouchEnd = useCallback(async () => {
|
|
8823
|
-
if (disabled || isRefreshing)
|
|
8824
|
-
return;
|
|
8825
|
-
if (pullDistance >= pullThreshold) {
|
|
8826
|
-
setIsRefreshing(true);
|
|
8827
|
-
try {
|
|
8828
|
-
await onRefresh();
|
|
8829
|
-
}
|
|
8830
|
-
finally {
|
|
8831
|
-
setIsRefreshing(false);
|
|
8832
|
-
}
|
|
8833
|
-
}
|
|
8834
|
-
setPullDistance(0);
|
|
8835
|
-
startY.current = 0;
|
|
8836
|
-
}, [disabled, isRefreshing, pullDistance, pullThreshold, onRefresh]);
|
|
8837
|
-
return {
|
|
8838
|
-
pullDistance,
|
|
8839
|
-
isRefreshing,
|
|
8840
|
-
isReady: pullDistance >= pullThreshold,
|
|
8841
|
-
progress: Math.min(pullDistance / pullThreshold, 1),
|
|
8842
|
-
bind: {
|
|
8843
|
-
onTouchStart: handleTouchStart,
|
|
8844
|
-
onTouchMove: handleTouchMove,
|
|
8845
|
-
onTouchEnd: handleTouchEnd,
|
|
8846
|
-
},
|
|
8847
|
-
};
|
|
8848
|
-
}
|
|
8849
9573
|
|
|
8850
9574
|
function Logo({ size = 'md', showText = true, text = 'Commora', className = '', }) {
|
|
8851
9575
|
const sizes = {
|
|
@@ -10348,44 +11072,52 @@ function getAugmentedNamespace(n) {
|
|
|
10348
11072
|
* (A1, A1:C5, ...)
|
|
10349
11073
|
*/
|
|
10350
11074
|
|
|
10351
|
-
|
|
11075
|
+
var collection;
|
|
11076
|
+
var hasRequiredCollection;
|
|
11077
|
+
|
|
11078
|
+
function requireCollection () {
|
|
11079
|
+
if (hasRequiredCollection) return collection;
|
|
11080
|
+
hasRequiredCollection = 1;
|
|
11081
|
+
class Collection {
|
|
10352
11082
|
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
11083
|
+
constructor(data, refs) {
|
|
11084
|
+
if (data == null && refs == null) {
|
|
11085
|
+
this._data = [];
|
|
11086
|
+
this._refs = [];
|
|
11087
|
+
} else {
|
|
11088
|
+
if (data.length !== refs.length)
|
|
11089
|
+
throw Error('Collection: data length should match references length.');
|
|
11090
|
+
this._data = data;
|
|
11091
|
+
this._refs = refs;
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
10364
11094
|
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
11095
|
+
get data() {
|
|
11096
|
+
return this._data;
|
|
11097
|
+
}
|
|
10368
11098
|
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
11099
|
+
get refs() {
|
|
11100
|
+
return this._refs;
|
|
11101
|
+
}
|
|
10372
11102
|
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
11103
|
+
get length() {
|
|
11104
|
+
return this._data.length;
|
|
11105
|
+
}
|
|
10376
11106
|
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
}
|
|
11107
|
+
/**
|
|
11108
|
+
* Add data and references to this collection.
|
|
11109
|
+
* @param {{}} obj - data
|
|
11110
|
+
* @param {{}} ref - reference
|
|
11111
|
+
*/
|
|
11112
|
+
add(obj, ref) {
|
|
11113
|
+
this._data.push(obj);
|
|
11114
|
+
this._refs.push(ref);
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
10387
11117
|
|
|
10388
|
-
|
|
11118
|
+
collection = Collection;
|
|
11119
|
+
return collection;
|
|
11120
|
+
}
|
|
10389
11121
|
|
|
10390
11122
|
var helpers;
|
|
10391
11123
|
var hasRequiredHelpers;
|
|
@@ -10394,7 +11126,7 @@ function requireHelpers () {
|
|
|
10394
11126
|
if (hasRequiredHelpers) return helpers;
|
|
10395
11127
|
hasRequiredHelpers = 1;
|
|
10396
11128
|
const FormulaError = requireError();
|
|
10397
|
-
const Collection =
|
|
11129
|
+
const Collection = requireCollection();
|
|
10398
11130
|
|
|
10399
11131
|
const Types = {
|
|
10400
11132
|
NUMBER: 0,
|
|
@@ -20048,7 +20780,7 @@ var engineering = EngineeringFunctions;
|
|
|
20048
20780
|
|
|
20049
20781
|
const FormulaError$b = requireError();
|
|
20050
20782
|
const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
|
|
20051
|
-
const Collection$2 =
|
|
20783
|
+
const Collection$2 = requireCollection();
|
|
20052
20784
|
const H$5 = FormulaHelpers$8;
|
|
20053
20785
|
|
|
20054
20786
|
const ReferenceFunctions$1 = {
|
|
@@ -31676,7 +32408,7 @@ var parsing = {
|
|
|
31676
32408
|
const FormulaError$4 = requireError();
|
|
31677
32409
|
const {Address: Address$1} = requireHelpers();
|
|
31678
32410
|
const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
|
|
31679
|
-
const Collection$1 =
|
|
32411
|
+
const Collection$1 = requireCollection();
|
|
31680
32412
|
const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
|
|
31681
32413
|
const {NotAllInputParsedException} = require$$4;
|
|
31682
32414
|
|
|
@@ -32438,7 +33170,7 @@ var hooks$1 = {
|
|
|
32438
33170
|
const FormulaError$2 = requireError();
|
|
32439
33171
|
const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
|
|
32440
33172
|
const {Prefix, Postfix, Infix, Operators} = operators;
|
|
32441
|
-
const Collection =
|
|
33173
|
+
const Collection = requireCollection();
|
|
32442
33174
|
const MAX_ROW = 1048576, MAX_COLUMN = 16384;
|
|
32443
33175
|
|
|
32444
33176
|
let Utils$1 = class Utils {
|
|
@@ -56928,5 +57660,5 @@ function Responsive({ mobile, tablet, desktop, }) {
|
|
|
56928
57660
|
return jsx(Fragment, { children: mobile || tablet || desktop });
|
|
56929
57661
|
}
|
|
56930
57662
|
|
|
56931
|
-
export { Accordion, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Checkbox, CheckboxList, Chip, ChipGroup, Collapsible, ColorPicker, Combobox, ComingSoon, CommandPalette, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, Hide, HoverCard, InfiniteScroll, Input, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MultiSelect, NotificationBar, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, Popover, Progress, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, SwipeActions, Switch, Tabs, Text, Textarea, ThemeToggle, TimePicker, Timeline, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile,
|
|
57663
|
+
export { Accordion, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, BottomSheetActions, BottomSheetContent, BottomSheetHeader, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Checkbox, CheckboxList, Chip, ChipGroup, Collapsible, ColorPicker, Combobox, ComingSoon, CommandPalette, CompactStat, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, Hide, HorizontalScroll, HoverCard, InfiniteScroll, Input, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MultiSelect, NotificationBanner, NotificationBar, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, Popover, Progress, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, SwipeActions, SwipeableCard, Switch, Tabs, Text, Textarea, ThemeToggle, TimePicker, Timeline, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile, useResponsiveCallback, useSafeAreaInsets, useViewportSize, withMobileContext };
|
|
56932
57664
|
//# sourceMappingURL=index.esm.js.map
|