infinity-ui-elements 1.9.27 → 1.13.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/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/components/Dropdown/DropdownMenu.d.ts +0 -6
- package/dist/components/Dropdown/DropdownMenu.d.ts.map +1 -1
- package/dist/components/FileInput/FileInput.d.ts +38 -0
- package/dist/components/FileInput/FileInput.d.ts.map +1 -0
- package/dist/components/FileInput/FileInput.stories.d.ts +27 -0
- package/dist/components/FileInput/FileInput.stories.d.ts.map +1 -0
- package/dist/components/FileInput/index.d.ts +3 -0
- package/dist/components/FileInput/index.d.ts.map +1 -0
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +262 -104
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +263 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -3117,79 +3117,29 @@ const DateRangePicker = React.forwardRef(({ className, value: controlledValue, d
|
|
|
3117
3117
|
});
|
|
3118
3118
|
DateRangePicker.displayName = "DateRangePicker";
|
|
3119
3119
|
|
|
3120
|
-
const DropdownMenu = React.forwardRef(({ items = [], customContent, customContentClassName, sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, showFooter, footerLayout = "horizontal", onClose, focusedIndex = -1, className, width = "auto", maxHeight = "400px", unstyled = false,
|
|
3121
|
-
const menuRef = React.useRef(null);
|
|
3122
|
-
const [viewportConstraints, setViewportConstraints] = React.useState({ maxHeight: null, maxWidth: null });
|
|
3123
|
-
const setRef = React.useCallback((el) => {
|
|
3124
|
-
menuRef.current = el;
|
|
3125
|
-
if (typeof ref === "function")
|
|
3126
|
-
ref(el);
|
|
3127
|
-
else if (ref)
|
|
3128
|
-
ref.current = el;
|
|
3129
|
-
}, [ref]);
|
|
3130
|
-
React.useEffect(() => {
|
|
3131
|
-
if (!constrainToViewport || !menuRef.current)
|
|
3132
|
-
return;
|
|
3133
|
-
const VIEWPORT_PADDING = 8;
|
|
3134
|
-
const updateConstraints = () => {
|
|
3135
|
-
const el = menuRef.current;
|
|
3136
|
-
if (!el)
|
|
3137
|
-
return;
|
|
3138
|
-
const rect = el.getBoundingClientRect();
|
|
3139
|
-
const viewportHeight = window.innerHeight;
|
|
3140
|
-
const viewportWidth = window.innerWidth;
|
|
3141
|
-
const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_PADDING;
|
|
3142
|
-
const spaceRight = viewportWidth - rect.left - VIEWPORT_PADDING;
|
|
3143
|
-
setViewportConstraints({
|
|
3144
|
-
maxHeight: Math.max(0, Math.floor(spaceBelow)),
|
|
3145
|
-
maxWidth: rect.right > viewportWidth - VIEWPORT_PADDING
|
|
3146
|
-
? Math.max(0, Math.floor(spaceRight))
|
|
3147
|
-
: null,
|
|
3148
|
-
});
|
|
3149
|
-
};
|
|
3150
|
-
const runAfterPaint = () => {
|
|
3151
|
-
requestAnimationFrame(() => {
|
|
3152
|
-
requestAnimationFrame(updateConstraints);
|
|
3153
|
-
});
|
|
3154
|
-
};
|
|
3155
|
-
runAfterPaint();
|
|
3156
|
-
window.addEventListener("resize", runAfterPaint);
|
|
3157
|
-
window.addEventListener("scroll", runAfterPaint, true);
|
|
3158
|
-
return () => {
|
|
3159
|
-
window.removeEventListener("resize", runAfterPaint);
|
|
3160
|
-
window.removeEventListener("scroll", runAfterPaint, true);
|
|
3161
|
-
};
|
|
3162
|
-
}, [constrainToViewport]);
|
|
3163
|
-
const parsedMaxHeight = parseInt(String(maxHeight), 10) || 400;
|
|
3164
|
-
const effectiveMaxHeight = viewportConstraints.maxHeight !== null
|
|
3165
|
-
? `${Math.min(parsedMaxHeight, viewportConstraints.maxHeight)}px`
|
|
3166
|
-
: maxHeight;
|
|
3167
|
-
const effectiveMaxWidth = viewportConstraints.maxWidth !== null
|
|
3168
|
-
? `${viewportConstraints.maxWidth}px`
|
|
3169
|
-
: undefined;
|
|
3120
|
+
const DropdownMenu = React.forwardRef(({ items = [], customContent, customContentClassName, sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, showFooter, footerLayout = "horizontal", onClose, focusedIndex = -1, className, width = "auto", maxHeight = "400px", unstyled = false, }, ref) => {
|
|
3170
3121
|
const renderContent = () => {
|
|
3171
3122
|
if (isLoading) {
|
|
3172
3123
|
return (jsx("div", { className: "flex flex-col items-center justify-center py-12 px-6", children: jsx(Loader2, { className: "w-12 h-12 text-action-ink-primary-normal mb-4 animate-spin" }) }));
|
|
3173
3124
|
}
|
|
3174
3125
|
if (customContent) {
|
|
3175
|
-
return (jsxs("div", { className: `py-3 px-3 overflow-y-auto ${customContentClassName}`, style: { maxHeight
|
|
3126
|
+
return (jsxs("div", { className: `py-3 px-3 overflow-y-auto ${customContentClassName}`, style: { maxHeight }, children: [sectionHeading && (jsx(Text, { as: "div", variant: "body", size: "small", weight: "medium", className: "text-body-small-medium text-surface-ink-neutral-muted px-3 py-2 mb-1", children: sectionHeading })), jsx("div", { children: customContent })] }));
|
|
3176
3127
|
}
|
|
3177
3128
|
if (isEmpty || items.length === 0) {
|
|
3178
3129
|
return (jsxs("div", { className: "flex flex-col items-center justify-center py-8 px-6 text-center", children: [emptyIcon || (jsx(Search, { className: "w-12 h-12 text-surface-ink-neutral-muted mb-4" })), jsx(Text, { as: "h3", variant: "body", size: "small", weight: "semibold", className: "text-surface-ink-neutral-normal mb-2", children: emptyTitle }), jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", className: "text-surface-ink-neutral-muted mb-3", children: emptyDescription }), emptyLinkText && (jsx(Link, { type: "anchor", color: "primary", size: "small", onClick: onEmptyLinkClick, children: emptyLinkText }))] }));
|
|
3179
3130
|
}
|
|
3180
|
-
return (jsxs("div", { className: `py-3 px-3 overflow-y-auto`, style: { maxHeight
|
|
3131
|
+
return (jsxs("div", { className: `py-3 px-3 overflow-y-auto`, style: { maxHeight }, children: [sectionHeading && (jsx(Text, { as: "div", variant: "body", size: "small", weight: "medium", className: "text-surface-ink-neutral-muted px-3 py-2 mb-1", children: sectionHeading })), jsx("div", { className: "flex flex-col gap-1", children: items.map((item, index) => (jsx(ListItem, { title: item.label, description: item.description, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon, showChevron: showChevron, isDisabled: item.isDisabled, isSelected: index === focusedIndex, variant: item.variant, onClick: () => {
|
|
3181
3132
|
item.onClick?.();
|
|
3182
3133
|
onClose?.();
|
|
3183
3134
|
}, containerClassName: cn(index === focusedIndex && "bg-action-fill-primary-faded") }, item.value))) })] }));
|
|
3184
3135
|
};
|
|
3185
3136
|
const widthClass = width === "full" ? "w-full" : width === "auto" ? "w-auto" : "";
|
|
3186
3137
|
const footerVisible = showFooter ?? !disableFooter;
|
|
3187
|
-
return (jsxs("div", { ref:
|
|
3138
|
+
return (jsxs("div", { ref: ref, className: cn(!unstyled && "bg-white rounded-large overflow-hidden", unstyled && "w-full", widthClass, className), style: {
|
|
3188
3139
|
...(!unstyled && {
|
|
3189
3140
|
boxShadow: "0 1px 2px rgba(25, 25, 30, 0.1), 0 2px 6px rgba(25, 25, 30, 0.06)",
|
|
3190
3141
|
}),
|
|
3191
3142
|
...(width !== "full" && width !== "auto" ? { width } : {}),
|
|
3192
|
-
...(effectiveMaxWidth ? { maxWidth: effectiveMaxWidth } : {}),
|
|
3193
3143
|
}, children: [renderContent(), footerVisible && (jsxs("div", { className: "flex flex-col", children: [jsx(Divider, { thickness: "thin", variant: "muted" }), jsxs("div", { className: cn("flex gap-3 p-4", footerLayout === "vertical"
|
|
3194
3144
|
? "flex-col"
|
|
3195
3145
|
: "items-center flex-row"), children: [onSecondaryClick && (jsx(Button, { variant: "secondary", color: "primary", size: "medium", isFullWidth: true, onClick: onSecondaryClick, children: secondaryButtonText })), onPrimaryClick && (jsx(Button, { variant: "primary", color: "primary", size: "medium", isFullWidth: true, onClick: onPrimaryClick, children: primaryButtonText }))] })] }))] }));
|
|
@@ -3213,14 +3163,12 @@ const Dropdown = React.forwardRef(({ className, trigger, items = [], customConte
|
|
|
3213
3163
|
const isOpen = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen;
|
|
3214
3164
|
const dropdownRef = React.useRef(null);
|
|
3215
3165
|
const menuRef = React.useRef(null);
|
|
3216
|
-
const VIEWPORT_PADDING = 8;
|
|
3217
|
-
const MENU_MAX_HEIGHT = 400;
|
|
3218
3166
|
const [menuPosition, setMenuPosition] = React.useState({
|
|
3219
3167
|
top: "100%",
|
|
3220
3168
|
bottom: "auto",
|
|
3221
3169
|
left: "0",
|
|
3222
3170
|
right: "auto",
|
|
3223
|
-
maxHeight:
|
|
3171
|
+
maxHeight: "400px",
|
|
3224
3172
|
});
|
|
3225
3173
|
// Detect if we're on mobile (< 768px)
|
|
3226
3174
|
const [isMobile, setIsMobile] = React.useState(false);
|
|
@@ -3279,56 +3227,68 @@ const Dropdown = React.forwardRef(({ className, trigger, items = [], customConte
|
|
|
3279
3227
|
const calculatePosition = () => {
|
|
3280
3228
|
const triggerRect = dropdownRef.current.getBoundingClientRect();
|
|
3281
3229
|
const menuElement = menuRef.current;
|
|
3282
|
-
|
|
3230
|
+
// Get menu dimensions (use a temporary measurement if needed)
|
|
3283
3231
|
const menuRect = menuElement.getBoundingClientRect();
|
|
3284
|
-
const menuHeight = menuRect.height ||
|
|
3285
|
-
const menuWidth = menuRect.width
|
|
3232
|
+
const menuHeight = menuRect.height || 400; // fallback to max-height
|
|
3233
|
+
const menuWidth = menuRect.width;
|
|
3286
3234
|
const viewportHeight = window.innerHeight;
|
|
3287
3235
|
const viewportWidth = window.innerWidth;
|
|
3288
|
-
const spaceBelow = viewportHeight - triggerRect.bottom
|
|
3289
|
-
const spaceAbove = triggerRect.top
|
|
3236
|
+
const spaceBelow = viewportHeight - triggerRect.bottom;
|
|
3237
|
+
const spaceAbove = triggerRect.top;
|
|
3238
|
+
const spaceRight = viewportWidth - triggerRect.left;
|
|
3239
|
+
const spaceLeft = triggerRect.right;
|
|
3290
3240
|
const position = {
|
|
3291
|
-
|
|
3241
|
+
top: "auto",
|
|
3242
|
+
bottom: "auto",
|
|
3243
|
+
left: "auto",
|
|
3244
|
+
right: "auto",
|
|
3245
|
+
maxHeight: "400px",
|
|
3292
3246
|
};
|
|
3293
|
-
// Vertical positioning
|
|
3247
|
+
// Vertical positioning
|
|
3294
3248
|
if (spaceBelow >= menuHeight || spaceBelow >= spaceAbove) {
|
|
3249
|
+
// Position below trigger
|
|
3295
3250
|
position.top = "100%";
|
|
3296
3251
|
position.bottom = "auto";
|
|
3297
|
-
position.maxHeight = `${Math.min(
|
|
3252
|
+
position.maxHeight = `${Math.min(400, spaceBelow - 16)}px`;
|
|
3298
3253
|
}
|
|
3299
3254
|
else {
|
|
3255
|
+
// Position above trigger
|
|
3300
3256
|
position.top = "auto";
|
|
3301
3257
|
position.bottom = "100%";
|
|
3302
|
-
position.maxHeight = `${Math.min(
|
|
3258
|
+
position.maxHeight = `${Math.min(400, spaceAbove - 16)}px`;
|
|
3259
|
+
}
|
|
3260
|
+
// Horizontal positioning
|
|
3261
|
+
if (spaceRight >= menuWidth) {
|
|
3262
|
+
// Align to left edge of trigger
|
|
3263
|
+
position.left = "0";
|
|
3264
|
+
position.right = "auto";
|
|
3265
|
+
}
|
|
3266
|
+
else if (spaceLeft >= menuWidth) {
|
|
3267
|
+
// Align to right edge of trigger
|
|
3268
|
+
position.left = "auto";
|
|
3269
|
+
position.right = "0";
|
|
3270
|
+
}
|
|
3271
|
+
else {
|
|
3272
|
+
// Not enough space on either side, try to center or align based on available space
|
|
3273
|
+
if (triggerRect.left + menuWidth > viewportWidth) {
|
|
3274
|
+
position.left = "auto";
|
|
3275
|
+
position.right = "0";
|
|
3276
|
+
}
|
|
3277
|
+
else {
|
|
3278
|
+
position.left = "0";
|
|
3279
|
+
position.right = "auto";
|
|
3280
|
+
}
|
|
3303
3281
|
}
|
|
3304
|
-
// Horizontal: clamp menu left so it stays within viewport
|
|
3305
|
-
const minMenuLeft = VIEWPORT_PADDING;
|
|
3306
|
-
const maxMenuLeft = viewportWidth - menuWidth - VIEWPORT_PADDING;
|
|
3307
|
-
const desiredMenuLeft = Math.max(minMenuLeft, Math.min(maxMenuLeft, triggerRect.left));
|
|
3308
|
-
position.left = `${desiredMenuLeft - currentContainerRect.left}px`;
|
|
3309
|
-
position.right = "auto";
|
|
3310
3282
|
setMenuPosition(position);
|
|
3311
3283
|
};
|
|
3312
|
-
//
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
if (!cancelled &&
|
|
3318
|
-
dropdownRef.current &&
|
|
3319
|
-
menuRef.current) {
|
|
3320
|
-
calculatePosition();
|
|
3321
|
-
}
|
|
3322
|
-
});
|
|
3323
|
-
});
|
|
3324
|
-
};
|
|
3325
|
-
scheduleUpdate();
|
|
3326
|
-
const handleResize = () => scheduleUpdate();
|
|
3327
|
-
const handleScroll = () => scheduleUpdate();
|
|
3284
|
+
// Calculate position after menu is rendered
|
|
3285
|
+
calculatePosition();
|
|
3286
|
+
// Recalculate on window resize or scroll
|
|
3287
|
+
const handleResize = () => calculatePosition();
|
|
3288
|
+
const handleScroll = () => calculatePosition();
|
|
3328
3289
|
window.addEventListener("resize", handleResize);
|
|
3329
3290
|
window.addEventListener("scroll", handleScroll, true);
|
|
3330
3291
|
return () => {
|
|
3331
|
-
cancelled = true;
|
|
3332
3292
|
window.removeEventListener("resize", handleResize);
|
|
3333
3293
|
window.removeEventListener("scroll", handleScroll, true);
|
|
3334
3294
|
};
|
|
@@ -3353,6 +3313,188 @@ const Dropdown = React.forwardRef(({ className, trigger, items = [], customConte
|
|
|
3353
3313
|
});
|
|
3354
3314
|
Dropdown.displayName = "Dropdown";
|
|
3355
3315
|
|
|
3316
|
+
const fileInputVariants = cva("relative flex items-center gap-3 border rounded-large transition-all font-functional font-size-100 leading-100", {
|
|
3317
|
+
variants: {
|
|
3318
|
+
size: {
|
|
3319
|
+
small: "h-[28px] px-3 text-xs gap-2",
|
|
3320
|
+
medium: "h-[36px] px-4 text-sm gap-2",
|
|
3321
|
+
large: "h-[44px] px-5 text-base gap-3",
|
|
3322
|
+
},
|
|
3323
|
+
validationState: {
|
|
3324
|
+
none: `
|
|
3325
|
+
border-action-outline-neutral-faded
|
|
3326
|
+
focus-within:border-action-outline-primary-default
|
|
3327
|
+
focus-within:bg-white!
|
|
3328
|
+
focus-within:ring-2
|
|
3329
|
+
ring-surface-outline-primary-muted`,
|
|
3330
|
+
positive: `
|
|
3331
|
+
border-action-outline-positive-default
|
|
3332
|
+
focus-within:border-action-outline-positive-hover
|
|
3333
|
+
focus-within:ring-2
|
|
3334
|
+
ring-action-outline-positive-faded-hover`,
|
|
3335
|
+
negative: `border-action-outline-negative-default
|
|
3336
|
+
focus-within:border-action-outline-negative-hover
|
|
3337
|
+
focus-within:ring-2
|
|
3338
|
+
ring-action-outline-negative-faded-hover`,
|
|
3339
|
+
},
|
|
3340
|
+
isDisabled: {
|
|
3341
|
+
true: `
|
|
3342
|
+
border
|
|
3343
|
+
border-action-outline-neutral-disabled
|
|
3344
|
+
hover:border-action-outline-neutral-disabled
|
|
3345
|
+
bg-surface-fill-neutral-intense
|
|
3346
|
+
hover:bg-surface-fill-neutral-intense
|
|
3347
|
+
cursor-not-allowed`,
|
|
3348
|
+
false: "",
|
|
3349
|
+
},
|
|
3350
|
+
},
|
|
3351
|
+
defaultVariants: {
|
|
3352
|
+
size: "medium",
|
|
3353
|
+
validationState: "none",
|
|
3354
|
+
isDisabled: false,
|
|
3355
|
+
},
|
|
3356
|
+
});
|
|
3357
|
+
const formatFileSize$1 = (bytes) => {
|
|
3358
|
+
if (bytes === 0)
|
|
3359
|
+
return "0 Bytes";
|
|
3360
|
+
const k = 1024;
|
|
3361
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
3362
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3363
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
3364
|
+
};
|
|
3365
|
+
const FileInput = React.forwardRef(({ label, helperText, errorText, successText, size = "medium", validationState = "none", isDisabled = false, isRequired = false, isOptional = false, accept, multiple = false, maxSize, value, onChange, containerClassName, labelClassName, inputClassName, infoHeading, infoDescription, LinkComponent, linkText, linkHref, onLinkClick, buttonText = "Choose File", placeholderText = "No file chosen", showClearButton = true, onClear, className, ...props }, ref) => {
|
|
3366
|
+
const fileInputRef = React.useRef(null);
|
|
3367
|
+
const [selectedFiles, setSelectedFiles] = React.useState([]);
|
|
3368
|
+
const [error, setError] = React.useState(null);
|
|
3369
|
+
// Initialize from value prop
|
|
3370
|
+
React.useEffect(() => {
|
|
3371
|
+
if (value) {
|
|
3372
|
+
const filesArray = Array.isArray(value) ? value : [value];
|
|
3373
|
+
setSelectedFiles(filesArray);
|
|
3374
|
+
}
|
|
3375
|
+
else {
|
|
3376
|
+
setSelectedFiles([]);
|
|
3377
|
+
}
|
|
3378
|
+
}, [value]);
|
|
3379
|
+
const processFiles = (files) => {
|
|
3380
|
+
const fileArray = Array.from(files);
|
|
3381
|
+
const validFiles = [];
|
|
3382
|
+
let currentError = null;
|
|
3383
|
+
fileArray.forEach((file) => {
|
|
3384
|
+
// Check file size
|
|
3385
|
+
if (maxSize && file.size > maxSize) {
|
|
3386
|
+
currentError = `File "${file.name}" exceeds maximum size of ${formatFileSize$1(maxSize)}`;
|
|
3387
|
+
setError(currentError);
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
// Check if file type is accepted
|
|
3391
|
+
if (accept) {
|
|
3392
|
+
const acceptedTypes = accept.split(",").map((type) => type.trim());
|
|
3393
|
+
const isAccepted = acceptedTypes.some((type) => {
|
|
3394
|
+
if (type.startsWith(".")) {
|
|
3395
|
+
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
|
3396
|
+
}
|
|
3397
|
+
if (type.includes("/*")) {
|
|
3398
|
+
const baseType = type.split("/")[0];
|
|
3399
|
+
return file.type.startsWith(baseType + "/");
|
|
3400
|
+
}
|
|
3401
|
+
return file.type === type;
|
|
3402
|
+
});
|
|
3403
|
+
if (!isAccepted) {
|
|
3404
|
+
currentError = `File type "${file.type}" is not accepted`;
|
|
3405
|
+
setError(currentError);
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
validFiles.push(file);
|
|
3410
|
+
});
|
|
3411
|
+
if (currentError) {
|
|
3412
|
+
setError(currentError);
|
|
3413
|
+
}
|
|
3414
|
+
else {
|
|
3415
|
+
setError(null);
|
|
3416
|
+
}
|
|
3417
|
+
return validFiles;
|
|
3418
|
+
};
|
|
3419
|
+
const handleFileInputChange = (e) => {
|
|
3420
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
3421
|
+
const validFiles = processFiles(e.target.files);
|
|
3422
|
+
if (validFiles.length > 0) {
|
|
3423
|
+
const newFiles = multiple
|
|
3424
|
+
? [...selectedFiles, ...validFiles]
|
|
3425
|
+
: validFiles;
|
|
3426
|
+
setSelectedFiles(newFiles);
|
|
3427
|
+
if (onChange) {
|
|
3428
|
+
onChange(multiple ? newFiles : newFiles[0] || null);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
// Reset input value to allow selecting the same file again
|
|
3432
|
+
e.target.value = "";
|
|
3433
|
+
}
|
|
3434
|
+
};
|
|
3435
|
+
const handleButtonClick = () => {
|
|
3436
|
+
if (!isDisabled && fileInputRef.current) {
|
|
3437
|
+
fileInputRef.current.click();
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
const handleClear = () => {
|
|
3441
|
+
if (isDisabled)
|
|
3442
|
+
return;
|
|
3443
|
+
setSelectedFiles([]);
|
|
3444
|
+
setError(null);
|
|
3445
|
+
// Clear the file input
|
|
3446
|
+
if (fileInputRef.current) {
|
|
3447
|
+
fileInputRef.current.value = "";
|
|
3448
|
+
}
|
|
3449
|
+
if (onClear) {
|
|
3450
|
+
onClear();
|
|
3451
|
+
}
|
|
3452
|
+
if (onChange) {
|
|
3453
|
+
onChange(null);
|
|
3454
|
+
}
|
|
3455
|
+
};
|
|
3456
|
+
// Determine which helper text to show
|
|
3457
|
+
const displayHelperText = errorText || successText || helperText || error;
|
|
3458
|
+
const currentValidationState = errorText
|
|
3459
|
+
? "negative"
|
|
3460
|
+
: successText
|
|
3461
|
+
? "positive"
|
|
3462
|
+
: validationState;
|
|
3463
|
+
const sizeConfig = {
|
|
3464
|
+
small: {
|
|
3465
|
+
gap: "gap-2",
|
|
3466
|
+
buttonSize: "xsmall",
|
|
3467
|
+
},
|
|
3468
|
+
medium: {
|
|
3469
|
+
gap: "gap-2",
|
|
3470
|
+
buttonSize: "small",
|
|
3471
|
+
},
|
|
3472
|
+
large: {
|
|
3473
|
+
gap: "gap-3",
|
|
3474
|
+
buttonSize: "small",
|
|
3475
|
+
},
|
|
3476
|
+
};
|
|
3477
|
+
const config = sizeConfig[size];
|
|
3478
|
+
const getDisplayText = () => {
|
|
3479
|
+
if (selectedFiles.length === 0) {
|
|
3480
|
+
return placeholderText;
|
|
3481
|
+
}
|
|
3482
|
+
if (selectedFiles.length === 1) {
|
|
3483
|
+
return selectedFiles[0].name;
|
|
3484
|
+
}
|
|
3485
|
+
return `${selectedFiles.length} file(s) selected`;
|
|
3486
|
+
};
|
|
3487
|
+
const hasFiles = selectedFiles.length > 0;
|
|
3488
|
+
return (jsxs("div", { ref: ref, className: cn("w-full flex flex-col", config.gap, containerClassName), ...props, children: [label && (jsx(FormHeader, { label: label, size: size, isRequired: isRequired, isOptional: isOptional, infoHeading: infoHeading, infoDescription: infoDescription, LinkComponent: LinkComponent, linkText: linkText, linkHref: linkHref, onLinkClick: onLinkClick, className: "mb-2", labelClassName: labelClassName })), jsxs("div", { className: cn(fileInputVariants({
|
|
3489
|
+
size,
|
|
3490
|
+
validationState: currentValidationState,
|
|
3491
|
+
isDisabled,
|
|
3492
|
+
}), className), children: [jsx("input", { ref: fileInputRef, type: "file", accept: accept, multiple: multiple, disabled: isDisabled, onChange: handleFileInputChange, className: "hidden", "aria-label": label || "File upload" }), jsx(Link, { type: "action", color: "primary", size: config.buttonSize, onClick: handleButtonClick, isDisabled: isDisabled, className: "shrink-0", children: buttonText }), jsx("div", { className: "flex-1 min-w-0", children: jsx(Text, { variant: "body", size: "small", color: isDisabled ? "disabled" : "muted", className: "truncate", title: getDisplayText(), children: getDisplayText() }) }), showClearButton && hasFiles && !isDisabled && (jsx(IconButton, { icon: "close", size: "xsmall", onClick: handleClear, "aria-label": "Clear selected files", className: "shrink-0" }))] }), jsx(FormFooter, { helperText: displayHelperText || undefined, validationState: currentValidationState === "none"
|
|
3493
|
+
? "default"
|
|
3494
|
+
: currentValidationState, size: size, isDisabled: isDisabled, className: "mt-1" })] }));
|
|
3495
|
+
});
|
|
3496
|
+
FileInput.displayName = "FileInput";
|
|
3497
|
+
|
|
3356
3498
|
const Modal = React.forwardRef(({ isOpen, onClose, title, description, footer, children, variant = "default", size = "medium", showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, ariaLabel, ariaDescribedBy, }, ref) => {
|
|
3357
3499
|
const modalRef = React.useRef(null);
|
|
3358
3500
|
const contentRef = ref || modalRef;
|
|
@@ -4170,11 +4312,12 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4170
4312
|
const inputRef = React.useRef(null);
|
|
4171
4313
|
const menuRef = React.useRef(null);
|
|
4172
4314
|
const VIEWPORT_PADDING = 8;
|
|
4315
|
+
const MENU_MAX_HEIGHT = 400;
|
|
4316
|
+
const MIN_SPACE_TO_OPEN_BELOW = 200;
|
|
4173
4317
|
const [position, setPosition] = React.useState({
|
|
4174
|
-
top: 0,
|
|
4175
4318
|
left: 0,
|
|
4176
|
-
width:
|
|
4177
|
-
maxHeight:
|
|
4319
|
+
width: 200,
|
|
4320
|
+
maxHeight: MENU_MAX_HEIGHT,
|
|
4178
4321
|
});
|
|
4179
4322
|
// Detect if we're on mobile (< 768px)
|
|
4180
4323
|
React.useEffect(() => {
|
|
@@ -4236,7 +4379,7 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4236
4379
|
}
|
|
4237
4380
|
}
|
|
4238
4381
|
}, [isOpen]);
|
|
4239
|
-
// Update position when dropdown opens or window resizes -
|
|
4382
|
+
// Update position when dropdown opens or window resizes - viewport coords, open above if not enough space below
|
|
4240
4383
|
const updateMenuPosition = React.useCallback(() => {
|
|
4241
4384
|
if (!dropdownRef.current)
|
|
4242
4385
|
return;
|
|
@@ -4244,14 +4387,26 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4244
4387
|
const viewportHeight = window.innerHeight;
|
|
4245
4388
|
const viewportWidth = window.innerWidth;
|
|
4246
4389
|
const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_PADDING;
|
|
4247
|
-
const
|
|
4248
|
-
const
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4390
|
+
const spaceAbove = rect.top - VIEWPORT_PADDING;
|
|
4391
|
+
const openAbove = spaceBelow < MIN_SPACE_TO_OPEN_BELOW || spaceBelow < spaceAbove;
|
|
4392
|
+
const menuLeft = Math.max(VIEWPORT_PADDING, Math.min(rect.left, viewportWidth - Math.max(rect.width, 200) - VIEWPORT_PADDING));
|
|
4393
|
+
const menuWidth = Math.max(rect.width, 200);
|
|
4394
|
+
if (openAbove) {
|
|
4395
|
+
setPosition({
|
|
4396
|
+
bottom: viewportHeight - rect.top + VIEWPORT_PADDING,
|
|
4397
|
+
left: menuLeft,
|
|
4398
|
+
width: menuWidth,
|
|
4399
|
+
maxHeight: Math.max(120, Math.min(MENU_MAX_HEIGHT, spaceAbove)),
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
else {
|
|
4403
|
+
setPosition({
|
|
4404
|
+
top: rect.bottom + VIEWPORT_PADDING,
|
|
4405
|
+
left: menuLeft,
|
|
4406
|
+
width: menuWidth,
|
|
4407
|
+
maxHeight: Math.max(120, Math.min(MENU_MAX_HEIGHT, spaceBelow)),
|
|
4408
|
+
});
|
|
4409
|
+
}
|
|
4255
4410
|
}, []);
|
|
4256
4411
|
React.useLayoutEffect(() => {
|
|
4257
4412
|
if (isOpen) {
|
|
@@ -4445,11 +4600,14 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4445
4600
|
// Render dropdown menu content
|
|
4446
4601
|
const renderDropdownContent = () => (jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: sectionHeading, isLoading: isLoading, isEmpty: itemsWithAddNew.length === 0 && !showAddNew, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyLinkText: emptyLinkText, onEmptyLinkClick: onEmptyLinkClick, primaryButtonText: primaryButtonText, secondaryButtonText: secondaryButtonText, onPrimaryClick: onPrimaryClick, onSecondaryClick: onSecondaryClick, showChevron: showChevron, emptyIcon: emptyIcon, disableFooter: disableFooter, showFooter: (primaryButtonText || secondaryButtonText) && !disableFooter
|
|
4447
4602
|
? true
|
|
4448
|
-
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"), maxHeight: `${position.maxHeight}px`, unstyled: isMobile
|
|
4603
|
+
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"), maxHeight: `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4449
4604
|
// Mobile: BottomSheet, Desktop: Regular Dropdown
|
|
4450
4605
|
const dropdownMenu = showDropdown && (isMobile ? (jsxs(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, children: [jsx("div", { className: "mb-4", children: jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", autoFocus: true, ...textFieldProps }) }), renderDropdownContent()] })) : (jsx("div", { ref: menuRef, style: {
|
|
4451
4606
|
position: "fixed",
|
|
4452
|
-
top: `${position.top}px
|
|
4607
|
+
...(position.top !== undefined && { top: `${position.top}px` }),
|
|
4608
|
+
...(position.bottom !== undefined && {
|
|
4609
|
+
bottom: `${position.bottom}px`,
|
|
4610
|
+
}),
|
|
4453
4611
|
left: `${position.left}px`,
|
|
4454
4612
|
width: dropdownWidth === "full" ? `${position.width}px` : "auto",
|
|
4455
4613
|
zIndex: isInsideModal ? 10001 : 9999,
|
|
@@ -6178,5 +6336,5 @@ const UploadBox = React.forwardRef(({ label, helperText, errorText, successText,
|
|
|
6178
6336
|
});
|
|
6179
6337
|
UploadBox.displayName = "UploadBox";
|
|
6180
6338
|
|
|
6181
|
-
export { Alert, Amount, Avatar, AvatarCell, Badge, BottomSheet, Button, ButtonGroup, Checkbox, Counter, DatePicker, DateRangePicker, Divider, Dropdown, DropdownMenu, FormFooter, FormHeader, Icon, IconButton, IconCell, Link, ListItem, Modal, NumberCell, Pagination, Radio, RadioGroup, SearchableDropdown, Select, SelectTextField, SidePanel, Skeleton, SlotCell, SpacerCell, Switch, TabItem, Table, TableDetailPanel, Tabs, Text, TextArea, TextField, Toast, ToastProvider, Tooltip, UploadBox, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, checkboxVariants, cn, counterVariants, datePickerVariants, dateRangePickerVariants, dropdownVariants, getAvailableIcons, hasIcon, iconButtonVariants, iconRegistry, linkVariants, listItemVariants, paginationVariants, radioVariants, selectTriggerVariants, switchVariants, tableCellVariants, tableHeaderVariants, tableVariants, textAreaVariants, textFieldVariants, tooltipVariants, uploadBoxVariants, useToast };
|
|
6339
|
+
export { Alert, Amount, Avatar, AvatarCell, Badge, BottomSheet, Button, ButtonGroup, Checkbox, Counter, DatePicker, DateRangePicker, Divider, Dropdown, DropdownMenu, FileInput, FormFooter, FormHeader, Icon, IconButton, IconCell, Link, ListItem, Modal, NumberCell, Pagination, Radio, RadioGroup, SearchableDropdown, Select, SelectTextField, SidePanel, Skeleton, SlotCell, SpacerCell, Switch, TabItem, Table, TableDetailPanel, Tabs, Text, TextArea, TextField, Toast, ToastProvider, Tooltip, UploadBox, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, checkboxVariants, cn, counterVariants, datePickerVariants, dateRangePickerVariants, dropdownVariants, fileInputVariants, getAvailableIcons, hasIcon, iconButtonVariants, iconRegistry, linkVariants, listItemVariants, paginationVariants, radioVariants, selectTriggerVariants, switchVariants, tableCellVariants, tableHeaderVariants, tableVariants, textAreaVariants, textFieldVariants, tooltipVariants, uploadBoxVariants, useToast };
|
|
6182
6340
|
//# sourceMappingURL=index.esm.js.map
|