infinity-ui-elements 1.9.28 → 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/index.js CHANGED
@@ -3334,6 +3334,188 @@ const Dropdown = React__namespace.forwardRef(({ className, trigger, items = [],
3334
3334
  });
3335
3335
  Dropdown.displayName = "Dropdown";
3336
3336
 
3337
+ const fileInputVariants = classVarianceAuthority.cva("relative flex items-center gap-3 border rounded-large transition-all font-functional font-size-100 leading-100", {
3338
+ variants: {
3339
+ size: {
3340
+ small: "h-[28px] px-3 text-xs gap-2",
3341
+ medium: "h-[36px] px-4 text-sm gap-2",
3342
+ large: "h-[44px] px-5 text-base gap-3",
3343
+ },
3344
+ validationState: {
3345
+ none: `
3346
+ border-action-outline-neutral-faded
3347
+ focus-within:border-action-outline-primary-default
3348
+ focus-within:bg-white!
3349
+ focus-within:ring-2
3350
+ ring-surface-outline-primary-muted`,
3351
+ positive: `
3352
+ border-action-outline-positive-default
3353
+ focus-within:border-action-outline-positive-hover
3354
+ focus-within:ring-2
3355
+ ring-action-outline-positive-faded-hover`,
3356
+ negative: `border-action-outline-negative-default
3357
+ focus-within:border-action-outline-negative-hover
3358
+ focus-within:ring-2
3359
+ ring-action-outline-negative-faded-hover`,
3360
+ },
3361
+ isDisabled: {
3362
+ true: `
3363
+ border
3364
+ border-action-outline-neutral-disabled
3365
+ hover:border-action-outline-neutral-disabled
3366
+ bg-surface-fill-neutral-intense
3367
+ hover:bg-surface-fill-neutral-intense
3368
+ cursor-not-allowed`,
3369
+ false: "",
3370
+ },
3371
+ },
3372
+ defaultVariants: {
3373
+ size: "medium",
3374
+ validationState: "none",
3375
+ isDisabled: false,
3376
+ },
3377
+ });
3378
+ const formatFileSize$1 = (bytes) => {
3379
+ if (bytes === 0)
3380
+ return "0 Bytes";
3381
+ const k = 1024;
3382
+ const sizes = ["Bytes", "KB", "MB", "GB"];
3383
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
3384
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
3385
+ };
3386
+ const FileInput = React__namespace.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) => {
3387
+ const fileInputRef = React__namespace.useRef(null);
3388
+ const [selectedFiles, setSelectedFiles] = React__namespace.useState([]);
3389
+ const [error, setError] = React__namespace.useState(null);
3390
+ // Initialize from value prop
3391
+ React__namespace.useEffect(() => {
3392
+ if (value) {
3393
+ const filesArray = Array.isArray(value) ? value : [value];
3394
+ setSelectedFiles(filesArray);
3395
+ }
3396
+ else {
3397
+ setSelectedFiles([]);
3398
+ }
3399
+ }, [value]);
3400
+ const processFiles = (files) => {
3401
+ const fileArray = Array.from(files);
3402
+ const validFiles = [];
3403
+ let currentError = null;
3404
+ fileArray.forEach((file) => {
3405
+ // Check file size
3406
+ if (maxSize && file.size > maxSize) {
3407
+ currentError = `File "${file.name}" exceeds maximum size of ${formatFileSize$1(maxSize)}`;
3408
+ setError(currentError);
3409
+ return;
3410
+ }
3411
+ // Check if file type is accepted
3412
+ if (accept) {
3413
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
3414
+ const isAccepted = acceptedTypes.some((type) => {
3415
+ if (type.startsWith(".")) {
3416
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
3417
+ }
3418
+ if (type.includes("/*")) {
3419
+ const baseType = type.split("/")[0];
3420
+ return file.type.startsWith(baseType + "/");
3421
+ }
3422
+ return file.type === type;
3423
+ });
3424
+ if (!isAccepted) {
3425
+ currentError = `File type "${file.type}" is not accepted`;
3426
+ setError(currentError);
3427
+ return;
3428
+ }
3429
+ }
3430
+ validFiles.push(file);
3431
+ });
3432
+ if (currentError) {
3433
+ setError(currentError);
3434
+ }
3435
+ else {
3436
+ setError(null);
3437
+ }
3438
+ return validFiles;
3439
+ };
3440
+ const handleFileInputChange = (e) => {
3441
+ if (e.target.files && e.target.files.length > 0) {
3442
+ const validFiles = processFiles(e.target.files);
3443
+ if (validFiles.length > 0) {
3444
+ const newFiles = multiple
3445
+ ? [...selectedFiles, ...validFiles]
3446
+ : validFiles;
3447
+ setSelectedFiles(newFiles);
3448
+ if (onChange) {
3449
+ onChange(multiple ? newFiles : newFiles[0] || null);
3450
+ }
3451
+ }
3452
+ // Reset input value to allow selecting the same file again
3453
+ e.target.value = "";
3454
+ }
3455
+ };
3456
+ const handleButtonClick = () => {
3457
+ if (!isDisabled && fileInputRef.current) {
3458
+ fileInputRef.current.click();
3459
+ }
3460
+ };
3461
+ const handleClear = () => {
3462
+ if (isDisabled)
3463
+ return;
3464
+ setSelectedFiles([]);
3465
+ setError(null);
3466
+ // Clear the file input
3467
+ if (fileInputRef.current) {
3468
+ fileInputRef.current.value = "";
3469
+ }
3470
+ if (onClear) {
3471
+ onClear();
3472
+ }
3473
+ if (onChange) {
3474
+ onChange(null);
3475
+ }
3476
+ };
3477
+ // Determine which helper text to show
3478
+ const displayHelperText = errorText || successText || helperText || error;
3479
+ const currentValidationState = errorText
3480
+ ? "negative"
3481
+ : successText
3482
+ ? "positive"
3483
+ : validationState;
3484
+ const sizeConfig = {
3485
+ small: {
3486
+ gap: "gap-2",
3487
+ buttonSize: "xsmall",
3488
+ },
3489
+ medium: {
3490
+ gap: "gap-2",
3491
+ buttonSize: "small",
3492
+ },
3493
+ large: {
3494
+ gap: "gap-3",
3495
+ buttonSize: "small",
3496
+ },
3497
+ };
3498
+ const config = sizeConfig[size];
3499
+ const getDisplayText = () => {
3500
+ if (selectedFiles.length === 0) {
3501
+ return placeholderText;
3502
+ }
3503
+ if (selectedFiles.length === 1) {
3504
+ return selectedFiles[0].name;
3505
+ }
3506
+ return `${selectedFiles.length} file(s) selected`;
3507
+ };
3508
+ const hasFiles = selectedFiles.length > 0;
3509
+ return (jsxRuntime.jsxs("div", { ref: ref, className: cn("w-full flex flex-col", config.gap, containerClassName), ...props, children: [label && (jsxRuntime.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 })), jsxRuntime.jsxs("div", { className: cn(fileInputVariants({
3510
+ size,
3511
+ validationState: currentValidationState,
3512
+ isDisabled,
3513
+ }), className), children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: accept, multiple: multiple, disabled: isDisabled, onChange: handleFileInputChange, className: "hidden", "aria-label": label || "File upload" }), jsxRuntime.jsx(Link, { type: "action", color: "primary", size: config.buttonSize, onClick: handleButtonClick, isDisabled: isDisabled, className: "shrink-0", children: buttonText }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(Text, { variant: "body", size: "small", color: isDisabled ? "disabled" : "muted", className: "truncate", title: getDisplayText(), children: getDisplayText() }) }), showClearButton && hasFiles && !isDisabled && (jsxRuntime.jsx(IconButton, { icon: "close", size: "xsmall", onClick: handleClear, "aria-label": "Clear selected files", className: "shrink-0" }))] }), jsxRuntime.jsx(FormFooter, { helperText: displayHelperText || undefined, validationState: currentValidationState === "none"
3514
+ ? "default"
3515
+ : currentValidationState, size: size, isDisabled: isDisabled, className: "mt-1" })] }));
3516
+ });
3517
+ FileInput.displayName = "FileInput";
3518
+
3337
3519
  const Modal = React__namespace.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) => {
3338
3520
  const modalRef = React__namespace.useRef(null);
3339
3521
  const contentRef = ref || modalRef;
@@ -6190,6 +6372,7 @@ exports.DateRangePicker = DateRangePicker;
6190
6372
  exports.Divider = Divider;
6191
6373
  exports.Dropdown = Dropdown;
6192
6374
  exports.DropdownMenu = DropdownMenu;
6375
+ exports.FileInput = FileInput;
6193
6376
  exports.FormFooter = FormFooter;
6194
6377
  exports.FormHeader = FormHeader;
6195
6378
  exports.Icon = Icon;
@@ -6232,6 +6415,7 @@ exports.counterVariants = counterVariants;
6232
6415
  exports.datePickerVariants = datePickerVariants;
6233
6416
  exports.dateRangePickerVariants = dateRangePickerVariants;
6234
6417
  exports.dropdownVariants = dropdownVariants;
6418
+ exports.fileInputVariants = fileInputVariants;
6235
6419
  exports.getAvailableIcons = getAvailableIcons;
6236
6420
  exports.hasIcon = hasIcon;
6237
6421
  exports.iconButtonVariants = iconButtonVariants;